diff --git a/src/include/Tetris.h b/src/include/Tetris.h index a6e7283..ad5358a 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -38,10 +38,14 @@ struct PlayerStats int exp; int requiredExp; int totalLinesCleared; + int scoreMultiplierPercent; + int expMultiplierPercent; + int slowFallStacks; }; struct UpgradeOption { + int id; const TCHAR* name; const TCHAR* category; const TCHAR* description; @@ -85,6 +89,7 @@ extern PlayerStats rogueStats; extern UpgradeUiState upgradeUiState; extern int currentScreen; extern int currentMode; +extern int currentFallInterval; extern int bricks[7][4][4][4]; extern COLORREF BrickColor[7]; diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index db1b9bf..89f992d 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -14,6 +14,12 @@ BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); +static void ResetGameTimer(HWND hWnd) +{ + KillTimer(hWnd, GAME_TIMER_ID); + SetTimer(hWnd, GAME_TIMER_ID, currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL, nullptr); +} + int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, @@ -118,7 +124,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_CREATE: srand((unsigned int)time(nullptr)); ReturnToMainMenu(); - SetTimer(hWnd, GAME_TIMER_ID, GAME_TIMER_INTERVAL, nullptr); + ResetGameTimer(hWnd); InvalidateRect(hWnd, nullptr, FALSE); break; case WM_COMMAND: @@ -198,6 +204,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case VK_RETURN: case VK_SPACE: StartGameWithMode(menuState.selectedIndex); + ResetGameTimer(hWnd); InvalidateRect(hWnd, nullptr, FALSE); break; case VK_ESCAPE: @@ -234,6 +241,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case VK_RETURN: case VK_SPACE: ConfirmUpgradeSelection(); + ResetGameTimer(hWnd); InvalidateRect(hWnd, nullptr, FALSE); break; default: @@ -252,6 +260,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (wParam == 'R') { StartGameWithMode(currentMode); + ResetGameTimer(hWnd); InvalidateRect(hWnd, nullptr, FALSE); break; } diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 2e06ec3..16d90c3 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -13,10 +13,18 @@ Point point = { 0, 0 }; Point target = { 0, 0 }; MenuState menuState = { 0, 2 }; PlayerStats classicStats = { 0, 1, 0, 0, 0 }; -PlayerStats rogueStats = { 0, 1, 0, 100, 0 }; +PlayerStats rogueStats = { 0, 1, 0, 30, 0, 100, 100, 0 }; UpgradeUiState upgradeUiState = { 0, 0, 0, 0, {} }; int currentScreen = SCREEN_MENU; int currentMode = MODE_CLASSIC; +int currentFallInterval = 500; + +enum UpgradeId +{ + UPGRADE_SCORE_MULTIPLIER = 0, + UPGRADE_EXP_MULTIPLIER = 1, + UPGRADE_SLOW_FALL = 2 +}; int bricks[7][4][4][4] = { @@ -151,8 +159,11 @@ static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) stats.score = 0; stats.level = 1; stats.exp = 0; - stats.requiredExp = useRogueRules ? 100 : 0; + stats.requiredExp = useRogueRules ? 30 : 0; stats.totalLinesCleared = 0; + stats.scoreMultiplierPercent = 100; + stats.expMultiplierPercent = 100; + stats.slowFallStacks = 0; } static int GetRogueScoreByLines(int linesCleared) @@ -187,7 +198,7 @@ static int ApplyLevelProgress(PlayerStats& stats) { stats.exp -= stats.requiredExp; stats.level++; - stats.requiredExp = 100 + stats.level * 50; + stats.requiredExp = 20 + stats.level * 10; levelUps++; } @@ -199,17 +210,44 @@ static void FillUpgradeOptions() upgradeUiState.optionCount = 3; upgradeUiState.selectedIndex = 0; + upgradeUiState.options[0].id = UPGRADE_SCORE_MULTIPLIER; upgradeUiState.options[0].name = _T("\u5206\u6570\u500d\u7387"); upgradeUiState.options[0].category = _T("\u5f97\u5206"); - upgradeUiState.options[0].description = _T("\u6240\u6709\u5f97\u5206\u5c0f\u5e45\u63d0\u5347\u3002"); + upgradeUiState.options[0].description = _T("\u6240\u6709\u5f97\u5206\u63d0\u9ad8 20%\u3002"); + upgradeUiState.options[1].id = UPGRADE_EXP_MULTIPLIER; upgradeUiState.options[1].name = _T("\u7ecf\u9a8c\u5f3a\u5316"); upgradeUiState.options[1].category = _T("\u6210\u957f"); - upgradeUiState.options[1].description = _T("\u540e\u7eed\u6d88\u884c\u53ef\u4ee5\u83b7\u5f97\u66f4\u591a EXP\u3002"); + upgradeUiState.options[1].description = _T("\u540e\u7eed\u6d88\u884c\u83b7\u5f97 EXP \u63d0\u9ad8 25%\u3002"); + upgradeUiState.options[2].id = UPGRADE_SLOW_FALL; upgradeUiState.options[2].name = _T("\u6162\u901f\u4e0b\u843d"); upgradeUiState.options[2].category = _T("\u64cd\u4f5c"); - upgradeUiState.options[2].description = _T("\u964d\u4f4e\u81ea\u7136\u4e0b\u843d\u901f\u5ea6\uff0c\u63d0\u9ad8\u5bb9\u9519\u3002"); + upgradeUiState.options[2].description = _T("\u81ea\u7136\u4e0b\u843d\u53d8\u6162\uff0c\u6bcf\u6b21\u63d0\u9ad8 80ms\u3002"); +} + +static int GetRogueFallInterval() +{ + return 500 + rogueStats.slowFallStacks * 80; +} + +static void ApplyUpgradeById(int upgradeId) +{ + switch (upgradeId) + { + case UPGRADE_SCORE_MULTIPLIER: + rogueStats.scoreMultiplierPercent += 20; + break; + case UPGRADE_EXP_MULTIPLIER: + rogueStats.expMultiplierPercent += 25; + break; + case UPGRADE_SLOW_FALL: + rogueStats.slowFallStacks++; + currentFallInterval = GetRogueFallInterval(); + break; + default: + break; + } } static void ApplyLineClearResult(int linesCleared) @@ -227,9 +265,15 @@ static void ApplyLineClearResult(int linesCleared) return; } + int scoreGain = GetRogueScoreByLines(linesCleared); + scoreGain = scoreGain * rogueStats.scoreMultiplierPercent / 100; + + int expGain = GetRogueExpByLines(linesCleared); + expGain = expGain * rogueStats.expMultiplierPercent / 100; + rogueStats.totalLinesCleared += linesCleared; - rogueStats.score += GetRogueScoreByLines(linesCleared); - rogueStats.exp += GetRogueExpByLines(linesCleared); + rogueStats.score += scoreGain; + rogueStats.exp += expGain; upgradeUiState.pendingCount += ApplyLevelProgress(rogueStats); tScore = rogueStats.score; @@ -610,6 +654,7 @@ void Restart() gameOverFlag = false; suspendFlag = false; targetFlag = true; + currentFallInterval = 500; ResetPlayerStats(classicStats, false); ResetPlayerStats(rogueStats, true); @@ -633,6 +678,7 @@ void StartGameWithMode(int mode) currentMode = mode; currentScreen = SCREEN_PLAYING; Restart(); + currentFallInterval = (currentMode == MODE_ROGUE) ? GetRogueFallInterval() : 500; tScore = (currentMode == MODE_CLASSIC) ? classicStats.score : rogueStats.score; } @@ -668,6 +714,7 @@ void ConfirmUpgradeSelection() return; } + ApplyUpgradeById(upgradeUiState.options[upgradeUiState.selectedIndex].id); upgradeUiState.totalChosenCount++; if (upgradeUiState.pendingCount > 0) diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index aa1ead0..13a156d 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -429,9 +429,21 @@ void TDrawScreen(HDC hdc, HWND hWnd) _stprintf_s(expText, _T("EXP %d / %d"), rogueStats.exp, rogueStats.requiredExp); TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(248), expText, lstrlen(expText)); + TCHAR scoreMultiplierText[64]; + _stprintf_s(scoreMultiplierText, _T("\u5206\u6570\u500d\u7387 %d%%"), rogueStats.scoreMultiplierPercent); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(284), scoreMultiplierText, lstrlen(scoreMultiplierText)); + + TCHAR expMultiplierText[64]; + _stprintf_s(expMultiplierText, _T("EXP \u500d\u7387 %d%%"), rogueStats.expMultiplierPercent); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(320), expMultiplierText, lstrlen(expMultiplierText)); + + TCHAR fallText[64]; + _stprintf_s(fallText, _T("\u4e0b\u843d\u95f4\u9694 %dms"), currentFallInterval); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(356), fallText, lstrlen(fallText)); + SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(284), _T("\u4e09\u9009\u4e00\u5f3a\u5316\uff1a\u4e0b\u4e00\u6b65\u63a5\u5165"), lstrlen(_T("\u4e09\u9009\u4e00\u5f3a\u5316\uff1a\u4e0b\u4e00\u6b65\u63a5\u5165"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(392), _T("\u5f53\u524d\u5df2\u9009\u5f3a\u5316\u6570\u91cf\u4ec5\u7528\u4e8e\u6d4b\u8bd5\u7248"), lstrlen(_T("\u5f53\u524d\u5df2\u9009\u5f3a\u5316\u6570\u91cf\u4ec5\u7528\u4e8e\u6d4b\u8bd5\u7248"))); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); } @@ -450,9 +462,9 @@ void TDrawScreen(HDC hdc, HWND hWnd) RECT nextCard = { panelRect.left + SS(24), - panelRect.top + SS(358), + panelRect.top + SS(430), panelRect.left + SS(24) + grid * 4 + SS(32), - panelRect.top + SS(358) + grid * 4 + SS(32) + panelRect.top + SS(430) + grid * 4 + SS(32) }; HBRUSH nextCardBrush = CreateSolidBrush(RGB(255, 238, 244)); @@ -493,14 +505,12 @@ void TDrawScreen(HDC hdc, HWND hWnd) } SelectObject(hdc, sectionFont); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(538), _T("\u64cd\u4f5c\u63d0\u793a"), lstrlen(_T("\u64cd\u4f5c\u63d0\u793a"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(610), _T("\u64cd\u4f5c\u63d0\u793a"), lstrlen(_T("\u64cd\u4f5c\u63d0\u793a"))); SelectObject(hdc, bodyFont); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(580), _T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"), lstrlen(_T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(616), _T("Space\uff1a\u5feb\u901f\u4e0b\u843d"), lstrlen(_T("Space\uff1a\u5feb\u901f\u4e0b\u843d"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(652), _T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"), lstrlen(_T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(688), _T("G\uff1a\u663e\u793a / \u9690\u85cf\u843d\u70b9"), lstrlen(_T("G\uff1a\u663e\u793a / \u9690\u85cf\u843d\u70b9"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(724), _T("M\uff1a\u8fd4\u56de\u83dc\u5355"), lstrlen(_T("M\uff1a\u8fd4\u56de\u83dc\u5355"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(652), _T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"), lstrlen(_T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(688), _T("Space\uff1a\u5feb\u901f\u4e0b\u843d"), lstrlen(_T("Space\uff1a\u5feb\u901f\u4e0b\u843d"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(724), _T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"), lstrlen(_T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"))); if (suspendFlag || gameOverFlag) {