Files
Tereis/src/source/Tetris.cpp
T
2026-04-28 20:31:41 +08:00

1748 lines
53 KiB
C++

#include "stdafx.h"
#include "Tetris.h"
#include <string>
#include <shellapi.h>
#define MAX_LOADSTRING 100
#define GAME_TIMER_ID 1
#define EFFECT_TIMER_ID 2
#define CREDIT_TIMER_ID 3
#define WM_CREDIT_TICK (WM_APP + 1)
#define GAME_TIMER_INTERVAL 500
#define EFFECT_TIMER_INTERVAL 16
#define CREDIT_TIMER_INTERVAL 5
HINSTANCE hInst;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
bool bgmEnabled = true;
static bool bgmPlaying = false;
static bool bgmUsingMci = false;
static MMRESULT creditTimerHandle = 0;
static constexpr const wchar_t* kBgmAlias = L"TereisBgm";
static constexpr const wchar_t* kReviveVideoAlias = L"TereisReviveVideo";
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
static std::wstring BuildAssetPath(const wchar_t* relativePath);
static std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath);
static bool FileExists(const std::wstring& path);
static void StopBackgroundMusic();
static void StartBackgroundMusic();
/**
* @brief 多媒体定时器回调,用于高频率请求致谢页动画刷新。
*/
static void CALLBACK CreditTimerCallback(UINT, UINT, DWORD_PTR userData, DWORD_PTR, DWORD_PTR)
{
HWND hWnd = reinterpret_cast<HWND>(userData);
if (hWnd != nullptr)
{
PostMessage(hWnd, WM_CREDIT_TICK, 0, 0);
}
}
/**
* @brief 将指定滚动偏移按步长调整,并限制在非负范围内。
*/
static void AdjustScrollOffset(int& scrollOffset, int delta)
{
scrollOffset += delta;
if (scrollOffset < 0)
{
scrollOffset = 0;
}
if (scrollOffset > 2400)
{
scrollOffset = 2400;
}
}
/**
* @brief 按当前窗口缩放返回一次滚动操作的像素距离。
*/
static int GetScrollStep(HWND hWnd, int baseStep)
{
RECT clientRect;
GetClientRect(hWnd, &clientRect);
int clientWidth = clientRect.right - clientRect.left;
int clientHeight = clientRect.bottom - clientRect.top;
int scaleX = MulDiv(clientWidth, 1000, WINDOW_CLIENT_WIDTH);
int scaleY = MulDiv(clientHeight, 1000, WINDOW_CLIENT_HEIGHT);
int scale = (scaleX < scaleY) ? scaleX : scaleY;
if (scale < 500)
{
scale = 500;
}
return MulDiv(baseStep, scale, 1000);
}
struct LayoutMetrics
{
int scale;
int offsetX;
int offsetY;
int layoutWidth;
int layoutHeight;
int grid;
};
static LayoutMetrics GetLayoutMetrics(HWND hWnd)
{
RECT clientRect;
GetClientRect(hWnd, &clientRect);
int clientWidth = clientRect.right - clientRect.left;
int clientHeight = clientRect.bottom - clientRect.top;
int scaleX = MulDiv(clientWidth, 1000, WINDOW_CLIENT_WIDTH);
int scaleY = MulDiv(clientHeight, 1000, WINDOW_CLIENT_HEIGHT);
int scale = (scaleX < scaleY) ? scaleX : scaleY;
if (scale < 500)
{
scale = 500;
}
LayoutMetrics metrics = {};
metrics.scale = scale;
metrics.layoutWidth = MulDiv(WINDOW_CLIENT_WIDTH, scale, 1000);
metrics.layoutHeight = MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000);
metrics.offsetX = (clientWidth - metrics.layoutWidth) / 2;
metrics.offsetY = 0;
metrics.grid = MulDiv(GRID, scale, 1000);
return metrics;
}
static int ScaleValue(const LayoutMetrics& metrics, int value)
{
return MulDiv(value, metrics.scale, 1000);
}
static int ScaleXValue(const LayoutMetrics& metrics, int value)
{
return metrics.offsetX + MulDiv(value, metrics.scale, 1000);
}
static int ScaleYValue(const LayoutMetrics& metrics, int value)
{
return metrics.offsetY + MulDiv(value, metrics.scale, 1000);
}
static RECT GetMenuCardRect(HWND hWnd)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rect =
{
ScaleXValue(metrics, 110),
ScaleYValue(metrics, 70),
ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 110),
ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 70)
};
return rect;
}
static RECT GetMenuOptionRect(HWND hWnd, int index)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT menuCard = GetMenuCardRect(hWnd);
int top = menuCard.top + ScaleValue(metrics, 140) + index * ScaleValue(metrics, 130);
RECT rect =
{
menuCard.left + ScaleValue(metrics, 36),
top,
menuCard.right - ScaleValue(metrics, 36),
top + ScaleValue(metrics, 104)
};
return rect;
}
static RECT GetRulesCardRect(HWND hWnd)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rect =
{
ScaleXValue(metrics, 76),
ScaleYValue(metrics, 54),
ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 76),
ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 54)
};
return rect;
}
static RECT GetHelpOptionRect(HWND hWnd, int index)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rulesCard = GetRulesCardRect(hWnd);
RECT contentRect =
{
rulesCard.left + ScaleValue(metrics, 36),
rulesCard.top + ScaleValue(metrics, 126),
rulesCard.right - ScaleValue(metrics, 36),
rulesCard.bottom - ScaleValue(metrics, 86)
};
int optionHeight = ScaleValue(metrics, 100);
int optionGap = ScaleValue(metrics, 22);
int optionTop = contentRect.top + ScaleValue(metrics, 18);
RECT rect =
{
contentRect.left,
optionTop + index * (optionHeight + optionGap),
contentRect.right,
optionTop + index * (optionHeight + optionGap) + optionHeight
};
return rect;
}
static RECT GetHelpSkillDemoItemRect(HWND hWnd, int index)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rulesCard = GetRulesCardRect(hWnd);
RECT contentRect =
{
rulesCard.left + ScaleValue(metrics, 36),
rulesCard.top + ScaleValue(metrics, 126),
rulesCard.right - ScaleValue(metrics, 36),
rulesCard.bottom - ScaleValue(metrics, 86)
};
int itemHeight = ScaleValue(metrics, 58);
int itemGap = ScaleValue(metrics, 10);
int itemTop = contentRect.top + ScaleValue(metrics, 8) - helpScrollOffset;
RECT rect =
{
contentRect.left,
itemTop + index * (itemHeight + itemGap),
contentRect.right,
itemTop + index * (itemHeight + itemGap) + itemHeight
};
return rect;
}
static RECT GetHelpBackHintRect(HWND hWnd)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rulesCard = GetRulesCardRect(hWnd);
RECT rect =
{
rulesCard.left + ScaleValue(metrics, 36),
rulesCard.bottom - ScaleValue(metrics, 58),
rulesCard.right - ScaleValue(metrics, 36),
rulesCard.bottom - ScaleValue(metrics, 24)
};
return rect;
}
/**
* @brief 获取致谢页左右切换按钮的绘制和点击区域。
*/
static RECT GetCreditArrowRect(HWND hWnd, int direction)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rulesCard = GetRulesCardRect(hWnd);
int size = ScaleValue(metrics, 54);
int centerY = (rulesCard.top + rulesCard.bottom) / 2;
int left = direction < 0
? rulesCard.left + ScaleValue(metrics, 52)
: rulesCard.right - ScaleValue(metrics, 52) - size;
RECT rect =
{
left,
centerY - size / 2,
left + size,
centerY + size / 2
};
return rect;
}
static RECT GetUpgradeOverlayRect(HWND hWnd)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rect =
{
ScaleXValue(metrics, 60),
ScaleYValue(metrics, 80),
ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 60),
ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 80)
};
return rect;
}
static RECT GetUpgradeCardRect(HWND hWnd, int index)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT overlayRect = GetUpgradeOverlayRect(hWnd);
int gap = ScaleValue(metrics, 18);
int horizontalPadding = ScaleValue(metrics, 36);
int verticalTop = overlayRect.top + ScaleValue(metrics, 138);
int columnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3;
if (columnCount < 1)
{
columnCount = 1;
}
int rowCount = (upgradeUiState.optionCount + columnCount - 1) / columnCount;
if (rowCount < 1)
{
rowCount = 1;
}
int cardWidth = (overlayRect.right - overlayRect.left - horizontalPadding * 2 - gap * (columnCount - 1)) / columnCount;
int availableHeight = overlayRect.bottom - verticalTop - ScaleValue(metrics, 72) - (rowCount - 1) * gap;
int cardHeight = availableHeight / rowCount;
int column = index % columnCount;
int row = index / columnCount;
int left = overlayRect.left + horizontalPadding + column * (cardWidth + gap);
int top = verticalTop + row * (cardHeight + gap);
RECT rect = { left, top, left + cardWidth, top + cardHeight };
return rect;
}
static RECT GetGameOverlayRect(HWND hWnd)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
int panelGap = ScaleValue(metrics, SIDE_PANEL_GAP);
int panelWidth = ScaleValue(metrics, SIDE_PANEL_WIDTH);
int boardLeft = ScaleXValue(metrics, WINDOW_PADDING) + panelWidth + panelGap;
int boardTop = ScaleYValue(metrics, WINDOW_PADDING);
int boardWidth = nGameWidth * metrics.grid;
RECT rect =
{
boardLeft + ScaleValue(metrics, 28),
boardTop + metrics.grid * 6 + ScaleValue(metrics, 10),
boardLeft + boardWidth - ScaleValue(metrics, 28),
boardTop + metrics.grid * 10 + ScaleValue(metrics, 30)
};
return rect;
}
static RECT GetOverlayButtonRect(HWND hWnd, int index, int buttonCount)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT overlayRect = GetGameOverlayRect(hWnd);
int gap = buttonCount == 3 ? ScaleValue(metrics, 8) : ScaleValue(metrics, 18);
int sidePadding = buttonCount == 3 ? ScaleValue(metrics, 14) : ScaleValue(metrics, 34);
int width = (overlayRect.right - overlayRect.left - sidePadding * 2 - gap * (buttonCount - 1)) / buttonCount;
int height = ScaleValue(metrics, 44);
int left = overlayRect.left + sidePadding + index * (width + gap);
int top = overlayRect.top + ScaleValue(metrics, 94);
RECT rect = { left, top, left + width, top + height };
return rect;
}
static RECT GetBackButtonRect(HWND hWnd)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
RECT rect =
{
ScaleXValue(metrics, 6),
ScaleYValue(metrics, 6),
ScaleXValue(metrics, 34),
ScaleYValue(metrics, 34)
};
return rect;
}
static void ResetGameTimer(HWND hWnd)
{
KillTimer(hWnd, GAME_TIMER_ID);
SetTimer(hWnd, GAME_TIMER_ID, currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL, nullptr);
}
static bool PlayReviveVideo(HWND hWnd)
{
std::wstring videoPath = BuildAssetPath(L"assets\\video\\video.avi");
if (!FileExists(videoPath))
{
videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.avi");
}
if (!FileExists(videoPath))
{
videoPath = BuildAssetPath(L"assets\\video\\video.mp4");
}
if (!FileExists(videoPath))
{
videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.mp4");
}
if (!FileExists(videoPath))
{
return false;
}
bool shouldResumeBgm = bgmEnabled;
if (bgmPlaying)
{
StopBackgroundMusic();
}
auto tryPlayWithMci = [&](bool forceMpegVideo) -> bool
{
mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr);
std::wstring openCommand = L"open \"" + videoPath + L"\" ";
if (forceMpegVideo)
{
openCommand += L"type mpegvideo ";
}
openCommand += L"alias ";
openCommand += kReviveVideoAlias;
if (mciSendStringW(openCommand.c_str(), nullptr, 0, hWnd) != 0)
{
return false;
}
std::wstring playCommand = std::wstring(L"play ") + kReviveVideoAlias + L" fullscreen wait";
MCIERROR playResult = mciSendStringW(playCommand.c_str(), nullptr, 0, hWnd);
mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr);
return playResult == 0;
};
bool played = tryPlayWithMci(true);
if (!played)
{
played = tryPlayWithMci(false);
}
if (!played)
{
SHELLEXECUTEINFOW shellInfo = {};
shellInfo.cbSize = sizeof(shellInfo);
shellInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shellInfo.hwnd = hWnd;
shellInfo.lpVerb = L"open";
shellInfo.lpFile = videoPath.c_str();
shellInfo.nShow = SW_SHOWNORMAL;
if (ShellExecuteExW(&shellInfo))
{
if (shellInfo.hProcess != nullptr)
{
WaitForSingleObject(shellInfo.hProcess, INFINITE);
CloseHandle(shellInfo.hProcess);
}
played = true;
}
}
if (shouldResumeBgm)
{
StartBackgroundMusic();
}
return played;
}
static std::wstring BuildAssetPath(const wchar_t* relativePath)
{
wchar_t modulePath[MAX_PATH] = {};
GetModuleFileNameW(nullptr, modulePath, MAX_PATH);
std::wstring basePath(modulePath);
size_t lastSlash = basePath.find_last_of(L"\\/");
if (lastSlash != std::wstring::npos)
{
basePath.resize(lastSlash);
}
std::wstring projectRelative = basePath + L"\\..\\..\\" + relativePath;
wchar_t fullPath[MAX_PATH] = {};
DWORD result = GetFullPathNameW(projectRelative.c_str(), MAX_PATH, fullPath, nullptr);
if (result > 0 && result < MAX_PATH)
{
return fullPath;
}
return projectRelative;
}
static std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath)
{
wchar_t currentDirectory[MAX_PATH] = {};
DWORD length = GetCurrentDirectoryW(MAX_PATH, currentDirectory);
if (length == 0 || length >= MAX_PATH)
{
return L"";
}
std::wstring candidate = std::wstring(currentDirectory) + L"\\" + relativePath;
wchar_t fullPath[MAX_PATH] = {};
DWORD result = GetFullPathNameW(candidate.c_str(), MAX_PATH, fullPath, nullptr);
if (result > 0 && result < MAX_PATH)
{
return fullPath;
}
return candidate;
}
static bool FileExists(const std::wstring& path)
{
DWORD attributes = GetFileAttributesW(path.c_str());
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
}
static RECT GetMusicButtonRect(HWND hWnd)
{
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
int size = ScaleValue(metrics, 28);
if (size < 22)
{
size = 22;
}
int marginRight = ScaleValue(metrics, 12);
if (marginRight < 6)
{
marginRight = 6;
}
int marginBottom = ScaleValue(metrics, 12);
if (marginBottom < 6)
{
marginBottom = 6;
}
RECT buttonRect =
{
metrics.offsetX + metrics.layoutWidth - marginRight - size,
metrics.offsetY + metrics.layoutHeight - marginBottom - size,
metrics.offsetX + metrics.layoutWidth - marginRight,
metrics.offsetY + metrics.layoutHeight - marginBottom
};
return buttonRect;
}
static bool IsPointInRect(const RECT& rect, int x, int y)
{
return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
}
static bool TryPlayMciLoop(const std::wstring& path, bool forceMpegVideo)
{
if (!FileExists(path))
{
return false;
}
mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr);
std::wstring openCommand = L"open \"" + path + L"\" ";
if (forceMpegVideo)
{
openCommand += L"type mpegvideo ";
}
openCommand += L"alias ";
openCommand += kBgmAlias;
if (mciSendStringW(openCommand.c_str(), nullptr, 0, nullptr) != 0)
{
return false;
}
std::wstring playCommand = std::wstring(L"play ") + kBgmAlias + L" repeat";
if (mciSendStringW(playCommand.c_str(), nullptr, 0, nullptr) != 0)
{
mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr);
return false;
}
bgmPlaying = true;
bgmUsingMci = true;
return true;
}
static void StopBackgroundMusic()
{
if (bgmUsingMci)
{
mciSendStringW((std::wstring(L"stop ") + kBgmAlias).c_str(), nullptr, 0, nullptr);
mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr);
}
else
{
PlaySoundW(nullptr, nullptr, 0);
}
bgmPlaying = false;
bgmUsingMci = false;
}
static void StartBackgroundMusic()
{
if (!bgmEnabled || bgmPlaying)
{
return;
}
const wchar_t* bgmWavRelativePath = L"assets\\audio\\bgm.wav";
const std::wstring bgmWavCandidates[] =
{
BuildAssetPath(bgmWavRelativePath),
BuildWorkingDirAssetPath(bgmWavRelativePath)
};
for (const std::wstring& candidate : bgmWavCandidates)
{
if (FileExists(candidate) &&
PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP))
{
bgmPlaying = true;
bgmUsingMci = false;
return;
}
}
const wchar_t* oggRelativePath = L"assets\\audio\\bgm.ogg";
const std::wstring oggCandidates[] =
{
BuildAssetPath(oggRelativePath),
BuildWorkingDirAssetPath(oggRelativePath)
};
for (const std::wstring& candidate : oggCandidates)
{
if (TryPlayMciLoop(candidate, false) || TryPlayMciLoop(candidate, true))
{
return;
}
}
const wchar_t* fallbackWavRelativePath = L"assets\\audio\\background.wav";
const std::wstring fallbackWavCandidates[] =
{
BuildAssetPath(fallbackWavRelativePath),
BuildWorkingDirAssetPath(fallbackWavRelativePath)
};
for (const std::wstring& candidate : fallbackWavCandidates)
{
if (FileExists(candidate) &&
PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP))
{
bgmPlaying = true;
bgmUsingMci = false;
return;
}
}
bgmEnabled = false;
}
static void ToggleBackgroundMusic(HWND hWnd)
{
bgmEnabled = !bgmEnabled;
if (bgmEnabled)
{
StartBackgroundMusic();
}
else
{
StopBackgroundMusic();
}
InvalidateRect(hWnd, nullptr, FALSE);
}
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:
timeBeginPeriod(1);
srand((unsigned int)time(nullptr));
ReturnToMainMenu();
StartBackgroundMusic();
ResetGameTimer(hWnd);
SetTimer(hWnd, EFFECT_TIMER_ID, EFFECT_TIMER_INTERVAL, nullptr);
creditTimerHandle = timeSetEvent(
CREDIT_TIMER_INTERVAL,
1,
CreditTimerCallback,
reinterpret_cast<DWORD_PTR>(hWnd),
TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
if (creditTimerHandle == 0)
{
SetTimer(hWnd, CREDIT_TIMER_ID, CREDIT_TIMER_INTERVAL, nullptr);
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
switch (wmId)
{
case IDM_SKILL_DEMO:
OpenSkillDemoScreen();
InvalidateRect(hWnd, nullptr, FALSE);
break;
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_CREDIT_TICK:
if (currentScreen == SCREEN_RULES && helpState.currentPage == 4 && TickCreditAnimation())
{
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
case WM_TIMER:
if (wParam == EFFECT_TIMER_ID)
{
if (TickVisualEffects())
{
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
}
if (wParam == CREDIT_TIMER_ID && creditTimerHandle == 0)
{
if (currentScreen == SCREEN_RULES && helpState.currentPage == 4 && TickCreditAnimation())
{
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
}
if (wParam == GAME_TIMER_ID)
{
bool shouldRefresh = false;
if (feedbackState.visibleTicks > 0)
{
feedbackState.visibleTicks--;
shouldRefresh = true;
}
if (IsRogueSkillDemoMode())
{
if (TickRogueSkillDemo())
{
shouldRefresh = true;
}
}
if (currentMode == MODE_ROGUE && !IsRogueSkillDemoMode() && rogueStats.feverTicks > 0)
{
rogueStats.feverTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentMode == MODE_ROGUE &&
!IsRogueSkillDemoMode() &&
rogueStats.timeDilationTicks > 0 &&
currentScreen == SCREEN_PLAYING &&
!suspendFlag &&
!gameOverFlag)
{
rogueStats.timeDilationTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentMode == MODE_ROGUE && !IsRogueSkillDemoMode() && rogueStats.extremeSlowTicks > 0)
{
rogueStats.extremeSlowTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentMode == MODE_ROGUE &&
!IsRogueSkillDemoMode() &&
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 && !IsRogueSkillDemoMode() && rogueStats.holdSlowTicks > 0)
{
rogueStats.holdSlowTicks--;
currentFallInterval = GetRogueFallInterval();
ResetGameTimer(hWnd);
shouldRefresh = true;
}
if (currentScreen == SCREEN_PLAYING &&
!suspendFlag &&
!gameOverFlag)
{
if (currentMode == MODE_ROGUE && !IsRogueSkillDemoMode())
{
int previousFallInterval = currentFallInterval;
AdvanceRogueDifficulty(currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL);
if (currentFallInterval != previousFallInterval)
{
ResetGameTimer(hWnd);
}
}
if (currentMode == MODE_ROGUE && !IsRogueSkillDemoMode() && 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();
CheckRogueLevelProgress();
}
}
if (!gameOverFlag)
{
ComputeTarget();
}
shouldRefresh = true;
}
if (shouldRefresh)
{
InvalidateRect(hWnd, nullptr, FALSE);
}
}
break;
case WM_SIZE:
InvalidateRect(hWnd, nullptr, FALSE);
break;
case WM_LBUTTONUP:
{
int mouseX = static_cast<short>(LOWORD(lParam));
int mouseY = static_cast<short>(HIWORD(lParam));
RECT musicButtonRect = GetMusicButtonRect(hWnd);
if (IsPointInRect(musicButtonRect, mouseX, mouseY))
{
ToggleBackgroundMusic(hWnd);
break;
}
if (currentScreen != SCREEN_MENU && IsPointInRect(GetBackButtonRect(hWnd), mouseX, mouseY))
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (currentScreen == SCREEN_MENU)
{
for (int i = 0; i < menuState.optionCount; i++)
{
if (!IsPointInRect(GetMenuOptionRect(hWnd, i), mouseX, mouseY))
{
continue;
}
menuState.selectedIndex = i;
if (i == 0)
{
StartGameWithMode(MODE_CLASSIC);
}
else if (i == 1)
{
StartGameWithMode(MODE_ROGUE);
}
else if (i == 2)
{
OpenRulesScreen();
}
else
{
OpenCreditScreen();
}
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
break;
}
if (currentScreen == SCREEN_RULES)
{
if (helpState.currentPage == 0)
{
for (int i = 0; i < helpState.optionCount; i++)
{
if (IsPointInRect(GetHelpOptionRect(hWnd, i), mouseX, mouseY))
{
helpState.selectedIndex = i;
if (i == 3)
{
helpState.currentPage = 5;
helpState.selectedIndex = 0;
helpScrollOffset = 0;
}
else
{
helpState.currentPage = i + 1;
helpScrollOffset = 0;
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
}
if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY))
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
}
}
else if (helpState.currentPage == 5)
{
if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY))
{
helpState.currentPage = 0;
helpState.selectedIndex = 3;
helpScrollOffset = 0;
InvalidateRect(hWnd, nullptr, FALSE);
}
else
{
int demoCount = GetRogueSkillDemoCount();
for (int i = 0; i < demoCount; i++)
{
if (IsPointInRect(GetHelpSkillDemoItemRect(hWnd, i), mouseX, mouseY))
{
helpState.selectedIndex = i;
StartRogueSkillDemoAt(i);
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
}
}
}
else if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY))
{
if (helpState.currentPage == 4)
{
ReturnToMainMenu();
}
else
{
helpState.currentPage = 0;
helpScrollOffset = 0;
}
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 4 && IsPointInRect(GetCreditArrowRect(hWnd, -1), mouseX, mouseY))
{
ChangeCreditPage(-1);
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 4 && IsPointInRect(GetCreditArrowRect(hWnd, 1), mouseX, mouseY))
{
ChangeCreditPage(1);
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
}
if (currentScreen == SCREEN_UPGRADE)
{
for (int i = 0; i < upgradeUiState.optionCount; i++)
{
if (!IsPointInRect(GetUpgradeCardRect(hWnd, i), mouseX, mouseY))
{
continue;
}
upgradeUiState.selectedIndex = i;
if (upgradeUiState.picksRemaining > 1)
{
bool currentlyMarked = upgradeUiState.marked[i];
if (currentlyMarked)
{
upgradeUiState.marked[i] = false;
if (upgradeUiState.markedCount > 0)
{
upgradeUiState.markedCount--;
}
}
else if (upgradeUiState.markedCount < upgradeUiState.picksRemaining)
{
upgradeUiState.marked[i] = true;
upgradeUiState.markedCount++;
}
if (upgradeUiState.markedCount == upgradeUiState.picksRemaining)
{
ConfirmUpgradeSelection();
ResetGameTimer(hWnd);
}
}
else
{
ConfirmUpgradeSelection();
ResetGameTimer(hWnd);
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
break;
}
if (currentScreen == SCREEN_PLAYING && suspendFlag)
{
if (IsPointInRect(GetOverlayButtonRect(hWnd, 0, 2), mouseX, mouseY))
{
suspendFlag = false;
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (IsPointInRect(GetOverlayButtonRect(hWnd, 1, 2), mouseX, mouseY))
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
}
if (currentScreen == SCREEN_PLAYING && gameOverFlag)
{
if (reviveAvailable)
{
if (IsPointInRect(GetOverlayButtonRect(hWnd, 0, 3), mouseX, mouseY))
{
if (PlayReviveVideo(hWnd))
{
ReviveAfterVideo();
ResetGameTimer(hWnd);
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (IsPointInRect(GetOverlayButtonRect(hWnd, 1, 3), mouseX, mouseY))
{
StartGameWithMode(currentMode);
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (IsPointInRect(GetOverlayButtonRect(hWnd, 2, 3), mouseX, mouseY))
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
}
else
{
if (IsPointInRect(GetOverlayButtonRect(hWnd, 0, 2), mouseX, mouseY))
{
StartGameWithMode(currentMode);
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (IsPointInRect(GetOverlayButtonRect(hWnd, 1, 2), mouseX, mouseY))
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
case WM_MOUSEWHEEL:
{
int wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
int direction = (wheelDelta > 0) ? -1 : 1;
int scrollStep = GetScrollStep(hWnd, 64);
if (currentScreen == SCREEN_RULES && helpState.currentPage != 0)
{
AdjustScrollOffset(helpScrollOffset, direction * scrollStep);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (currentScreen == SCREEN_PLAYING && currentMode == MODE_ROGUE)
{
AdjustScrollOffset(upgradeListScrollOffset, direction * scrollStep);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
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 if (menuState.selectedIndex == 2)
{
OpenRulesScreen();
}
else
{
OpenCreditScreen();
}
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_ESCAPE:
DestroyWindow(hWnd);
break;
default:
break;
}
break;
}
if (currentScreen == SCREEN_RULES)
{
switch (wParam)
{
case VK_UP:
case VK_LEFT:
case 'W':
case 'A':
if (helpState.currentPage == 0)
{
helpState.selectedIndex--;
if (helpState.selectedIndex < 0)
{
helpState.selectedIndex = helpState.optionCount - 1;
}
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 4)
{
ChangeCreditPage(-1);
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 5)
{
helpState.selectedIndex--;
if (helpState.selectedIndex < 0)
{
helpState.selectedIndex = GetRogueSkillDemoCount() - 1;
}
if (helpState.selectedIndex * 68 < helpScrollOffset)
{
helpScrollOffset = helpState.selectedIndex * 68;
}
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
case VK_DOWN:
case VK_RIGHT:
case 'S':
case 'D':
if (helpState.currentPage == 0)
{
helpState.selectedIndex++;
if (helpState.selectedIndex >= helpState.optionCount)
{
helpState.selectedIndex = 0;
}
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 4)
{
ChangeCreditPage(1);
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 5)
{
helpState.selectedIndex++;
if (helpState.selectedIndex >= GetRogueSkillDemoCount())
{
helpState.selectedIndex = 0;
helpScrollOffset = 0;
}
else if (helpState.selectedIndex * 68 > helpScrollOffset + 360)
{
helpScrollOffset = helpState.selectedIndex * 68 - 360;
}
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
case VK_RETURN:
case VK_SPACE:
if (helpState.currentPage == 0)
{
if (helpState.selectedIndex == 3)
{
helpState.currentPage = 5;
helpState.selectedIndex = 0;
helpScrollOffset = 0;
}
else
{
helpState.currentPage = helpState.selectedIndex + 1;
helpScrollOffset = 0;
}
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 5)
{
StartRogueSkillDemoAt(helpState.selectedIndex);
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
case VK_ESCAPE:
case VK_BACK:
case 'M':
if (helpState.currentPage == 0)
{
ReturnToMainMenu();
}
else if (helpState.currentPage == 4)
{
ReturnToMainMenu();
}
else if (helpState.currentPage == 5)
{
helpState.currentPage = 0;
helpState.selectedIndex = 3;
helpScrollOffset = 0;
}
else
{
helpState.currentPage = 0;
helpScrollOffset = 0;
}
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:
ConfirmUpgradeSelection();
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_SPACE:
if (upgradeUiState.picksRemaining > 1 && upgradeUiState.optionCount > 0)
{
bool currentlyMarked = upgradeUiState.marked[upgradeUiState.selectedIndex];
if (currentlyMarked)
{
upgradeUiState.marked[upgradeUiState.selectedIndex] = false;
if (upgradeUiState.markedCount > 0)
{
upgradeUiState.markedCount--;
}
}
else if (upgradeUiState.markedCount < upgradeUiState.picksRemaining)
{
upgradeUiState.marked[upgradeUiState.selectedIndex] = true;
upgradeUiState.markedCount++;
}
InvalidateRect(hWnd, nullptr, FALSE);
}
else
{
ConfirmUpgradeSelection();
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
case 'M':
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
default:
break;
}
break;
}
if (IsRogueSkillDemoMode())
{
if (wParam == 'N')
{
AdvanceRogueSkillDemo();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
else if (wParam == 'R')
{
RestartCurrentRogueSkillDemo();
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
else if (wParam == VK_ESCAPE || wParam == VK_BACK || wParam == 'M')
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
}
if (!IsRogueSkillDemoMode() && wParam == 'M')
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (!IsRogueSkillDemoMode() && wParam == 'R')
{
StartGameWithMode(currentMode);
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (!IsRogueSkillDemoMode() && wParam == 'P')
{
suspendFlag = !suspendFlag;
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (wParam == 'G')
{
targetFlag = !targetFlag;
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (gameOverFlag && reviveAvailable && wParam == 'V')
{
if (PlayReviveVideo(hWnd))
{
ReviveAfterVideo();
ResetGameTimer(hWnd);
}
else
{
SetFeedbackMessage(_T("视频播放失败"), _T("无法打开复活视频,复活机会未消耗。"), 14);
}
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
if (gameOverFlag || suspendFlag)
{
break;
}
if (currentMode == MODE_ROGUE && (wParam == 'J' || wParam == 'K'))
{
int direction = (wParam == 'J') ? 1 : -1;
AdjustScrollOffset(upgradeListScrollOffset, direction * GetScrollStep(hWnd, 52));
InvalidateRect(hWnd, nullptr, FALSE);
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();
CheckRogueLevelProgress();
}
}
break;
case VK_UP:
case 'W':
Rotate();
break;
case VK_SPACE:
DropDown();
Fixing();
if (!gameOverFlag)
{
DeleteLines();
CheckRogueLevelProgress();
}
break;
case 'C':
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
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);
KillTimer(hWnd, EFFECT_TIMER_ID);
if (creditTimerHandle != 0)
{
timeKillEvent(creditTimerHandle);
creditTimerHandle = 0;
}
else
{
KillTimer(hWnd, CREDIT_TIMER_ID);
}
StopBackgroundMusic();
timeEndPeriod(1);
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;
}