1022 lines
29 KiB
C++
1022 lines
29 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 GAME_TIMER_INTERVAL 500
|
|
#define EFFECT_TIMER_INTERVAL 33
|
|
|
|
HINSTANCE hInst;
|
|
TCHAR szTitle[MAX_LOADSTRING];
|
|
TCHAR szWindowClass[MAX_LOADSTRING];
|
|
bool bgmEnabled = true;
|
|
|
|
static bool bgmPlaying = false;
|
|
static bool bgmUsingMci = false;
|
|
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();
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
int layoutWidth = MulDiv(WINDOW_CLIENT_WIDTH, scale, 1000);
|
|
int offsetX = (clientWidth - layoutWidth) / 2;
|
|
int offsetY = 0;
|
|
int size = MulDiv(28, scale, 1000);
|
|
if (size < 22)
|
|
{
|
|
size = 22;
|
|
}
|
|
int marginRight = MulDiv(12, scale, 1000);
|
|
if (marginRight < 6)
|
|
{
|
|
marginRight = 6;
|
|
}
|
|
int marginBottom = MulDiv(12, scale, 1000);
|
|
if (marginBottom < 6)
|
|
{
|
|
marginBottom = 6;
|
|
}
|
|
|
|
RECT buttonRect =
|
|
{
|
|
offsetX + layoutWidth - marginRight - size,
|
|
offsetY + MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000) - marginBottom - size,
|
|
offsetX + layoutWidth - marginRight,
|
|
offsetY + MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000) - 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:
|
|
srand((unsigned int)time(nullptr));
|
|
ReturnToMainMenu();
|
|
StartBackgroundMusic();
|
|
ResetGameTimer(hWnd);
|
|
SetTimer(hWnd, EFFECT_TIMER_ID, EFFECT_TIMER_INTERVAL, nullptr);
|
|
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 == EFFECT_TIMER_ID)
|
|
{
|
|
if (TickVisualEffects())
|
|
{
|
|
InvalidateRect(hWnd, nullptr, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
|
|
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();
|
|
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;
|
|
}
|
|
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
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_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);
|
|
}
|
|
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);
|
|
}
|
|
break;
|
|
case VK_RETURN:
|
|
case VK_SPACE:
|
|
if (helpState.currentPage == 0)
|
|
{
|
|
helpState.currentPage = helpState.selectedIndex + 1;
|
|
InvalidateRect(hWnd, nullptr, FALSE);
|
|
}
|
|
break;
|
|
case VK_ESCAPE:
|
|
case VK_BACK:
|
|
case 'M':
|
|
if (helpState.currentPage == 0)
|
|
{
|
|
ReturnToMainMenu();
|
|
}
|
|
else
|
|
{
|
|
helpState.currentPage = 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:
|
|
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 && reviveAvailable && wParam == 'V')
|
|
{
|
|
if (PlayReviveVideo(hWnd))
|
|
{
|
|
ReviveAfterVideo();
|
|
ResetGameTimer(hWnd);
|
|
}
|
|
else
|
|
{
|
|
SetFeedbackMessage(_T("视频播放失败"), _T("无法打开复活视频,复活机会未消耗。"), 14);
|
|
}
|
|
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();
|
|
CheckRogueLevelProgress();
|
|
}
|
|
}
|
|
break;
|
|
case VK_UP:
|
|
case 'W':
|
|
Rotate();
|
|
break;
|
|
case VK_SPACE:
|
|
DropDown();
|
|
Fixing();
|
|
if (!gameOverFlag)
|
|
{
|
|
DeleteLines();
|
|
CheckRogueLevelProgress();
|
|
}
|
|
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);
|
|
KillTimer(hWnd, EFFECT_TIMER_ID);
|
|
StopBackgroundMusic();
|
|
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;
|
|
}
|