diff --git a/TODO.md b/TODO.md index 02fd9ca..30552fa 100644 --- a/TODO.md +++ b/TODO.md @@ -7,7 +7,7 @@ - [x] 保留当前原版俄罗斯方块玩法,作为“经典模式” - [x] 新增开始菜单,进入游戏前先选择模式 - [x] 模式至少包含:`经典模式`、`Rogue 模式` -- [x] 强化选择界面固定为“三选一”三个选项框,视觉参考《吸血鬼幸存者》 +- [x] 强化选择界面固定为“四选一”四个选项框,视觉参考《吸血鬼幸存者》 - [ ] 美术、图标、特效、音频先全部使用占位符资源 ## 阶段 0:重构准备与技术基线 @@ -100,7 +100,7 @@ - [x] 消行后可以稳定增加分数和经验 - [x] 达到阈值后必定进入升级选择状态 -## 阶段 5:强化池与三选一系统 +## 阶段 5:强化池与四选一系统 目标:先完成最关键的 Rogue 核心体验。 @@ -112,33 +112,33 @@ - [x] `preview_plus_one` - [x] `exp_multiplier` - [x] `last_chance` -- [x] 随机抽取 3 个不重复选项,避免当前局内明显无效选项 +- [x] 随机抽取 4 个不重复选项,避免当前局内明显无效选项 - [x] 支持重复强化的层数叠加 - [x] 选中后立即应用效果并返回游戏 - [x] 为后续强化扩展预留 `applyUpgradeById()` 分发函数 完成标准: -- [x] 每次升级都稳定弹出三个选项框 +- [x] 每次升级都稳定弹出四个选项框 - [x] 选择任一选项后效果立即生效 ## 阶段 6:升级界面 UI -目标:实现参考《吸血鬼幸存者》的三选一视觉结构,先用占位表现。 +目标:实现参考《吸血鬼幸存者》的四选一视觉结构,先用占位表现。 - [x] 增加全屏或半透明遮罩,压暗游戏场景 -- [x] 中央显示三个横向或纵向排列的选项框 +- [x] 中央显示四个横向或纵向排列的选项框 - [x] 每个选项框至少包含:占位图标、强化名、强化说明、强化分类、当前层数 - [x] 支持键盘选择:`A/D` 或方向键切换,`Enter/Space` 确认 - [ ] 高亮态、选中态、禁用态视觉区分明确 -- [x] 保证三个框尺寸一致、布局稳定,不因文本长度错位 +- [x] 保证四个框尺寸一致、布局稳定,不因文本长度错位 - [x] 右侧现有信息面板在升级状态下保留或弱化显示,但不能抢焦点 - [ ] 占位资源统一放入 `assets`,命名先按 `placeholder_*` 完成标准: - [x] 升级界面可以独立显示 -- [x] 三个选项框可操作、可确认、可关闭 +- [x] 四个选项框可操作、可确认、可关闭 - [x] 视觉结构已经接近目标形式,后续只需替换资源 ## 阶段 7:玩法强化第一轮 @@ -204,7 +204,7 @@ - [ ] 开局正常生成方块 - [ ] 消行得分与经验正确 -- [x] 升级必出 3 个选项框 +- [x] 升级必出 4 个选项框 - [x] 选择后恢复游戏且不丢状态 - [ ] 复活、暂停、失败、重开互不冲突 diff --git a/src/include/Tetris.h b/src/include/Tetris.h index dd1b039..73c62e8 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -56,14 +56,15 @@ struct PlayerStats int explosiveLevel; int stableStructureLevel; int doubleGrowthLevel; - int luckyRollLevel; int gamblerLevel; + int pieceTuningLevels[7]; }; struct UpgradeOption { int id; int currentLevel; + int targetPieceType; const TCHAR* name; const TCHAR* category; const TCHAR* description; @@ -85,7 +86,7 @@ struct UpgradeUiState int optionCount; int pendingCount; int totalChosenCount; - UpgradeOption options[3]; + UpgradeOption options[4]; }; struct FeedbackState diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index e326444..3cba999 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -264,19 +264,53 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { case VK_LEFT: case 'A': - upgradeUiState.selectedIndex--; - if (upgradeUiState.selectedIndex < 0) + if (upgradeUiState.optionCount > 1) { - upgradeUiState.selectedIndex = upgradeUiState.optionCount - 1; + if ((upgradeUiState.selectedIndex % 2) == 1) + { + upgradeUiState.selectedIndex--; + } + else + { + upgradeUiState.selectedIndex = (upgradeUiState.selectedIndex + 1 < upgradeUiState.optionCount) + ? upgradeUiState.selectedIndex + 1 + : upgradeUiState.selectedIndex; + } } InvalidateRect(hWnd, nullptr, FALSE); break; case VK_RIGHT: case 'D': - upgradeUiState.selectedIndex++; - if (upgradeUiState.selectedIndex >= upgradeUiState.optionCount) + if (upgradeUiState.optionCount > 1) { - upgradeUiState.selectedIndex = 0; + if ((upgradeUiState.selectedIndex % 2) == 0 && upgradeUiState.selectedIndex + 1 < upgradeUiState.optionCount) + { + upgradeUiState.selectedIndex++; + } + else + { + upgradeUiState.selectedIndex--; + if (upgradeUiState.selectedIndex < 0) + { + upgradeUiState.selectedIndex = 0; + } + } + } + InvalidateRect(hWnd, nullptr, FALSE); + break; + case VK_UP: + case 'W': + if (upgradeUiState.optionCount > 2 && upgradeUiState.selectedIndex >= 2) + { + upgradeUiState.selectedIndex -= 2; + } + InvalidateRect(hWnd, nullptr, FALSE); + break; + case VK_DOWN: + case 'S': + if (upgradeUiState.optionCount > 2 && upgradeUiState.selectedIndex + 2 < upgradeUiState.optionCount) + { + upgradeUiState.selectedIndex += 2; } InvalidateRect(hWnd, nullptr, FALSE); break; diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 5f5f68f..dd08783 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -38,7 +38,7 @@ enum UpgradeId UPGRADE_EXPLOSIVE_PIECE = 9, UPGRADE_STABLE_STRUCTURE = 10, UPGRADE_DOUBLE_GROWTH = 11, - UPGRADE_LUCKY_ROLL = 12, + UPGRADE_PIECE_TUNING = 12, UPGRADE_GAMBLER = 13 }; @@ -56,8 +56,8 @@ static const UpgradeEntry kUpgradePool[] = { UPGRADE_EXPLOSIVE_PIECE, -1, true, _T("\u7206\u7834\u65b9\u5757"), _T("\u7279\u6b8a"), _T("\u63d0\u9ad8\u7206\u7834\u65b9\u5757\u51fa\u73b0\u6982\u7387\uff0c\u843d\u5730\u65f6\u89e6\u53d1 3x3 \u6e05\u9664\u3002") }, { UPGRADE_STABLE_STRUCTURE, -1, true, _T("\u7a33\u5b9a\u7ed3\u6784"), _T("\u7279\u6b8a"), _T("\u843d\u5730\u540e\u5c0f\u6982\u7387\u81ea\u52a8\u586b\u8865\u90bb\u8fd1\u7a7a\u6d1e\uff0c\u63d0\u9ad8\u76d8\u9762\u7ed3\u6784\u7a33\u5b9a\u6027\u3002") }, { UPGRADE_DOUBLE_GROWTH, -1, true, _T("\u53cc\u500d\u6210\u957f"), _T("\u7279\u6b8a"), _T("\u989d\u5916\u63d0\u9ad8\u6d88\u884c\u5f97\u5206\u4e0e EXP \u6536\u76ca\uff0c\u6bcf\u5c42\u518d\u8ffd\u52a0 15%\u3002") }, - { UPGRADE_LUCKY_ROLL, -1, true, _T("\u65b9\u5757\u6539\u8fd0"), _T("\u7279\u6b8a"), _T("\u4f18\u5316 Next \u961f\u5217\uff0c\u964d\u4f4e\u8fde\u7eed\u91cd\u590d\u65b9\u5757\u51fa\u73b0\u7684\u6982\u7387\u3002") }, - { UPGRADE_GAMBLER, -1, true, _T("\u8d4c\u5f92"), _T("\u7279\u6b8a"), _T("\u6d88\u884c\u6536\u76ca\u4f1a\u989d\u5916\u968f\u673a\u6ce2\u52a8\uff0c\u5c42\u6570\u8d8a\u9ad8\u6ce2\u52a8\u8d8a\u5927\u3002") } + { UPGRADE_PIECE_TUNING, -1, true, _T("\u65b9\u5757\u6539\u9020"), _T("\u7279\u6b8a"), _T("\u9009\u62e9\u4e00\u79cd\u65b9\u5757\uff0c\u964d\u4f4e\u5176\u540e\u7eed\u51fa\u73b0\u6982\u7387\u3002") }, + { UPGRADE_GAMBLER, -1, true, _T("\u8d4c\u5f92"), _T("\u7279\u6b8a"), _T("\u9009\u62e9\u5f3a\u5316\u65f6\uff0c\u6709\u6982\u7387\u53cc\u500d\u751f\u6548\uff0c\u4e5f\u6709\u6982\u7387\u5b8c\u5168\u843d\u7a7a\u3002") } }; static constexpr int kUpgradePoolSize = sizeof(kUpgradePool) / sizeof(kUpgradePool[0]); @@ -215,8 +215,11 @@ static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) stats.explosiveLevel = 0; stats.stableStructureLevel = 0; stats.doubleGrowthLevel = 0; - stats.luckyRollLevel = 0; stats.gamblerLevel = 0; + for (int i = 0; i < 7; i++) + { + stats.pieceTuningLevels[i] = 0; + } } static int GetNextPreviewLimit() @@ -267,8 +270,6 @@ static int GetUpgradeCurrentLevel(int upgradeId) return rogueStats.stableStructureLevel; case UPGRADE_DOUBLE_GROWTH: return rogueStats.doubleGrowthLevel; - case UPGRADE_LUCKY_ROLL: - return rogueStats.luckyRollLevel; case UPGRADE_GAMBLER: return rogueStats.gamblerLevel; default: @@ -276,6 +277,27 @@ static int GetUpgradeCurrentLevel(int upgradeId) } } +static const TCHAR* GetPieceShortName(int pieceType) +{ + static const TCHAR* kPieceNames[7] = + { + _T("I"), + _T("T"), + _T("L"), + _T("J"), + _T("O"), + _T("S"), + _T("Z") + }; + + if (pieceType < 0 || pieceType >= 7) + { + return _T("?"); + } + + return kPieceNames[pieceType]; +} + static bool IsUpgradeSelectable(const UpgradeEntry& entry) { if (entry.repeatable) @@ -390,42 +412,37 @@ static int ClearExplosiveAreaAt(int centerY, int centerX) static int RollNextPieceType() { - int candidate = rand() % 7; + int weights[7] = { 100, 100, 100, 100, 100, 100, 100 }; - if (currentMode != MODE_ROGUE || rogueStats.luckyRollLevel <= 0) + if (currentMode == MODE_ROGUE) { - return candidate; - } - - int rerollCount = rogueStats.luckyRollLevel; - if (rerollCount > 3) - { - rerollCount = 3; - } - - for (int attempt = 0; attempt < rerollCount; attempt++) - { - bool matchesCurrent = (candidate == type); - bool matchesQueue = false; - - for (int i = 0; i < 3; i++) + for (int i = 0; i < 7; i++) { - if (nextTypes[i] == candidate) + weights[i] -= rogueStats.pieceTuningLevels[i] * 20; + if (weights[i] < 20) { - matchesQueue = true; - break; + weights[i] = 20; } } - - if (!matchesCurrent && !matchesQueue) - { - break; - } - - candidate = rand() % 7; } - return candidate; + int totalWeight = 0; + for (int i = 0; i < 7; i++) + { + totalWeight += weights[i]; + } + + int roll = rand() % totalWeight; + for (int i = 0; i < 7; i++) + { + if (roll < weights[i]) + { + return i; + } + roll -= weights[i]; + } + + return rand() % 7; } static int TryStabilizeBoard() @@ -544,7 +561,7 @@ static void FillUpgradeOptions() } } - int optionCount = selectableCount < 3 ? selectableCount : 3; + int optionCount = selectableCount < 4 ? selectableCount : 4; upgradeUiState.optionCount = optionCount; upgradeUiState.selectedIndex = 0; @@ -556,10 +573,45 @@ static void FillUpgradeOptions() upgradeUiState.options[i].id = pickedEntry.id; upgradeUiState.options[i].currentLevel = GetUpgradeCurrentLevel(pickedEntry.id); + upgradeUiState.options[i].targetPieceType = -1; upgradeUiState.options[i].name = pickedEntry.name; upgradeUiState.options[i].category = pickedEntry.category; upgradeUiState.options[i].description = pickedEntry.description; + if (pickedEntry.id == UPGRADE_PIECE_TUNING) + { + int targetPieceType = rand() % 7; + bool duplicatedPiece = true; + int guard = 0; + + while (duplicatedPiece && guard < 16) + { + duplicatedPiece = false; + for (int optionIndex = 0; optionIndex < i; optionIndex++) + { + if (upgradeUiState.options[optionIndex].id == UPGRADE_PIECE_TUNING && + upgradeUiState.options[optionIndex].targetPieceType == targetPieceType) + { + duplicatedPiece = true; + targetPieceType = (targetPieceType + 1) % 7; + break; + } + } + guard++; + } + + upgradeUiState.options[i].targetPieceType = targetPieceType; + upgradeUiState.options[i].currentLevel = rogueStats.pieceTuningLevels[targetPieceType]; + upgradeUiState.options[i].name = _T("\u65b9\u5757\u6539\u9020"); + + static TCHAR tuningDescriptions[4][64]; + _stprintf_s( + tuningDescriptions[i], + _T("\u964d\u4f4e %s \u65b9\u5757\u7684\u51fa\u73b0\u6982\u7387\u3002"), + GetPieceShortName(targetPieceType)); + upgradeUiState.options[i].description = tuningDescriptions[i]; + } + selectableIndexes[pickSlot] = selectableIndexes[selectableCount - 1]; selectableCount--; } @@ -570,77 +622,80 @@ static int GetRogueFallInterval() return 500 + rogueStats.slowFallStacks * 80; } -static void ApplyUpgradeById(int upgradeId) +static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) { + if (applyCount <= 0) + { + return; + } + switch (upgradeId) { case UPGRADE_SCORE_MULTIPLIER: - rogueStats.scoreMultiplierPercent += 20; - rogueStats.scoreUpgradeLevel++; + rogueStats.scoreMultiplierPercent += 20 * applyCount; + rogueStats.scoreUpgradeLevel += applyCount; break; case UPGRADE_COMBO_BONUS: - rogueStats.comboBonusStacks++; + rogueStats.comboBonusStacks += applyCount; break; case UPGRADE_EXP_MULTIPLIER: - rogueStats.expMultiplierPercent += 25; - rogueStats.expUpgradeLevel++; + rogueStats.expMultiplierPercent += 25 * applyCount; + rogueStats.expUpgradeLevel += applyCount; break; case UPGRADE_SLOW_FALL: - rogueStats.slowFallStacks++; + rogueStats.slowFallStacks += applyCount; currentFallInterval = GetRogueFallInterval(); break; case UPGRADE_PREVIEW_PLUS_ONE: - if (rogueStats.previewCount < 3) + for (int i = 0; i < applyCount; i++) { - rogueStats.previewCount++; + if (rogueStats.previewCount < 3) + { + rogueStats.previewCount++; + } } rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; break; case UPGRADE_LAST_CHANCE: - rogueStats.lastChanceCount = 1; - rogueStats.lastChanceUpgradeLevel = 1; + rogueStats.lastChanceCount += applyCount; + rogueStats.lastChanceUpgradeLevel += applyCount; break; case UPGRADE_HOLD_UNLOCK: rogueStats.holdUnlocked = 1; break; case UPGRADE_PRESSURE_RELIEF: { - rogueStats.pressureReliefLevel++; - int topOccupiedRow = GetTopOccupiedRow(); - if (topOccupiedRow >= 0) + rogueStats.pressureReliefLevel += applyCount; + for (int i = 0; i < applyCount; i++) { - DeleteOneLine(topOccupiedRow); - SetFeedbackMessage(_T("\u51cf\u538b\u751f\u6548"), _T("\u5df2\u7acb\u5373\u6e05\u9664\u5f53\u524d\u6700\u9ad8\u5360\u7528\u884c\u3002"), 12); - } - else - { - SetFeedbackMessage(_T("\u51cf\u538b\u751f\u6548"), _T("\u5f53\u524d\u76d8\u9762\u8fd8\u5f88\u7a7a\uff0c\u672c\u6b21\u6ca1\u6709\u989d\u5916\u6e05\u9664\u884c\u3002"), 12); + int topOccupiedRow = GetTopOccupiedRow(); + if (topOccupiedRow >= 0) + { + DeleteOneLine(topOccupiedRow); + } } break; } case UPGRADE_SWEEPER: - rogueStats.sweeperLevel++; - SetFeedbackMessage(_T("\u6e05\u626b\u8005\u5df2\u52a0\u5165"), _T("\u7d2f\u8ba1\u6d88\u884c\u540e\u5c06\u81ea\u52a8\u6e05\u7406\u5e95\u90e8\u79ef\u538b\u3002"), 12); + rogueStats.sweeperLevel += applyCount; break; case UPGRADE_EXPLOSIVE_PIECE: - rogueStats.explosiveLevel++; - SetFeedbackMessage(_T("\u7206\u7834\u65b9\u5757\u5df2\u5f3a\u5316"), _T("\u540e\u7eed\u5c06\u6709\u66f4\u9ad8\u6982\u7387\u51fa\u73b0\u7206\u7834\u65b9\u5757\u3002"), 12); + rogueStats.explosiveLevel += applyCount; break; case UPGRADE_STABLE_STRUCTURE: - rogueStats.stableStructureLevel++; - SetFeedbackMessage(_T("\u7a33\u5b9a\u7ed3\u6784\u5df2\u5f3a\u5316"), _T("\u540e\u7eed\u843d\u5730\u7ed3\u7b97\u65f6\u5c06\u6709\u6982\u7387\u81ea\u52a8\u8865\u6d1e\u3002"), 12); + rogueStats.stableStructureLevel += applyCount; break; case UPGRADE_DOUBLE_GROWTH: - rogueStats.doubleGrowthLevel++; - SetFeedbackMessage(_T("\u53cc\u500d\u6210\u957f\u5df2\u53e0\u52a0"), _T("\u540e\u7eed\u6d88\u884c\u7684\u5f97\u5206\u548c EXP \u6536\u76ca\u4f1a\u66f4\u9ad8\u3002"), 12); + rogueStats.doubleGrowthLevel += applyCount; break; - case UPGRADE_LUCKY_ROLL: - rogueStats.luckyRollLevel++; - SetFeedbackMessage(_T("\u65b9\u5757\u6539\u8fd0\u5df2\u5f3a\u5316"), _T("Next \u961f\u5217\u4f1a\u66f4\u5c11\u51fa\u73b0\u8fde\u7eed\u91cd\u590d\u5757\u578b\u3002"), 12); + case UPGRADE_PIECE_TUNING: + if (targetPieceType >= 0 && targetPieceType < 7) + { + rogueStats.pieceTuningLevels[targetPieceType] += applyCount; + } break; case UPGRADE_GAMBLER: - rogueStats.gamblerLevel++; - SetFeedbackMessage(_T("\u8d4c\u5f92\u5df2\u53e0\u52a0"), _T("\u540e\u7eed\u6d88\u884c\u7684\u5206\u6570\u548c EXP \u4f1a\u989d\u5916\u968f\u673a\u6ce2\u52a8\u3002"), 12); + rogueStats.gamblerLevel += applyCount; break; default: break; @@ -1272,12 +1327,47 @@ void ConfirmUpgradeSelection() } UpgradeOption selectedOption = upgradeUiState.options[upgradeUiState.selectedIndex]; - ApplyUpgradeById(selectedOption.id); + int applyCount = 1; + TCHAR gamblerSuffix[64] = _T(""); + + if (currentMode == MODE_ROGUE && rogueStats.gamblerLevel > 0) + { + int gamblerChance = 20 + (rogueStats.gamblerLevel - 1) * 5; + if (gamblerChance > 40) + { + gamblerChance = 40; + } + + int roll = rand() % 100; + if (roll < gamblerChance) + { + applyCount = 2; + _stprintf_s(gamblerSuffix, _T(" \u8d4c\u5f92\uff1a\u53cc\u500d")); + } + else if (roll >= 100 - gamblerChance) + { + applyCount = 0; + _stprintf_s(gamblerSuffix, _T(" \u8d4c\u5f92\uff1a\u843d\u7a7a")); + } + } + + ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount); upgradeUiState.totalChosenCount++; TCHAR feedbackTitle[64]; TCHAR feedbackDetail[128]; _stprintf_s(feedbackTitle, _T("\u5df2\u83b7\u5f97\uff1a%s"), selectedOption.name); - _stprintf_s(feedbackDetail, _T("%s"), selectedOption.description); + if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0) + { + _stprintf_s( + feedbackDetail, + _T("\u964d\u4f4e %s \u65b9\u5757\u51fa\u73b0\u6982\u7387%s"), + GetPieceShortName(selectedOption.targetPieceType), + gamblerSuffix); + } + else + { + _stprintf_s(feedbackDetail, _T("%s%s"), selectedOption.description, gamblerSuffix); + } SetFeedbackMessage(feedbackTitle, feedbackDetail, 12); if (upgradeUiState.pendingCount > 0) diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 6a3d039..7a8701d 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -362,7 +362,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) hdc, _T("\u7ecf\u5178\u6a21\u5f0f\uff1a\u4fdd\u6301\u539f\u7248\u4fc4\u7f57\u65af\u65b9\u5757\u73a9\u6cd5\uff0c\u4ee5\u6d88\u884c\u548c\u751f\u5b58\u4e3a\u4e3b\u3002\r\n\r\n") _T("Rogue \u6a21\u5f0f\uff1a\u6d88\u884c\u540e\u9664\u4e86\u83b7\u5f97\u5206\u6570\uff0c\u8fd8\u4f1a\u83b7\u5f97 EXP\u3002EXP \u8fbe\u5230\u9608\u503c\u540e\u89e6\u53d1\u5347\u7ea7\uff0c\u4ece\u4e09\u4e2a\u5f3a\u5316\u4e2d\u9009\u4e00\u4e2a\u3002\r\n\r\n") - _T("\u5f53\u524d\u5df2\u63a5\u5165\uff1a\u5206\u6570\u500d\u7387\u3001EXP \u500d\u7387\u3001\u6162\u901f\u4e0b\u843d\u3001Hold \u89e3\u9501\u3001\u51cf\u538b\u3001\u6e05\u626b\u8005\u3001\u7206\u7834\u65b9\u5757\u3001\u7a33\u5b9a\u7ed3\u6784\u3001\u53cc\u500d\u6210\u957f\u3001\u65b9\u5757\u6539\u8fd0\u3001\u8d4c\u5f92\u3002\r\n\r\n") + _T("\u5f53\u524d\u5df2\u63a5\u5165\uff1a\u5206\u6570\u500d\u7387\u3001EXP \u500d\u7387\u3001\u6162\u901f\u4e0b\u843d\u3001Hold \u89e3\u9501\u3001\u51cf\u538b\u3001\u6e05\u626b\u8005\u3001\u7206\u7834\u65b9\u5757\u3001\u7a33\u5b9a\u7ed3\u6784\u3001\u53cc\u500d\u6210\u957f\u3001\u65b9\u5757\u6539\u9020\u3001\u8d4c\u5f92\u3002\r\n\r\n") _T("\u63d0\u793a\uff1a\u6682\u505c\u3001\u5931\u8d25\u548c\u5347\u7ea7\u4f1a\u8fdb\u5165\u4e0d\u540c\u754c\u9762\uff0c\u8bf7\u6839\u636e\u5c4f\u5e55\u63d0\u793a\u64cd\u4f5c\u3002"), -1, &rulesBody, @@ -699,10 +699,18 @@ void TDrawScreen(HDC hdc, HWND hWnd) TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(178), growthText, lstrlen(growthText)); } - if (rogueStats.luckyRollLevel > 0) + int tunedPieces = 0; + for (int pieceType = 0; pieceType < 7; pieceType++) + { + if (rogueStats.pieceTuningLevels[pieceType] > 0) + { + tunedPieces++; + } + } + if (tunedPieces > 0) { TCHAR luckyText[96]; - _stprintf_s(luckyText, _T("\u6539\u8fd0\u91cd\u63b7 %d \u6b21"), rogueStats.luckyRollLevel > 3 ? 3 : rogueStats.luckyRollLevel); + _stprintf_s(luckyText, _T("\u65b9\u5757\u6539\u9020 %d \u79cd\u5df2\u8c03\u6574"), tunedPieces); TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(210), luckyText, lstrlen(luckyText)); } @@ -710,11 +718,11 @@ void TDrawScreen(HDC hdc, HWND hWnd) { TCHAR gamblerText[96]; int variance = 20 + (rogueStats.gamblerLevel - 1) * 10; - if (variance > 50) + if (variance > 40) { - variance = 50; + variance = 40; } - _stprintf_s(gamblerText, _T("\u8d4c\u5f92\u6ce2\u52a8 \u00b1%d%%"), variance); + _stprintf_s(gamblerText, _T("\u8d4c\u5f92\u6982\u7387 \u53cc\u500d/%u\u6548 %d%%"), variance, variance); TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(242), gamblerText, lstrlen(gamblerText)); } @@ -788,9 +796,24 @@ void TDrawScreen(HDC hdc, HWND hWnd) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u53cc\u500d\u6210\u957f Lv.%d\r\n"), rogueStats.doubleGrowthLevel); } - if (rogueStats.luckyRollLevel > 0) + int tunedPieceCount = 0; + for (int pieceType = 0; pieceType < 7; pieceType++) { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65b9\u5757\u6539\u8fd0 Lv.%d\r\n"), rogueStats.luckyRollLevel); + if (rogueStats.pieceTuningLevels[pieceType] > 0) + { + tunedPieceCount++; + _stprintf_s( + upgradeSummary + lstrlen(upgradeSummary), + 512 - lstrlen(upgradeSummary), + _T("%s \u65b9\u5757\u6539\u9020 Lv.%d\r\n"), + (pieceType == 0 ? _T("I") : + pieceType == 1 ? _T("T") : + pieceType == 2 ? _T("L") : + pieceType == 3 ? _T("J") : + pieceType == 4 ? _T("O") : + pieceType == 5 ? _T("S") : _T("Z")), + rogueStats.pieceTuningLevels[pieceType]); + } } if (rogueStats.gamblerLevel > 0) { @@ -1108,22 +1131,26 @@ void TDrawScreen(HDC hdc, HWND hWnd) DrawText(hdc, _T("\u8bf7\u4ece\u4e09\u4e2a\u9009\u9879\u4e2d\u9009\u62e9\u4e00\u4e2a\u5f3a\u5316"), -1, &overlaySubtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); int gap = SS(18); - int cardWidth = (overlayRect.right - overlayRect.left - SS(72) - gap * 2) / 3; - int cardTop = overlayRect.top + SS(138); - int cardBottom = overlayRect.bottom - SS(72); + int horizontalPadding = SS(36); + int verticalTop = overlayRect.top + SS(138); + int cardWidth = (overlayRect.right - overlayRect.left - horizontalPadding * 2 - gap) / 2; + int cardHeight = (overlayRect.bottom - verticalTop - SS(72) - gap) / 2; for (int i = 0; i < upgradeUiState.optionCount; i++) { bool isSelected = (i == upgradeUiState.selectedIndex); - int left = overlayRect.left + SS(36) + i * (cardWidth + gap); + int column = i % 2; + int row = i / 2; + int left = overlayRect.left + horizontalPadding + column * (cardWidth + gap); + int top = verticalTop + row * (cardHeight + gap); int right = left + cardWidth; RECT cardRect = { left, - cardTop, + top, right, - cardBottom + top + cardHeight }; HBRUSH cardBrush = CreateSolidBrush(isSelected ? RGB(255, 235, 242) : RGB(255, 247, 250)); @@ -1219,7 +1246,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) overlayRect.right - SS(30), overlayRect.bottom - SS(18) }; - DrawText(hdc, _T("A / D \u6216\u65b9\u5411\u952e\u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("W/A/S/D \u6216\u65b9\u5411\u952e\u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } SelectObject(hdc, oldFont); diff --git a/强化升级.md b/强化升级.md new file mode 100644 index 0000000..a4dd48b --- /dev/null +++ b/强化升级.md @@ -0,0 +1,1095 @@ +# 《Rogue Tetris》强化系统与合成路线设计 + +## 1. 设计目标 + +本系统用于在基础俄罗斯方块玩法中加入类肉鸽成长体验。 + +核心目标: + +1. 玩家通过消行获得经验并升级。 +2. 每次升级随机出现若干强化。 +3. 强化之间存在前置关系和合成路线。 +4. 玩家可以围绕强化组合形成不同流派。 +5. 强化既提供数值成长,也提供清屏、爆破、狂热等爽感反馈。 + +--- + +## 2. 强化层级 + +强化分为三类: + +| 层级 | 说明 | 示例 | +|---|---|---| +| 基础强化 | 可以直接随机获得,提供基础能力 | 积分倍率、经验强化、慢速下落 | +| 进阶强化 | 需要拥有指定基础强化后才会出现 | 连锁爆破、雷霆四消、狂热模式 | +| 进化强化 | 由两个或多个强化合成解锁 | 连环炸弹、雷霆激光、无限狂热 | + +--- + +## 3. 基础强化列表 + +### 3.1 积分倍率 + +**类型:** 得分强化 + +**效果:** 所有得分提高 20%。 + +**可叠加:** 可以。 + +**作用:** 提高基础收益,是得分流派的通用强化。 + +**相关联动:** + +- 与狂热模式叠加后,可以进一步提高爆发期得分。 +- 与暴走堆叠叠加后,连击收益更高。 + +--- + +### 3.2 经验强化 + +**类型:** 成长强化 + +**效果:** 消行获得的经验提高 25%。 + +**可叠加:** 可以。 + +**作用:** 加快升级速度,提高获取后续强化的频率。 + +**相关联动:** + +- 与成长雪球组合,可以形成快速升级流派。 +- 与狂热模式组合,可以在狂热期间获得更高经验收益。 + +--- + +### 3.3 慢速下落 + +**类型:** 操作强化 + +**效果:** 方块自然下落速度降低 10%。 + +**可叠加:** 可以,但建议设置最低速度限制。 + +**作用:** 降低操作压力,提高容错率。 + +**相关合成:** + +```text +慢速下落 + 幽灵落点 = 精准控制 +``` + +--- + +### 3.4 额外预览 + +**类型:** 操作强化 + +**效果:** 增加下一个方块预览数量。 + +**可叠加:** 可以。 + +**作用:** 提高玩家规划能力。 + +**相关联动:** + +- 与暂存槽组合后,可以显著提升方块规划能力。 +- 与操控大师组合后,进一步提高稳定性。 + +--- + +### 3.5 暂存槽 + +**类型:** 操作强化 + +**效果:** 解锁 Hold 功能,允许暂存当前方块。 + +**可叠加:** 不建议重复获得;重复获得时可减少 Hold 冷却。 + +**作用:** 提高操作策略性。 + +**相关合成:** + +```text +暂存槽 + 完美旋转 = 操控大师 +``` + +--- + +### 3.6 最后一搏 + +**类型:** 生存强化 + +**效果:** 当玩家即将失败时,自动清除底部 3 行。 + +**可叠加:** 不建议。 + +**限制:** 每局只能触发一次。 + +**作用:** 提供绝境翻盘能力。 + +**相关合成:** + +```text +最后一搏 + 清屏炸弹 = 终末清场 +``` + +--- + +### 3.7 成长雪球 + +**类型:** 成长强化 + +**效果:** 每次升级后,永久提高 5% 得分和 5% 经验获取。 + +**可叠加:** 不建议重复获得,可通过升级自动成长。 + +**作用:** 适合长期成长流派。 + +**相关合成:** + +```text +成长雪球 + 升级冲击波 = 进化冲击 +``` + +--- + +## 4. 爽感强化列表 + +### 4.1 炸弹方块 + +**类型:** 特殊方块 + +**效果:** 每隔 10 个方块,生成一个炸弹方块。炸弹方块落地后清除周围 3x3 区域。 + +**作用:** 提供范围清除能力,适合爆破流。 + +**相关合成:** + +```text +炸弹方块 + 连锁爆破 = 连环炸弹 +``` + +--- + +### 4.2 激光方块 + +**类型:** 特殊方块 + +**效果:** 特殊方块落地后,清除所在整列。 + +**作用:** 解决高塔死局,适合雷电流。 + +**相关合成:** + +```text +激光方块 + 雷霆四消 = 雷霆激光 +``` + +--- + +### 4.3 十字方块 + +**类型:** 特殊方块 + +**效果:** 特殊方块落地后,清除所在行与所在列。 + +**作用:** 强力清场,适合作为稀有强化。 + +**相关联动:** + +- 与狂热模式组合后,可以在爆发期快速清理棋盘。 +- 与雷电流强化组合后,可以形成大范围行列清除。 + +--- + +### 4.4 彩虹方块 + +**类型:** 特殊方块 + +**效果:** 可以替代任意方块格子,用于补齐消行。 + +**作用:** 提高清行概率,适合与黑洞联动。 + +**相关合成:** + +```text +彩虹方块 + 黑洞 = 虚空核心 +``` + +--- + +### 4.5 连锁爆破 + +**类型:** 消行强化 + +**效果:** 消行后,对被消除行附近随机清除若干格。 + +**作用:** 增强连续清屏爽感。 + +**相关合成:** + +```text +连锁爆破 + 炸弹方块 = 连环炸弹 +``` + +--- + +### 4.6 雷霆四消 + +**类型:** 消行强化 + +**效果:** 每次完成四消时,额外清除随机 2 行。 + +**作用:** 提高四消收益,让四消成为爆发事件。 + +**相关合成:** + +```text +雷霆四消 + 激光方块 = 雷霆激光 +``` + +--- + +### 4.7 升级冲击波 + +**类型:** 成长爆发 + +**效果:** 每次升级时,自动清除底部 2 行。 + +**作用:** 让升级本身产生明显反馈。 + +**相关合成:** + +```text +升级冲击波 + 成长雪球 = 进化冲击 +``` + +--- + +### 4.8 狂热模式 + +**类型:** 爆发强化 + +**效果:** 每累计消除 20 行,进入 10 秒狂热模式。 + +**狂热期间效果:** + +- 得分 x2 +- 经验 x2 +- 下落速度降低 +- 特殊方块出现率提高 + +**相关合成:** + +```text +狂热模式 + 暴走堆叠 = 无限狂热 +``` + +--- + +### 4.9 暴走堆叠 + +**类型:** 连击强化 + +**效果:** 短时间内连续消行越多,得分倍率越高。 + +**作用:** 鼓励玩家保持连击节奏。 + +**相关合成:** + +```text +暴走堆叠 + 狂热模式 = 无限狂热 +``` + +--- + +### 4.10 清屏炸弹 + +**类型:** 主动技能 + +**效果:** 累计消除 30 行后,获得一次主动技能,释放后清除底部 5 行。 + +**作用:** 提供强力翻盘手段。 + +**相关合成:** + +```text +清屏炸弹 + 最后一搏 = 终末清场 +``` + +--- + +### 4.11 黑洞 + +**类型:** 特殊强化 + +**效果:** 清除棋盘中数量最多的一种方块。 + +**作用:** 提供大范围清除能力。 + +**相关合成:** + +```text +黑洞 + 彩虹方块 = 虚空核心 +``` + +--- + +### 4.12 方块风暴 + +**类型:** 爆发强化 + +**效果:** 进入短时间特殊模式,接下来 5 个方块全部变成 I 方块。 + +**作用:** 让玩家快速制造四消机会。 + +**相关联动:** + +- 与雷霆四消组合,可以快速触发四消爆发。 +- 与赌命四消组合,可以形成高风险高收益打法。 + +--- + +## 5. 操作强化列表 + +### 5.1 完美旋转 + +**类型:** 操作强化 + +**效果:** 方块旋转失败时,自动尝试左右偏移一格。 + +**作用:** 优化手感,减少误操作。 + +**相关合成:** + +```text +完美旋转 + 暂存槽 = 操控大师 +``` + +--- + +### 5.2 幽灵落点 + +**类型:** 操作强化 + +**效果:** 显示当前方块最终落点。 + +**作用:** 提高摆放准确性。 + +**相关合成:** + +```text +幽灵落点 + 慢速下落 = 精准控制 +``` + +--- + +### 5.3 时间缓流 + +**类型:** 生存强化 + +**效果:** 当棋盘高度超过 15 行时,自动降低下落速度 30%,持续 8 秒。 + +**作用:** 提供绝境反应时间。 + +**相关联动:** + +- 与最后一搏组合,可以增强濒死翻盘能力。 +- 与精准控制组合,可以帮助玩家稳定修复棋盘。 + +--- + +### 5.4 空中换形 + +**类型:** 主动技能 + +**效果:** 可以将当前方块变成指定方块,例如 I 方块。 + +**限制:** 每局使用次数有限。 + +**作用:** 避免关键时刻抽不到合适方块。 + +**相关联动:** + +- 与雷霆四消组合,可以主动创造四消机会。 +- 与赌命四消组合,可以提高高风险玩法的成功率。 + +--- + +## 6. 风险收益强化列表 + +### 6.1 赌徒 + +**类型:** 风险强化 + +**效果:** 每次升级时,有概率获得双倍强化,也有概率本次强化无效果。 + +**作用:** 增加随机性。 + +**相关合成:** + +```text +赌徒 + 双重选择 = 命运轮盘 +``` + +--- + +### 6.2 双重选择 + +**类型:** 成长强化 + +**效果:** 每次升级可以选择 2 个强化,但下一次升级所需经验提高 30%。 + +**作用:** 提高构筑速度,但增加成长压力。 + +**相关合成:** + +```text +双重选择 + 赌徒 = 命运轮盘 +``` + +--- + +### 6.3 高压奖励 + +**类型:** 风险强化 + +**效果:** 方块下落速度提高 15%,但得分和经验提高 50%。 + +**作用:** 高风险高收益。 + +**相关合成:** + +```text +高压奖励 + 赌命四消 = 极限玩家 +``` + +--- + +### 6.4 赌命四消 + +**类型:** 风险强化 + +**效果:** 普通消行得分降低,但四消奖励提高 300%。 + +**作用:** 鼓励高风险四消打法。 + +**相关合成:** + +```text +赌命四消 + 高压奖励 = 极限玩家 +``` + +--- + +## 7. 进化强化与合成路线 + +### 7.1 连环炸弹 + +**合成路线:** + +```text +炸弹方块 + 连锁爆破 = 连环炸弹 +``` + +**效果:** + +- 炸弹爆炸范围从 3x3 提升为 5x5。 +- 炸弹爆炸后如果造成消行,则再次触发一次小型爆炸。 +- 适合爆破流核心构筑。 + +--- + +### 7.2 雷霆激光 + +**合成路线:** + +```text +激光方块 + 雷霆四消 = 雷霆激光 +``` + +**效果:** + +- 四消时额外发射 2 道激光。 +- 激光随机清除 2 列。 +- 如果激光造成新的消行,额外获得经验。 + +--- + +### 7.3 进化冲击 + +**合成路线:** + +```text +升级冲击波 + 成长雪球 = 进化冲击 +``` + +**效果:** + +- 每次升级时清除底部 3 行。 +- 同时获得 10 秒双倍经验。 +- 等级越高,冲击波奖励越强。 + +--- + +### 7.4 无限狂热 + +**合成路线:** + +```text +狂热模式 + 暴走堆叠 = 无限狂热 +``` + +**效果:** + +- 狂热模式中每次消行都会延长持续时间。 +- 连击数越高,狂热倍率越高。 +- 狂热期间完成四消,额外延长 3 秒。 + +--- + +### 7.5 终末清场 + +**合成路线:** + +```text +清屏炸弹 + 最后一搏 = 终末清场 +``` + +**效果:** + +- 当玩家即将失败时,自动释放一次清屏炸弹。 +- 若成功避免失败,进入 10 秒狂热模式。 +- 每局最多触发一次。 + +--- + +### 7.6 操控大师 + +**合成路线:** + +```text +完美旋转 + 暂存槽 = 操控大师 +``` + +**效果:** + +- Hold 后当前方块短时间内下落速度降低。 +- 每个方块第一次旋转失败时自动修正。 +- 增加一个预览方块。 + +--- + +### 7.7 精准控制 + +**合成路线:** + +```text +幽灵落点 + 慢速下落 = 精准控制 +``` + +**效果:** + +- 显示更明确的落点预测。 +- 连续 3 次无空洞锁定方块,奖励额外经验。 +- 鼓励高质量摆放。 + +--- + +### 7.8 命运轮盘 + +**合成路线:** + +```text +赌徒 + 双重选择 = 命运轮盘 +``` + +**效果:** + +- 每次升级出现 5 个强化选项。 +- 可以选择 2 个。 +- 但其中 1 个可能附带负面效果。 + +--- + +### 7.9 极限玩家 + +**合成路线:** + +```text +高压奖励 + 赌命四消 = 极限玩家 +``` + +**效果:** + +- 方块下落速度继续提高。 +- 四消得分提升 500%。 +- 四消后立刻降低下落速度 5 秒。 +- 如果 30 秒内没有四消,增加一层危险等级。 + +--- + +### 7.10 虚空核心 + +**合成路线:** + +```text +黑洞 + 彩虹方块 = 虚空核心 +``` + +**效果:** + +- 黑洞触发后,随机生成一个彩虹方块。 +- 彩虹方块参与消行时,额外触发一次小型黑洞。 +- 小型黑洞清除棋盘中数量最多的 5 个格子。 + +--- + +## 8. 强化合成总表 + +| 进化强化 | 前置强化 1 | 前置强化 2 | 主要效果 | +|---|---|---|---| +| 连环炸弹 | 炸弹方块 | 连锁爆破 | 爆炸范围扩大并触发二次爆炸 | +| 雷霆激光 | 激光方块 | 雷霆四消 | 四消时额外清除随机列 | +| 进化冲击 | 升级冲击波 | 成长雪球 | 升级时清除更多行并获得双倍经验 | +| 无限狂热 | 狂热模式 | 暴走堆叠 | 狂热期间消行延长持续时间 | +| 终末清场 | 清屏炸弹 | 最后一搏 | 濒死时自动释放清屏炸弹 | +| 操控大师 | 完美旋转 | 暂存槽 | Hold 后减速并增加预览 | +| 精准控制 | 幽灵落点 | 慢速下落 | 高质量摆放获得额外经验 | +| 命运轮盘 | 赌徒 | 双重选择 | 更多强化选择,但可能附带负面 | +| 极限玩家 | 高压奖励 | 赌命四消 | 四消收益极高但风险增加 | +| 虚空核心 | 黑洞 | 彩虹方块 | 黑洞和彩虹方块互相触发 | + +--- + +## 9. 强化联动设计 + +### 9.1 消行联动 + +| 条件 | 联动效果 | +|---|---| +| 消行 + 连锁爆破 | 额外清除附近格子 | +| 四消 + 雷霆四消 | 随机清除额外行 | +| 四消 + 狂热模式 | 狂热时间延长 | +| 炸弹爆炸 + 消行 | 触发二次爆炸 | +| 激光清列 + 消行 | 获得额外经验 | +| 彩虹方块 + 消行 | 提高特殊强化触发概率 | + +--- + +### 9.2 升级联动 + +| 条件 | 联动效果 | +|---|---| +| 升级 + 升级冲击波 | 清除底部行 | +| 升级 + 成长雪球 | 提升永久倍率 | +| 升级 + 双重选择 | 可多选一个强化 | +| 升级 + 赌徒 | 强化可能翻倍或失效 | +| 升级 + 命运轮盘 | 出现更多选项,但可能带诅咒 | + +--- + +### 9.3 生存联动 + +| 条件 | 联动效果 | +|---|---| +| 接近失败 + 最后一搏 | 自动清除底部 3 行 | +| 接近失败 + 时间缓流 | 自动减速 | +| 接近失败 + 清屏炸弹 | 自动释放清屏技能 | +| 复活成功 + 狂热模式 | 进入短时间爆发 | +| 高度过高 + 激光方块 | 提高激光方块出现率 | + +--- + +## 10. 流派设计 + +### 10.1 爆破流 + +**核心强化:** + +```text +炸弹方块 +连锁爆破 +连环炸弹 +清屏炸弹 +黑洞 +``` + +**玩法特点:** + +- 大量范围清除。 +- 容易产生连续爆炸。 +- 爽感强。 +- 需要控制特殊方块出现频率,防止过强。 + +--- + +### 10.2 雷电流 + +**核心强化:** + +```text +激光方块 +雷霆四消 +雷霆激光 +十字方块 +方块风暴 +``` + +**玩法特点:** + +- 行列清除能力强。 +- 四消收益极高。 +- 适合技术型玩家。 +- 视觉表现可以做成闪电、光柱等效果。 + +--- + +### 10.3 狂热流 + +**核心强化:** + +```text +狂热模式 +暴走堆叠 +无限狂热 +成长雪球 +经验强化 +``` + +**玩法特点:** + +- 越消越强。 +- 连击收益高。 +- 后期成长明显。 +- 适合追求高分。 + +--- + +### 10.4 操作流 + +**核心强化:** + +```text +幽灵落点 +慢速下落 +暂存槽 +完美旋转 +操控大师 +精准控制 +``` + +**玩法特点:** + +- 提高手感。 +- 降低失误。 +- 适合稳定通关。 +- 实现难度较低。 + +--- + +### 10.5 赌徒流 + +**核心强化:** + +```text +赌徒 +双重选择 +高压奖励 +赌命四消 +命运轮盘 +极限玩家 +``` + +**玩法特点:** + +- 收益高。 +- 风险高。 +- 随机性强。 +- 肉鸽味更明显。 + +--- + +## 11. C++ 数据结构设计 + +### 11.1 强化标签 + +```cpp +enum class UpgradeTag { + Score, + Exp, + Bomb, + Laser, + Fever, + Survival, + Control, + Gamble, + Special +}; +``` + +--- + +### 11.2 强化类型 + +```cpp +enum class UpgradeType { + Score, + Growth, + Control, + Clear, + Survival, + Special, + Gamble, + Evolution +}; +``` + +--- + +### 11.3 强化结构体 + +```cpp +struct Upgrade { + string name; + string description; + + UpgradeType type; + vector tags; + + vector requiredUpgrades; + + bool isEvolution = false; + bool repeatable = false; +}; +``` + +--- + +### 11.4 玩家强化记录 + +```cpp +struct PlayerStats { + int score = 0; + int level = 1; + int exp = 0; + int requiredExp = 100; + + float scoreMultiplier = 1.0f; + float expMultiplier = 1.0f; + float fallSpeed = 1.0f; + + int previewCount = 1; + int upgradeChoiceCount = 3; + int upgradePickCount = 1; + + bool canHold = false; + bool hasRevive = false; + bool enableRandomCurse = false; + + vector ownedUpgrades; +}; +``` + +--- + +## 12. 强化出现条件判断 + +### 12.1 判断是否拥有指定强化 + +```cpp +bool hasUpgrade(const vector& ownedUpgrades, const string& name) { + return find(ownedUpgrades.begin(), ownedUpgrades.end(), name) != ownedUpgrades.end(); +} +``` + +--- + +### 12.2 判断强化是否可出现 + +```cpp +bool canAppear(const Upgrade& upgrade, const vector& ownedUpgrades) { + for (const string& required : upgrade.requiredUpgrades) { + if (!hasUpgrade(ownedUpgrades, required)) { + return false; + } + } + + if (!upgrade.repeatable && hasUpgrade(ownedUpgrades, upgrade.name)) { + return false; + } + + return true; +} +``` + +--- + +### 12.3 获取可用强化池 + +```cpp +vector getAvailableUpgrades( + const vector& upgradePool, + const vector& ownedUpgrades +) { + vector available; + + for (const Upgrade& upgrade : upgradePool) { + if (canAppear(upgrade, ownedUpgrades)) { + available.push_back(upgrade); + } + } + + return available; +} +``` + +--- + +## 13. 强化池初始化示例 + +```cpp +void initUpgradePool(vector& upgradePool) { + upgradePool.push_back({ + "积分倍率", + "所有得分提高20%。", + UpgradeType::Score, + {UpgradeTag::Score}, + {}, + false, + true + }); + + upgradePool.push_back({ + "经验强化", + "消行获得的经验提高25%。", + UpgradeType::Growth, + {UpgradeTag::Exp}, + {}, + false, + true + }); + + upgradePool.push_back({ + "炸弹方块", + "每隔10个方块,生成一个炸弹方块。", + UpgradeType::Special, + {UpgradeTag::Bomb}, + {}, + false, + false + }); + + upgradePool.push_back({ + "连锁爆破", + "消行后,额外清除附近随机格子。", + UpgradeType::Clear, + {UpgradeTag::Bomb}, + {"炸弹方块"}, + false, + false + }); + + upgradePool.push_back({ + "连环炸弹", + "炸弹范围扩大,并在造成消行后触发二次爆炸。", + UpgradeType::Evolution, + {UpgradeTag::Bomb}, + {"炸弹方块", "连锁爆破"}, + true, + false + }); + + upgradePool.push_back({ + "激光方块", + "特殊方块落地后清除所在整列。", + UpgradeType::Special, + {UpgradeTag::Laser}, + {}, + false, + false + }); + + upgradePool.push_back({ + "雷霆四消", + "四消时额外清除随机2行。", + UpgradeType::Clear, + {UpgradeTag::Laser}, + {}, + false, + false + }); + + upgradePool.push_back({ + "雷霆激光", + "四消时额外发射2道激光,清除随机2列。", + UpgradeType::Evolution, + {UpgradeTag::Laser}, + {"激光方块", "雷霆四消"}, + true, + false + }); +} +``` + +--- + +## 14. 推荐实现优先级 + +### 14.1 必做内容 + +```text +经验升级 +随机三选一 +强化池 +强化前置条件 +玩家已拥有强化记录 +``` + +--- + +### 14.2 推荐实现内容 + +```text +炸弹方块 +激光方块 +狂热模式 +清屏炸弹 +5 个进化强化 +``` + +--- + +### 14.3 可选加分内容 + +```text +主动技能 +负面诅咒 +特殊视觉效果 +多流派成就 +强化稀有度 +``` + +--- + +## 15. 推荐最终实现组合 + +为了控制开发难度,建议优先实现以下 5 条路线: + +```text +1. 炸弹方块 + 连锁爆破 = 连环炸弹 +2. 激光方块 + 雷霆四消 = 雷霆激光 +3. 狂热模式 + 暴走堆叠 = 无限狂热 +4. 清屏炸弹 + 最后一搏 = 终末清场 +5. 赌徒 + 双重选择 = 命运轮盘 +``` + +这些路线辨识度高,爽感强,实现难度适中,适合作为 C++ 大作业的核心创新点。 + +--- + +## 16. 总结 + +本强化系统通过“基础强化、进阶强化、进化强化”三层结构,将传统俄罗斯方块的单局挑战扩展为具有成长路线和构筑策略的肉鸽玩法。 + +玩家不只是被动消行,而是可以围绕爆破流、雷电流、狂热流、操作流、赌徒流等方向进行构筑。不同强化之间的合成和联动提高了游戏的策略深度,也增强了每局游戏的随机性与爽感。 \ No newline at end of file