Files
Tereis/src/source/Tetris.cpp
T

613 lines
18 KiB
C++

#include "stdafx.h"
#include "Tetris.h"
#define MAX_LOADSTRING 100
#define GAME_TIMER_ID 1
#define GAME_TIMER_INTERVAL 500
HINSTANCE hInst;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
ATOM MyRegisterClass(HINSTANCE hInstance);
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,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
HMODULE user32Module = GetModuleHandle(_T("user32.dll"));
if (user32Module != nullptr)
{
typedef BOOL(WINAPI* SetProcessDPIAwareFunc)();
SetProcessDPIAwareFunc setProcessDPIAware =
(SetProcessDPIAwareFunc)GetProcAddress(user32Module, "SetProcessDPIAware");
if (setProcessDPIAware != nullptr)
{
setProcessDPIAware();
}
}
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_TETRIS, szWindowClass, MAX_LOADSTRING);
lstrcpy(szTitle, _T("\u4fc4\u7f57\u65af\u65b9\u5757"));
MyRegisterClass(hInstance);
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TETRIS));
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TETRIS));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
RECT rect = { 0, 0, WINDOW_CLIENT_WIDTH, WINDOW_CLIENT_HEIGHT };
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
hInst = hInstance;
HWND hWnd = CreateWindow(
szWindowClass,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
rect.right - rect.left,
rect.bottom - rect.top,
nullptr,
nullptr,
hInstance,
nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
srand((unsigned int)time(nullptr));
ReturnToMainMenu();
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_TIMER:
if (wParam == GAME_TIMER_ID)
{
bool shouldRefresh = false;
if (feedbackState.visibleTicks > 0)
{
feedbackState.visibleTicks--;
shouldRefresh = true;
}
if (currentMode == MODE_ROGUE && rogueStats.feverTicks > 0)
{
rogueStats.feverTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentMode == MODE_ROGUE &&
rogueStats.timeDilationTicks > 0 &&
currentScreen == SCREEN_PLAYING &&
!suspendFlag &&
!gameOverFlag)
{
rogueStats.timeDilationTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentMode == MODE_ROGUE && rogueStats.extremeSlowTicks > 0)
{
rogueStats.extremeSlowTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentMode == MODE_ROGUE &&
rogueStats.extremePlayerLevel > 0 &&
currentScreen == SCREEN_PLAYING &&
!suspendFlag &&
!gameOverFlag)
{
if (rogueStats.extremeDangerTicks > 0)
{
rogueStats.extremeDangerTicks--;
}
else
{
rogueStats.extremeDangerTicks = 30;
if (rogueStats.extremeDangerLevel < 5)
{
rogueStats.extremeDangerLevel++;
}
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
feedbackState.visibleTicks = 10;
lstrcpyn(feedbackState.title, _T("极限压力升高"), sizeof(feedbackState.title) / sizeof(TCHAR));
lstrcpyn(feedbackState.detail, _T("30 秒内没有完成四消,危险等级提升,下落速度进一步加快。"), sizeof(feedbackState.detail) / sizeof(TCHAR));
shouldRefresh = true;
}
}
if (currentMode == MODE_ROGUE && rogueStats.holdSlowTicks > 0)
{
rogueStats.holdSlowTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentScreen == SCREEN_PLAYING &&
!suspendFlag &&
!gameOverFlag)
{
if (currentMode == MODE_ROGUE)
{
int previousFallInterval = currentFallInterval;
AdvanceRogueDifficulty(currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL);
if (currentFallInterval != previousFallInterval)
{
ResetGameTimer(hWnd);
}
}
if (currentMode == MODE_ROGUE && rogueStats.timeDilationLevel > 0 && rogueStats.timeDilationTicks <= 0)
{
int occupiedHeight = 0;
int playableHeight = GetRoguePlayableHeight();
for (int y = 0; y < playableHeight; y++)
{
bool hasCell = false;
for (int x = 0; x < nGameWidth; x++)
{
if (workRegion[y][x] != 0)
{
hasCell = true;
break;
}
}
if (hasCell)
{
occupiedHeight = playableHeight - y;
break;
}
}
if (occupiedHeight > 15)
{
rogueStats.timeDilationTicks = 8;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
feedbackState.visibleTicks = 10;
lstrcpyn(feedbackState.title, _T("\u65f6\u95f4\u7f13\u6d41"), sizeof(feedbackState.title) / sizeof(TCHAR));
lstrcpyn(feedbackState.detail, _T("堆叠高度超过 15 行,接下来 8 秒下落速度降低 30%。"), sizeof(feedbackState.detail) / sizeof(TCHAR));
shouldRefresh = true;
}
}
if (CanMoveDown())
{
MoveDown();
}
else
{
Fixing();
if (!gameOverFlag)
{
DeleteLines();
}
}
if (!gameOverFlag)
{
ComputeTarget();
}
shouldRefresh = true;
}
if (shouldRefresh)
{
InvalidateRect(hWnd, nullptr, FALSE);
}
}
break;
case WM_SIZE:
InvalidateRect(hWnd, nullptr, FALSE);
break;
case WM_KEYDOWN:
if (currentScreen == SCREEN_MENU)
{
switch (wParam)
{
case VK_UP:
case VK_LEFT:
case 'W':
case 'A':
menuState.selectedIndex--;
if (menuState.selectedIndex < 0)
{
menuState.selectedIndex = menuState.optionCount - 1;
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_DOWN:
case VK_RIGHT:
case 'S':
case 'D':
menuState.selectedIndex++;
if (menuState.selectedIndex >= menuState.optionCount)
{
menuState.selectedIndex = 0;
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_RETURN:
case VK_SPACE:
if (menuState.selectedIndex == 0)
{
StartGameWithMode(MODE_CLASSIC);
}
else if (menuState.selectedIndex == 1)
{
StartGameWithMode(MODE_ROGUE);
}
else
{
OpenRulesScreen();
}
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_ESCAPE:
DestroyWindow(hWnd);
break;
default:
break;
}
break;
}
if (currentScreen == SCREEN_RULES)
{
switch (wParam)
{
case VK_ESCAPE:
case VK_BACK:
case 'M':
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
default:
break;
}
break;
}
if (currentScreen == SCREEN_UPGRADE)
{
int upgradeColumnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3;
if (upgradeColumnCount < 1)
{
upgradeColumnCount = 1;
}
switch (wParam)
{
case VK_LEFT:
case 'A':
if (upgradeUiState.optionCount > 1)
{
int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount);
if (upgradeUiState.selectedIndex > rowStart)
{
upgradeUiState.selectedIndex--;
}
else
{
int rowEnd = rowStart + upgradeColumnCount - 1;
if (rowEnd >= upgradeUiState.optionCount)
{
rowEnd = upgradeUiState.optionCount - 1;
}
upgradeUiState.selectedIndex = rowEnd;
}
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_RIGHT:
case 'D':
if (upgradeUiState.optionCount > 1)
{
int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount);
int rowEnd = rowStart + upgradeColumnCount - 1;
if (rowEnd >= upgradeUiState.optionCount)
{
rowEnd = upgradeUiState.optionCount - 1;
}
if (upgradeUiState.selectedIndex < rowEnd)
{
upgradeUiState.selectedIndex++;
}
else
{
upgradeUiState.selectedIndex = rowStart;
}
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_UP:
case 'W':
if (upgradeUiState.selectedIndex >= upgradeColumnCount)
{
upgradeUiState.selectedIndex -= upgradeColumnCount;
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_DOWN:
case 'S':
if (upgradeUiState.selectedIndex + upgradeColumnCount < upgradeUiState.optionCount)
{
upgradeUiState.selectedIndex += upgradeColumnCount;
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_RETURN:
case VK_SPACE:
ConfirmUpgradeSelection();
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
default:
break;
}
break;
}
if (wParam == 'M')
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (wParam == 'R')
{
StartGameWithMode(currentMode);
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (wParam == 'P')
{
suspendFlag = !suspendFlag;
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (wParam == 'G')
{
targetFlag = !targetFlag;
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (gameOverFlag || suspendFlag)
{
break;
}
switch (wParam)
{
case VK_LEFT:
case 'A':
if (CanMoveLeft())
{
MoveLeft();
}
break;
case VK_RIGHT:
case 'D':
if (CanMoveRight())
{
MoveRight();
}
break;
case VK_DOWN:
case 'S':
if (CanMoveDown())
{
MoveDown();
}
else
{
Fixing();
if (!gameOverFlag)
{
DeleteLines();
}
}
break;
case VK_UP:
case 'W':
Rotate();
break;
case VK_SPACE:
DropDown();
Fixing();
if (!gameOverFlag)
{
DeleteLines();
}
break;
case 'C':
case VK_SHIFT:
HoldCurrentPiece();
break;
case 'Z':
UseBlackHole();
break;
case 'X':
UseScreenBomb();
break;
case 'V':
UseAirReshape();
break;
default:
break;
}
if (!gameOverFlag)
{
ComputeTarget();
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT clientRect;
GetClientRect(hWnd, &clientRect);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(
hdc,
clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top);
HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
TDrawScreen(memDC, hWnd);
BitBlt(
hdc,
0, 0,
clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top,
memDC,
0, 0,
SRCCOPY);
SelectObject(memDC, oldBitmap);
DeleteObject(memBitmap);
DeleteDC(memDC);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
KillTimer(hWnd, GAME_TIMER_ID);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
SetWindowText(hDlg, _T("\u5173\u4e8e\u4fc4\u7f57\u65af\u65b9\u5757"));
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}