diff --git a/src/include/Tetris.h b/src/include/Tetris.h index f3d3abe..9579c97 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -329,6 +329,12 @@ void DeleteOneLine(int number); */ int DeleteLines(); +/** + * @brief 判断当前游戏是否已经结束。 + * @return 游戏结束返回 true,否则返回 false。 + */ +bool GameOver(); + /** * @brief 计算当前活动方块的预测落点。 */ diff --git a/src/include/TetrisLogicInternal.h b/src/include/TetrisLogicInternal.h index 7eef381..a98f4d8 100644 --- a/src/include/TetrisLogicInternal.h +++ b/src/include/TetrisLogicInternal.h @@ -21,6 +21,55 @@ extern int pendingLineClearEffectLineCount; */ Point GetSpawnPoint(int brickType); +/** + * @brief 收集当前方块将要固定到棋盘上的格子,并写入工作区。 + * @param overflowTop 返回是否有方块格位于可视区域顶部之外。 + * @param fixedCells 返回普通落地格数组。 + * @param fixedCellCount 返回普通落地格数量。 + * @param explosiveCells 返回爆破方块落地格数组。 + * @param explosiveCellCount 返回爆破格数量。 + */ +void CollectAndWriteFixedCells( + bool& overflowTop, + Point fixedCells[], + int& fixedCellCount, + Point explosiveCells[], + int& explosiveCellCount); + +/** + * @brief 处理方块固定时的顶部溢出、终末清场和最后一搏。 + * @param overflowTop 是否出现顶部溢出。 + * @return 游戏可以继续返回 true,需要结束返回 false。 + */ +bool ResolveFixingOverflow(bool overflowTop); + +/** + * @brief 生成下一枚活动方块,并刷新 Hold、特殊方块和预测落点状态。 + */ +void SpawnNextFallingPiece(); + +/** + * @brief 从底向上扫描满行并删除,记录本次消除的原始行号。 + * @param clearedRows 返回最多 8 个被消除行号。 + * @param clearedRowCount 返回记录的行号数量。 + * @return 本次标准消行数量。 + */ +int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount); + +/** + * @brief 根据当前界面状态立即播放或暂存消行动画。 + * @param clearedRows 已消除行号数组。 + * @param clearedRowCount 行号数量。 + * @param clearedLines 本次消行数量。 + */ +void DispatchLineClearEffect(const int clearedRows[], int clearedRowCount, int clearedLines); + +/** + * @brief 处理连环炸弹因消行触发的一次追加爆破。 + * @param clearedLines 本次标准消行数量。 + */ +void ResolveChainBombFollowup(int clearedLines); + /** * @brief 重置经典或 Rogue 模式使用的玩家统计数据。 * @param stats 需要重置的统计结构。 diff --git a/src/include/TetrisRenderInternal.h b/src/include/TetrisRenderInternal.h index 269971e..867519c 100644 --- a/src/include/TetrisRenderInternal.h +++ b/src/include/TetrisRenderInternal.h @@ -21,3 +21,10 @@ Gdiplus::Bitmap* LoadBackgroundImage(); * @return 成功时返回缓存位图指针,失败或越界时返回 nullptr。 */ Gdiplus::Bitmap* LoadCreditImage(int index); + +/** + * @brief 绘制完整游戏界面,供 TDrawScreen 总入口调用。 + * @param hdc 目标绘图设备上下文。 + * @param hWnd 当前窗口句柄。 + */ +void RenderFullScreen(HDC hdc, HWND hWnd); diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index ce7e7a4..7230476 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -105,77 +105,6 @@ COLORREF BrickColor[7] = RGB(197, 170, 255) }; -/** - * @brief 计算指定方块在指定旋转状态下的最小包围盒边界。 - * - * 该函数会遍历 4x4 形状矩阵,找出所有非空单元的上下左右边界, - * 供后续统一计算生成位置和对齐方式时使用。 - * - * @param brickType 方块类型编号。 - * @param brickState 方块旋转状态编号。 - * @param minRow 返回最上方非空行号。 - * @param maxRow 返回最下方非空行号。 - * @param minCol 返回最左侧非空列号。 - * @param maxCol 返回最右侧非空列号。 - */ -static void GetBrickBounds(int brickType, int brickState, int& minRow, int& maxRow, int& minCol, int& maxCol) -{ - minRow = 4; - maxRow = -1; - minCol = 4; - maxCol = -1; - - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (bricks[brickType][brickState][i][j] != 0) - { - if (i < minRow) - { - minRow = i; - } - if (i > maxRow) - { - maxRow = i; - } - if (j < minCol) - { - minCol = j; - } - if (j > maxCol) - { - maxCol = j; - } - } - } - } -} - -/** - * @brief 计算指定方块的统一生成位置。 - * - * 该函数会根据方块在初始旋转状态下的最小包围盒, - * 自动把方块水平居中到游戏区附近,并将顶部非空行对齐到可视区域顶部。 - * 这样不同形状的方块在生成时看起来会更加统一。 - * - * @param brickType 方块类型编号。 - * @return Point 计算得到的生成坐标。 - */ -Point GetSpawnPoint(int brickType) -{ - int minRow, maxRow, minCol, maxCol; - GetBrickBounds(brickType, 0, minRow, maxRow, minCol, maxCol); - - int brickWidth = maxCol - minCol + 1; - int brickHeight = maxRow - minRow + 1; - Point spawnPoint; - spawnPoint.x = (nGameWidth - brickWidth) / 2 - minCol; - spawnPoint.y = -brickHeight; - - return spawnPoint; -} - /** * @brief 判断当前方块是否可以继续向下移动。 * @@ -381,129 +310,6 @@ void DropDown() } } -/** - * @brief 收集当前方块将要固定到棋盘上的格子,并标记是否越过顶部。 - * @param overflowTop 返回是否有方块格位于可视区域顶部之外。 - * @param fixedCells 返回普通落地格,用于后续特殊效果定位。 - * @param fixedCellCount 返回普通落地格数量。 - * @param explosiveCells 返回爆破方块落地格。 - * @param explosiveCellCount 返回爆破方块落地格数量。 - */ -static void CollectAndWriteFixedCells( - bool& overflowTop, - Point fixedCells[], - int& fixedCellCount, - Point explosiveCells[], - int& explosiveCellCount) -{ - overflowTop = false; - fixedCellCount = 0; - explosiveCellCount = 0; - - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (bricks[type][state][i][j] == 0) - { - continue; - } - - int fixY = point.y + i; - int fixX = point.x + j; - - // 顶部溢出只记录状态,真正的复活或结束逻辑在后续统一处理。 - if (fixY < 0) - { - overflowTop = true; - } - - if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth) - { - workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j]; - if (fixedCellCount < 4) - { - fixedCells[fixedCellCount].x = fixX; - fixedCells[fixedCellCount].y = fixY; - fixedCellCount++; - } - if (currentPieceIsExplosive && explosiveCellCount < 4) - { - explosiveCells[explosiveCellCount].x = fixX; - explosiveCells[explosiveCellCount].y = fixY; - explosiveCellCount++; - } - } - } - } -} - -/** - * @brief 处理方块固定时的顶部溢出、终末清场和最后一搏。 - * @param overflowTop 是否出现顶部溢出。 - * @return 溢出已被处理且游戏可以继续时返回 true;需要结束游戏时返回 false。 - */ -static bool ResolveFixingOverflow(bool overflowTop) -{ - if (!overflowTop) - { - return true; - } - - if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0) - { - rogueStats.lastChanceCount--; - rogueStats.screenBombCount--; - - int clearedByTerminal = TriggerScreenBomb(); - rogueStats.feverTicks = 10; - currentFallInterval = GetRogueFallInterval(); - - TCHAR terminalDetail[128]; - _stprintf_s( - terminalDetail, - _T("终末清场启动,清除 %d 格,并进入 10 秒狂热。"), - clearedByTerminal); - SetFeedbackMessage(_T("终末清场"), terminalDetail, 14); - return true; - } - - if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0) - { - rogueStats.lastChanceCount--; - - for (int i = 0; i < 3; i++) - { - DeleteOneLine(GetRoguePlayableHeight() - 1); - } - - SetFeedbackMessage( - _T("最后一搏"), - _T("底部 3 行被清除,战局得以延续。"), - 14); - return true; - } - - gameOverFlag = true; - return false; -} - -/** - * @brief 生成下一枚活动方块,并刷新 Hold、特殊方块和预测落点状态。 - */ -static void SpawnNextFallingPiece() -{ - // 消耗预览队列后重置本回合状态,确保 Hold 和特殊标记只影响新方块。 - type = ConsumeNextType(); - nType = nextTypes[0]; - state = 0; - holdUsedThisTurn = false; - RollCurrentPieceSpecialFlags(true); - point = GetSpawnPoint(type); - target = point; - ComputeTarget(); -} - /** * @brief 将当前活动方块固定到工作区,并生成下一个活动方块。 * @@ -567,120 +373,6 @@ void DeleteOneLine(int number) } } -/** - * @brief 从底向上扫描满行并删除,记录本次消除的原始行号。 - * @param clearedRows 返回最多 8 个被消除行号,用于播放消行动画。 - * @param clearedRowCount 返回记录的行号数量。 - * @return 本次标准消行数量。 - */ -static int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount) -{ - int clearedLines = 0; - clearedRowCount = 0; - - int playableHeight = GetRoguePlayableHeight(); - for (int i = playableHeight - 1; i >= 0; i--) - { - bool fullLine = true; - - for (int j = 0; j < nGameWidth; j++) - { - if (workRegion[i][j] == 0) - { - fullLine = false; - break; - } - } - - if (fullLine) - { - if (clearedRowCount < 8) - { - clearedRows[clearedRowCount] = i; - clearedRowCount++; - } - - DeleteOneLine(i); - clearedLines++; - i++; - } - } - - return clearedLines; -} - -/** - * @brief 根据当前界面状态立即播放或暂存消行动画。 - * @param clearedRows 已消除行号数组。 - * @param clearedRowCount 行号数量。 - * @param clearedLines 本次消行数量。 - */ -static void DispatchLineClearEffect(const int clearedRows[], int clearedRowCount, int clearedLines) -{ - if (currentScreen == SCREEN_UPGRADE) - { - QueueLineClearEffect(clearedRows, clearedRowCount, clearedLines); - } - else - { - TriggerLineClearEffect(clearedRows, clearedRowCount, clearedLines); - } -} - -/** - * @brief 处理连环炸弹因消行触发的一次追加 3x3 爆破。 - * @param clearedLines 本次标准消行数量。 - */ -static void ResolveChainBombFollowup(int clearedLines) -{ - if (!pendingChainBombFollowup || clearedLines <= 0) - { - pendingChainBombFollowup = false; - return; - } - - pendingChainBombFollowup = false; - - int followupCleared = 0; - int centerY = pendingChainBombCenter.y; - int centerX = pendingChainBombCenter.x; - Point followupCells[9] = {}; - - for (int y = centerY - 1; y <= centerY + 1; y++) - { - for (int x = centerX - 1; x <= centerX + 1; x++) - { - if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0) - { - if (followupCleared < 9) - { - followupCells[followupCleared].x = x; - followupCells[followupCleared].y = y; - } - workRegion[y][x] = 0; - followupCleared++; - } - } - } - - if (currentMode == MODE_ROGUE && followupCleared > 0) - { - TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true); - int followupScore = 0; - int followupExp = 0; - AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false); - - TCHAR followupDetail[128]; - _stprintf_s( - followupDetail, - _T("追加爆炸清除 %d 格 +%d 分 +%d EXP"), - followupCleared, - followupScore, - followupExp); - SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12); - } -} - /** * @brief 检查并删除所有已满的行,同时更新当前得分。 * @@ -707,6 +399,19 @@ int DeleteLines() return clearedLines; } +/** + * @brief 判断当前游戏是否已经结束。 + * + * 老师作业框架中保留该函数名,当前项目内部的结束状态统一记录在 + * gameOverFlag 中,因此这里直接返回该标记,避免改变原有流程。 + * + * @return 游戏结束返回 true,否则返回 false。 + */ +bool GameOver() +{ + return gameOverFlag; +} + /** * @brief 计算当前活动方块的预测落点位置。 * diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index e23618f..6fb9c7b 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -1,2725 +1,22 @@ -#include "stdafx.h" +#include "stdafx.h" /** * @file TetrisRender.cpp - * @brief 实现主菜单、帮助页、游戏棋盘、侧栏、覆盖层和升级界面的完整绘制逻辑。 + * @brief TDrawScreen 总入口,并把具体绘制委托给 render 模块。 */ #include "Tetris.h" #include "TetrisRenderInternal.h" -#include -#include - -#pragma comment(lib, "gdiplus.lib") - -using namespace Gdiplus; - -/** - * @brief 按颜色缓存粒子画刷,减少动画绘制时重复创建 GDI 对象。 - * @param color 画刷颜色。 - * @return 可复用的实心画刷句柄;缓存满时返回临时新建画刷。 - */ -static HBRUSH GetCachedParticleBrush(COLORREF color) -{ - static COLORREF cachedColors[16] = {}; - static HBRUSH cachedBrushes[16] = {}; - - for (int i = 0; i < 16; i++) - { - if (cachedBrushes[i] != nullptr && cachedColors[i] == color) - { - return cachedBrushes[i]; - } - } - - for (int i = 0; i < 16; i++) - { - if (cachedBrushes[i] == nullptr) - { - cachedColors[i] = color; - cachedBrushes[i] = CreateSolidBrush(color); - return cachedBrushes[i]; - } - } - - return CreateSolidBrush(color); -} /** * @brief 绘制当前游戏窗口的完整界面。 * - * 函数按当前屏幕状态绘制主菜单、帮助页、游戏棋盘、侧栏、覆盖层和升级选择。 - * 由于大量绘图辅助逻辑共享当前缩放、字体和颜色,保持在同一函数内集中管理, - * 避免拆分时改变 GDI 对象的选择和释放顺序。 + * 该函数保留绘制总入口名称,具体菜单、棋盘、侧栏、 + * 覆盖层、升级界面和特效绘制由 render 目录下的实现函数完成。 * * @param hdc 目标绘图设备上下文。 * @param hWnd 当前窗口句柄,用于读取客户区大小。 */ void TDrawScreen(HDC hdc, 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 layoutHeight = MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000); - int offsetX = (clientWidth - layoutWidth) / 2; - int offsetY = 0; - - auto SX = [offsetX, scale](int value) -> int - { - return offsetX + MulDiv(value, scale, 1000); - }; - - auto SY = [offsetY, scale](int value) -> int - { - return offsetY + MulDiv(value, scale, 1000); - }; - - auto SS = [scale](int value) -> int - { - int result = MulDiv(value, scale, 1000); - return result < 1 ? 1 : result; - }; - - int grid = SS(GRID); - int previewMiniCell = SS(16); - int panelGap = SS(SIDE_PANEL_GAP); - int panelWidth = SS(SIDE_PANEL_WIDTH); - int panelHeight = SS(SIDE_PANEL_HEIGHT - WINDOW_PADDING * 2); - int boardWidth = grid * nGameWidth; - int boardHeight = grid * nGameHeight; - - const RECT leftPanelRect = - { - SX(WINDOW_PADDING), - SY(WINDOW_PADDING), - SX(WINDOW_PADDING) + panelWidth, - SY(WINDOW_PADDING) + panelHeight - }; - - const RECT gameRect = - { - leftPanelRect.right + panelGap, - SY(WINDOW_PADDING), - leftPanelRect.right + panelGap + boardWidth, - SY(WINDOW_PADDING) + boardHeight - }; - - const RECT rightPanelRect = - { - gameRect.right + panelGap, - SY(WINDOW_PADDING), - gameRect.right + panelGap + panelWidth, - SY(WINDOW_PADDING) + panelHeight - }; - - const COLORREF pageColor = RGB(255, 244, 248); - const COLORREF blobColorA = RGB(255, 230, 238); - const COLORREF blobColorB = RGB(250, 221, 233); - const COLORREF cardColor = RGB(255, 252, 253); - const COLORREF boardColor = RGB(70, 57, 78); - const COLORREF boardInnerColor = RGB(54, 44, 62); - const COLORREF lineColor = RGB(104, 90, 116); - const COLORREF frameColor = RGB(212, 168, 188); - const COLORREF titleColor = RGB(104, 62, 84); - const COLORREF textColor = RGB(92, 73, 88); - const COLORREF accentColor = RGB(228, 145, 182); - const BYTE shellAlpha = 142; - const BYTE panelAlpha = 150; - const BYTE panelNestedAlpha = 128; - const BYTE panelStrongAlpha = 168; - - // 背景图片存在时优先绘制图片并叠加浅色遮罩,否则使用纯色和装饰形状。 - Bitmap* backgroundImage = LoadBackgroundImage(); - if (backgroundImage != nullptr) - { - Graphics graphics(hdc); - graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); - graphics.DrawImage( - backgroundImage, - Rect(0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top)); - SolidBrush washBrush(Color(76, 255, 250, 252)); - graphics.FillRectangle(&washBrush, 0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); - } - else - { - HBRUSH pageBrush = CreateSolidBrush(pageColor); - FillRect(hdc, &clientRect, pageBrush); - DeleteObject(pageBrush); - } - - HBRUSH oldBrush = nullptr; - HPEN oldPen = nullptr; - if (backgroundImage == nullptr) - { - HBRUSH blobBrushA = CreateSolidBrush(blobColorA); - HBRUSH blobBrushB = CreateSolidBrush(blobColorB); - oldBrush = (HBRUSH)SelectObject(hdc, blobBrushA); - oldPen = (HPEN)SelectObject(hdc, GetStockObject(NULL_PEN)); - Ellipse(hdc, clientRect.right - 260, -20, clientRect.right + 80, 260); - Ellipse(hdc, -80, clientRect.bottom - 200, 220, clientRect.bottom + 70); - SelectObject(hdc, blobBrushB); - Ellipse(hdc, clientRect.right - 180, clientRect.bottom - 220, clientRect.right + 120, clientRect.bottom + 40); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(blobBrushA); - DeleteObject(blobBrushB); - } - - // 本函数集中创建字体,所有提前 return 分支都要在返回前释放这些 GDI 对象。 - HFONT titleFont = CreateFont( - -SS(36), 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, - DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, - VARIABLE_PITCH, _T("Microsoft YaHei UI")); - - HFONT sectionFont = CreateFont( - -SS(22), 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, - DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, - VARIABLE_PITCH, _T("Microsoft YaHei UI")); - - HFONT bodyFont = CreateFont( - -SS(18), 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, - DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, - VARIABLE_PITCH, _T("Microsoft YaHei UI")); - - HFONT smallFont = CreateFont( - -SS(15), 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, - DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, - VARIABLE_PITCH, _T("Microsoft YaHei UI")); - - SetBkMode(hdc, TRANSPARENT); - SetTextColor(hdc, textColor); - - // 以下局部绘图函数共享 hdc、缩放函数和颜色,避免每个小控件重复计算上下文。 - auto DrawPanelCard = [&](const RECT& rect, COLORREF fillColor, COLORREF borderColor, int radius) - { - HBRUSH cardBrush = CreateSolidBrush(fillColor); - HPEN cardPen = CreatePen(PS_SOLID, 1, borderColor); - HGDIOBJ savedPen = SelectObject(hdc, cardPen); - HGDIOBJ savedBrush = SelectObject(hdc, cardBrush); - RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, SS(radius), SS(radius)); - SelectObject(hdc, savedBrush); - SelectObject(hdc, savedPen); - DeleteObject(cardBrush); - DeleteObject(cardPen); - }; - - auto DrawPanelCardAlpha = [&](const RECT& rect, COLORREF fillColor, COLORREF borderColor, int radius, BYTE alpha) - { - Graphics graphics(hdc); - graphics.SetSmoothingMode(SmoothingModeAntiAlias); - - GraphicsPath path; - INT left = static_cast(rect.left); - INT top = static_cast(rect.top); - INT width = static_cast(rect.right - rect.left); - INT height = static_cast(rect.bottom - rect.top); - INT corner = static_cast(SS(radius)); - - path.AddArc(left, top, corner, corner, 180.0f, 90.0f); - path.AddArc(left + width - corner, top, corner, corner, 270.0f, 90.0f); - path.AddArc(left + width - corner, top + height - corner, corner, corner, 0.0f, 90.0f); - path.AddArc(left, top + height - corner, corner, corner, 90.0f, 90.0f); - path.CloseFigure(); - - SolidBrush brush(Color(alpha, GetRValue(fillColor), GetGValue(fillColor), GetBValue(fillColor))); - Pen pen(Color(232, GetRValue(borderColor), GetGValue(borderColor), GetBValue(borderColor)), 1.0f); - graphics.FillPath(&brush, &path); - graphics.DrawPath(&pen, &path); - }; - - auto DrawPanelHeader = [&](const RECT& panelRect, const TCHAR* title, int lineWidth) - { - SelectObject(hdc, titleFont); - SetTextColor(hdc, titleColor); - RECT titleRect = - { - panelRect.left + SS(24), - panelRect.top + SS(20), - panelRect.right - SS(24), - panelRect.top + SS(62) - }; - DrawText(hdc, title, -1, &titleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - HPEN localAccentPen = CreatePen(PS_SOLID, SS(3), accentColor); - HPEN savedPen = (HPEN)SelectObject(hdc, localAccentPen); - MoveToEx(hdc, panelRect.left + SS(24), panelRect.top + SS(78), nullptr); - LineTo(hdc, panelRect.left + SS(24) + SS(lineWidth), panelRect.top + SS(78)); - SelectObject(hdc, savedPen); - DeleteObject(localAccentPen); - }; - - auto DrawMusicButton = [&]() - { - RECT musicButtonRect = - { - SX(WINDOW_CLIENT_WIDTH - 40), - SY(WINDOW_CLIENT_HEIGHT - 40), - SX(WINDOW_CLIENT_WIDTH - 12), - SY(WINDOW_CLIENT_HEIGHT - 12) - }; - DrawPanelCardAlpha( - musicButtonRect, - bgmEnabled ? RGB(255, 238, 246) : RGB(244, 236, 240), - bgmEnabled ? RGB(222, 130, 166) : RGB(170, 148, 158), - 12, - bgmEnabled ? 218 : 190); - - HPEN musicPen = CreatePen(PS_SOLID, SS(3), bgmEnabled ? RGB(128, 70, 100) : RGB(128, 112, 120)); - HPEN oldMusicPen = (HPEN)SelectObject(hdc, musicPen); - HBRUSH oldMusicBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); - - int noteStemX = musicButtonRect.left + SS(17); - int noteTop = musicButtonRect.top + SS(7); - int noteBottom = musicButtonRect.bottom - SS(9); - MoveToEx(hdc, noteStemX, noteTop, nullptr); - LineTo(hdc, noteStemX, noteBottom); - MoveToEx(hdc, noteStemX, noteTop, nullptr); - LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(4)); - LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(10)); - Ellipse( - hdc, - musicButtonRect.left + SS(7), - musicButtonRect.bottom - SS(13), - musicButtonRect.left + SS(19), - musicButtonRect.bottom - SS(4)); - - if (!bgmEnabled) - { - MoveToEx(hdc, musicButtonRect.left + SS(7), musicButtonRect.bottom - SS(7), nullptr); - LineTo(hdc, musicButtonRect.right - SS(6), musicButtonRect.top + SS(6)); - } - - SelectObject(hdc, oldMusicBrush); - SelectObject(hdc, oldMusicPen); - DeleteObject(musicPen); - }; - - auto DrawBackButton = [&]() - { - RECT backButtonRect = - { - SX(6), - SY(6), - SX(34), - SY(34) - }; - HBRUSH backBrush = CreateSolidBrush(RGB(255, 242, 247)); - HPEN backFramePen = CreatePen(PS_SOLID, 1, RGB(222, 130, 166)); - HBRUSH oldBackBrush = (HBRUSH)SelectObject(hdc, backBrush); - HPEN oldBackFramePen = (HPEN)SelectObject(hdc, backFramePen); - RoundRect(hdc, backButtonRect.left, backButtonRect.top, backButtonRect.right, backButtonRect.bottom, SS(12), SS(12)); - SelectObject(hdc, oldBackBrush); - SelectObject(hdc, oldBackFramePen); - DeleteObject(backFramePen); - DeleteObject(backBrush); - - HPEN backPen = CreatePen(PS_SOLID, SS(3), RGB(128, 70, 100)); - HPEN oldBackPen = (HPEN)SelectObject(hdc, backPen); - MoveToEx(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(8), nullptr); - LineTo(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14)); - LineTo(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(20)); - MoveToEx(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14), nullptr); - LineTo(hdc, backButtonRect.right - SS(8), backButtonRect.top + SS(14)); - SelectObject(hdc, oldBackPen); - DeleteObject(backPen); - }; - - // 主菜单独立绘制并提前返回,避免后续游戏棋盘和侧栏在菜单后面继续绘制。 - if (currentScreen == SCREEN_MENU) - { - RECT menuCard = - { - SX(110), - SY(70), - SX(WINDOW_CLIENT_WIDTH - 110), - SY(WINDOW_CLIENT_HEIGHT - 70) - }; - - DrawPanelCardAlpha(menuCard, cardColor, frameColor, 34, 214); - - HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); - SetTextColor(hdc, titleColor); - - RECT titleRect = - { - menuCard.left, - menuCard.top + SS(24), - menuCard.right, - menuCard.top + SS(70) - }; - DrawText(hdc, _T("Rogue Tetris"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - RECT subtitleRect = - { - menuCard.left, - menuCard.top + SS(72), - menuCard.right, - menuCard.top + SS(110) - }; - SelectObject(hdc, bodyFont); - SetTextColor(hdc, textColor); - DrawText(hdc, _T("选择你的战局"), -1, &subtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - const TCHAR* modeNames[4] = - { - _T("\u7ecf\u5178\u6a21\u5f0f"), - _T("Rogue \u6a21\u5f0f"), - _T("\u5e2e\u52a9"), - _T("\u81f4\u8c22") - }; - - const TCHAR* modeDescriptions[4] = - { - _T("纯粹方块挑战,专注消行、堆叠与生存。"), - _T("在不断升级的棋盘中收集强化,构筑本局专属流派。"), - _T("查看操作、模式规则与全部强化效果。"), - _T("感谢程序测试者与代码贡献者。") - }; - - for (int i = 0; i < menuState.optionCount; i++) - { - bool isSelected = (i == menuState.selectedIndex); - int top = menuCard.top + SS(140) + i * SS(130); - - RECT optionRect = - { - menuCard.left + SS(36), - top, - menuCard.right - SS(36), - top + SS(104) - }; - - DrawPanelCardAlpha( - optionRect, - isSelected ? RGB(255, 232, 240) : RGB(255, 247, 250), - isSelected ? accentColor : RGB(226, 198, 210), - 24, - isSelected ? 208 : 172); - if (isSelected) - { - HPEN optionPen = CreatePen(PS_SOLID, SS(3), accentColor); - oldPen = (HPEN)SelectObject(hdc, optionPen); - oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); - RoundRect(hdc, optionRect.left, optionRect.top, optionRect.right, optionRect.bottom, SS(24), SS(24)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(optionPen); - } - - RECT modeNameRect = - { - optionRect.left + SS(24), - optionRect.top + SS(14), - optionRect.right - SS(24), - optionRect.top + SS(44) - }; - - RECT modeDescriptionRect = - { - optionRect.left + SS(24), - optionRect.top + SS(46), - optionRect.right - SS(24), - optionRect.bottom - SS(14) - }; - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, isSelected ? titleColor : textColor); - DrawText(hdc, modeNames[i], -1, &modeNameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(118, 96, 110)); - DrawText(hdc, modeDescriptions[i], -1, &modeDescriptionRect, DT_LEFT | DT_WORDBREAK); - } - - RECT hintRect = - { - menuCard.left + SS(36), - menuCard.bottom - SS(92), - menuCard.right - SS(36), - menuCard.bottom - SS(36) - }; - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - DrawText(hdc, _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4\uff0cEsc \u9000\u51fa"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - DrawMusicButton(); - SelectObject(hdc, oldFont); - DeleteObject(titleFont); - DeleteObject(sectionFont); - DeleteObject(bodyFont); - DeleteObject(smallFont); - return; - } - - // 帮助、规则、图鉴、致谢和技能演示入口共用规则页卡片框架。 - if (currentScreen == SCREEN_RULES) - { - RECT rulesCard = - { - SX(76), - SY(54), - SX(WINDOW_CLIENT_WIDTH - 76), - SY(WINDOW_CLIENT_HEIGHT - 54) - }; - - HPEN rulesPen = CreatePen(PS_SOLID, 1, frameColor); - HBRUSH rulesBrush = CreateSolidBrush(cardColor); - HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); - oldPen = (HPEN)SelectObject(hdc, rulesPen); - oldBrush = (HBRUSH)SelectObject(hdc, rulesBrush); - RoundRect(hdc, rulesCard.left, rulesCard.top, rulesCard.right, rulesCard.bottom, SS(34), SS(34)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(rulesBrush); - DeleteObject(rulesPen); - - SetTextColor(hdc, titleColor); - RECT rulesTitleRect = - { - rulesCard.left + SS(36), - rulesCard.top + SS(26), - rulesCard.right - SS(36), - rulesCard.top + SS(78) - }; - const TCHAR* helpTitle = _T("\u5e2e\u52a9"); - if (helpState.currentPage == 1) - { - helpTitle = _T("\u6e38\u620f\u4ecb\u7ecd"); - } - else if (helpState.currentPage == 2) - { - helpTitle = _T("\u64cd\u4f5c\u8bf4\u660e"); - } - else if (helpState.currentPage == 3) - { - helpTitle = _T("\u5f3a\u5316\u56fe\u9274"); - } - else if (helpState.currentPage == 4) - { - helpTitle = _T("\u81f4\u8c22"); - } - else if (helpState.currentPage == 5) - { - helpTitle = _T("\u6280\u80fd\u6f14\u793a"); - } - DrawText(hdc, helpTitle, -1, &rulesTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - HPEN rulesAccentPen = CreatePen(PS_SOLID, SS(3), accentColor); - oldPen = (HPEN)SelectObject(hdc, rulesAccentPen); - MoveToEx(hdc, rulesCard.left + SS(38), rulesCard.top + SS(92), nullptr); - LineTo(hdc, rulesCard.left + SS(186), rulesCard.top + SS(92)); - SelectObject(hdc, oldPen); - DeleteObject(rulesAccentPen); - - RECT contentRect = - { - rulesCard.left + SS(36), - rulesCard.top + SS(126), - rulesCard.right - SS(36), - rulesCard.bottom - SS(86) - }; - - if (helpState.currentPage == 0) - { - const TCHAR* optionTitles[4] = - { - _T("\u6e38\u620f\u4ecb\u7ecd"), - _T("\u64cd\u4f5c\u8bf4\u660e"), - _T("\u5f3a\u5316\u56fe\u9274"), - _T("\u6280\u80fd\u6f14\u793a") - }; - const TCHAR* optionDetails[4] = - { - _T("\u7ecf\u5178\u6a21\u5f0f\u3001Rogue \u6a21\u5f0f\u548c\u590d\u6d3b\u89c4\u5219\u6982\u89c8\u3002"), - _T("\u79fb\u52a8\u3001\u65cb\u8f6c\u3001\u786c\u964d\u3001Hold \u4e0e\u6280\u80fd\u5feb\u6377\u952e\u3002"), - _T("\u67e5\u770b Rogue \u6a21\u5f0f\u5168\u90e8\u5f3a\u5316\u7684\u7b80\u8981\u6548\u679c\u3002"), - _T("\u9009\u62e9\u4e00\u4e2a\u9884\u8bbe\u573a\u666f\uff0c\u4eb2\u81ea\u64cd\u4f5c\u89e6\u53d1 Rogue \u6280\u80fd\u6548\u679c\u3002") - }; - - int optionHeight = SS(100); - int optionGap = SS(22); - int optionTop = contentRect.top + SS(18); - for (int i = 0; i < helpState.optionCount; ++i) - { - RECT optionRect = - { - contentRect.left, - optionTop + i * (optionHeight + optionGap), - contentRect.right, - optionTop + i * (optionHeight + optionGap) + optionHeight - }; - bool selected = (i == helpState.selectedIndex); - COLORREF optionFill = selected ? RGB(255, 245, 249) : RGB(255, 252, 250); - COLORREF optionFrame = selected ? accentColor : frameColor; - HPEN optionPen = CreatePen(PS_SOLID, selected ? SS(2) : SS(1), optionFrame); - HBRUSH optionBrush = CreateSolidBrush(optionFill); - oldPen = (HPEN)SelectObject(hdc, optionPen); - oldBrush = (HBRUSH)SelectObject(hdc, optionBrush); - RoundRect(hdc, optionRect.left, optionRect.top, optionRect.right, optionRect.bottom, SS(24), SS(24)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(optionBrush); - DeleteObject(optionPen); - - SetTextColor(hdc, selected ? titleColor : textColor); - SelectObject(hdc, sectionFont); - RECT optionTitleRect = - { - optionRect.left + SS(28), - optionRect.top + SS(16), - optionRect.right - SS(28), - optionRect.top + SS(48) - }; - DrawText(hdc, optionTitles[i], -1, &optionTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SetTextColor(hdc, RGB(112, 91, 104)); - SelectObject(hdc, bodyFont); - RECT optionDetailRect = - { - optionRect.left + SS(28), - optionRect.top + SS(52), - optionRect.right - SS(28), - optionRect.bottom - SS(14) - }; - DrawText(hdc, optionDetails[i], -1, &optionDetailRect, DT_LEFT | DT_TOP | DT_WORDBREAK); - } - } - else - { - SetTextColor(hdc, textColor); - SelectObject(hdc, bodyFont); - const TCHAR* pageText = _T(""); - UINT pageFlags = DT_LEFT | DT_TOP | DT_WORDBREAK; - if (helpState.currentPage == 1) - { - pageText = - _T("经典模式:考验堆叠、消行和生存。消行后悬空方块会继续受重力下落。\r\n\r\n") - _T("Rogue:消行得分和 EXP,升级后选择强化,构筑会改变方块、清场、得分和保命方式。\r\n\r\n") - _T("特殊方块用粗色边框提示:橙红=爆破,青色=激光,绿色=十字,紫色=彩虹。\r\n\r\n") - _T("战斗越久危险越高,底部封锁区会压缩空间。\r\n\r\n") - _T("死亡后可以按 V 看视频复活一次,经典模式和 Rogue 模式都支持。"); - } - else if (helpState.currentPage == 2) - { - pageText = - _T("\u2190 / A\uff1a\u5411\u5de6\u79fb\u52a8\r\n") - _T("\u2192 / D\uff1a\u5411\u53f3\u79fb\u52a8\r\n") - _T("\u2191 / W\uff1a\u65cb\u8f6c\u65b9\u5757\r\n") - _T("\u2193 / S\uff1a\u8f6f\u964d\r\n") - _T("Space\uff1a\u786c\u964d\r\n") - _T("C / Shift\uff1a备用仓\uff08获得后\uff09\r\n") - _T("Z\uff1a黑洞奇点\uff08获得后\uff09\r\n") - _T("X\uff1a清屏炸弹\uff08获得后\uff09\r\n") - _T("V\uff1a空中换形\uff08获得后\uff09\r\n") - _T("P\uff1a\u6682\u505c / \u7ee7\u7eed\r\n") - _T("R\uff1a\u91cd\u5f00\u5f53\u524d\u5bf9\u5c40\r\n") - _T("M\uff1a\u8fd4\u56de\u4e3b\u83dc\u5355\r\n") - _T("死亡后 V:看视频复活一次"); - } - else if (helpState.currentPage == 3) - { - const TCHAR* categoryNames[8] = - { - _T("基础"), - _T("保命 / 操作"), - _T("清场"), - _T("特殊方块"), - _T("进阶"), - _T("狂热 / 风险"), - _T("升级系"), - _T("构筑") - }; - const TCHAR* categoryTexts[8] = - { - _T("赏金纹章:所有得分收益提高 20%,可重复叠加,是最直接的分数成长。\r\n成长印记:所有 EXP 收益提高 25%,可重复叠加,用来更快进入后续构筑。\r\n缓坠羽翼:降低自然下落速度,最多叠加 4 次。\r\n连击律动:连续消行时追加得分和 EXP,断连后重新累计。\r\n先见之眼:额外显示 1 个后续方块;第三个预览由操控大师解锁。"), - _T("最后一搏:首次濒死时自动清理底部 3 行,并保留本局继续机会。\r\n备用仓:解锁 C / Shift,将当前方块放入 Hold 仓或取出备用方块。\r\n完美旋转:旋转被阻挡时尝试左右修正,提高贴墙和缝隙旋转成功率。\r\n时间缓流:堆叠过高时自动减速,给危险局面留出处理时间。\r\n空中换形:可重复补充次数,按 V 将当前下落方块变成 I 块。"), - _T("卸压清场:获得时立刻清除最高的一条占用行,直接降低顶部压力。\r\n底线清道夫:通过消行充能自动清底;每级降低需求,最多 4 级。\r\n清屏炸弹:一次性解锁,之后通过消行充能;满 16 行获得 1 枚,按 X 清底 5 行。\r\n黑洞奇点:可重复补充次数,按 Z 吞噬当前场上数量最多的一种颜色方块。"), - _T("爆破核心:一次性解锁橙红边框方块,落地后以落点为中心清除 3x3 区域。\r\n棱镜激光:一次性解锁青色边框方块,按落地中心列清除整列固定方块。\r\n十字方块:一次性解锁绿色边框方块,按落地中心同时清除一行一列。\r\n彩虹方块:一次性解锁紫色边框方块,清中心行主色并把覆盖行染成场上主色。\r\n方块改造:提高指定方块出现概率,目前主要强化 I 块生成。"), - _T("连锁火花:完成消行后追加随机破坏,适合配合稳定堆叠扩大收益。\r\n连环炸弹:强化爆破核心,将爆破范围从 3x3 扩大为 5x5。\r\n雷霆四消:三消或四消后追加雷击,额外清除局部方块。\r\n雷霆棱镜:三消或四消后追加激光清列,强化四消后的清场能力。"), - _T("狂热节拍:累计清除 12 行进入狂热,狂热期间收益更高。\r\n怒火连段:连击越高倍率越高,适合连续小消和稳定节奏。\r\n无尽狂热:狂热期间继续消行会延长狂热时间。\r\n高压悬赏:游戏速度更快,但高压下收益也更高。\r\n豪赌四消:四消收益更高,但非四消表现更不稳定。\r\n极限玩家:危险高度下获得更高收益,同时承担更高操作压力。\r\n赌徒契约:后续强化有概率翻倍或落空,每级提高概率,最多 4 级。"), - _T("双重抉择:升级时可额外选择 1 个强化,加快构筑成型。\r\n命运轮盘:升级变为 6 选 2,但选项中可能混入诅咒。\r\n升级冲击波:每次升级后清理底部 2 行。\r\n进化冲击:升级后清理底部 3 行,是升级冲击波的进阶路线。\r\n成长核心:永久提高得分和 EXP 收益 15%。"), - _T("操控大师:使用 Hold 后短暂减速,并额外增加预览能力。\r\n方块风暴:接下来 5 个方块都变为 I 块,适合冲四消或紧急清场。\r\n稳定结构:落地后概率填补局部小洞,让场地更平整。\r\n虚空核心:黑洞生效后生成彩虹方块,彩虹生效时会追加小型黑洞。") - }; - - int sectionGap = SS(18); - int titleHeight = SS(34); - int bodyPadding = SS(16); - int contentHeight = contentRect.bottom - contentRect.top; - int virtualHeight = 0; - SelectObject(hdc, bodyFont); - for (int i = 0; i < 8; ++i) - { - RECT calculateBodyRect = - { - contentRect.left + bodyPadding, - 0, - contentRect.right - bodyPadding, - 0 - }; - DrawText(hdc, categoryTexts[i], -1, &calculateBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); - virtualHeight += titleHeight + (calculateBodyRect.bottom - calculateBodyRect.top) + sectionGap; - } - int maxHelpScroll = virtualHeight - contentHeight; - if (maxHelpScroll < 0) - { - maxHelpScroll = 0; - } - if (helpScrollOffset > maxHelpScroll) - { - helpScrollOffset = maxHelpScroll; - } - - HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); - int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); - HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); - SelectClipRgn(hdc, contentClipRegion); - - int sectionTop = contentRect.top - helpScrollOffset; - for (int i = 0; i < 8; ++i) - { - RECT bodyCalculateRect = - { - contentRect.left + bodyPadding, - 0, - contentRect.right - bodyPadding, - 0 - }; - SelectObject(hdc, bodyFont); - DrawText(hdc, categoryTexts[i], -1, &bodyCalculateRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); - int bodyHeight = bodyCalculateRect.bottom - bodyCalculateRect.top; - RECT categoryRect = - { - contentRect.left, - sectionTop, - contentRect.right, - sectionTop + titleHeight + bodyHeight + SS(12) - }; - - if (categoryRect.bottom < contentRect.top || categoryRect.top > contentRect.bottom) - { - sectionTop += titleHeight + bodyHeight + sectionGap; - continue; - } - - SetTextColor(hdc, titleColor); - SelectObject(hdc, sectionFont); - RECT categoryTitleRect = - { - categoryRect.left, - categoryRect.top, - categoryRect.right, - categoryRect.top + titleHeight - }; - DrawText(hdc, categoryNames[i], -1, &categoryTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - HPEN categoryLinePen = CreatePen(PS_SOLID, 1, RGB(232, 202, 215)); - oldPen = (HPEN)SelectObject(hdc, categoryLinePen); - MoveToEx(hdc, categoryRect.left, categoryTitleRect.bottom, nullptr); - LineTo(hdc, categoryRect.right, categoryTitleRect.bottom); - SelectObject(hdc, oldPen); - DeleteObject(categoryLinePen); - - SetTextColor(hdc, RGB(86, 66, 80)); - SelectObject(hdc, bodyFont); - RECT categoryBodyRect = - { - categoryRect.left + bodyPadding, - categoryTitleRect.bottom + SS(10), - categoryRect.right - bodyPadding, - categoryTitleRect.bottom + SS(10) + bodyHeight - }; - DrawText(hdc, categoryTexts[i], -1, &categoryBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); - sectionTop += titleHeight + bodyHeight + sectionGap; - } - - if (hasOldClipRegion == 1) - { - SelectClipRgn(hdc, oldClipRegion); - } - else - { - SelectClipRgn(hdc, nullptr); - } - DeleteObject(contentClipRegion); - DeleteObject(oldClipRegion); - } - else if (helpState.currentPage == 5) - { - int demoCount = GetRogueSkillDemoCount(); - int itemHeight = SS(58); - int itemGap = SS(10); - int contentHeight = contentRect.bottom - contentRect.top; - int virtualHeight = SS(8) + demoCount * (itemHeight + itemGap); - int maxHelpScroll = virtualHeight - contentHeight; - if (maxHelpScroll < 0) - { - maxHelpScroll = 0; - } - if (helpScrollOffset > maxHelpScroll) - { - helpScrollOffset = maxHelpScroll; - } - - HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); - int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); - HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); - SelectClipRgn(hdc, contentClipRegion); - - int itemTop = contentRect.top + SS(8) - helpScrollOffset; - for (int i = 0; i < demoCount; i++) - { - RECT itemRect = - { - contentRect.left, - itemTop + i * (itemHeight + itemGap), - contentRect.right, - itemTop + i * (itemHeight + itemGap) + itemHeight - }; - - if (itemRect.bottom < contentRect.top || itemRect.top > contentRect.bottom) - { - continue; - } - - bool selected = (i == helpState.selectedIndex); - COLORREF itemFill = selected ? RGB(255, 245, 249) : RGB(255, 252, 250); - COLORREF itemFrame = selected ? accentColor : frameColor; - HPEN itemPen = CreatePen(PS_SOLID, selected ? SS(2) : SS(1), itemFrame); - HBRUSH itemBrush = CreateSolidBrush(itemFill); - oldPen = (HPEN)SelectObject(hdc, itemPen); - oldBrush = (HBRUSH)SelectObject(hdc, itemBrush); - RoundRect(hdc, itemRect.left, itemRect.top, itemRect.right, itemRect.bottom, SS(18), SS(18)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(itemBrush); - DeleteObject(itemPen); - - SetTextColor(hdc, selected ? titleColor : textColor); - SelectObject(hdc, sectionFont); - RECT nameRect = - { - itemRect.left + SS(22), - itemRect.top + SS(8), - itemRect.left + SS(210), - itemRect.bottom - SS(8) - }; - DrawText(hdc, GetRogueSkillDemoName(i), -1, &nameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SetTextColor(hdc, RGB(112, 91, 104)); - SelectObject(hdc, bodyFont); - RECT detailRect = - { - itemRect.left + SS(220), - itemRect.top + SS(8), - itemRect.right - SS(22), - itemRect.bottom - SS(8) - }; - DrawText(hdc, GetRogueSkillDemoDetail(i), -1, &detailRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); - } - - if (hasOldClipRegion == 1) - { - SelectClipRgn(hdc, oldClipRegion); - } - else - { - SelectClipRgn(hdc, nullptr); - } - DeleteObject(contentClipRegion); - DeleteObject(oldClipRegion); - } - else if (helpState.currentPage == 4) - { - const int creditAnimationTotalTicks = 60; - constexpr int creditPageCount = 5; - const TCHAR* creditNames[creditPageCount] = - { - _T("qls"), - _T("wyk"), - _T("swj"), - _T("qhy"), - _T("syc") - }; - const TCHAR* creditTexts[creditPageCount] = - { - _T("\u611f\u8c22\u6fc0\u60c5\u6295\u8eab\u4e8e\u6d4b\u8bd5\u4e4b\u4e2d\u7684Lisa"), - _T("\u611f\u8c22\u70ed\u5ff1coding\u7684\u5c0f\u86cb\u7cd5"), - _T("\u611f\u8c22\u8bfe\u524d\u95f2\u91cc\u5077\u5fd9\u7684juju"), - _T("\u611f\u8c22qhy\u7684\u5929\u624d\u6784\u60f3"), - _T("\u611f\u8c22syc\u63fd\u4e0b\u6240\u6709\u6742\u6d3b") - }; - - int currentCredit = creditPageIndex; - if (currentCredit < 0) - { - currentCredit = 0; - } - if (currentCredit >= creditPageCount) - { - currentCredit = creditPageCount - 1; - } - - int previousCredit = currentCredit - creditAnimationDirection; - if (previousCredit < 0) - { - previousCredit = creditPageCount - 1; - } - if (previousCredit >= creditPageCount) - { - previousCredit = 0; - } - - RECT imageArea = - { - contentRect.left + SS(92), - contentRect.top + SS(10), - contentRect.right - SS(92), - contentRect.bottom - SS(112) - }; - RECT textArea = - { - contentRect.left + SS(72), - contentRect.bottom - SS(94), - contentRect.right - SS(72), - contentRect.bottom - SS(18) - }; - - HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); - int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); - HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); - SelectClipRgn(hdc, contentClipRegion); - - int slideDistance = contentRect.right - contentRect.left; - int currentOffset = 0; - int previousOffset = 0; - if (creditAnimationTicks > 0 && creditAnimationDirection != 0) - { - currentOffset = creditAnimationDirection * slideDistance * creditAnimationTicks / creditAnimationTotalTicks; - previousOffset = currentOffset - creditAnimationDirection * slideDistance; - } - - Bitmap* preloadedCreditImages[creditPageCount] = {}; - for (int i = 0; i < creditPageCount; i++) - { - preloadedCreditImages[i] = LoadCreditImage(i); - } - Graphics creditGraphics(hdc); - creditGraphics.SetInterpolationMode(InterpolationModeHighQualityBilinear); - - auto DrawCreditCard = [&](int cardIndex, int offset) - { - RECT shiftedImageArea = imageArea; - RECT shiftedTextArea = textArea; - OffsetRect(&shiftedImageArea, offset, 0); - OffsetRect(&shiftedTextArea, offset, 0); - - Bitmap* creditImage = nullptr; - if (cardIndex >= 0 && cardIndex < creditPageCount) - { - creditImage = preloadedCreditImages[cardIndex]; - } - if (creditImage != nullptr) - { - int imageWidth = static_cast(creditImage->GetWidth()); - int imageHeight = static_cast(creditImage->GetHeight()); - int areaWidth = shiftedImageArea.right - shiftedImageArea.left; - int areaHeight = shiftedImageArea.bottom - shiftedImageArea.top; - int drawWidth = areaWidth; - int drawHeight = imageHeight * drawWidth / imageWidth; - if (drawHeight > areaHeight) - { - drawHeight = areaHeight; - drawWidth = imageWidth * drawHeight / imageHeight; - } - - int drawLeft = shiftedImageArea.left + (areaWidth - drawWidth) / 2; - int drawTop = shiftedImageArea.top + (areaHeight - drawHeight) / 2; - creditGraphics.DrawImage(creditImage, Rect(drawLeft, drawTop, drawWidth, drawHeight)); - } - else - { - HBRUSH missingBrush = CreateSolidBrush(RGB(255, 245, 249)); - HPEN missingPen = CreatePen(PS_DASH, SS(2), frameColor); - oldBrush = (HBRUSH)SelectObject(hdc, missingBrush); - oldPen = (HPEN)SelectObject(hdc, missingPen); - RoundRect(hdc, shiftedImageArea.left, shiftedImageArea.top, shiftedImageArea.right, shiftedImageArea.bottom, SS(24), SS(24)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(missingPen); - DeleteObject(missingBrush); - - SetTextColor(hdc, RGB(128, 104, 118)); - SelectObject(hdc, bodyFont); - DrawText(hdc, _T("\u56fe\u7247\u8d44\u6e90\u672a\u627e\u5230"), -1, &shiftedImageArea, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } - - if (creditAnimationTicks <= 0) - { - SetTextColor(hdc, titleColor); - SelectObject(hdc, sectionFont); - RECT nameRect = - { - shiftedTextArea.left, - shiftedTextArea.top, - shiftedTextArea.right, - shiftedTextArea.top + SS(30) - }; - DrawText(hdc, creditNames[cardIndex], -1, &nameRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - SetTextColor(hdc, textColor); - SelectObject(hdc, bodyFont); - RECT detailRect = - { - shiftedTextArea.left, - shiftedTextArea.top + SS(34), - shiftedTextArea.right, - shiftedTextArea.bottom - }; - DrawText(hdc, creditTexts[cardIndex], -1, &detailRect, DT_CENTER | DT_TOP | DT_WORDBREAK); - } - }; - - if (creditAnimationTicks > 0 && creditAnimationDirection != 0) - { - DrawCreditCard(previousCredit, previousOffset); - } - DrawCreditCard(currentCredit, currentOffset); - - if (hasOldClipRegion == 1) - { - SelectClipRgn(hdc, oldClipRegion); - } - else - { - SelectClipRgn(hdc, nullptr); - } - DeleteObject(contentClipRegion); - DeleteObject(oldClipRegion); - - RECT leftArrow = - { - rulesCard.left + SS(52), - (rulesCard.top + rulesCard.bottom) / 2 - SS(27), - rulesCard.left + SS(106), - (rulesCard.top + rulesCard.bottom) / 2 + SS(27) - }; - RECT rightArrow = - { - rulesCard.right - SS(106), - (rulesCard.top + rulesCard.bottom) / 2 - SS(27), - rulesCard.right - SS(52), - (rulesCard.top + rulesCard.bottom) / 2 + SS(27) - }; - - HBRUSH arrowBrush = CreateSolidBrush(RGB(255, 245, 249)); - HPEN arrowPen = CreatePen(PS_SOLID, SS(2), accentColor); - oldBrush = (HBRUSH)SelectObject(hdc, arrowBrush); - oldPen = (HPEN)SelectObject(hdc, arrowPen); - RoundRect(hdc, leftArrow.left, leftArrow.top, leftArrow.right, leftArrow.bottom, SS(18), SS(18)); - RoundRect(hdc, rightArrow.left, rightArrow.top, rightArrow.right, rightArrow.bottom, SS(18), SS(18)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(arrowPen); - DeleteObject(arrowBrush); - - HPEN chevronPen = CreatePen(PS_SOLID, SS(4), titleColor); - oldPen = (HPEN)SelectObject(hdc, chevronPen); - MoveToEx(hdc, leftArrow.left + SS(32), leftArrow.top + SS(16), nullptr); - LineTo(hdc, leftArrow.left + SS(22), leftArrow.top + SS(27)); - LineTo(hdc, leftArrow.left + SS(32), leftArrow.bottom - SS(16)); - MoveToEx(hdc, rightArrow.right - SS(32), rightArrow.top + SS(16), nullptr); - LineTo(hdc, rightArrow.right - SS(22), rightArrow.top + SS(27)); - LineTo(hdc, rightArrow.right - SS(32), rightArrow.bottom - SS(16)); - SelectObject(hdc, oldPen); - DeleteObject(chevronPen); - } - if (helpState.currentPage != 3 && helpState.currentPage != 4 && helpState.currentPage != 5) - { - RECT calculateRect = { contentRect.left, contentRect.top, contentRect.right, contentRect.top }; - DrawText(hdc, pageText, -1, &calculateRect, pageFlags | DT_CALCRECT); - int maxHelpScroll = (calculateRect.bottom - calculateRect.top) - (contentRect.bottom - contentRect.top); - if (maxHelpScroll < 0) - { - maxHelpScroll = 0; - } - if (helpScrollOffset > maxHelpScroll) - { - helpScrollOffset = maxHelpScroll; - } - - HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); - int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); - HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); - SelectClipRgn(hdc, contentClipRegion); - - RECT scrolledContentRect = contentRect; - scrolledContentRect.top -= helpScrollOffset; - scrolledContentRect.bottom += maxHelpScroll; - DrawText(hdc, pageText, -1, &scrolledContentRect, pageFlags); - - if (hasOldClipRegion == 1) - { - SelectClipRgn(hdc, oldClipRegion); - } - else - { - SelectClipRgn(hdc, nullptr); - } - DeleteObject(contentClipRegion); - DeleteObject(oldClipRegion); - } - } - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT backHintRect = - { - rulesCard.left + SS(36), - rulesCard.bottom - SS(58), - rulesCard.right - SS(36), - rulesCard.bottom - SS(24) - }; - const TCHAR* helpHint = helpState.currentPage == 0 - ? _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter / Space \u786e\u8ba4\uff0cEsc / M \u8fd4\u56de\u4e3b\u83dc\u5355") - : (helpState.currentPage == 4 - ? _T("\u5de6\u53f3\u65b9\u5411\u952e / A D \u5207\u6362\uff0cEsc / Backspace / M \u8fd4\u56de\u4e3b\u83dc\u5355") - : (helpState.currentPage == 5 - ? _T("\u70b9\u51fb\u6280\u80fd\u540d\u79f0\u6216 Enter \u8fdb\u5165\u64cd\u4f5c\u573a\u666f\uff0c\u6eda\u8f6e\u7ffb\u52a8\uff0cEsc / Backspace / M \u8fd4\u56de\u5e2e\u52a9") - : _T("\u9f20\u6807\u6eda\u8f6e\u4e0a\u4e0b\u7ffb\u52a8\uff0cEsc / Backspace / M \u8fd4\u56de\u5e2e\u52a9"))); - DrawText(hdc, helpHint, -1, &backHintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - SelectClipRgn(hdc, nullptr); - DrawBackButton(); - DrawMusicButton(); - SelectObject(hdc, oldFont); - DeleteObject(titleFont); - DeleteObject(sectionFont); - DeleteObject(bodyFont); - DeleteObject(smallFont); - return; - } - - DrawPanelCardAlpha( - { gameRect.left - SS(10), gameRect.top - SS(10), gameRect.right + SS(10), gameRect.bottom + SS(10) }, - cardColor, - frameColor, - 28, - shellAlpha); - DrawPanelCardAlpha(leftPanelRect, cardColor, frameColor, 30, shellAlpha); - DrawPanelCardAlpha(rightPanelRect, cardColor, frameColor, 30, shellAlpha); - - RECT innerRect = - { - gameRect.left + SS(6), - gameRect.top + SS(6), - gameRect.right - SS(6), - gameRect.bottom - SS(6) - }; - - { - Graphics boardGraphics(hdc); - SolidBrush boardBrush(Color(164, GetRValue(boardColor), GetGValue(boardColor), GetBValue(boardColor))); - SolidBrush innerBrush(Color(176, GetRValue(boardInnerColor), GetGValue(boardInnerColor), GetBValue(boardInnerColor))); - boardGraphics.FillRectangle( - &boardBrush, - static_cast(gameRect.left), - static_cast(gameRect.top), - static_cast(gameRect.right - gameRect.left), - static_cast(gameRect.bottom - gameRect.top)); - boardGraphics.FillRectangle( - &innerBrush, - static_cast(innerRect.left), - static_cast(innerRect.top), - static_cast(innerRect.right - innerRect.left), - static_cast(innerRect.bottom - innerRect.top)); - } - - HPEN borderPen = CreatePen(PS_SOLID, SS(2), RGB(132, 108, 146)); - oldPen = (HPEN)SelectObject(hdc, borderPen); - oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); - RoundRect(hdc, gameRect.left, gameRect.top, gameRect.right, gameRect.bottom, SS(18), SS(18)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(borderPen); - - HPEN gridPen = CreatePen(PS_SOLID, 1, lineColor); - oldPen = (HPEN)SelectObject(hdc, gridPen); - - for (int i = 1; i < nGameWidth; i++) - { - int x = gameRect.left + i * grid; - MoveToEx(hdc, x, gameRect.top, nullptr); - LineTo(hdc, x, gameRect.bottom); - } - - for (int i = 1; i < nGameHeight; i++) - { - int y = gameRect.top + i * grid; - MoveToEx(hdc, gameRect.left, y, nullptr); - LineTo(hdc, gameRect.right, y); - } - - SelectObject(hdc, oldPen); - DeleteObject(gridPen); - - int lockedRows = GetRogueLockedRows(); - if (lockedRows > 0) - { - RECT lockedRect = - { - gameRect.left, - gameRect.top + (nGameHeight - lockedRows) * grid, - gameRect.right, - gameRect.bottom - }; - - Graphics lockedGraphics(hdc); - SolidBrush lockedBrush(Color(150, 58, 48, 68)); - Pen lockedPen(Color(210, 232, 170, 194), 1.0f); - lockedGraphics.FillRectangle( - &lockedBrush, - static_cast(lockedRect.left), - static_cast(lockedRect.top), - static_cast(lockedRect.right - lockedRect.left), - static_cast(lockedRect.bottom - lockedRect.top)); - lockedGraphics.DrawRectangle( - &lockedPen, - static_cast(lockedRect.left), - static_cast(lockedRect.top), - static_cast(lockedRect.right - lockedRect.left), - static_cast(lockedRect.bottom - lockedRect.top)); - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(238, 204, 216)); - RECT lockedTextRect = - { - lockedRect.left + SS(12), - lockedRect.top + SS(8), - lockedRect.right - SS(12), - lockedRect.bottom - SS(8) - }; - DrawText(hdc, _T("封锁区"), -1, &lockedTextRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } - - for (int i = 0; i < nGameHeight; i++) - { - for (int j = 0; j < nGameWidth; j++) - { - if (workRegion[i][j] != 0) - { - int cellValue = workRegion[i][j]; - bool isRainbowCell = (cellValue == 8); - int colorIndex = isRainbowCell ? 0 : (cellValue - 1); - RECT brickRect = - { - gameRect.left + j * grid + SS(2), - gameRect.top + i * grid + SS(2), - gameRect.left + (j + 1) * grid - SS(2), - gameRect.top + (i + 1) * grid - SS(2) - }; - - COLORREF fillColor = isRainbowCell ? RGB(186, 154, 255) : BrickColor[colorIndex]; - COLORREF borderColor = isRainbowCell ? RGB(250, 238, 255) : RGB(255, 248, 250); - HBRUSH brickBrush = CreateSolidBrush(fillColor); - HPEN brickPen = CreatePen(PS_SOLID, isRainbowCell ? SS(2) : 1, borderColor); - oldPen = (HPEN)SelectObject(hdc, brickPen); - oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); - RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(10), SS(10)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(brickBrush); - DeleteObject(brickPen); - } - } - } - - if (targetFlag && !gameOverFlag) - { - HPEN targetPen = CreatePen(PS_DOT, SS(2), RGB(255, 240, 245)); - oldPen = (HPEN)SelectObject(hdc, targetPen); - oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); - - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (bricks[type][state][i][j] != 0) - { - int drawY = target.y + i; - int drawX = target.x + j; - - if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) - { - RoundRect( - hdc, - gameRect.left + drawX * grid + SS(5), - gameRect.top + drawY * grid + SS(5), - gameRect.left + (drawX + 1) * grid - SS(5), - gameRect.top + (drawY + 1) * grid - SS(5), - SS(8), SS(8)); - } - } - } - } - - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(targetPen); - } - - if (!gameOverFlag) - { - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (bricks[type][state][i][j] != 0) - { - int drawY = point.y + i; - int drawX = point.x + j; - - if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) - { - RECT brickRect = - { - gameRect.left + drawX * grid + SS(2), - gameRect.top + drawY * grid + SS(2), - gameRect.left + (drawX + 1) * grid - SS(2), - gameRect.top + (drawY + 1) * grid - SS(2) - }; - - HBRUSH brickBrush = CreateSolidBrush(BrickColor[type]); - COLORREF activeOutlineColor = RGB(255, 250, 252); - int activeOutlineWidth = 1; - if (currentPieceIsExplosive) - { - activeOutlineColor = RGB(255, 116, 78); - activeOutlineWidth = SS(3); - } - else if (currentPieceIsLaser) - { - activeOutlineColor = RGB(120, 232, 255); - activeOutlineWidth = SS(3); - } - else if (currentPieceIsCross) - { - activeOutlineColor = RGB(196, 255, 132); - activeOutlineWidth = SS(3); - } - else if (currentPieceIsRainbow) - { - activeOutlineColor = RGB(186, 126, 255); - activeOutlineWidth = SS(3); - } - HPEN brickPen = CreatePen(PS_SOLID, activeOutlineWidth, activeOutlineColor); - oldPen = (HPEN)SelectObject(hdc, brickPen); - oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); - RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(12), SS(12)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(brickBrush); - DeleteObject(brickPen); - } - } - } - } - } - - for (int i = 0; i < 80; i++) - { - if (gravityFallEffects[i].ticks <= 0 || gravityFallEffects[i].totalTicks <= 0) - { - continue; - } - - if (gravityFallEffects[i].x < 0 || gravityFallEffects[i].x >= nGameWidth || - gravityFallEffects[i].fromY < 0 || gravityFallEffects[i].fromY >= nGameHeight || - gravityFallEffects[i].toY < 0 || gravityFallEffects[i].toY >= nGameHeight) - { - continue; - } - - int elapsed = gravityFallEffects[i].totalTicks - gravityFallEffects[i].ticks; - int travel = gravityFallEffects[i].toY - gravityFallEffects[i].fromY; - int offsetY = travel * grid * elapsed / gravityFallEffects[i].totalTicks; - int drawX = gameRect.left + gravityFallEffects[i].x * grid; - int fromPixelY = gameRect.top + gravityFallEffects[i].fromY * grid; - int toPixelY = gameRect.top + gravityFallEffects[i].toY * grid; - int currentPixelY = fromPixelY + offsetY; - int colorIndex = gravityFallEffects[i].cellValue - 1; - if (colorIndex < 0 || colorIndex >= 7) - { - colorIndex = (gravityFallEffects[i].x + gravityFallEffects[i].toY) % 7; - } - COLORREF fallColor = BrickColor[colorIndex]; - - int alpha = 56 + gravityFallEffects[i].ticks * 150 / gravityFallEffects[i].totalTicks; - int trailTop = fromPixelY + grid / 2; - int trailBottom = currentPixelY + grid / 2; - if (trailBottom < trailTop + SS(8)) - { - trailBottom = trailTop + SS(8); - } - if (trailBottom > toPixelY + grid / 2) - { - trailBottom = toPixelY + grid / 2; - } - - Graphics fallGraphics(hdc); - fallGraphics.SetSmoothingMode(SmoothingModeAntiAlias); - SolidBrush trailBrush(Color(alpha / 2, GetRValue(fallColor), GetGValue(fallColor), GetBValue(fallColor))); - fallGraphics.FillRectangle( - &trailBrush, - static_cast(drawX + SS(12)), - static_cast(trailTop), - static_cast(grid - SS(24)), - static_cast(trailBottom - trailTop)); - - SolidBrush ghostBrush(Color(alpha, GetRValue(fallColor), GetGValue(fallColor), GetBValue(fallColor))); - Pen ghostPen(Color(230, 255, 255, 255), static_cast(SS(2))); - fallGraphics.FillRectangle( - &ghostBrush, - static_cast(drawX + SS(3)), - static_cast(currentPixelY + SS(3)), - static_cast(grid - SS(6)), - static_cast(grid - SS(6))); - fallGraphics.DrawRectangle( - &ghostPen, - static_cast(drawX + SS(3)), - static_cast(currentPixelY + SS(3)), - static_cast(grid - SS(6)), - static_cast(grid - SS(6))); - } - - if (clearEffectState.ticks > 0 && clearEffectState.totalTicks > 0) - { - int elapsed = clearEffectState.totalTicks - clearEffectState.ticks; - int alpha = 42 + clearEffectState.ticks * 150 / clearEffectState.totalTicks; - int inset = SS(elapsed * 2); - Graphics flashGraphics(hdc); - - for (int i = 0; i < clearEffectState.rowCount; i++) - { - int row = clearEffectState.rows[i]; - if (row < 0 || row >= nGameHeight) - { - continue; - } - - int top = gameRect.top + row * grid + inset; - int height = grid - inset * 2; - if (height < SS(4)) - { - height = SS(4); - } - - SolidBrush flashBrush(Color(alpha, 255, 248, 174)); - flashGraphics.FillRectangle( - &flashBrush, - static_cast(gameRect.left + SS(2)), - static_cast(top), - static_cast(gameRect.right - gameRect.left - SS(4)), - static_cast(height)); - } - } - - for (int i = 0; i < 64; i++) - { - if (cellFlashEffects[i].ticks <= 0 || cellFlashEffects[i].totalTicks <= 0) - { - continue; - } - - if (cellFlashEffects[i].x < 0 || cellFlashEffects[i].x >= nGameWidth || - cellFlashEffects[i].y < 0 || cellFlashEffects[i].y >= nGameHeight) - { - continue; - } - - int alpha = 48 + cellFlashEffects[i].ticks * 168 / cellFlashEffects[i].totalTicks; - int pulseInset = SS(2 + (cellFlashEffects[i].totalTicks - cellFlashEffects[i].ticks) % 3); - RECT flashRect = - { - gameRect.left + cellFlashEffects[i].x * grid + pulseInset, - gameRect.top + cellFlashEffects[i].y * grid + pulseInset, - gameRect.left + (cellFlashEffects[i].x + 1) * grid - pulseInset, - gameRect.top + (cellFlashEffects[i].y + 1) * grid - pulseInset - }; - - Graphics cellGraphics(hdc); - cellGraphics.SetSmoothingMode(SmoothingModeAntiAlias); - SolidBrush cellBrush(Color(alpha, GetRValue(cellFlashEffects[i].color), GetGValue(cellFlashEffects[i].color), GetBValue(cellFlashEffects[i].color))); - Pen cellPen(Color(230, GetRValue(cellFlashEffects[i].color), GetGValue(cellFlashEffects[i].color), GetBValue(cellFlashEffects[i].color)), static_cast(SS(2))); - cellGraphics.FillRectangle( - &cellBrush, - static_cast(flashRect.left), - static_cast(flashRect.top), - static_cast(flashRect.right - flashRect.left), - static_cast(flashRect.bottom - flashRect.top)); - cellGraphics.DrawRectangle( - &cellPen, - static_cast(flashRect.left), - static_cast(flashRect.top), - static_cast(flashRect.right - flashRect.left), - static_cast(flashRect.bottom - flashRect.top)); - } - - for (int i = 0; i < 96; i++) - { - if (particleEffects[i].ticks <= 0 || particleEffects[i].totalTicks <= 0) - { - continue; - } - - int elapsed = particleEffects[i].totalTicks - particleEffects[i].ticks; - int particleX = gameRect.left + particleEffects[i].boardX * grid / 100 + SS(particleEffects[i].velocityX * elapsed); - int particleY = gameRect.top + particleEffects[i].boardY * grid / 100 + SS(particleEffects[i].velocityY * elapsed + elapsed * elapsed / 4); - int particleSize = particleEffects[i].size * particleEffects[i].ticks / particleEffects[i].totalTicks; - if (particleSize < 2) - { - particleSize = 2; - } - if (particleSize > 6) - { - particleSize = 6; - } - - RECT particleRect = - { - particleX - particleSize, - particleY - particleSize, - particleX + particleSize, - particleY + particleSize - }; - - HBRUSH particleBrush = GetCachedParticleBrush(particleEffects[i].color); - FillRect(hdc, &particleRect, particleBrush); - } - - for (int i = 0; i < 8; i++) - { - if (floatingTextEffects[i].ticks <= 0 || floatingTextEffects[i].totalTicks <= 0) - { - continue; - } - - int elapsed = floatingTextEffects[i].totalTicks - floatingTextEffects[i].ticks; - int textX = gameRect.left + floatingTextEffects[i].boardX * grid / 100; - int textY = gameRect.top + floatingTextEffects[i].boardY * grid / 100 - SS(elapsed * 4); - - HFONT oldFloatFont = (HFONT)SelectObject(hdc, sectionFont); - SetTextColor(hdc, floatingTextEffects[i].color); - RECT floatRect = - { - textX - SS(160), - textY - SS(24), - textX + SS(160), - textY + SS(28) - }; - DrawText(hdc, floatingTextEffects[i].text, -1, &floatRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - SelectObject(hdc, oldFloatFont); - } - - HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); - DrawPanelHeader(leftPanelRect, _T("战局信息"), 120); - DrawPanelHeader(rightPanelRect, _T("预览与战术"), 148); - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - - RECT overviewRect = - { - leftPanelRect.left + SS(20), - leftPanelRect.top + SS(98), - leftPanelRect.right - SS(20), - leftPanelRect.top + SS(252) - }; - DrawPanelCardAlpha(overviewRect, RGB(255, 247, 250), RGB(233, 191, 208), 24, panelAlpha); - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - RECT overviewTitleRect = - { - overviewRect.left + SS(18), - overviewRect.top + SS(12), - overviewRect.right - SS(18), - overviewRect.top + SS(42) - }; - DrawText(hdc, _T("战场状态"), -1, &overviewTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, bodyFont); - TCHAR overviewText[160]; - int totalLines = (currentMode == MODE_CLASSIC) ? classicStats.totalLinesCleared : rogueStats.totalLinesCleared; - if (currentMode == MODE_ROGUE) - { - _stprintf_s( - overviewText, - _T("得分 %d\r\n模式 Rogue\r\n消行 %d\r\n危险 Lv.%d 封锁 %d"), - tScore, - totalLines, - rogueStats.difficultyLevel, - GetRogueLockedRows()); - } - else - { - _stprintf_s( - overviewText, - _T("得分 %d\r\n模式 经典\r\n消行 %d"), - tScore, - totalLines); - } - RECT overviewBodyRect = - { - overviewRect.left + SS(22), - overviewRect.top + SS(62), - overviewRect.right - SS(22), - overviewRect.bottom - SS(22) - }; - DrawText(hdc, overviewText, -1, &overviewBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); - - if (currentMode == MODE_ROGUE) - { - int progressTop = IsRogueSkillDemoMode() ? 350 : 270; - int progressBottom = IsRogueSkillDemoMode() ? 568 : 488; - int upgradeTop = IsRogueSkillDemoMode() ? 590 : 510; - - if (IsRogueSkillDemoMode()) - { - RECT demoSkillRect = - { - leftPanelRect.left + SS(20), - leftPanelRect.top + SS(270), - leftPanelRect.right - SS(20), - leftPanelRect.top + SS(332) - }; - DrawPanelCardAlpha(demoSkillRect, RGB(255, 244, 248), RGB(232, 184, 202), 20, panelNestedAlpha); - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(132, 102, 118)); - RECT demoSkillLabelRect = - { - demoSkillRect.left + SS(18), - demoSkillRect.top + SS(8), - demoSkillRect.right - SS(18), - demoSkillRect.top + SS(26) - }; - DrawText(hdc, _T("\u5f53\u524d\u6280\u80fd"), -1, &demoSkillLabelRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, titleColor); - RECT demoSkillNameRect = - { - demoSkillRect.left + SS(18), - demoSkillRect.top + SS(26), - demoSkillRect.right - SS(18), - demoSkillRect.bottom - SS(8) - }; - DrawText(hdc, GetCurrentRogueSkillDemoName(), -1, &demoSkillNameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); - } - - RECT progressRect = - { - leftPanelRect.left + SS(20), - leftPanelRect.top + SS(progressTop), - leftPanelRect.right - SS(20), - leftPanelRect.top + SS(progressBottom) - }; - DrawPanelCardAlpha(progressRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - RECT progressTitleRect = - { - progressRect.left + SS(18), - progressRect.top + SS(12), - progressRect.right - SS(18), - progressRect.top + SS(42) - }; - DrawText(hdc, _T("成长进度"), -1, &progressTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, bodyFont); - TCHAR progressText[96]; - _stprintf_s(progressText, _T("等级 %d\r\nEXP %d / %d"), rogueStats.level, rogueStats.exp, rogueStats.requiredExp); - RECT progressTextRect = - { - progressRect.left + SS(22), - progressRect.top + SS(56), - progressRect.right - SS(22), - progressRect.top + SS(120) - }; - DrawText(hdc, progressText, -1, &progressTextRect, DT_LEFT | DT_TOP | DT_WORDBREAK); - - RECT expBarRect = - { - progressRect.left + SS(22), - progressRect.top + SS(122), - progressRect.right - SS(22), - progressRect.top + SS(148) - }; - HBRUSH expTrackBrush = CreateSolidBrush(RGB(240, 220, 229)); - FillRect(hdc, &expBarRect, expTrackBrush); - DeleteObject(expTrackBrush); - - RECT expFillRect = expBarRect; - int expWidth = expBarRect.right - expBarRect.left; - if (rogueStats.requiredExp > 0) - { - expFillRect.right = expBarRect.left + expWidth * rogueStats.exp / rogueStats.requiredExp; - } - if (expFillRect.right < expFillRect.left) - { - expFillRect.right = expFillRect.left; - } - if (expFillRect.right > expBarRect.right) - { - expFillRect.right = expBarRect.right; - } - HBRUSH expFillBrush = CreateSolidBrush(accentColor); - FillRect(hdc, &expFillRect, expFillBrush); - DeleteObject(expFillBrush); - - const TCHAR* statLabels[2] = - { - _T("\u5f97\u5206\u500d\u7387"), - _T("EXP \u500d\u7387") - }; - TCHAR statValues[2][32]; - _stprintf_s(statValues[0], _T("%d%%"), rogueStats.scoreMultiplierPercent); - _stprintf_s(statValues[1], _T("%d%%"), rogueStats.expMultiplierPercent); - - int statGap = SS(8); - int statWidth = (progressRect.right - progressRect.left - SS(44) - statGap) / 2; - for (int statIndex = 0; statIndex < 2; statIndex++) - { - RECT statRect = - { - progressRect.left + SS(22) + statIndex * (statWidth + statGap), - progressRect.top + SS(154), - progressRect.left + SS(22) + statIndex * (statWidth + statGap) + statWidth, - progressRect.top + SS(204) - }; - DrawPanelCardAlpha(statRect, RGB(255, 239, 245), RGB(232, 184, 202), 16, panelNestedAlpha); - - RECT statLabelRect = - { - statRect.left + SS(8), - statRect.top + SS(4), - statRect.right - SS(8), - statRect.top + SS(22) - }; - RECT statValueRect = - { - statRect.left + SS(8), - statRect.top + SS(21), - statRect.right - SS(8), - statRect.bottom - SS(2) - }; - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(132, 102, 118)); - DrawText(hdc, statLabels[statIndex], -1, &statLabelRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - SelectObject(hdc, bodyFont); - SetTextColor(hdc, titleColor); - DrawText(hdc, statValues[statIndex], -1, &statValueRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } - - RECT upgradeListRect = - { - leftPanelRect.left + SS(20), - leftPanelRect.top + SS(upgradeTop), - leftPanelRect.right - SS(20), - leftPanelRect.bottom - SS(20) - }; - DrawPanelCardAlpha(upgradeListRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - TextOut(hdc, upgradeListRect.left + SS(18), upgradeListRect.top + SS(16), _T("已掌握强化"), lstrlen(_T("已掌握强化"))); - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(148, 118, 132)); - RECT upgradeScrollHintRect = - { - upgradeListRect.left + SS(116), - upgradeListRect.top + SS(17), - upgradeListRect.right - SS(18), - upgradeListRect.top + SS(42) - }; - DrawText(hdc, _T("J/K / 滚轮"), -1, &upgradeScrollHintRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT upgradeBodyRect = - { - upgradeListRect.left + SS(22), - upgradeListRect.top + SS(56), - upgradeListRect.right - SS(22), - upgradeListRect.bottom - SS(20) - }; - - TCHAR upgradeSummary[512]; - upgradeSummary[0] = _T('\0'); - - if (rogueStats.scoreUpgradeLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5206\u6570\u500d\u7387 Lv.%d\r\n"), rogueStats.scoreUpgradeLevel); - } - if (rogueStats.expUpgradeLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("EXP \u5f3a\u5316 Lv.%d\r\n"), rogueStats.expUpgradeLevel); - } - if (rogueStats.slowFallStacks > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6162\u901f\u4e0b\u843d Lv.%d\r\n"), rogueStats.slowFallStacks); - } - if (rogueStats.comboBonusStacks > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u51fb\u52a0\u6210 Lv.%d\r\n"), rogueStats.comboBonusStacks); - } - if (rogueStats.previewUpgradeLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u989d\u5916\u9884\u89c8 Lv.%d\r\n"), rogueStats.previewUpgradeLevel); - } - if (rogueStats.lastChanceUpgradeLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6700\u540e\u4e00\u640f Lv.1 \u5269\u4f59 %d \u6b21\r\n"), rogueStats.lastChanceCount); - } - if (rogueStats.pressureReliefLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u51cf\u538b Lv.%d\r\n"), rogueStats.pressureReliefLevel); - } - if (rogueStats.sweeperLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6e05\u626b\u8005 Lv.%d\r\n"), rogueStats.sweeperLevel); - } - if (rogueStats.explosiveLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7206\u7834\u65b9\u5757 Lv.%d\r\n"), rogueStats.explosiveLevel); - } - if (rogueStats.chainBlastLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u9501\u7206\u7834 Lv.1\r\n")); - } - if (rogueStats.chainBombLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u73af\u70b8\u5f39 Lv.1\r\n")); - } - if (rogueStats.laserLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6fc0\u5149\u65b9\u5757 Lv.%d\r\n"), rogueStats.laserLevel); - } - if (rogueStats.thunderTetrisLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u96f7\u9706\u56db\u6d88 Lv.1\r\n")); - } - if (rogueStats.thunderLaserLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u96f7\u9706\u6fc0\u5149 Lv.1\r\n")); - } - if (rogueStats.feverLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u72c2\u70ed\u6a21\u5f0f Lv.1\r\n")); - } - if (rogueStats.rageStackLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u66b4\u8d70\u5806\u53e0 Lv.1\r\n")); - } - if (rogueStats.infiniteFeverLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65e0\u9650\u72c2\u70ed Lv.1\r\n")); - } - if (rogueStats.screenBombLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6e05\u5c4f\u70b8\u5f39 Lv.%d \u5269\u4f59 %d \u679a\r\n"), rogueStats.screenBombLevel, rogueStats.screenBombCount); - } - if (rogueStats.terminalClearLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7ec8\u672b\u6e05\u573a Lv.1\r\n")); - } - if (rogueStats.perfectRotateLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5b8c\u7f8e\u65cb\u8f6c Lv.1\r\n")); - } - if (rogueStats.timeDilationLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65f6\u95f4\u7f13\u6d41 Lv.1\r\n")); - } - if (rogueStats.highPressureLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u9ad8\u538b\u5956\u52b1 Lv.1\r\n")); - } - if (rogueStats.tetrisGambleLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8d4c\u547d\u56db\u6d88 Lv.1\r\n")); - } - if (rogueStats.extremePlayerLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6781\u9650\u73a9\u5bb6 Lv.1\r\n")); - } - if (rogueStats.upgradeShockwaveLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5347\u7ea7\u51b2\u51fb\u6ce2 Lv.1\r\n")); - } - if (rogueStats.evolutionImpactLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fdb\u5316\u51b2\u51fb Lv.1\r\n")); - } - if (rogueStats.controlMasterLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u64cd\u63a7\u5927\u5e08 Lv.1\r\n")); - } - if (rogueStats.blockStormLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65b9\u5757\u98ce\u66b4 Lv.1\r\n")); - } - if (rogueStats.crossPieceLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5341\u5b57\u65b9\u5757 Lv.%d\r\n"), rogueStats.crossPieceLevel); - } - if (rogueStats.blackHoleLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u9ed1\u6d1e Lv.%d \u5269\u4f59 %d \u6b21\r\n"), rogueStats.blackHoleLevel, rogueStats.blackHoleCharges); - } - if (rogueStats.reshapeLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a7a\u4e2d\u6362\u5f62 Lv.%d \u5269\u4f59 %d \u6b21\r\n"), rogueStats.reshapeLevel, rogueStats.reshapeCharges); - } - if (rogueStats.rainbowPieceLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5f69\u8679\u65b9\u5757 Lv.1\r\n")); - } - if (rogueStats.voidCoreLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u865a\u7a7a\u6838\u5fc3 Lv.1\r\n")); - } - if (rogueStats.stableStructureLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a33\u5b9a\u7ed3\u6784 Lv.%d\r\n"), rogueStats.stableStructureLevel); - } - if (rogueStats.doubleGrowthLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6210\u957f\u6838\u5fc3 Lv.1\r\n")); - } - if (rogueStats.pieceTuningLevels[0] > 0) - { - _stprintf_s( - upgradeSummary + lstrlen(upgradeSummary), - 512 - lstrlen(upgradeSummary), - _T("I \u65b9\u5757\u6539\u9020 Lv.%d\r\n"), - rogueStats.pieceTuningLevels[0]); - } - if (rogueStats.gamblerLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8d4c\u5f92 Lv.%d\r\n"), rogueStats.gamblerLevel); - } - if (rogueStats.dualChoiceLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u53cc\u91cd\u9009\u62e9 Lv.1\r\n")); - } - if (rogueStats.destinyWheelLevel > 0) - { - _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u547d\u8fd0\u8f6e\u76d8 Lv.1\r\n")); - } - if (lstrlen(upgradeSummary) == 0) - { - _stprintf_s(upgradeSummary, _T("尚未掌握强化。\r\n下一次升级将开启构筑。")); - } - - RECT upgradeCalculateRect = - { - upgradeBodyRect.left, - upgradeBodyRect.top, - upgradeBodyRect.right, - upgradeBodyRect.top - }; - DrawText(hdc, upgradeSummary, -1, &upgradeCalculateRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); - int maxUpgradeScroll = (upgradeCalculateRect.bottom - upgradeCalculateRect.top) - (upgradeBodyRect.bottom - upgradeBodyRect.top); - if (maxUpgradeScroll < 0) - { - maxUpgradeScroll = 0; - } - if (upgradeListScrollOffset > maxUpgradeScroll) - { - upgradeListScrollOffset = maxUpgradeScroll; - } - - HRGN oldUpgradeClipRegion = CreateRectRgn(0, 0, 0, 0); - int hasOldUpgradeClipRegion = GetClipRgn(hdc, oldUpgradeClipRegion); - HRGN upgradeClipRegion = CreateRectRgn(upgradeBodyRect.left, upgradeBodyRect.top, upgradeBodyRect.right, upgradeBodyRect.bottom); - SelectClipRgn(hdc, upgradeClipRegion); - - RECT scrolledUpgradeBodyRect = upgradeBodyRect; - scrolledUpgradeBodyRect.top -= upgradeListScrollOffset; - scrolledUpgradeBodyRect.bottom += maxUpgradeScroll; - DrawText(hdc, upgradeSummary, -1, &scrolledUpgradeBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); - - if (hasOldUpgradeClipRegion == 1) - { - SelectClipRgn(hdc, oldUpgradeClipRegion); - } - else - { - SelectClipRgn(hdc, nullptr); - } - DeleteObject(upgradeClipRegion); - DeleteObject(oldUpgradeClipRegion); - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - } - else - { - RECT classicInfoRect = - { - leftPanelRect.left + SS(20), - leftPanelRect.top + SS(270), - leftPanelRect.right - SS(20), - leftPanelRect.top + SS(458) - }; - DrawPanelCardAlpha(classicInfoRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT classicBodyRect = - { - classicInfoRect.left + SS(22), - classicInfoRect.top + SS(24), - classicInfoRect.right - SS(22), - classicInfoRect.bottom - SS(24) - }; - DrawText(hdc, _T("经典模式:没有成长、EXP 和强化,只保留纯粹的消行挑战。\r\n\r\n在有限空间中保持阵型,尽可能活得更久。"), -1, &classicBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); - } - RECT holdNextRect = - { - rightPanelRect.left + SS(20), - rightPanelRect.top + SS(98), - rightPanelRect.right - SS(20), - rightPanelRect.top + SS(326) - }; - DrawPanelCardAlpha(holdNextRect, RGB(255, 247, 250), RGB(233, 191, 208), 24, panelAlpha); - - RECT holdCard = - { - holdNextRect.left + SS(88), - holdNextRect.top + SS(24), - holdNextRect.left + SS(150), - holdNextRect.top + SS(86) - }; - - RECT nextSectionRect = - { - holdNextRect.left + SS(18), - holdNextRect.top + SS(146), - holdNextRect.right - SS(18), - holdNextRect.bottom - SS(22) - }; - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - RECT holdTitleRect = - { - holdNextRect.left + SS(18), - holdNextRect.top + SS(34), - holdNextRect.left + SS(80), - holdNextRect.top + SS(70) - }; - DrawText(hdc, _T("Hold"), -1, &holdTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - RECT nextTitleRect = - { - nextSectionRect.left, - holdNextRect.top + SS(102), - holdNextRect.right - SS(18), - holdNextRect.top + SS(136) - }; - DrawText(hdc, _T("\u4e0b\u4e00\u4e2a"), -1, &nextTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - DrawPanelCardAlpha(holdCard, RGB(255, 238, 244), RGB(233, 191, 208), 22, panelNestedAlpha); - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT holdHintRect = - { - holdCard.right + SS(14), - holdCard.top + SS(2), - holdNextRect.right - SS(18), - holdCard.bottom - SS(2) - }; - DrawText( - hdc, - currentMode == MODE_ROGUE && rogueStats.holdUnlocked > 0 - ? (holdUsedThisTurn ? _T("已使用\r\n落地后恢复") : _T("C / Shift\r\n暂存一次")) - : _T("未开启\r\n需备用仓"), - -1, - &holdHintRect, - DT_LEFT | DT_VCENTER | DT_WORDBREAK); - - if (currentMode == MODE_ROGUE) - { - if (rogueStats.holdUnlocked == 0) - { - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT holdLockedRect = - { - holdCard.left + SS(10), - holdCard.top + SS(10), - holdCard.right - SS(10), - holdCard.bottom - SS(10) - }; - DrawText(hdc, _T("封存"), -1, &holdLockedRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); - } - else if (holdType >= 0) - { - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (bricks[holdType][0][i][j] != 0) - { - int holdAvailableWidth = holdCard.right - holdCard.left - SS(16); - int holdAvailableHeight = holdCard.bottom - holdCard.top - SS(16); - int holdMiniCell = holdAvailableWidth < holdAvailableHeight ? holdAvailableWidth / 4 : holdAvailableHeight / 4; - if (holdMiniCell < SS(7)) - { - holdMiniCell = SS(7); - } - int holdOffsetX = holdCard.left + (holdCard.right - holdCard.left - holdMiniCell * 4) / 2; - int holdOffsetY = holdCard.top + (holdCard.bottom - holdCard.top - holdMiniCell * 4) / 2; - RECT brickRect = - { - holdOffsetX + j * holdMiniCell, - holdOffsetY + i * holdMiniCell, - holdOffsetX + (j + 1) * holdMiniCell - SS(1), - holdOffsetY + (i + 1) * holdMiniCell - SS(1) - }; - - HBRUSH brickBrush = CreateSolidBrush(BrickColor[holdType]); - HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 248, 250)); - oldPen = (HPEN)SelectObject(hdc, brickPen); - oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); - RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(8), SS(8)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(brickBrush); - DeleteObject(brickPen); - } - } - } - } - else - { - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT emptyHoldRect = - { - holdCard.left + SS(10), - holdCard.top + SS(10), - holdCard.right - SS(10), - holdCard.bottom - SS(10) - }; - DrawText(hdc, _T("\u7a7a"), -1, &emptyHoldRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); - } - } - else - { - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT classicHoldRect = - { - holdCard.left + SS(10), - holdCard.top + SS(10), - holdCard.right - SS(10), - holdCard.bottom - SS(10) - }; - DrawText(hdc, _T("\u5173"), -1, &classicHoldRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); - } - - int previewCount = 1; - if (currentMode == MODE_ROGUE) - { - previewCount = rogueStats.previewCount; - if (previewCount < 1) - { - previewCount = 1; - } - if (previewCount > 3) - { - previewCount = 3; - } - } - - for (int previewIndex = 0; previewIndex < previewCount; previewIndex++) - { - int nextCardGap = SS(8); - int nextCardWidth = (nextSectionRect.right - nextSectionRect.left - nextCardGap * 2) / 3; - RECT nextCard = - { - nextSectionRect.left + previewIndex * (nextCardWidth + nextCardGap), - nextSectionRect.top, - nextSectionRect.left + previewIndex * (nextCardWidth + nextCardGap) + nextCardWidth, - nextSectionRect.bottom - }; - - DrawPanelCardAlpha(nextCard, RGB(255, 247, 250), RGB(233, 191, 208), 18, panelStrongAlpha); - - int previewType = nextTypes[previewIndex]; - - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (bricks[previewType][0][i][j] != 0) - { - int nextAvailableWidth = nextCard.right - nextCard.left - SS(14); - int nextAvailableHeight = nextCard.bottom - nextCard.top - SS(14); - int nextMiniCell = nextAvailableWidth < nextAvailableHeight ? nextAvailableWidth / 4 : nextAvailableHeight / 4; - if (nextMiniCell < SS(7)) - { - nextMiniCell = SS(7); - } - int previewOffsetX = nextCard.left + (nextCard.right - nextCard.left - nextMiniCell * 4) / 2; - int previewOffsetY = nextCard.top + (nextCard.bottom - nextCard.top - nextMiniCell * 4) / 2; - RECT brickRect = - { - previewOffsetX + j * nextMiniCell, - previewOffsetY + i * nextMiniCell, - previewOffsetX + (j + 1) * nextMiniCell - SS(1), - previewOffsetY + (i + 1) * nextMiniCell - SS(1) - }; - - HBRUSH brickBrush = CreateSolidBrush(BrickColor[previewType]); - HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 248, 250)); - oldPen = (HPEN)SelectObject(hdc, brickPen); - oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); - RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(8), SS(8)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(brickBrush); - DeleteObject(brickPen); - } - } - } - } - - RECT controlRect = - { - rightPanelRect.left + SS(20), - rightPanelRect.top + SS(346), - rightPanelRect.right - SS(20), - rightPanelRect.bottom - SS(20) - }; - DrawPanelCardAlpha(controlRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - TextOut(hdc, controlRect.left + SS(18), controlRect.top + SS(16), _T("战斗日志"), lstrlen(_T("战斗日志"))); - - RECT feedbackPanelRect = - { - controlRect.left + SS(18), - controlRect.top + SS(56), - controlRect.right - SS(18), - controlRect.top + SS(172) - }; - DrawPanelCardAlpha(feedbackPanelRect, RGB(255, 244, 248), RGB(232, 184, 202), 20, panelNestedAlpha); - - SelectObject(hdc, bodyFont); - SetTextColor(hdc, titleColor); - RECT feedbackTitleRect = - { - feedbackPanelRect.left + SS(16), - feedbackPanelRect.top + SS(14), - feedbackPanelRect.right - SS(16), - feedbackPanelRect.top + SS(42) - }; - DrawText( - hdc, - feedbackState.visibleTicks > 0 ? feedbackState.title : _T("局势平稳"), - -1, - &feedbackTitleRect, - DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(116, 90, 104)); - RECT feedbackBodyRect = - { - feedbackPanelRect.left + SS(16), - feedbackPanelRect.top + SS(46), - feedbackPanelRect.right - SS(16), - feedbackPanelRect.bottom - SS(14) - }; - DrawText( - hdc, - feedbackState.visibleTicks > 0 - ? feedbackState.detail - : _T("连消、主动技能和特殊方块的战斗记录会出现在这里。"), - -1, - &feedbackBodyRect, - DT_LEFT | DT_TOP | DT_WORDBREAK); - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, textColor); - RECT tipsTitleRect = - { - controlRect.left + SS(22), - controlRect.top + SS(192), - controlRect.right - SS(22), - controlRect.top + SS(224) - }; - DrawText(hdc, _T("操作指令"), -1, &tipsTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, bodyFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT controlBodyRect = - { - controlRect.left + SS(22), - controlRect.top + SS(232), - controlRect.right - SS(22), - controlRect.bottom - SS(22) - }; - if (currentMode == MODE_ROGUE) - { - if (IsRogueSkillDemoMode()) - { - DrawText( - hdc, - _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") - _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") - _T("演示:R 重置当前 N 下一个 M 菜单\r\n") - _T("技能:C 备用仓 Z 黑洞 X 炸弹 V 换形"), - -1, - &controlBodyRect, - DT_LEFT | DT_TOP | DT_WORDBREAK); - } - else - { - DrawText( - hdc, - _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") - _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") - _T("战局:P 暂停 R 重开 M 菜单\r\n") - _T("技能:C 备用仓 Z 黑洞 X 炸弹 V 换形"), - -1, - &controlBodyRect, - DT_LEFT | DT_TOP | DT_WORDBREAK); - } - } - else - { - DrawText( - hdc, - _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") - _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") - _T("战局:P 暂停 R 重开 M 菜单"), - -1, - &controlBodyRect, - DT_LEFT | DT_TOP | DT_WORDBREAK); - } - - // 暂停和结束覆盖层只盖住棋盘区域,让两侧战斗信息仍然可见。 - if (suspendFlag || gameOverFlag) - { - RECT overlayRect = - { - gameRect.left + SS(28), - gameRect.top + grid * 6 + SS(10), - gameRect.right - SS(28), - gameRect.top + grid * 10 + SS(30) - }; - - HBRUSH overlayBrush = CreateSolidBrush(RGB(255, 241, 246)); - HPEN overlayPen = CreatePen(PS_SOLID, 2, RGB(232, 170, 194)); - oldPen = (HPEN)SelectObject(hdc, overlayPen); - oldBrush = (HBRUSH)SelectObject(hdc, overlayBrush); - RoundRect(hdc, overlayRect.left, overlayRect.top, overlayRect.right, overlayRect.bottom, SS(26), SS(26)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(overlayBrush); - DeleteObject(overlayPen); - - RECT titleRect = - { - overlayRect.left + SS(24), - overlayRect.top + SS(18), - overlayRect.right - SS(24), - overlayRect.top + SS(78) - }; - - RECT tipRect = - { - overlayRect.left + SS(28), - overlayRect.top + SS(86), - overlayRect.right - SS(28), - overlayRect.bottom - SS(20) - }; - - SetTextColor(hdc, RGB(121, 76, 99)); - SelectObject(hdc, titleFont); - - if (suspendFlag) - { - DrawText(hdc, _T("时间静止"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - int buttonGap = SS(18); - int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; - RECT continueRect = - { - overlayRect.left + SS(34), - overlayRect.top + SS(94), - overlayRect.left + SS(34) + buttonWidth, - overlayRect.top + SS(138) - }; - RECT menuRect = - { - continueRect.right + buttonGap, - continueRect.top, - overlayRect.right - SS(34), - continueRect.bottom - }; - DrawPanelCardAlpha(continueRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); - DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); - SelectObject(hdc, bodyFont); - DrawText(hdc, _T("继续 (P)"), -1, &continueRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } - else - { - DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - if (reviveAvailable) - { - int buttonGap = SS(8); - int buttonWidth = (overlayRect.right - overlayRect.left - SS(28) - buttonGap * 2) / 3; - RECT reviveRect = - { - overlayRect.left + SS(14), - overlayRect.top + SS(94), - overlayRect.left + SS(14) + buttonWidth, - overlayRect.top + SS(138) - }; - RECT restartRect = - { - reviveRect.right + buttonGap, - reviveRect.top, - reviveRect.right + buttonGap + buttonWidth, - reviveRect.bottom - }; - RECT menuRect = - { - restartRect.right + buttonGap, - reviveRect.top, - overlayRect.right - SS(14), - reviveRect.bottom - }; - DrawPanelCardAlpha(reviveRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); - DrawPanelCardAlpha(restartRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); - DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); - SelectObject(hdc, bodyFont); - DrawText(hdc, _T("复活 (V)"), -1, &reviveRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } - else - { - int buttonGap = SS(18); - int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; - RECT restartRect = - { - overlayRect.left + SS(34), - overlayRect.top + SS(94), - overlayRect.left + SS(34) + buttonWidth, - overlayRect.top + SS(138) - }; - RECT menuRect = - { - restartRect.right + buttonGap, - restartRect.top, - overlayRect.right - SS(34), - restartRect.bottom - }; - DrawPanelCardAlpha(restartRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); - DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); - SelectObject(hdc, bodyFont); - DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } - } - } - - // 升级选择界面在当前战局上方绘制半透明遮罩,保留背景局势作为上下文。 - if (currentScreen == SCREEN_UPGRADE) - { - RECT dimRect = - { - SX(20), - SY(20), - SX(WINDOW_CLIENT_WIDTH - 20), - SY(WINDOW_CLIENT_HEIGHT - 20) - }; - Graphics dimGraphics(hdc); - SolidBrush dimBrush(Color(214, 246, 232, 238)); - dimGraphics.FillRectangle( - &dimBrush, - static_cast(dimRect.left), - static_cast(dimRect.top), - static_cast(dimRect.right - dimRect.left), - static_cast(dimRect.bottom - dimRect.top)); - - RECT overlayRect = - { - SX(60), - SY(80), - SX(WINDOW_CLIENT_WIDTH - 60), - SY(WINDOW_CLIENT_HEIGHT - 80) - }; - - DrawPanelCardAlpha(overlayRect, RGB(255, 250, 252), RGB(232, 170, 194), 30, 238); - - SetTextColor(hdc, titleColor); - SelectObject(hdc, titleFont); - - RECT overlayTitleRect = - { - overlayRect.left, - overlayRect.top + SS(24), - overlayRect.right, - overlayRect.top + SS(68) - }; - DrawText(hdc, _T("选择强化"), -1, &overlayTitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, bodyFont); - SetTextColor(hdc, textColor); - RECT overlaySubtitleRect = - { - overlayRect.left, - overlayRect.top + SS(70), - overlayRect.right, - overlayRect.top + SS(106) - }; - TCHAR overlaySubtitle[128]; - if (upgradeUiState.picksRemaining > 1) - { - _stprintf_s( - overlaySubtitle, - _T("出现 %d 个强化,请标记 %d 个(已选 %d 个)"), - upgradeUiState.optionCount, - upgradeUiState.picksRemaining, - upgradeUiState.markedCount); - } - else - { - _stprintf_s( - overlaySubtitle, - _T("出现 %d 个强化,还可选择 %d 个"), - upgradeUiState.optionCount, - upgradeUiState.picksRemaining); - } - DrawText(hdc, overlaySubtitle, -1, &overlaySubtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - int gap = SS(18); - int horizontalPadding = SS(36); - int verticalTop = overlayRect.top + SS(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 - SS(72) - (rowCount - 1) * gap; - int cardHeight = availableHeight / rowCount; - - for (int i = 0; i < upgradeUiState.optionCount; i++) - { - bool isSelected = (i == upgradeUiState.selectedIndex); - bool isMarked = upgradeUiState.marked[i]; - int column = i % columnCount; - int row = i / columnCount; - int left = overlayRect.left + horizontalPadding + column * (cardWidth + gap); - int top = verticalTop + row * (cardHeight + gap); - int right = left + cardWidth; - - RECT cardRect = - { - left, - top, - right, - top + cardHeight - }; - - COLORREF cardFill = RGB(255, 247, 250); - COLORREF cardBorder = RGB(221, 197, 208); - COLORREF descColor = RGB(108, 86, 99); - COLORREF footerColor = RGB(128, 104, 118); - const TCHAR* synthesisPath = GetUpgradeSynthesisPath(upgradeUiState.options[i].id); - bool hasSynthesisPath = synthesisPath != nullptr && lstrlen(synthesisPath) > 0; - const TCHAR* rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u666e\u901a"); - - if (upgradeUiState.options[i].cursed) - { - cardFill = isSelected ? RGB(238, 232, 238) : RGB(250, 245, 248); - cardBorder = RGB(38, 34, 42); - descColor = RGB(72, 62, 72); - footerColor = RGB(48, 42, 50); - rarityText = _T("\u8bc5\u5492\uff1a\u4e0b\u6b21\u5347\u7ea7\u6240\u9700 EXP +25%"); - } - else if (hasSynthesisPath) - { - cardBorder = RGB(208, 74, 78); - footerColor = RGB(164, 58, 64); - rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u8fdb\u9636"); - } - else if (upgradeUiState.options[i].rarity == UPGRADE_RARITY_RARE) - { - cardBorder = RGB(218, 172, 72); - footerColor = RGB(150, 111, 40); - rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u73cd\u7a00"); - } - else if (upgradeUiState.options[i].rarity == UPGRADE_RARITY_UNCOMMON) - { - cardBorder = RGB(92, 152, 218); - footerColor = RGB(68, 112, 166); - rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u7f55\u89c1"); - } - - if (isMarked && !upgradeUiState.options[i].cursed) - { - cardFill = RGB(255, 226, 236); - } - - if (isSelected && !upgradeUiState.options[i].cursed) - { - cardFill = RGB(255, 235, 242); - } - - DrawPanelCardAlpha(cardRect, cardFill, cardBorder, 24, isSelected ? 238 : 226); - if (isSelected) - { - HPEN selectedPen = CreatePen(PS_SOLID, SS(3), cardBorder); - oldPen = (HPEN)SelectObject(hdc, selectedPen); - oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); - RoundRect(hdc, cardRect.left, cardRect.top, cardRect.right, cardRect.bottom, SS(24), SS(24)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(selectedPen); - } - - TCHAR levelText[32]; - _stprintf_s(levelText, _T("Lv.%d"), upgradeUiState.options[i].currentLevel + 1); - RECT levelRect = - { - cardRect.right - SS(88), - cardRect.top + SS(20), - cardRect.right - SS(20), - cardRect.top + SS(48) - }; - SelectObject(hdc, smallFont); - SetTextColor(hdc, footerColor); - if (isMarked) - { - DrawText(hdc, _T("[已选]"), -1, &levelRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); - } - else - { - DrawText(hdc, levelText, -1, &levelRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); - } - - if (hasSynthesisPath) - { - SelectObject(hdc, smallFont); - SetTextColor(hdc, upgradeUiState.options[i].cursed ? RGB(142, 78, 78) : RGB(116, 82, 104)); - RECT synthesisRect = - { - cardRect.left + SS(20), - cardRect.top + SS(18), - cardRect.right - SS(116), - cardRect.top + SS(40) - }; - DrawText(hdc, synthesisPath, -1, &synthesisRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - } - - SelectObject(hdc, sectionFont); - SetTextColor(hdc, upgradeUiState.options[i].cursed ? RGB(130, 54, 54) : (isSelected ? titleColor : textColor)); - RECT nameRect = - { - cardRect.left + SS(20), - cardRect.top + (hasSynthesisPath ? SS(44) : SS(38)), - cardRect.right - SS(20), - cardRect.top + (hasSynthesisPath ? SS(76) : SS(70)) - }; - DrawText(hdc, upgradeUiState.options[i].name, -1, &nameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - - SelectObject(hdc, bodyFont); - SetTextColor(hdc, descColor); - RECT descRect = - { - cardRect.left + SS(20), - cardRect.top + (hasSynthesisPath ? SS(84) : SS(78)), - cardRect.right - SS(20), - cardRect.bottom - SS(48) - }; - DrawText(hdc, upgradeUiState.options[i].description, -1, &descRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_END_ELLIPSIS); - - SelectObject(hdc, smallFont); - RECT footerRect = - { - cardRect.left + SS(20), - cardRect.bottom - SS(34), - cardRect.right - SS(20), - cardRect.bottom - SS(10) - }; - SetTextColor(hdc, footerColor); - DrawText( - hdc, - rarityText, - -1, - &footerRect, - DT_LEFT | DT_VCENTER | DT_SINGLELINE); - } - - SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(128, 104, 118)); - RECT hintRect = - { - overlayRect.left + SS(30), - overlayRect.bottom - SS(52), - overlayRect.right - SS(30), - overlayRect.bottom - SS(18) - }; - DrawText( - hdc, - upgradeUiState.picksRemaining > 1 - ? _T("W/A/S/D 或方向键切换,Space 标记,Enter 确认已选强化") - : _T("W/A/S/D 或方向键切换,Enter 或 Space 确认"), - -1, - &hintRect, - DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } - - if (currentScreen != SCREEN_MENU) - { - DrawBackButton(); - } - DrawMusicButton(); - - SelectObject(hdc, oldFont); - DeleteObject(titleFont); - DeleteObject(sectionFont); - DeleteObject(bodyFont); - DeleteObject(smallFont); + RenderFullScreen(hdc, hWnd); } - diff --git a/src/source/logic/TetrisCoreHelpers.cpp b/src/source/logic/TetrisCoreHelpers.cpp new file mode 100644 index 0000000..26e55f8 --- /dev/null +++ b/src/source/logic/TetrisCoreHelpers.cpp @@ -0,0 +1,316 @@ +#include "stdafx.h" +/** + * @file TetrisCoreHelpers.cpp + * @brief 存放基础逻辑框架函数之外的内部辅助流程。 + */ + +#include "Tetris.h" +#include "TetrisLogicInternal.h" + +/** + * @brief 计算指定方块在指定旋转状态下的最小包围盒边界。 + * + * 该函数会遍历 4x4 形状矩阵,找出所有非空单元的上下左右边界, + * 供后续统一计算生成位置和对齐方式时使用。 + * + * @param brickType 方块类型编号。 + * @param brickState 方块旋转状态编号。 + * @param minRow 返回最上方非空行号。 + * @param maxRow 返回最下方非空行号。 + * @param minCol 返回最左侧非空列号。 + * @param maxCol 返回最右侧非空列号。 + */ +static void GetBrickBounds(int brickType, int brickState, int& minRow, int& maxRow, int& minCol, int& maxCol) +{ + minRow = 4; + maxRow = -1; + minCol = 4; + maxCol = -1; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[brickType][brickState][i][j] != 0) + { + if (i < minRow) + { + minRow = i; + } + if (i > maxRow) + { + maxRow = i; + } + if (j < minCol) + { + minCol = j; + } + if (j > maxCol) + { + maxCol = j; + } + } + } + } +} + +/** + * @brief 计算指定方块的统一生成位置。 + * + * 该函数会根据方块在初始旋转状态下的最小包围盒, + * 自动把方块水平居中到游戏区附近,并将顶部非空行对齐到可视区域顶部。 + * 这样不同形状的方块在生成时看起来会更加统一。 + * + * @param brickType 方块类型编号。 + * @return Point 计算得到的生成坐标。 + */ +Point GetSpawnPoint(int brickType) +{ + int minRow, maxRow, minCol, maxCol; + GetBrickBounds(brickType, 0, minRow, maxRow, minCol, maxCol); + + int brickWidth = maxCol - minCol + 1; + int brickHeight = maxRow - minRow + 1; + Point spawnPoint; + spawnPoint.x = (nGameWidth - brickWidth) / 2 - minCol; + spawnPoint.y = -brickHeight; + + return spawnPoint; +} + +/** + * @brief 收集当前方块将要固定到棋盘上的格子,并标记是否越过顶部。 + * @param overflowTop 返回是否有方块格位于可视区域顶部之外。 + * @param fixedCells 返回普通落地格,用于后续特殊效果定位。 + * @param fixedCellCount 返回普通落地格数量。 + * @param explosiveCells 返回爆破方块落地格。 + * @param explosiveCellCount 返回爆破方块落地格数量。 + */ +void CollectAndWriteFixedCells( + bool& overflowTop, + Point fixedCells[], + int& fixedCellCount, + Point explosiveCells[], + int& explosiveCellCount) +{ + overflowTop = false; + fixedCellCount = 0; + explosiveCellCount = 0; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[type][state][i][j] == 0) + { + continue; + } + + int fixY = point.y + i; + int fixX = point.x + j; + + // 顶部溢出只记录状态,真正的复活或结束逻辑在后续统一处理。 + if (fixY < 0) + { + overflowTop = true; + } + + if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth) + { + workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j]; + if (fixedCellCount < 4) + { + fixedCells[fixedCellCount].x = fixX; + fixedCells[fixedCellCount].y = fixY; + fixedCellCount++; + } + if (currentPieceIsExplosive && explosiveCellCount < 4) + { + explosiveCells[explosiveCellCount].x = fixX; + explosiveCells[explosiveCellCount].y = fixY; + explosiveCellCount++; + } + } + } + } +} + +/** + * @brief 处理方块固定时的顶部溢出、终末清场和最后一搏。 + * @param overflowTop 是否出现顶部溢出。 + * @return 溢出已被处理且游戏可以继续时返回 true;需要结束游戏时返回 false。 + */ +bool ResolveFixingOverflow(bool overflowTop) +{ + if (!overflowTop) + { + return true; + } + + if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0) + { + rogueStats.lastChanceCount--; + rogueStats.screenBombCount--; + + int clearedByTerminal = TriggerScreenBomb(); + rogueStats.feverTicks = 10; + currentFallInterval = GetRogueFallInterval(); + + TCHAR terminalDetail[128]; + _stprintf_s( + terminalDetail, + _T("终末清场启动,清除 %d 格,并进入 10 秒狂热。"), + clearedByTerminal); + SetFeedbackMessage(_T("终末清场"), terminalDetail, 14); + return true; + } + + if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0) + { + rogueStats.lastChanceCount--; + + for (int i = 0; i < 3; i++) + { + DeleteOneLine(GetRoguePlayableHeight() - 1); + } + + SetFeedbackMessage( + _T("最后一搏"), + _T("底部 3 行被清除,战局得以延续。"), + 14); + return true; + } + + gameOverFlag = true; + return false; +} + +/** + * @brief 生成下一枚活动方块,并刷新 Hold、特殊方块和预测落点状态。 + */ +void SpawnNextFallingPiece() +{ + // 消耗预览队列后重置本回合状态,确保 Hold 和特殊标记只影响新方块。 + type = ConsumeNextType(); + nType = nextTypes[0]; + state = 0; + holdUsedThisTurn = false; + RollCurrentPieceSpecialFlags(true); + point = GetSpawnPoint(type); + target = point; + ComputeTarget(); +} + +/** + * @brief 从底向上扫描满行并删除,记录本次消除的原始行号。 + * @param clearedRows 返回最多 8 个被消除行号,用于播放消行动画。 + * @param clearedRowCount 返回记录的行号数量。 + * @return 本次标准消行数量。 + */ +int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount) +{ + int clearedLines = 0; + clearedRowCount = 0; + + int playableHeight = GetRoguePlayableHeight(); + for (int i = playableHeight - 1; i >= 0; i--) + { + bool fullLine = true; + + for (int j = 0; j < nGameWidth; j++) + { + if (workRegion[i][j] == 0) + { + fullLine = false; + break; + } + } + + if (fullLine) + { + if (clearedRowCount < 8) + { + clearedRows[clearedRowCount] = i; + clearedRowCount++; + } + + DeleteOneLine(i); + clearedLines++; + i++; + } + } + + return clearedLines; +} + +/** + * @brief 根据当前界面状态立即播放或暂存消行动画。 + * @param clearedRows 已消除行号数组。 + * @param clearedRowCount 行号数量。 + * @param clearedLines 本次消行数量。 + */ +void DispatchLineClearEffect(const int clearedRows[], int clearedRowCount, int clearedLines) +{ + if (currentScreen == SCREEN_UPGRADE) + { + QueueLineClearEffect(clearedRows, clearedRowCount, clearedLines); + } + else + { + TriggerLineClearEffect(clearedRows, clearedRowCount, clearedLines); + } +} + +/** + * @brief 处理连环炸弹因消行触发的一次追加 3x3 爆破。 + * @param clearedLines 本次标准消行数量。 + */ +void ResolveChainBombFollowup(int clearedLines) +{ + if (!pendingChainBombFollowup || clearedLines <= 0) + { + pendingChainBombFollowup = false; + return; + } + + pendingChainBombFollowup = false; + + int followupCleared = 0; + int centerY = pendingChainBombCenter.y; + int centerX = pendingChainBombCenter.x; + Point followupCells[9] = {}; + + for (int y = centerY - 1; y <= centerY + 1; y++) + { + for (int x = centerX - 1; x <= centerX + 1; x++) + { + if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0) + { + if (followupCleared < 9) + { + followupCells[followupCleared].x = x; + followupCells[followupCleared].y = y; + } + workRegion[y][x] = 0; + followupCleared++; + } + } + } + + if (currentMode == MODE_ROGUE && followupCleared > 0) + { + TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true); + int followupScore = 0; + int followupExp = 0; + AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false); + + TCHAR followupDetail[128]; + _stprintf_s( + followupDetail, + _T("追加爆炸清除 %d 格 +%d 分 +%d EXP"), + followupCleared, + followupScore, + followupExp); + SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12); + } +} diff --git a/src/source/render/TetrisRenderMain.cpp b/src/source/render/TetrisRenderMain.cpp new file mode 100644 index 0000000..49d6d4f --- /dev/null +++ b/src/source/render/TetrisRenderMain.cpp @@ -0,0 +1,2725 @@ +#include "stdafx.h" +/** + * @file TetrisRender.cpp + * @brief 实现主菜单、帮助页、游戏棋盘、侧栏、覆盖层和升级界面的完整绘制逻辑。 + */ + +#include "Tetris.h" +#include "TetrisRenderInternal.h" +#include +#include + +#pragma comment(lib, "gdiplus.lib") + +using namespace Gdiplus; + +/** + * @brief 按颜色缓存粒子画刷,减少动画绘制时重复创建 GDI 对象。 + * @param color 画刷颜色。 + * @return 可复用的实心画刷句柄;缓存满时返回临时新建画刷。 + */ +static HBRUSH GetCachedParticleBrush(COLORREF color) +{ + static COLORREF cachedColors[16] = {}; + static HBRUSH cachedBrushes[16] = {}; + + for (int i = 0; i < 16; i++) + { + if (cachedBrushes[i] != nullptr && cachedColors[i] == color) + { + return cachedBrushes[i]; + } + } + + for (int i = 0; i < 16; i++) + { + if (cachedBrushes[i] == nullptr) + { + cachedColors[i] = color; + cachedBrushes[i] = CreateSolidBrush(color); + return cachedBrushes[i]; + } + } + + return CreateSolidBrush(color); +} + +/** + * @brief 绘制当前游戏窗口的完整界面。 + * + * 函数按当前屏幕状态绘制主菜单、帮助页、游戏棋盘、侧栏、覆盖层和升级选择。 + * 由于大量绘图辅助逻辑共享当前缩放、字体和颜色,保持在同一函数内集中管理, + * 避免拆分时改变 GDI 对象的选择和释放顺序。 + * + * @param hdc 目标绘图设备上下文。 + * @param hWnd 当前窗口句柄,用于读取客户区大小。 + */ +void RenderFullScreen(HDC hdc, 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 layoutHeight = MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000); + int offsetX = (clientWidth - layoutWidth) / 2; + int offsetY = 0; + + auto SX = [offsetX, scale](int value) -> int + { + return offsetX + MulDiv(value, scale, 1000); + }; + + auto SY = [offsetY, scale](int value) -> int + { + return offsetY + MulDiv(value, scale, 1000); + }; + + auto SS = [scale](int value) -> int + { + int result = MulDiv(value, scale, 1000); + return result < 1 ? 1 : result; + }; + + int grid = SS(GRID); + int previewMiniCell = SS(16); + int panelGap = SS(SIDE_PANEL_GAP); + int panelWidth = SS(SIDE_PANEL_WIDTH); + int panelHeight = SS(SIDE_PANEL_HEIGHT - WINDOW_PADDING * 2); + int boardWidth = grid * nGameWidth; + int boardHeight = grid * nGameHeight; + + const RECT leftPanelRect = + { + SX(WINDOW_PADDING), + SY(WINDOW_PADDING), + SX(WINDOW_PADDING) + panelWidth, + SY(WINDOW_PADDING) + panelHeight + }; + + const RECT gameRect = + { + leftPanelRect.right + panelGap, + SY(WINDOW_PADDING), + leftPanelRect.right + panelGap + boardWidth, + SY(WINDOW_PADDING) + boardHeight + }; + + const RECT rightPanelRect = + { + gameRect.right + panelGap, + SY(WINDOW_PADDING), + gameRect.right + panelGap + panelWidth, + SY(WINDOW_PADDING) + panelHeight + }; + + const COLORREF pageColor = RGB(255, 244, 248); + const COLORREF blobColorA = RGB(255, 230, 238); + const COLORREF blobColorB = RGB(250, 221, 233); + const COLORREF cardColor = RGB(255, 252, 253); + const COLORREF boardColor = RGB(70, 57, 78); + const COLORREF boardInnerColor = RGB(54, 44, 62); + const COLORREF lineColor = RGB(104, 90, 116); + const COLORREF frameColor = RGB(212, 168, 188); + const COLORREF titleColor = RGB(104, 62, 84); + const COLORREF textColor = RGB(92, 73, 88); + const COLORREF accentColor = RGB(228, 145, 182); + const BYTE shellAlpha = 142; + const BYTE panelAlpha = 150; + const BYTE panelNestedAlpha = 128; + const BYTE panelStrongAlpha = 168; + + // 背景图片存在时优先绘制图片并叠加浅色遮罩,否则使用纯色和装饰形状。 + Bitmap* backgroundImage = LoadBackgroundImage(); + if (backgroundImage != nullptr) + { + Graphics graphics(hdc); + graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); + graphics.DrawImage( + backgroundImage, + Rect(0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top)); + SolidBrush washBrush(Color(76, 255, 250, 252)); + graphics.FillRectangle(&washBrush, 0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); + } + else + { + HBRUSH pageBrush = CreateSolidBrush(pageColor); + FillRect(hdc, &clientRect, pageBrush); + DeleteObject(pageBrush); + } + + HBRUSH oldBrush = nullptr; + HPEN oldPen = nullptr; + if (backgroundImage == nullptr) + { + HBRUSH blobBrushA = CreateSolidBrush(blobColorA); + HBRUSH blobBrushB = CreateSolidBrush(blobColorB); + oldBrush = (HBRUSH)SelectObject(hdc, blobBrushA); + oldPen = (HPEN)SelectObject(hdc, GetStockObject(NULL_PEN)); + Ellipse(hdc, clientRect.right - 260, -20, clientRect.right + 80, 260); + Ellipse(hdc, -80, clientRect.bottom - 200, 220, clientRect.bottom + 70); + SelectObject(hdc, blobBrushB); + Ellipse(hdc, clientRect.right - 180, clientRect.bottom - 220, clientRect.right + 120, clientRect.bottom + 40); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(blobBrushA); + DeleteObject(blobBrushB); + } + + // 本函数集中创建字体,所有提前 return 分支都要在返回前释放这些 GDI 对象。 + HFONT titleFont = CreateFont( + -SS(36), 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, + VARIABLE_PITCH, _T("Microsoft YaHei UI")); + + HFONT sectionFont = CreateFont( + -SS(22), 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, + VARIABLE_PITCH, _T("Microsoft YaHei UI")); + + HFONT bodyFont = CreateFont( + -SS(18), 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, + VARIABLE_PITCH, _T("Microsoft YaHei UI")); + + HFONT smallFont = CreateFont( + -SS(15), 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, + VARIABLE_PITCH, _T("Microsoft YaHei UI")); + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, textColor); + + // 以下局部绘图函数共享 hdc、缩放函数和颜色,避免每个小控件重复计算上下文。 + auto DrawPanelCard = [&](const RECT& rect, COLORREF fillColor, COLORREF borderColor, int radius) + { + HBRUSH cardBrush = CreateSolidBrush(fillColor); + HPEN cardPen = CreatePen(PS_SOLID, 1, borderColor); + HGDIOBJ savedPen = SelectObject(hdc, cardPen); + HGDIOBJ savedBrush = SelectObject(hdc, cardBrush); + RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, SS(radius), SS(radius)); + SelectObject(hdc, savedBrush); + SelectObject(hdc, savedPen); + DeleteObject(cardBrush); + DeleteObject(cardPen); + }; + + auto DrawPanelCardAlpha = [&](const RECT& rect, COLORREF fillColor, COLORREF borderColor, int radius, BYTE alpha) + { + Graphics graphics(hdc); + graphics.SetSmoothingMode(SmoothingModeAntiAlias); + + GraphicsPath path; + INT left = static_cast(rect.left); + INT top = static_cast(rect.top); + INT width = static_cast(rect.right - rect.left); + INT height = static_cast(rect.bottom - rect.top); + INT corner = static_cast(SS(radius)); + + path.AddArc(left, top, corner, corner, 180.0f, 90.0f); + path.AddArc(left + width - corner, top, corner, corner, 270.0f, 90.0f); + path.AddArc(left + width - corner, top + height - corner, corner, corner, 0.0f, 90.0f); + path.AddArc(left, top + height - corner, corner, corner, 90.0f, 90.0f); + path.CloseFigure(); + + SolidBrush brush(Color(alpha, GetRValue(fillColor), GetGValue(fillColor), GetBValue(fillColor))); + Pen pen(Color(232, GetRValue(borderColor), GetGValue(borderColor), GetBValue(borderColor)), 1.0f); + graphics.FillPath(&brush, &path); + graphics.DrawPath(&pen, &path); + }; + + auto DrawPanelHeader = [&](const RECT& panelRect, const TCHAR* title, int lineWidth) + { + SelectObject(hdc, titleFont); + SetTextColor(hdc, titleColor); + RECT titleRect = + { + panelRect.left + SS(24), + panelRect.top + SS(20), + panelRect.right - SS(24), + panelRect.top + SS(62) + }; + DrawText(hdc, title, -1, &titleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + HPEN localAccentPen = CreatePen(PS_SOLID, SS(3), accentColor); + HPEN savedPen = (HPEN)SelectObject(hdc, localAccentPen); + MoveToEx(hdc, panelRect.left + SS(24), panelRect.top + SS(78), nullptr); + LineTo(hdc, panelRect.left + SS(24) + SS(lineWidth), panelRect.top + SS(78)); + SelectObject(hdc, savedPen); + DeleteObject(localAccentPen); + }; + + auto DrawMusicButton = [&]() + { + RECT musicButtonRect = + { + SX(WINDOW_CLIENT_WIDTH - 40), + SY(WINDOW_CLIENT_HEIGHT - 40), + SX(WINDOW_CLIENT_WIDTH - 12), + SY(WINDOW_CLIENT_HEIGHT - 12) + }; + DrawPanelCardAlpha( + musicButtonRect, + bgmEnabled ? RGB(255, 238, 246) : RGB(244, 236, 240), + bgmEnabled ? RGB(222, 130, 166) : RGB(170, 148, 158), + 12, + bgmEnabled ? 218 : 190); + + HPEN musicPen = CreatePen(PS_SOLID, SS(3), bgmEnabled ? RGB(128, 70, 100) : RGB(128, 112, 120)); + HPEN oldMusicPen = (HPEN)SelectObject(hdc, musicPen); + HBRUSH oldMusicBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); + + int noteStemX = musicButtonRect.left + SS(17); + int noteTop = musicButtonRect.top + SS(7); + int noteBottom = musicButtonRect.bottom - SS(9); + MoveToEx(hdc, noteStemX, noteTop, nullptr); + LineTo(hdc, noteStemX, noteBottom); + MoveToEx(hdc, noteStemX, noteTop, nullptr); + LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(4)); + LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(10)); + Ellipse( + hdc, + musicButtonRect.left + SS(7), + musicButtonRect.bottom - SS(13), + musicButtonRect.left + SS(19), + musicButtonRect.bottom - SS(4)); + + if (!bgmEnabled) + { + MoveToEx(hdc, musicButtonRect.left + SS(7), musicButtonRect.bottom - SS(7), nullptr); + LineTo(hdc, musicButtonRect.right - SS(6), musicButtonRect.top + SS(6)); + } + + SelectObject(hdc, oldMusicBrush); + SelectObject(hdc, oldMusicPen); + DeleteObject(musicPen); + }; + + auto DrawBackButton = [&]() + { + RECT backButtonRect = + { + SX(6), + SY(6), + SX(34), + SY(34) + }; + HBRUSH backBrush = CreateSolidBrush(RGB(255, 242, 247)); + HPEN backFramePen = CreatePen(PS_SOLID, 1, RGB(222, 130, 166)); + HBRUSH oldBackBrush = (HBRUSH)SelectObject(hdc, backBrush); + HPEN oldBackFramePen = (HPEN)SelectObject(hdc, backFramePen); + RoundRect(hdc, backButtonRect.left, backButtonRect.top, backButtonRect.right, backButtonRect.bottom, SS(12), SS(12)); + SelectObject(hdc, oldBackBrush); + SelectObject(hdc, oldBackFramePen); + DeleteObject(backFramePen); + DeleteObject(backBrush); + + HPEN backPen = CreatePen(PS_SOLID, SS(3), RGB(128, 70, 100)); + HPEN oldBackPen = (HPEN)SelectObject(hdc, backPen); + MoveToEx(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(8), nullptr); + LineTo(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14)); + LineTo(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(20)); + MoveToEx(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14), nullptr); + LineTo(hdc, backButtonRect.right - SS(8), backButtonRect.top + SS(14)); + SelectObject(hdc, oldBackPen); + DeleteObject(backPen); + }; + + // 主菜单独立绘制并提前返回,避免后续游戏棋盘和侧栏在菜单后面继续绘制。 + if (currentScreen == SCREEN_MENU) + { + RECT menuCard = + { + SX(110), + SY(70), + SX(WINDOW_CLIENT_WIDTH - 110), + SY(WINDOW_CLIENT_HEIGHT - 70) + }; + + DrawPanelCardAlpha(menuCard, cardColor, frameColor, 34, 214); + + HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); + SetTextColor(hdc, titleColor); + + RECT titleRect = + { + menuCard.left, + menuCard.top + SS(24), + menuCard.right, + menuCard.top + SS(70) + }; + DrawText(hdc, _T("Rogue Tetris"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + RECT subtitleRect = + { + menuCard.left, + menuCard.top + SS(72), + menuCard.right, + menuCard.top + SS(110) + }; + SelectObject(hdc, bodyFont); + SetTextColor(hdc, textColor); + DrawText(hdc, _T("选择你的战局"), -1, &subtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + const TCHAR* modeNames[4] = + { + _T("\u7ecf\u5178\u6a21\u5f0f"), + _T("Rogue \u6a21\u5f0f"), + _T("\u5e2e\u52a9"), + _T("\u81f4\u8c22") + }; + + const TCHAR* modeDescriptions[4] = + { + _T("纯粹方块挑战,专注消行、堆叠与生存。"), + _T("在不断升级的棋盘中收集强化,构筑本局专属流派。"), + _T("查看操作、模式规则与全部强化效果。"), + _T("感谢程序测试者与代码贡献者。") + }; + + for (int i = 0; i < menuState.optionCount; i++) + { + bool isSelected = (i == menuState.selectedIndex); + int top = menuCard.top + SS(140) + i * SS(130); + + RECT optionRect = + { + menuCard.left + SS(36), + top, + menuCard.right - SS(36), + top + SS(104) + }; + + DrawPanelCardAlpha( + optionRect, + isSelected ? RGB(255, 232, 240) : RGB(255, 247, 250), + isSelected ? accentColor : RGB(226, 198, 210), + 24, + isSelected ? 208 : 172); + if (isSelected) + { + HPEN optionPen = CreatePen(PS_SOLID, SS(3), accentColor); + oldPen = (HPEN)SelectObject(hdc, optionPen); + oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); + RoundRect(hdc, optionRect.left, optionRect.top, optionRect.right, optionRect.bottom, SS(24), SS(24)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(optionPen); + } + + RECT modeNameRect = + { + optionRect.left + SS(24), + optionRect.top + SS(14), + optionRect.right - SS(24), + optionRect.top + SS(44) + }; + + RECT modeDescriptionRect = + { + optionRect.left + SS(24), + optionRect.top + SS(46), + optionRect.right - SS(24), + optionRect.bottom - SS(14) + }; + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, isSelected ? titleColor : textColor); + DrawText(hdc, modeNames[i], -1, &modeNameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(118, 96, 110)); + DrawText(hdc, modeDescriptions[i], -1, &modeDescriptionRect, DT_LEFT | DT_WORDBREAK); + } + + RECT hintRect = + { + menuCard.left + SS(36), + menuCard.bottom - SS(92), + menuCard.right - SS(36), + menuCard.bottom - SS(36) + }; + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + DrawText(hdc, _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4\uff0cEsc \u9000\u51fa"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + DrawMusicButton(); + SelectObject(hdc, oldFont); + DeleteObject(titleFont); + DeleteObject(sectionFont); + DeleteObject(bodyFont); + DeleteObject(smallFont); + return; + } + + // 帮助、规则、图鉴、致谢和技能演示入口共用规则页卡片框架。 + if (currentScreen == SCREEN_RULES) + { + RECT rulesCard = + { + SX(76), + SY(54), + SX(WINDOW_CLIENT_WIDTH - 76), + SY(WINDOW_CLIENT_HEIGHT - 54) + }; + + HPEN rulesPen = CreatePen(PS_SOLID, 1, frameColor); + HBRUSH rulesBrush = CreateSolidBrush(cardColor); + HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); + oldPen = (HPEN)SelectObject(hdc, rulesPen); + oldBrush = (HBRUSH)SelectObject(hdc, rulesBrush); + RoundRect(hdc, rulesCard.left, rulesCard.top, rulesCard.right, rulesCard.bottom, SS(34), SS(34)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(rulesBrush); + DeleteObject(rulesPen); + + SetTextColor(hdc, titleColor); + RECT rulesTitleRect = + { + rulesCard.left + SS(36), + rulesCard.top + SS(26), + rulesCard.right - SS(36), + rulesCard.top + SS(78) + }; + const TCHAR* helpTitle = _T("\u5e2e\u52a9"); + if (helpState.currentPage == 1) + { + helpTitle = _T("\u6e38\u620f\u4ecb\u7ecd"); + } + else if (helpState.currentPage == 2) + { + helpTitle = _T("\u64cd\u4f5c\u8bf4\u660e"); + } + else if (helpState.currentPage == 3) + { + helpTitle = _T("\u5f3a\u5316\u56fe\u9274"); + } + else if (helpState.currentPage == 4) + { + helpTitle = _T("\u81f4\u8c22"); + } + else if (helpState.currentPage == 5) + { + helpTitle = _T("\u6280\u80fd\u6f14\u793a"); + } + DrawText(hdc, helpTitle, -1, &rulesTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + HPEN rulesAccentPen = CreatePen(PS_SOLID, SS(3), accentColor); + oldPen = (HPEN)SelectObject(hdc, rulesAccentPen); + MoveToEx(hdc, rulesCard.left + SS(38), rulesCard.top + SS(92), nullptr); + LineTo(hdc, rulesCard.left + SS(186), rulesCard.top + SS(92)); + SelectObject(hdc, oldPen); + DeleteObject(rulesAccentPen); + + RECT contentRect = + { + rulesCard.left + SS(36), + rulesCard.top + SS(126), + rulesCard.right - SS(36), + rulesCard.bottom - SS(86) + }; + + if (helpState.currentPage == 0) + { + const TCHAR* optionTitles[4] = + { + _T("\u6e38\u620f\u4ecb\u7ecd"), + _T("\u64cd\u4f5c\u8bf4\u660e"), + _T("\u5f3a\u5316\u56fe\u9274"), + _T("\u6280\u80fd\u6f14\u793a") + }; + const TCHAR* optionDetails[4] = + { + _T("\u7ecf\u5178\u6a21\u5f0f\u3001Rogue \u6a21\u5f0f\u548c\u590d\u6d3b\u89c4\u5219\u6982\u89c8\u3002"), + _T("\u79fb\u52a8\u3001\u65cb\u8f6c\u3001\u786c\u964d\u3001Hold \u4e0e\u6280\u80fd\u5feb\u6377\u952e\u3002"), + _T("\u67e5\u770b Rogue \u6a21\u5f0f\u5168\u90e8\u5f3a\u5316\u7684\u7b80\u8981\u6548\u679c\u3002"), + _T("\u9009\u62e9\u4e00\u4e2a\u9884\u8bbe\u573a\u666f\uff0c\u4eb2\u81ea\u64cd\u4f5c\u89e6\u53d1 Rogue \u6280\u80fd\u6548\u679c\u3002") + }; + + int optionHeight = SS(100); + int optionGap = SS(22); + int optionTop = contentRect.top + SS(18); + for (int i = 0; i < helpState.optionCount; ++i) + { + RECT optionRect = + { + contentRect.left, + optionTop + i * (optionHeight + optionGap), + contentRect.right, + optionTop + i * (optionHeight + optionGap) + optionHeight + }; + bool selected = (i == helpState.selectedIndex); + COLORREF optionFill = selected ? RGB(255, 245, 249) : RGB(255, 252, 250); + COLORREF optionFrame = selected ? accentColor : frameColor; + HPEN optionPen = CreatePen(PS_SOLID, selected ? SS(2) : SS(1), optionFrame); + HBRUSH optionBrush = CreateSolidBrush(optionFill); + oldPen = (HPEN)SelectObject(hdc, optionPen); + oldBrush = (HBRUSH)SelectObject(hdc, optionBrush); + RoundRect(hdc, optionRect.left, optionRect.top, optionRect.right, optionRect.bottom, SS(24), SS(24)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(optionBrush); + DeleteObject(optionPen); + + SetTextColor(hdc, selected ? titleColor : textColor); + SelectObject(hdc, sectionFont); + RECT optionTitleRect = + { + optionRect.left + SS(28), + optionRect.top + SS(16), + optionRect.right - SS(28), + optionRect.top + SS(48) + }; + DrawText(hdc, optionTitles[i], -1, &optionTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SetTextColor(hdc, RGB(112, 91, 104)); + SelectObject(hdc, bodyFont); + RECT optionDetailRect = + { + optionRect.left + SS(28), + optionRect.top + SS(52), + optionRect.right - SS(28), + optionRect.bottom - SS(14) + }; + DrawText(hdc, optionDetails[i], -1, &optionDetailRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + } + } + else + { + SetTextColor(hdc, textColor); + SelectObject(hdc, bodyFont); + const TCHAR* pageText = _T(""); + UINT pageFlags = DT_LEFT | DT_TOP | DT_WORDBREAK; + if (helpState.currentPage == 1) + { + pageText = + _T("经典模式:考验堆叠、消行和生存。消行后悬空方块会继续受重力下落。\r\n\r\n") + _T("Rogue:消行得分和 EXP,升级后选择强化,构筑会改变方块、清场、得分和保命方式。\r\n\r\n") + _T("特殊方块用粗色边框提示:橙红=爆破,青色=激光,绿色=十字,紫色=彩虹。\r\n\r\n") + _T("战斗越久危险越高,底部封锁区会压缩空间。\r\n\r\n") + _T("死亡后可以按 V 看视频复活一次,经典模式和 Rogue 模式都支持。"); + } + else if (helpState.currentPage == 2) + { + pageText = + _T("\u2190 / A\uff1a\u5411\u5de6\u79fb\u52a8\r\n") + _T("\u2192 / D\uff1a\u5411\u53f3\u79fb\u52a8\r\n") + _T("\u2191 / W\uff1a\u65cb\u8f6c\u65b9\u5757\r\n") + _T("\u2193 / S\uff1a\u8f6f\u964d\r\n") + _T("Space\uff1a\u786c\u964d\r\n") + _T("C / Shift\uff1a备用仓\uff08获得后\uff09\r\n") + _T("Z\uff1a黑洞奇点\uff08获得后\uff09\r\n") + _T("X\uff1a清屏炸弹\uff08获得后\uff09\r\n") + _T("V\uff1a空中换形\uff08获得后\uff09\r\n") + _T("P\uff1a\u6682\u505c / \u7ee7\u7eed\r\n") + _T("R\uff1a\u91cd\u5f00\u5f53\u524d\u5bf9\u5c40\r\n") + _T("M\uff1a\u8fd4\u56de\u4e3b\u83dc\u5355\r\n") + _T("死亡后 V:看视频复活一次"); + } + else if (helpState.currentPage == 3) + { + const TCHAR* categoryNames[8] = + { + _T("基础"), + _T("保命 / 操作"), + _T("清场"), + _T("特殊方块"), + _T("进阶"), + _T("狂热 / 风险"), + _T("升级系"), + _T("构筑") + }; + const TCHAR* categoryTexts[8] = + { + _T("赏金纹章:所有得分收益提高 20%,可重复叠加,是最直接的分数成长。\r\n成长印记:所有 EXP 收益提高 25%,可重复叠加,用来更快进入后续构筑。\r\n缓坠羽翼:降低自然下落速度,最多叠加 4 次。\r\n连击律动:连续消行时追加得分和 EXP,断连后重新累计。\r\n先见之眼:额外显示 1 个后续方块;第三个预览由操控大师解锁。"), + _T("最后一搏:首次濒死时自动清理底部 3 行,并保留本局继续机会。\r\n备用仓:解锁 C / Shift,将当前方块放入 Hold 仓或取出备用方块。\r\n完美旋转:旋转被阻挡时尝试左右修正,提高贴墙和缝隙旋转成功率。\r\n时间缓流:堆叠过高时自动减速,给危险局面留出处理时间。\r\n空中换形:可重复补充次数,按 V 将当前下落方块变成 I 块。"), + _T("卸压清场:获得时立刻清除最高的一条占用行,直接降低顶部压力。\r\n底线清道夫:通过消行充能自动清底;每级降低需求,最多 4 级。\r\n清屏炸弹:一次性解锁,之后通过消行充能;满 16 行获得 1 枚,按 X 清底 5 行。\r\n黑洞奇点:可重复补充次数,按 Z 吞噬当前场上数量最多的一种颜色方块。"), + _T("爆破核心:一次性解锁橙红边框方块,落地后以落点为中心清除 3x3 区域。\r\n棱镜激光:一次性解锁青色边框方块,按落地中心列清除整列固定方块。\r\n十字方块:一次性解锁绿色边框方块,按落地中心同时清除一行一列。\r\n彩虹方块:一次性解锁紫色边框方块,清中心行主色并把覆盖行染成场上主色。\r\n方块改造:提高指定方块出现概率,目前主要强化 I 块生成。"), + _T("连锁火花:完成消行后追加随机破坏,适合配合稳定堆叠扩大收益。\r\n连环炸弹:强化爆破核心,将爆破范围从 3x3 扩大为 5x5。\r\n雷霆四消:三消或四消后追加雷击,额外清除局部方块。\r\n雷霆棱镜:三消或四消后追加激光清列,强化四消后的清场能力。"), + _T("狂热节拍:累计清除 12 行进入狂热,狂热期间收益更高。\r\n怒火连段:连击越高倍率越高,适合连续小消和稳定节奏。\r\n无尽狂热:狂热期间继续消行会延长狂热时间。\r\n高压悬赏:游戏速度更快,但高压下收益也更高。\r\n豪赌四消:四消收益更高,但非四消表现更不稳定。\r\n极限玩家:危险高度下获得更高收益,同时承担更高操作压力。\r\n赌徒契约:后续强化有概率翻倍或落空,每级提高概率,最多 4 级。"), + _T("双重抉择:升级时可额外选择 1 个强化,加快构筑成型。\r\n命运轮盘:升级变为 6 选 2,但选项中可能混入诅咒。\r\n升级冲击波:每次升级后清理底部 2 行。\r\n进化冲击:升级后清理底部 3 行,是升级冲击波的进阶路线。\r\n成长核心:永久提高得分和 EXP 收益 15%。"), + _T("操控大师:使用 Hold 后短暂减速,并额外增加预览能力。\r\n方块风暴:接下来 5 个方块都变为 I 块,适合冲四消或紧急清场。\r\n稳定结构:落地后概率填补局部小洞,让场地更平整。\r\n虚空核心:黑洞生效后生成彩虹方块,彩虹生效时会追加小型黑洞。") + }; + + int sectionGap = SS(18); + int titleHeight = SS(34); + int bodyPadding = SS(16); + int contentHeight = contentRect.bottom - contentRect.top; + int virtualHeight = 0; + SelectObject(hdc, bodyFont); + for (int i = 0; i < 8; ++i) + { + RECT calculateBodyRect = + { + contentRect.left + bodyPadding, + 0, + contentRect.right - bodyPadding, + 0 + }; + DrawText(hdc, categoryTexts[i], -1, &calculateBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); + virtualHeight += titleHeight + (calculateBodyRect.bottom - calculateBodyRect.top) + sectionGap; + } + int maxHelpScroll = virtualHeight - contentHeight; + if (maxHelpScroll < 0) + { + maxHelpScroll = 0; + } + if (helpScrollOffset > maxHelpScroll) + { + helpScrollOffset = maxHelpScroll; + } + + HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); + int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); + HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); + SelectClipRgn(hdc, contentClipRegion); + + int sectionTop = contentRect.top - helpScrollOffset; + for (int i = 0; i < 8; ++i) + { + RECT bodyCalculateRect = + { + contentRect.left + bodyPadding, + 0, + contentRect.right - bodyPadding, + 0 + }; + SelectObject(hdc, bodyFont); + DrawText(hdc, categoryTexts[i], -1, &bodyCalculateRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); + int bodyHeight = bodyCalculateRect.bottom - bodyCalculateRect.top; + RECT categoryRect = + { + contentRect.left, + sectionTop, + contentRect.right, + sectionTop + titleHeight + bodyHeight + SS(12) + }; + + if (categoryRect.bottom < contentRect.top || categoryRect.top > contentRect.bottom) + { + sectionTop += titleHeight + bodyHeight + sectionGap; + continue; + } + + SetTextColor(hdc, titleColor); + SelectObject(hdc, sectionFont); + RECT categoryTitleRect = + { + categoryRect.left, + categoryRect.top, + categoryRect.right, + categoryRect.top + titleHeight + }; + DrawText(hdc, categoryNames[i], -1, &categoryTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + HPEN categoryLinePen = CreatePen(PS_SOLID, 1, RGB(232, 202, 215)); + oldPen = (HPEN)SelectObject(hdc, categoryLinePen); + MoveToEx(hdc, categoryRect.left, categoryTitleRect.bottom, nullptr); + LineTo(hdc, categoryRect.right, categoryTitleRect.bottom); + SelectObject(hdc, oldPen); + DeleteObject(categoryLinePen); + + SetTextColor(hdc, RGB(86, 66, 80)); + SelectObject(hdc, bodyFont); + RECT categoryBodyRect = + { + categoryRect.left + bodyPadding, + categoryTitleRect.bottom + SS(10), + categoryRect.right - bodyPadding, + categoryTitleRect.bottom + SS(10) + bodyHeight + }; + DrawText(hdc, categoryTexts[i], -1, &categoryBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + sectionTop += titleHeight + bodyHeight + sectionGap; + } + + if (hasOldClipRegion == 1) + { + SelectClipRgn(hdc, oldClipRegion); + } + else + { + SelectClipRgn(hdc, nullptr); + } + DeleteObject(contentClipRegion); + DeleteObject(oldClipRegion); + } + else if (helpState.currentPage == 5) + { + int demoCount = GetRogueSkillDemoCount(); + int itemHeight = SS(58); + int itemGap = SS(10); + int contentHeight = contentRect.bottom - contentRect.top; + int virtualHeight = SS(8) + demoCount * (itemHeight + itemGap); + int maxHelpScroll = virtualHeight - contentHeight; + if (maxHelpScroll < 0) + { + maxHelpScroll = 0; + } + if (helpScrollOffset > maxHelpScroll) + { + helpScrollOffset = maxHelpScroll; + } + + HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); + int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); + HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); + SelectClipRgn(hdc, contentClipRegion); + + int itemTop = contentRect.top + SS(8) - helpScrollOffset; + for (int i = 0; i < demoCount; i++) + { + RECT itemRect = + { + contentRect.left, + itemTop + i * (itemHeight + itemGap), + contentRect.right, + itemTop + i * (itemHeight + itemGap) + itemHeight + }; + + if (itemRect.bottom < contentRect.top || itemRect.top > contentRect.bottom) + { + continue; + } + + bool selected = (i == helpState.selectedIndex); + COLORREF itemFill = selected ? RGB(255, 245, 249) : RGB(255, 252, 250); + COLORREF itemFrame = selected ? accentColor : frameColor; + HPEN itemPen = CreatePen(PS_SOLID, selected ? SS(2) : SS(1), itemFrame); + HBRUSH itemBrush = CreateSolidBrush(itemFill); + oldPen = (HPEN)SelectObject(hdc, itemPen); + oldBrush = (HBRUSH)SelectObject(hdc, itemBrush); + RoundRect(hdc, itemRect.left, itemRect.top, itemRect.right, itemRect.bottom, SS(18), SS(18)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(itemBrush); + DeleteObject(itemPen); + + SetTextColor(hdc, selected ? titleColor : textColor); + SelectObject(hdc, sectionFont); + RECT nameRect = + { + itemRect.left + SS(22), + itemRect.top + SS(8), + itemRect.left + SS(210), + itemRect.bottom - SS(8) + }; + DrawText(hdc, GetRogueSkillDemoName(i), -1, &nameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SetTextColor(hdc, RGB(112, 91, 104)); + SelectObject(hdc, bodyFont); + RECT detailRect = + { + itemRect.left + SS(220), + itemRect.top + SS(8), + itemRect.right - SS(22), + itemRect.bottom - SS(8) + }; + DrawText(hdc, GetRogueSkillDemoDetail(i), -1, &detailRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + + if (hasOldClipRegion == 1) + { + SelectClipRgn(hdc, oldClipRegion); + } + else + { + SelectClipRgn(hdc, nullptr); + } + DeleteObject(contentClipRegion); + DeleteObject(oldClipRegion); + } + else if (helpState.currentPage == 4) + { + const int creditAnimationTotalTicks = 60; + constexpr int creditPageCount = 5; + const TCHAR* creditNames[creditPageCount] = + { + _T("qls"), + _T("wyk"), + _T("swj"), + _T("qhy"), + _T("syc") + }; + const TCHAR* creditTexts[creditPageCount] = + { + _T("\u611f\u8c22\u6fc0\u60c5\u6295\u8eab\u4e8e\u6d4b\u8bd5\u4e4b\u4e2d\u7684Lisa"), + _T("\u611f\u8c22\u70ed\u5ff1coding\u7684\u5c0f\u86cb\u7cd5"), + _T("\u611f\u8c22\u8bfe\u524d\u95f2\u91cc\u5077\u5fd9\u7684juju"), + _T("\u611f\u8c22qhy\u7684\u5929\u624d\u6784\u60f3"), + _T("\u611f\u8c22syc\u63fd\u4e0b\u6240\u6709\u6742\u6d3b") + }; + + int currentCredit = creditPageIndex; + if (currentCredit < 0) + { + currentCredit = 0; + } + if (currentCredit >= creditPageCount) + { + currentCredit = creditPageCount - 1; + } + + int previousCredit = currentCredit - creditAnimationDirection; + if (previousCredit < 0) + { + previousCredit = creditPageCount - 1; + } + if (previousCredit >= creditPageCount) + { + previousCredit = 0; + } + + RECT imageArea = + { + contentRect.left + SS(92), + contentRect.top + SS(10), + contentRect.right - SS(92), + contentRect.bottom - SS(112) + }; + RECT textArea = + { + contentRect.left + SS(72), + contentRect.bottom - SS(94), + contentRect.right - SS(72), + contentRect.bottom - SS(18) + }; + + HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); + int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); + HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); + SelectClipRgn(hdc, contentClipRegion); + + int slideDistance = contentRect.right - contentRect.left; + int currentOffset = 0; + int previousOffset = 0; + if (creditAnimationTicks > 0 && creditAnimationDirection != 0) + { + currentOffset = creditAnimationDirection * slideDistance * creditAnimationTicks / creditAnimationTotalTicks; + previousOffset = currentOffset - creditAnimationDirection * slideDistance; + } + + Bitmap* preloadedCreditImages[creditPageCount] = {}; + for (int i = 0; i < creditPageCount; i++) + { + preloadedCreditImages[i] = LoadCreditImage(i); + } + Graphics creditGraphics(hdc); + creditGraphics.SetInterpolationMode(InterpolationModeHighQualityBilinear); + + auto DrawCreditCard = [&](int cardIndex, int offset) + { + RECT shiftedImageArea = imageArea; + RECT shiftedTextArea = textArea; + OffsetRect(&shiftedImageArea, offset, 0); + OffsetRect(&shiftedTextArea, offset, 0); + + Bitmap* creditImage = nullptr; + if (cardIndex >= 0 && cardIndex < creditPageCount) + { + creditImage = preloadedCreditImages[cardIndex]; + } + if (creditImage != nullptr) + { + int imageWidth = static_cast(creditImage->GetWidth()); + int imageHeight = static_cast(creditImage->GetHeight()); + int areaWidth = shiftedImageArea.right - shiftedImageArea.left; + int areaHeight = shiftedImageArea.bottom - shiftedImageArea.top; + int drawWidth = areaWidth; + int drawHeight = imageHeight * drawWidth / imageWidth; + if (drawHeight > areaHeight) + { + drawHeight = areaHeight; + drawWidth = imageWidth * drawHeight / imageHeight; + } + + int drawLeft = shiftedImageArea.left + (areaWidth - drawWidth) / 2; + int drawTop = shiftedImageArea.top + (areaHeight - drawHeight) / 2; + creditGraphics.DrawImage(creditImage, Rect(drawLeft, drawTop, drawWidth, drawHeight)); + } + else + { + HBRUSH missingBrush = CreateSolidBrush(RGB(255, 245, 249)); + HPEN missingPen = CreatePen(PS_DASH, SS(2), frameColor); + oldBrush = (HBRUSH)SelectObject(hdc, missingBrush); + oldPen = (HPEN)SelectObject(hdc, missingPen); + RoundRect(hdc, shiftedImageArea.left, shiftedImageArea.top, shiftedImageArea.right, shiftedImageArea.bottom, SS(24), SS(24)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(missingPen); + DeleteObject(missingBrush); + + SetTextColor(hdc, RGB(128, 104, 118)); + SelectObject(hdc, bodyFont); + DrawText(hdc, _T("\u56fe\u7247\u8d44\u6e90\u672a\u627e\u5230"), -1, &shiftedImageArea, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + + if (creditAnimationTicks <= 0) + { + SetTextColor(hdc, titleColor); + SelectObject(hdc, sectionFont); + RECT nameRect = + { + shiftedTextArea.left, + shiftedTextArea.top, + shiftedTextArea.right, + shiftedTextArea.top + SS(30) + }; + DrawText(hdc, creditNames[cardIndex], -1, &nameRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + SetTextColor(hdc, textColor); + SelectObject(hdc, bodyFont); + RECT detailRect = + { + shiftedTextArea.left, + shiftedTextArea.top + SS(34), + shiftedTextArea.right, + shiftedTextArea.bottom + }; + DrawText(hdc, creditTexts[cardIndex], -1, &detailRect, DT_CENTER | DT_TOP | DT_WORDBREAK); + } + }; + + if (creditAnimationTicks > 0 && creditAnimationDirection != 0) + { + DrawCreditCard(previousCredit, previousOffset); + } + DrawCreditCard(currentCredit, currentOffset); + + if (hasOldClipRegion == 1) + { + SelectClipRgn(hdc, oldClipRegion); + } + else + { + SelectClipRgn(hdc, nullptr); + } + DeleteObject(contentClipRegion); + DeleteObject(oldClipRegion); + + RECT leftArrow = + { + rulesCard.left + SS(52), + (rulesCard.top + rulesCard.bottom) / 2 - SS(27), + rulesCard.left + SS(106), + (rulesCard.top + rulesCard.bottom) / 2 + SS(27) + }; + RECT rightArrow = + { + rulesCard.right - SS(106), + (rulesCard.top + rulesCard.bottom) / 2 - SS(27), + rulesCard.right - SS(52), + (rulesCard.top + rulesCard.bottom) / 2 + SS(27) + }; + + HBRUSH arrowBrush = CreateSolidBrush(RGB(255, 245, 249)); + HPEN arrowPen = CreatePen(PS_SOLID, SS(2), accentColor); + oldBrush = (HBRUSH)SelectObject(hdc, arrowBrush); + oldPen = (HPEN)SelectObject(hdc, arrowPen); + RoundRect(hdc, leftArrow.left, leftArrow.top, leftArrow.right, leftArrow.bottom, SS(18), SS(18)); + RoundRect(hdc, rightArrow.left, rightArrow.top, rightArrow.right, rightArrow.bottom, SS(18), SS(18)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(arrowPen); + DeleteObject(arrowBrush); + + HPEN chevronPen = CreatePen(PS_SOLID, SS(4), titleColor); + oldPen = (HPEN)SelectObject(hdc, chevronPen); + MoveToEx(hdc, leftArrow.left + SS(32), leftArrow.top + SS(16), nullptr); + LineTo(hdc, leftArrow.left + SS(22), leftArrow.top + SS(27)); + LineTo(hdc, leftArrow.left + SS(32), leftArrow.bottom - SS(16)); + MoveToEx(hdc, rightArrow.right - SS(32), rightArrow.top + SS(16), nullptr); + LineTo(hdc, rightArrow.right - SS(22), rightArrow.top + SS(27)); + LineTo(hdc, rightArrow.right - SS(32), rightArrow.bottom - SS(16)); + SelectObject(hdc, oldPen); + DeleteObject(chevronPen); + } + if (helpState.currentPage != 3 && helpState.currentPage != 4 && helpState.currentPage != 5) + { + RECT calculateRect = { contentRect.left, contentRect.top, contentRect.right, contentRect.top }; + DrawText(hdc, pageText, -1, &calculateRect, pageFlags | DT_CALCRECT); + int maxHelpScroll = (calculateRect.bottom - calculateRect.top) - (contentRect.bottom - contentRect.top); + if (maxHelpScroll < 0) + { + maxHelpScroll = 0; + } + if (helpScrollOffset > maxHelpScroll) + { + helpScrollOffset = maxHelpScroll; + } + + HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); + int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); + HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); + SelectClipRgn(hdc, contentClipRegion); + + RECT scrolledContentRect = contentRect; + scrolledContentRect.top -= helpScrollOffset; + scrolledContentRect.bottom += maxHelpScroll; + DrawText(hdc, pageText, -1, &scrolledContentRect, pageFlags); + + if (hasOldClipRegion == 1) + { + SelectClipRgn(hdc, oldClipRegion); + } + else + { + SelectClipRgn(hdc, nullptr); + } + DeleteObject(contentClipRegion); + DeleteObject(oldClipRegion); + } + } + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT backHintRect = + { + rulesCard.left + SS(36), + rulesCard.bottom - SS(58), + rulesCard.right - SS(36), + rulesCard.bottom - SS(24) + }; + const TCHAR* helpHint = helpState.currentPage == 0 + ? _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter / Space \u786e\u8ba4\uff0cEsc / M \u8fd4\u56de\u4e3b\u83dc\u5355") + : (helpState.currentPage == 4 + ? _T("\u5de6\u53f3\u65b9\u5411\u952e / A D \u5207\u6362\uff0cEsc / Backspace / M \u8fd4\u56de\u4e3b\u83dc\u5355") + : (helpState.currentPage == 5 + ? _T("\u70b9\u51fb\u6280\u80fd\u540d\u79f0\u6216 Enter \u8fdb\u5165\u64cd\u4f5c\u573a\u666f\uff0c\u6eda\u8f6e\u7ffb\u52a8\uff0cEsc / Backspace / M \u8fd4\u56de\u5e2e\u52a9") + : _T("\u9f20\u6807\u6eda\u8f6e\u4e0a\u4e0b\u7ffb\u52a8\uff0cEsc / Backspace / M \u8fd4\u56de\u5e2e\u52a9"))); + DrawText(hdc, helpHint, -1, &backHintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + SelectClipRgn(hdc, nullptr); + DrawBackButton(); + DrawMusicButton(); + SelectObject(hdc, oldFont); + DeleteObject(titleFont); + DeleteObject(sectionFont); + DeleteObject(bodyFont); + DeleteObject(smallFont); + return; + } + + DrawPanelCardAlpha( + { gameRect.left - SS(10), gameRect.top - SS(10), gameRect.right + SS(10), gameRect.bottom + SS(10) }, + cardColor, + frameColor, + 28, + shellAlpha); + DrawPanelCardAlpha(leftPanelRect, cardColor, frameColor, 30, shellAlpha); + DrawPanelCardAlpha(rightPanelRect, cardColor, frameColor, 30, shellAlpha); + + RECT innerRect = + { + gameRect.left + SS(6), + gameRect.top + SS(6), + gameRect.right - SS(6), + gameRect.bottom - SS(6) + }; + + { + Graphics boardGraphics(hdc); + SolidBrush boardBrush(Color(164, GetRValue(boardColor), GetGValue(boardColor), GetBValue(boardColor))); + SolidBrush innerBrush(Color(176, GetRValue(boardInnerColor), GetGValue(boardInnerColor), GetBValue(boardInnerColor))); + boardGraphics.FillRectangle( + &boardBrush, + static_cast(gameRect.left), + static_cast(gameRect.top), + static_cast(gameRect.right - gameRect.left), + static_cast(gameRect.bottom - gameRect.top)); + boardGraphics.FillRectangle( + &innerBrush, + static_cast(innerRect.left), + static_cast(innerRect.top), + static_cast(innerRect.right - innerRect.left), + static_cast(innerRect.bottom - innerRect.top)); + } + + HPEN borderPen = CreatePen(PS_SOLID, SS(2), RGB(132, 108, 146)); + oldPen = (HPEN)SelectObject(hdc, borderPen); + oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); + RoundRect(hdc, gameRect.left, gameRect.top, gameRect.right, gameRect.bottom, SS(18), SS(18)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(borderPen); + + HPEN gridPen = CreatePen(PS_SOLID, 1, lineColor); + oldPen = (HPEN)SelectObject(hdc, gridPen); + + for (int i = 1; i < nGameWidth; i++) + { + int x = gameRect.left + i * grid; + MoveToEx(hdc, x, gameRect.top, nullptr); + LineTo(hdc, x, gameRect.bottom); + } + + for (int i = 1; i < nGameHeight; i++) + { + int y = gameRect.top + i * grid; + MoveToEx(hdc, gameRect.left, y, nullptr); + LineTo(hdc, gameRect.right, y); + } + + SelectObject(hdc, oldPen); + DeleteObject(gridPen); + + int lockedRows = GetRogueLockedRows(); + if (lockedRows > 0) + { + RECT lockedRect = + { + gameRect.left, + gameRect.top + (nGameHeight - lockedRows) * grid, + gameRect.right, + gameRect.bottom + }; + + Graphics lockedGraphics(hdc); + SolidBrush lockedBrush(Color(150, 58, 48, 68)); + Pen lockedPen(Color(210, 232, 170, 194), 1.0f); + lockedGraphics.FillRectangle( + &lockedBrush, + static_cast(lockedRect.left), + static_cast(lockedRect.top), + static_cast(lockedRect.right - lockedRect.left), + static_cast(lockedRect.bottom - lockedRect.top)); + lockedGraphics.DrawRectangle( + &lockedPen, + static_cast(lockedRect.left), + static_cast(lockedRect.top), + static_cast(lockedRect.right - lockedRect.left), + static_cast(lockedRect.bottom - lockedRect.top)); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(238, 204, 216)); + RECT lockedTextRect = + { + lockedRect.left + SS(12), + lockedRect.top + SS(8), + lockedRect.right - SS(12), + lockedRect.bottom - SS(8) + }; + DrawText(hdc, _T("封锁区"), -1, &lockedTextRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + + for (int i = 0; i < nGameHeight; i++) + { + for (int j = 0; j < nGameWidth; j++) + { + if (workRegion[i][j] != 0) + { + int cellValue = workRegion[i][j]; + bool isRainbowCell = (cellValue == 8); + int colorIndex = isRainbowCell ? 0 : (cellValue - 1); + RECT brickRect = + { + gameRect.left + j * grid + SS(2), + gameRect.top + i * grid + SS(2), + gameRect.left + (j + 1) * grid - SS(2), + gameRect.top + (i + 1) * grid - SS(2) + }; + + COLORREF fillColor = isRainbowCell ? RGB(186, 154, 255) : BrickColor[colorIndex]; + COLORREF borderColor = isRainbowCell ? RGB(250, 238, 255) : RGB(255, 248, 250); + HBRUSH brickBrush = CreateSolidBrush(fillColor); + HPEN brickPen = CreatePen(PS_SOLID, isRainbowCell ? SS(2) : 1, borderColor); + oldPen = (HPEN)SelectObject(hdc, brickPen); + oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); + RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(10), SS(10)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(brickBrush); + DeleteObject(brickPen); + } + } + } + + if (targetFlag && !gameOverFlag) + { + HPEN targetPen = CreatePen(PS_DOT, SS(2), RGB(255, 240, 245)); + oldPen = (HPEN)SelectObject(hdc, targetPen); + oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[type][state][i][j] != 0) + { + int drawY = target.y + i; + int drawX = target.x + j; + + if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) + { + RoundRect( + hdc, + gameRect.left + drawX * grid + SS(5), + gameRect.top + drawY * grid + SS(5), + gameRect.left + (drawX + 1) * grid - SS(5), + gameRect.top + (drawY + 1) * grid - SS(5), + SS(8), SS(8)); + } + } + } + } + + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(targetPen); + } + + if (!gameOverFlag) + { + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[type][state][i][j] != 0) + { + int drawY = point.y + i; + int drawX = point.x + j; + + if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) + { + RECT brickRect = + { + gameRect.left + drawX * grid + SS(2), + gameRect.top + drawY * grid + SS(2), + gameRect.left + (drawX + 1) * grid - SS(2), + gameRect.top + (drawY + 1) * grid - SS(2) + }; + + HBRUSH brickBrush = CreateSolidBrush(BrickColor[type]); + COLORREF activeOutlineColor = RGB(255, 250, 252); + int activeOutlineWidth = 1; + if (currentPieceIsExplosive) + { + activeOutlineColor = RGB(255, 116, 78); + activeOutlineWidth = SS(3); + } + else if (currentPieceIsLaser) + { + activeOutlineColor = RGB(120, 232, 255); + activeOutlineWidth = SS(3); + } + else if (currentPieceIsCross) + { + activeOutlineColor = RGB(196, 255, 132); + activeOutlineWidth = SS(3); + } + else if (currentPieceIsRainbow) + { + activeOutlineColor = RGB(186, 126, 255); + activeOutlineWidth = SS(3); + } + HPEN brickPen = CreatePen(PS_SOLID, activeOutlineWidth, activeOutlineColor); + oldPen = (HPEN)SelectObject(hdc, brickPen); + oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); + RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(12), SS(12)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(brickBrush); + DeleteObject(brickPen); + } + } + } + } + } + + for (int i = 0; i < 80; i++) + { + if (gravityFallEffects[i].ticks <= 0 || gravityFallEffects[i].totalTicks <= 0) + { + continue; + } + + if (gravityFallEffects[i].x < 0 || gravityFallEffects[i].x >= nGameWidth || + gravityFallEffects[i].fromY < 0 || gravityFallEffects[i].fromY >= nGameHeight || + gravityFallEffects[i].toY < 0 || gravityFallEffects[i].toY >= nGameHeight) + { + continue; + } + + int elapsed = gravityFallEffects[i].totalTicks - gravityFallEffects[i].ticks; + int travel = gravityFallEffects[i].toY - gravityFallEffects[i].fromY; + int offsetY = travel * grid * elapsed / gravityFallEffects[i].totalTicks; + int drawX = gameRect.left + gravityFallEffects[i].x * grid; + int fromPixelY = gameRect.top + gravityFallEffects[i].fromY * grid; + int toPixelY = gameRect.top + gravityFallEffects[i].toY * grid; + int currentPixelY = fromPixelY + offsetY; + int colorIndex = gravityFallEffects[i].cellValue - 1; + if (colorIndex < 0 || colorIndex >= 7) + { + colorIndex = (gravityFallEffects[i].x + gravityFallEffects[i].toY) % 7; + } + COLORREF fallColor = BrickColor[colorIndex]; + + int alpha = 56 + gravityFallEffects[i].ticks * 150 / gravityFallEffects[i].totalTicks; + int trailTop = fromPixelY + grid / 2; + int trailBottom = currentPixelY + grid / 2; + if (trailBottom < trailTop + SS(8)) + { + trailBottom = trailTop + SS(8); + } + if (trailBottom > toPixelY + grid / 2) + { + trailBottom = toPixelY + grid / 2; + } + + Graphics fallGraphics(hdc); + fallGraphics.SetSmoothingMode(SmoothingModeAntiAlias); + SolidBrush trailBrush(Color(alpha / 2, GetRValue(fallColor), GetGValue(fallColor), GetBValue(fallColor))); + fallGraphics.FillRectangle( + &trailBrush, + static_cast(drawX + SS(12)), + static_cast(trailTop), + static_cast(grid - SS(24)), + static_cast(trailBottom - trailTop)); + + SolidBrush ghostBrush(Color(alpha, GetRValue(fallColor), GetGValue(fallColor), GetBValue(fallColor))); + Pen ghostPen(Color(230, 255, 255, 255), static_cast(SS(2))); + fallGraphics.FillRectangle( + &ghostBrush, + static_cast(drawX + SS(3)), + static_cast(currentPixelY + SS(3)), + static_cast(grid - SS(6)), + static_cast(grid - SS(6))); + fallGraphics.DrawRectangle( + &ghostPen, + static_cast(drawX + SS(3)), + static_cast(currentPixelY + SS(3)), + static_cast(grid - SS(6)), + static_cast(grid - SS(6))); + } + + if (clearEffectState.ticks > 0 && clearEffectState.totalTicks > 0) + { + int elapsed = clearEffectState.totalTicks - clearEffectState.ticks; + int alpha = 42 + clearEffectState.ticks * 150 / clearEffectState.totalTicks; + int inset = SS(elapsed * 2); + Graphics flashGraphics(hdc); + + for (int i = 0; i < clearEffectState.rowCount; i++) + { + int row = clearEffectState.rows[i]; + if (row < 0 || row >= nGameHeight) + { + continue; + } + + int top = gameRect.top + row * grid + inset; + int height = grid - inset * 2; + if (height < SS(4)) + { + height = SS(4); + } + + SolidBrush flashBrush(Color(alpha, 255, 248, 174)); + flashGraphics.FillRectangle( + &flashBrush, + static_cast(gameRect.left + SS(2)), + static_cast(top), + static_cast(gameRect.right - gameRect.left - SS(4)), + static_cast(height)); + } + } + + for (int i = 0; i < 64; i++) + { + if (cellFlashEffects[i].ticks <= 0 || cellFlashEffects[i].totalTicks <= 0) + { + continue; + } + + if (cellFlashEffects[i].x < 0 || cellFlashEffects[i].x >= nGameWidth || + cellFlashEffects[i].y < 0 || cellFlashEffects[i].y >= nGameHeight) + { + continue; + } + + int alpha = 48 + cellFlashEffects[i].ticks * 168 / cellFlashEffects[i].totalTicks; + int pulseInset = SS(2 + (cellFlashEffects[i].totalTicks - cellFlashEffects[i].ticks) % 3); + RECT flashRect = + { + gameRect.left + cellFlashEffects[i].x * grid + pulseInset, + gameRect.top + cellFlashEffects[i].y * grid + pulseInset, + gameRect.left + (cellFlashEffects[i].x + 1) * grid - pulseInset, + gameRect.top + (cellFlashEffects[i].y + 1) * grid - pulseInset + }; + + Graphics cellGraphics(hdc); + cellGraphics.SetSmoothingMode(SmoothingModeAntiAlias); + SolidBrush cellBrush(Color(alpha, GetRValue(cellFlashEffects[i].color), GetGValue(cellFlashEffects[i].color), GetBValue(cellFlashEffects[i].color))); + Pen cellPen(Color(230, GetRValue(cellFlashEffects[i].color), GetGValue(cellFlashEffects[i].color), GetBValue(cellFlashEffects[i].color)), static_cast(SS(2))); + cellGraphics.FillRectangle( + &cellBrush, + static_cast(flashRect.left), + static_cast(flashRect.top), + static_cast(flashRect.right - flashRect.left), + static_cast(flashRect.bottom - flashRect.top)); + cellGraphics.DrawRectangle( + &cellPen, + static_cast(flashRect.left), + static_cast(flashRect.top), + static_cast(flashRect.right - flashRect.left), + static_cast(flashRect.bottom - flashRect.top)); + } + + for (int i = 0; i < 96; i++) + { + if (particleEffects[i].ticks <= 0 || particleEffects[i].totalTicks <= 0) + { + continue; + } + + int elapsed = particleEffects[i].totalTicks - particleEffects[i].ticks; + int particleX = gameRect.left + particleEffects[i].boardX * grid / 100 + SS(particleEffects[i].velocityX * elapsed); + int particleY = gameRect.top + particleEffects[i].boardY * grid / 100 + SS(particleEffects[i].velocityY * elapsed + elapsed * elapsed / 4); + int particleSize = particleEffects[i].size * particleEffects[i].ticks / particleEffects[i].totalTicks; + if (particleSize < 2) + { + particleSize = 2; + } + if (particleSize > 6) + { + particleSize = 6; + } + + RECT particleRect = + { + particleX - particleSize, + particleY - particleSize, + particleX + particleSize, + particleY + particleSize + }; + + HBRUSH particleBrush = GetCachedParticleBrush(particleEffects[i].color); + FillRect(hdc, &particleRect, particleBrush); + } + + for (int i = 0; i < 8; i++) + { + if (floatingTextEffects[i].ticks <= 0 || floatingTextEffects[i].totalTicks <= 0) + { + continue; + } + + int elapsed = floatingTextEffects[i].totalTicks - floatingTextEffects[i].ticks; + int textX = gameRect.left + floatingTextEffects[i].boardX * grid / 100; + int textY = gameRect.top + floatingTextEffects[i].boardY * grid / 100 - SS(elapsed * 4); + + HFONT oldFloatFont = (HFONT)SelectObject(hdc, sectionFont); + SetTextColor(hdc, floatingTextEffects[i].color); + RECT floatRect = + { + textX - SS(160), + textY - SS(24), + textX + SS(160), + textY + SS(28) + }; + DrawText(hdc, floatingTextEffects[i].text, -1, &floatRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + SelectObject(hdc, oldFloatFont); + } + + HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); + DrawPanelHeader(leftPanelRect, _T("战局信息"), 120); + DrawPanelHeader(rightPanelRect, _T("预览与战术"), 148); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + + RECT overviewRect = + { + leftPanelRect.left + SS(20), + leftPanelRect.top + SS(98), + leftPanelRect.right - SS(20), + leftPanelRect.top + SS(252) + }; + DrawPanelCardAlpha(overviewRect, RGB(255, 247, 250), RGB(233, 191, 208), 24, panelAlpha); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + RECT overviewTitleRect = + { + overviewRect.left + SS(18), + overviewRect.top + SS(12), + overviewRect.right - SS(18), + overviewRect.top + SS(42) + }; + DrawText(hdc, _T("战场状态"), -1, &overviewTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, bodyFont); + TCHAR overviewText[160]; + int totalLines = (currentMode == MODE_CLASSIC) ? classicStats.totalLinesCleared : rogueStats.totalLinesCleared; + if (currentMode == MODE_ROGUE) + { + _stprintf_s( + overviewText, + _T("得分 %d\r\n模式 Rogue\r\n消行 %d\r\n危险 Lv.%d 封锁 %d"), + tScore, + totalLines, + rogueStats.difficultyLevel, + GetRogueLockedRows()); + } + else + { + _stprintf_s( + overviewText, + _T("得分 %d\r\n模式 经典\r\n消行 %d"), + tScore, + totalLines); + } + RECT overviewBodyRect = + { + overviewRect.left + SS(22), + overviewRect.top + SS(62), + overviewRect.right - SS(22), + overviewRect.bottom - SS(22) + }; + DrawText(hdc, overviewText, -1, &overviewBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + + if (currentMode == MODE_ROGUE) + { + int progressTop = IsRogueSkillDemoMode() ? 350 : 270; + int progressBottom = IsRogueSkillDemoMode() ? 568 : 488; + int upgradeTop = IsRogueSkillDemoMode() ? 590 : 510; + + if (IsRogueSkillDemoMode()) + { + RECT demoSkillRect = + { + leftPanelRect.left + SS(20), + leftPanelRect.top + SS(270), + leftPanelRect.right - SS(20), + leftPanelRect.top + SS(332) + }; + DrawPanelCardAlpha(demoSkillRect, RGB(255, 244, 248), RGB(232, 184, 202), 20, panelNestedAlpha); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(132, 102, 118)); + RECT demoSkillLabelRect = + { + demoSkillRect.left + SS(18), + demoSkillRect.top + SS(8), + demoSkillRect.right - SS(18), + demoSkillRect.top + SS(26) + }; + DrawText(hdc, _T("\u5f53\u524d\u6280\u80fd"), -1, &demoSkillLabelRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, titleColor); + RECT demoSkillNameRect = + { + demoSkillRect.left + SS(18), + demoSkillRect.top + SS(26), + demoSkillRect.right - SS(18), + demoSkillRect.bottom - SS(8) + }; + DrawText(hdc, GetCurrentRogueSkillDemoName(), -1, &demoSkillNameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + + RECT progressRect = + { + leftPanelRect.left + SS(20), + leftPanelRect.top + SS(progressTop), + leftPanelRect.right - SS(20), + leftPanelRect.top + SS(progressBottom) + }; + DrawPanelCardAlpha(progressRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + RECT progressTitleRect = + { + progressRect.left + SS(18), + progressRect.top + SS(12), + progressRect.right - SS(18), + progressRect.top + SS(42) + }; + DrawText(hdc, _T("成长进度"), -1, &progressTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, bodyFont); + TCHAR progressText[96]; + _stprintf_s(progressText, _T("等级 %d\r\nEXP %d / %d"), rogueStats.level, rogueStats.exp, rogueStats.requiredExp); + RECT progressTextRect = + { + progressRect.left + SS(22), + progressRect.top + SS(56), + progressRect.right - SS(22), + progressRect.top + SS(120) + }; + DrawText(hdc, progressText, -1, &progressTextRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + + RECT expBarRect = + { + progressRect.left + SS(22), + progressRect.top + SS(122), + progressRect.right - SS(22), + progressRect.top + SS(148) + }; + HBRUSH expTrackBrush = CreateSolidBrush(RGB(240, 220, 229)); + FillRect(hdc, &expBarRect, expTrackBrush); + DeleteObject(expTrackBrush); + + RECT expFillRect = expBarRect; + int expWidth = expBarRect.right - expBarRect.left; + if (rogueStats.requiredExp > 0) + { + expFillRect.right = expBarRect.left + expWidth * rogueStats.exp / rogueStats.requiredExp; + } + if (expFillRect.right < expFillRect.left) + { + expFillRect.right = expFillRect.left; + } + if (expFillRect.right > expBarRect.right) + { + expFillRect.right = expBarRect.right; + } + HBRUSH expFillBrush = CreateSolidBrush(accentColor); + FillRect(hdc, &expFillRect, expFillBrush); + DeleteObject(expFillBrush); + + const TCHAR* statLabels[2] = + { + _T("\u5f97\u5206\u500d\u7387"), + _T("EXP \u500d\u7387") + }; + TCHAR statValues[2][32]; + _stprintf_s(statValues[0], _T("%d%%"), rogueStats.scoreMultiplierPercent); + _stprintf_s(statValues[1], _T("%d%%"), rogueStats.expMultiplierPercent); + + int statGap = SS(8); + int statWidth = (progressRect.right - progressRect.left - SS(44) - statGap) / 2; + for (int statIndex = 0; statIndex < 2; statIndex++) + { + RECT statRect = + { + progressRect.left + SS(22) + statIndex * (statWidth + statGap), + progressRect.top + SS(154), + progressRect.left + SS(22) + statIndex * (statWidth + statGap) + statWidth, + progressRect.top + SS(204) + }; + DrawPanelCardAlpha(statRect, RGB(255, 239, 245), RGB(232, 184, 202), 16, panelNestedAlpha); + + RECT statLabelRect = + { + statRect.left + SS(8), + statRect.top + SS(4), + statRect.right - SS(8), + statRect.top + SS(22) + }; + RECT statValueRect = + { + statRect.left + SS(8), + statRect.top + SS(21), + statRect.right - SS(8), + statRect.bottom - SS(2) + }; + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(132, 102, 118)); + DrawText(hdc, statLabels[statIndex], -1, &statLabelRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + SelectObject(hdc, bodyFont); + SetTextColor(hdc, titleColor); + DrawText(hdc, statValues[statIndex], -1, &statValueRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + + RECT upgradeListRect = + { + leftPanelRect.left + SS(20), + leftPanelRect.top + SS(upgradeTop), + leftPanelRect.right - SS(20), + leftPanelRect.bottom - SS(20) + }; + DrawPanelCardAlpha(upgradeListRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + TextOut(hdc, upgradeListRect.left + SS(18), upgradeListRect.top + SS(16), _T("已掌握强化"), lstrlen(_T("已掌握强化"))); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(148, 118, 132)); + RECT upgradeScrollHintRect = + { + upgradeListRect.left + SS(116), + upgradeListRect.top + SS(17), + upgradeListRect.right - SS(18), + upgradeListRect.top + SS(42) + }; + DrawText(hdc, _T("J/K / 滚轮"), -1, &upgradeScrollHintRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT upgradeBodyRect = + { + upgradeListRect.left + SS(22), + upgradeListRect.top + SS(56), + upgradeListRect.right - SS(22), + upgradeListRect.bottom - SS(20) + }; + + TCHAR upgradeSummary[512]; + upgradeSummary[0] = _T('\0'); + + if (rogueStats.scoreUpgradeLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5206\u6570\u500d\u7387 Lv.%d\r\n"), rogueStats.scoreUpgradeLevel); + } + if (rogueStats.expUpgradeLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("EXP \u5f3a\u5316 Lv.%d\r\n"), rogueStats.expUpgradeLevel); + } + if (rogueStats.slowFallStacks > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6162\u901f\u4e0b\u843d Lv.%d\r\n"), rogueStats.slowFallStacks); + } + if (rogueStats.comboBonusStacks > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u51fb\u52a0\u6210 Lv.%d\r\n"), rogueStats.comboBonusStacks); + } + if (rogueStats.previewUpgradeLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u989d\u5916\u9884\u89c8 Lv.%d\r\n"), rogueStats.previewUpgradeLevel); + } + if (rogueStats.lastChanceUpgradeLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6700\u540e\u4e00\u640f Lv.1 \u5269\u4f59 %d \u6b21\r\n"), rogueStats.lastChanceCount); + } + if (rogueStats.pressureReliefLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u51cf\u538b Lv.%d\r\n"), rogueStats.pressureReliefLevel); + } + if (rogueStats.sweeperLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6e05\u626b\u8005 Lv.%d\r\n"), rogueStats.sweeperLevel); + } + if (rogueStats.explosiveLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7206\u7834\u65b9\u5757 Lv.%d\r\n"), rogueStats.explosiveLevel); + } + if (rogueStats.chainBlastLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u9501\u7206\u7834 Lv.1\r\n")); + } + if (rogueStats.chainBombLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u73af\u70b8\u5f39 Lv.1\r\n")); + } + if (rogueStats.laserLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6fc0\u5149\u65b9\u5757 Lv.%d\r\n"), rogueStats.laserLevel); + } + if (rogueStats.thunderTetrisLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u96f7\u9706\u56db\u6d88 Lv.1\r\n")); + } + if (rogueStats.thunderLaserLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u96f7\u9706\u6fc0\u5149 Lv.1\r\n")); + } + if (rogueStats.feverLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u72c2\u70ed\u6a21\u5f0f Lv.1\r\n")); + } + if (rogueStats.rageStackLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u66b4\u8d70\u5806\u53e0 Lv.1\r\n")); + } + if (rogueStats.infiniteFeverLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65e0\u9650\u72c2\u70ed Lv.1\r\n")); + } + if (rogueStats.screenBombLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6e05\u5c4f\u70b8\u5f39 Lv.%d \u5269\u4f59 %d \u679a\r\n"), rogueStats.screenBombLevel, rogueStats.screenBombCount); + } + if (rogueStats.terminalClearLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7ec8\u672b\u6e05\u573a Lv.1\r\n")); + } + if (rogueStats.perfectRotateLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5b8c\u7f8e\u65cb\u8f6c Lv.1\r\n")); + } + if (rogueStats.timeDilationLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65f6\u95f4\u7f13\u6d41 Lv.1\r\n")); + } + if (rogueStats.highPressureLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u9ad8\u538b\u5956\u52b1 Lv.1\r\n")); + } + if (rogueStats.tetrisGambleLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8d4c\u547d\u56db\u6d88 Lv.1\r\n")); + } + if (rogueStats.extremePlayerLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6781\u9650\u73a9\u5bb6 Lv.1\r\n")); + } + if (rogueStats.upgradeShockwaveLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5347\u7ea7\u51b2\u51fb\u6ce2 Lv.1\r\n")); + } + if (rogueStats.evolutionImpactLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fdb\u5316\u51b2\u51fb Lv.1\r\n")); + } + if (rogueStats.controlMasterLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u64cd\u63a7\u5927\u5e08 Lv.1\r\n")); + } + if (rogueStats.blockStormLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65b9\u5757\u98ce\u66b4 Lv.1\r\n")); + } + if (rogueStats.crossPieceLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5341\u5b57\u65b9\u5757 Lv.%d\r\n"), rogueStats.crossPieceLevel); + } + if (rogueStats.blackHoleLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u9ed1\u6d1e Lv.%d \u5269\u4f59 %d \u6b21\r\n"), rogueStats.blackHoleLevel, rogueStats.blackHoleCharges); + } + if (rogueStats.reshapeLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a7a\u4e2d\u6362\u5f62 Lv.%d \u5269\u4f59 %d \u6b21\r\n"), rogueStats.reshapeLevel, rogueStats.reshapeCharges); + } + if (rogueStats.rainbowPieceLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5f69\u8679\u65b9\u5757 Lv.1\r\n")); + } + if (rogueStats.voidCoreLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u865a\u7a7a\u6838\u5fc3 Lv.1\r\n")); + } + if (rogueStats.stableStructureLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a33\u5b9a\u7ed3\u6784 Lv.%d\r\n"), rogueStats.stableStructureLevel); + } + if (rogueStats.doubleGrowthLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6210\u957f\u6838\u5fc3 Lv.1\r\n")); + } + if (rogueStats.pieceTuningLevels[0] > 0) + { + _stprintf_s( + upgradeSummary + lstrlen(upgradeSummary), + 512 - lstrlen(upgradeSummary), + _T("I \u65b9\u5757\u6539\u9020 Lv.%d\r\n"), + rogueStats.pieceTuningLevels[0]); + } + if (rogueStats.gamblerLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8d4c\u5f92 Lv.%d\r\n"), rogueStats.gamblerLevel); + } + if (rogueStats.dualChoiceLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u53cc\u91cd\u9009\u62e9 Lv.1\r\n")); + } + if (rogueStats.destinyWheelLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u547d\u8fd0\u8f6e\u76d8 Lv.1\r\n")); + } + if (lstrlen(upgradeSummary) == 0) + { + _stprintf_s(upgradeSummary, _T("尚未掌握强化。\r\n下一次升级将开启构筑。")); + } + + RECT upgradeCalculateRect = + { + upgradeBodyRect.left, + upgradeBodyRect.top, + upgradeBodyRect.right, + upgradeBodyRect.top + }; + DrawText(hdc, upgradeSummary, -1, &upgradeCalculateRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); + int maxUpgradeScroll = (upgradeCalculateRect.bottom - upgradeCalculateRect.top) - (upgradeBodyRect.bottom - upgradeBodyRect.top); + if (maxUpgradeScroll < 0) + { + maxUpgradeScroll = 0; + } + if (upgradeListScrollOffset > maxUpgradeScroll) + { + upgradeListScrollOffset = maxUpgradeScroll; + } + + HRGN oldUpgradeClipRegion = CreateRectRgn(0, 0, 0, 0); + int hasOldUpgradeClipRegion = GetClipRgn(hdc, oldUpgradeClipRegion); + HRGN upgradeClipRegion = CreateRectRgn(upgradeBodyRect.left, upgradeBodyRect.top, upgradeBodyRect.right, upgradeBodyRect.bottom); + SelectClipRgn(hdc, upgradeClipRegion); + + RECT scrolledUpgradeBodyRect = upgradeBodyRect; + scrolledUpgradeBodyRect.top -= upgradeListScrollOffset; + scrolledUpgradeBodyRect.bottom += maxUpgradeScroll; + DrawText(hdc, upgradeSummary, -1, &scrolledUpgradeBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + + if (hasOldUpgradeClipRegion == 1) + { + SelectClipRgn(hdc, oldUpgradeClipRegion); + } + else + { + SelectClipRgn(hdc, nullptr); + } + DeleteObject(upgradeClipRegion); + DeleteObject(oldUpgradeClipRegion); + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + } + else + { + RECT classicInfoRect = + { + leftPanelRect.left + SS(20), + leftPanelRect.top + SS(270), + leftPanelRect.right - SS(20), + leftPanelRect.top + SS(458) + }; + DrawPanelCardAlpha(classicInfoRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT classicBodyRect = + { + classicInfoRect.left + SS(22), + classicInfoRect.top + SS(24), + classicInfoRect.right - SS(22), + classicInfoRect.bottom - SS(24) + }; + DrawText(hdc, _T("经典模式:没有成长、EXP 和强化,只保留纯粹的消行挑战。\r\n\r\n在有限空间中保持阵型,尽可能活得更久。"), -1, &classicBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + } + RECT holdNextRect = + { + rightPanelRect.left + SS(20), + rightPanelRect.top + SS(98), + rightPanelRect.right - SS(20), + rightPanelRect.top + SS(326) + }; + DrawPanelCardAlpha(holdNextRect, RGB(255, 247, 250), RGB(233, 191, 208), 24, panelAlpha); + + RECT holdCard = + { + holdNextRect.left + SS(88), + holdNextRect.top + SS(24), + holdNextRect.left + SS(150), + holdNextRect.top + SS(86) + }; + + RECT nextSectionRect = + { + holdNextRect.left + SS(18), + holdNextRect.top + SS(146), + holdNextRect.right - SS(18), + holdNextRect.bottom - SS(22) + }; + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + RECT holdTitleRect = + { + holdNextRect.left + SS(18), + holdNextRect.top + SS(34), + holdNextRect.left + SS(80), + holdNextRect.top + SS(70) + }; + DrawText(hdc, _T("Hold"), -1, &holdTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + RECT nextTitleRect = + { + nextSectionRect.left, + holdNextRect.top + SS(102), + holdNextRect.right - SS(18), + holdNextRect.top + SS(136) + }; + DrawText(hdc, _T("\u4e0b\u4e00\u4e2a"), -1, &nextTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + DrawPanelCardAlpha(holdCard, RGB(255, 238, 244), RGB(233, 191, 208), 22, panelNestedAlpha); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT holdHintRect = + { + holdCard.right + SS(14), + holdCard.top + SS(2), + holdNextRect.right - SS(18), + holdCard.bottom - SS(2) + }; + DrawText( + hdc, + currentMode == MODE_ROGUE && rogueStats.holdUnlocked > 0 + ? (holdUsedThisTurn ? _T("已使用\r\n落地后恢复") : _T("C / Shift\r\n暂存一次")) + : _T("未开启\r\n需备用仓"), + -1, + &holdHintRect, + DT_LEFT | DT_VCENTER | DT_WORDBREAK); + + if (currentMode == MODE_ROGUE) + { + if (rogueStats.holdUnlocked == 0) + { + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT holdLockedRect = + { + holdCard.left + SS(10), + holdCard.top + SS(10), + holdCard.right - SS(10), + holdCard.bottom - SS(10) + }; + DrawText(hdc, _T("封存"), -1, &holdLockedRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); + } + else if (holdType >= 0) + { + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[holdType][0][i][j] != 0) + { + int holdAvailableWidth = holdCard.right - holdCard.left - SS(16); + int holdAvailableHeight = holdCard.bottom - holdCard.top - SS(16); + int holdMiniCell = holdAvailableWidth < holdAvailableHeight ? holdAvailableWidth / 4 : holdAvailableHeight / 4; + if (holdMiniCell < SS(7)) + { + holdMiniCell = SS(7); + } + int holdOffsetX = holdCard.left + (holdCard.right - holdCard.left - holdMiniCell * 4) / 2; + int holdOffsetY = holdCard.top + (holdCard.bottom - holdCard.top - holdMiniCell * 4) / 2; + RECT brickRect = + { + holdOffsetX + j * holdMiniCell, + holdOffsetY + i * holdMiniCell, + holdOffsetX + (j + 1) * holdMiniCell - SS(1), + holdOffsetY + (i + 1) * holdMiniCell - SS(1) + }; + + HBRUSH brickBrush = CreateSolidBrush(BrickColor[holdType]); + HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 248, 250)); + oldPen = (HPEN)SelectObject(hdc, brickPen); + oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); + RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(8), SS(8)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(brickBrush); + DeleteObject(brickPen); + } + } + } + } + else + { + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT emptyHoldRect = + { + holdCard.left + SS(10), + holdCard.top + SS(10), + holdCard.right - SS(10), + holdCard.bottom - SS(10) + }; + DrawText(hdc, _T("\u7a7a"), -1, &emptyHoldRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); + } + } + else + { + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT classicHoldRect = + { + holdCard.left + SS(10), + holdCard.top + SS(10), + holdCard.right - SS(10), + holdCard.bottom - SS(10) + }; + DrawText(hdc, _T("\u5173"), -1, &classicHoldRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); + } + + int previewCount = 1; + if (currentMode == MODE_ROGUE) + { + previewCount = rogueStats.previewCount; + if (previewCount < 1) + { + previewCount = 1; + } + if (previewCount > 3) + { + previewCount = 3; + } + } + + for (int previewIndex = 0; previewIndex < previewCount; previewIndex++) + { + int nextCardGap = SS(8); + int nextCardWidth = (nextSectionRect.right - nextSectionRect.left - nextCardGap * 2) / 3; + RECT nextCard = + { + nextSectionRect.left + previewIndex * (nextCardWidth + nextCardGap), + nextSectionRect.top, + nextSectionRect.left + previewIndex * (nextCardWidth + nextCardGap) + nextCardWidth, + nextSectionRect.bottom + }; + + DrawPanelCardAlpha(nextCard, RGB(255, 247, 250), RGB(233, 191, 208), 18, panelStrongAlpha); + + int previewType = nextTypes[previewIndex]; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[previewType][0][i][j] != 0) + { + int nextAvailableWidth = nextCard.right - nextCard.left - SS(14); + int nextAvailableHeight = nextCard.bottom - nextCard.top - SS(14); + int nextMiniCell = nextAvailableWidth < nextAvailableHeight ? nextAvailableWidth / 4 : nextAvailableHeight / 4; + if (nextMiniCell < SS(7)) + { + nextMiniCell = SS(7); + } + int previewOffsetX = nextCard.left + (nextCard.right - nextCard.left - nextMiniCell * 4) / 2; + int previewOffsetY = nextCard.top + (nextCard.bottom - nextCard.top - nextMiniCell * 4) / 2; + RECT brickRect = + { + previewOffsetX + j * nextMiniCell, + previewOffsetY + i * nextMiniCell, + previewOffsetX + (j + 1) * nextMiniCell - SS(1), + previewOffsetY + (i + 1) * nextMiniCell - SS(1) + }; + + HBRUSH brickBrush = CreateSolidBrush(BrickColor[previewType]); + HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 248, 250)); + oldPen = (HPEN)SelectObject(hdc, brickPen); + oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); + RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(8), SS(8)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(brickBrush); + DeleteObject(brickPen); + } + } + } + } + + RECT controlRect = + { + rightPanelRect.left + SS(20), + rightPanelRect.top + SS(346), + rightPanelRect.right - SS(20), + rightPanelRect.bottom - SS(20) + }; + DrawPanelCardAlpha(controlRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + TextOut(hdc, controlRect.left + SS(18), controlRect.top + SS(16), _T("战斗日志"), lstrlen(_T("战斗日志"))); + + RECT feedbackPanelRect = + { + controlRect.left + SS(18), + controlRect.top + SS(56), + controlRect.right - SS(18), + controlRect.top + SS(172) + }; + DrawPanelCardAlpha(feedbackPanelRect, RGB(255, 244, 248), RGB(232, 184, 202), 20, panelNestedAlpha); + + SelectObject(hdc, bodyFont); + SetTextColor(hdc, titleColor); + RECT feedbackTitleRect = + { + feedbackPanelRect.left + SS(16), + feedbackPanelRect.top + SS(14), + feedbackPanelRect.right - SS(16), + feedbackPanelRect.top + SS(42) + }; + DrawText( + hdc, + feedbackState.visibleTicks > 0 ? feedbackState.title : _T("局势平稳"), + -1, + &feedbackTitleRect, + DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(116, 90, 104)); + RECT feedbackBodyRect = + { + feedbackPanelRect.left + SS(16), + feedbackPanelRect.top + SS(46), + feedbackPanelRect.right - SS(16), + feedbackPanelRect.bottom - SS(14) + }; + DrawText( + hdc, + feedbackState.visibleTicks > 0 + ? feedbackState.detail + : _T("连消、主动技能和特殊方块的战斗记录会出现在这里。"), + -1, + &feedbackBodyRect, + DT_LEFT | DT_TOP | DT_WORDBREAK); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + RECT tipsTitleRect = + { + controlRect.left + SS(22), + controlRect.top + SS(192), + controlRect.right - SS(22), + controlRect.top + SS(224) + }; + DrawText(hdc, _T("操作指令"), -1, &tipsTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, bodyFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT controlBodyRect = + { + controlRect.left + SS(22), + controlRect.top + SS(232), + controlRect.right - SS(22), + controlRect.bottom - SS(22) + }; + if (currentMode == MODE_ROGUE) + { + if (IsRogueSkillDemoMode()) + { + DrawText( + hdc, + _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") + _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") + _T("演示:R 重置当前 N 下一个 M 菜单\r\n") + _T("技能:C 备用仓 Z 黑洞 X 炸弹 V 换形"), + -1, + &controlBodyRect, + DT_LEFT | DT_TOP | DT_WORDBREAK); + } + else + { + DrawText( + hdc, + _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") + _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") + _T("战局:P 暂停 R 重开 M 菜单\r\n") + _T("技能:C 备用仓 Z 黑洞 X 炸弹 V 换形"), + -1, + &controlBodyRect, + DT_LEFT | DT_TOP | DT_WORDBREAK); + } + } + else + { + DrawText( + hdc, + _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") + _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") + _T("战局:P 暂停 R 重开 M 菜单"), + -1, + &controlBodyRect, + DT_LEFT | DT_TOP | DT_WORDBREAK); + } + + // 暂停和结束覆盖层只盖住棋盘区域,让两侧战斗信息仍然可见。 + if (suspendFlag || gameOverFlag) + { + RECT overlayRect = + { + gameRect.left + SS(28), + gameRect.top + grid * 6 + SS(10), + gameRect.right - SS(28), + gameRect.top + grid * 10 + SS(30) + }; + + HBRUSH overlayBrush = CreateSolidBrush(RGB(255, 241, 246)); + HPEN overlayPen = CreatePen(PS_SOLID, 2, RGB(232, 170, 194)); + oldPen = (HPEN)SelectObject(hdc, overlayPen); + oldBrush = (HBRUSH)SelectObject(hdc, overlayBrush); + RoundRect(hdc, overlayRect.left, overlayRect.top, overlayRect.right, overlayRect.bottom, SS(26), SS(26)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(overlayBrush); + DeleteObject(overlayPen); + + RECT titleRect = + { + overlayRect.left + SS(24), + overlayRect.top + SS(18), + overlayRect.right - SS(24), + overlayRect.top + SS(78) + }; + + RECT tipRect = + { + overlayRect.left + SS(28), + overlayRect.top + SS(86), + overlayRect.right - SS(28), + overlayRect.bottom - SS(20) + }; + + SetTextColor(hdc, RGB(121, 76, 99)); + SelectObject(hdc, titleFont); + + if (suspendFlag) + { + DrawText(hdc, _T("时间静止"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + int buttonGap = SS(18); + int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; + RECT continueRect = + { + overlayRect.left + SS(34), + overlayRect.top + SS(94), + overlayRect.left + SS(34) + buttonWidth, + overlayRect.top + SS(138) + }; + RECT menuRect = + { + continueRect.right + buttonGap, + continueRect.top, + overlayRect.right - SS(34), + continueRect.bottom + }; + DrawPanelCardAlpha(continueRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); + DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); + SelectObject(hdc, bodyFont); + DrawText(hdc, _T("继续 (P)"), -1, &continueRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + else + { + DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + if (reviveAvailable) + { + int buttonGap = SS(8); + int buttonWidth = (overlayRect.right - overlayRect.left - SS(28) - buttonGap * 2) / 3; + RECT reviveRect = + { + overlayRect.left + SS(14), + overlayRect.top + SS(94), + overlayRect.left + SS(14) + buttonWidth, + overlayRect.top + SS(138) + }; + RECT restartRect = + { + reviveRect.right + buttonGap, + reviveRect.top, + reviveRect.right + buttonGap + buttonWidth, + reviveRect.bottom + }; + RECT menuRect = + { + restartRect.right + buttonGap, + reviveRect.top, + overlayRect.right - SS(14), + reviveRect.bottom + }; + DrawPanelCardAlpha(reviveRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); + DrawPanelCardAlpha(restartRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); + DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); + SelectObject(hdc, bodyFont); + DrawText(hdc, _T("复活 (V)"), -1, &reviveRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + else + { + int buttonGap = SS(18); + int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; + RECT restartRect = + { + overlayRect.left + SS(34), + overlayRect.top + SS(94), + overlayRect.left + SS(34) + buttonWidth, + overlayRect.top + SS(138) + }; + RECT menuRect = + { + restartRect.right + buttonGap, + restartRect.top, + overlayRect.right - SS(34), + restartRect.bottom + }; + DrawPanelCardAlpha(restartRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); + DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); + SelectObject(hdc, bodyFont); + DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + } + } + + // 升级选择界面在当前战局上方绘制半透明遮罩,保留背景局势作为上下文。 + if (currentScreen == SCREEN_UPGRADE) + { + RECT dimRect = + { + SX(20), + SY(20), + SX(WINDOW_CLIENT_WIDTH - 20), + SY(WINDOW_CLIENT_HEIGHT - 20) + }; + Graphics dimGraphics(hdc); + SolidBrush dimBrush(Color(214, 246, 232, 238)); + dimGraphics.FillRectangle( + &dimBrush, + static_cast(dimRect.left), + static_cast(dimRect.top), + static_cast(dimRect.right - dimRect.left), + static_cast(dimRect.bottom - dimRect.top)); + + RECT overlayRect = + { + SX(60), + SY(80), + SX(WINDOW_CLIENT_WIDTH - 60), + SY(WINDOW_CLIENT_HEIGHT - 80) + }; + + DrawPanelCardAlpha(overlayRect, RGB(255, 250, 252), RGB(232, 170, 194), 30, 238); + + SetTextColor(hdc, titleColor); + SelectObject(hdc, titleFont); + + RECT overlayTitleRect = + { + overlayRect.left, + overlayRect.top + SS(24), + overlayRect.right, + overlayRect.top + SS(68) + }; + DrawText(hdc, _T("选择强化"), -1, &overlayTitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, bodyFont); + SetTextColor(hdc, textColor); + RECT overlaySubtitleRect = + { + overlayRect.left, + overlayRect.top + SS(70), + overlayRect.right, + overlayRect.top + SS(106) + }; + TCHAR overlaySubtitle[128]; + if (upgradeUiState.picksRemaining > 1) + { + _stprintf_s( + overlaySubtitle, + _T("出现 %d 个强化,请标记 %d 个(已选 %d 个)"), + upgradeUiState.optionCount, + upgradeUiState.picksRemaining, + upgradeUiState.markedCount); + } + else + { + _stprintf_s( + overlaySubtitle, + _T("出现 %d 个强化,还可选择 %d 个"), + upgradeUiState.optionCount, + upgradeUiState.picksRemaining); + } + DrawText(hdc, overlaySubtitle, -1, &overlaySubtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + int gap = SS(18); + int horizontalPadding = SS(36); + int verticalTop = overlayRect.top + SS(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 - SS(72) - (rowCount - 1) * gap; + int cardHeight = availableHeight / rowCount; + + for (int i = 0; i < upgradeUiState.optionCount; i++) + { + bool isSelected = (i == upgradeUiState.selectedIndex); + bool isMarked = upgradeUiState.marked[i]; + int column = i % columnCount; + int row = i / columnCount; + int left = overlayRect.left + horizontalPadding + column * (cardWidth + gap); + int top = verticalTop + row * (cardHeight + gap); + int right = left + cardWidth; + + RECT cardRect = + { + left, + top, + right, + top + cardHeight + }; + + COLORREF cardFill = RGB(255, 247, 250); + COLORREF cardBorder = RGB(221, 197, 208); + COLORREF descColor = RGB(108, 86, 99); + COLORREF footerColor = RGB(128, 104, 118); + const TCHAR* synthesisPath = GetUpgradeSynthesisPath(upgradeUiState.options[i].id); + bool hasSynthesisPath = synthesisPath != nullptr && lstrlen(synthesisPath) > 0; + const TCHAR* rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u666e\u901a"); + + if (upgradeUiState.options[i].cursed) + { + cardFill = isSelected ? RGB(238, 232, 238) : RGB(250, 245, 248); + cardBorder = RGB(38, 34, 42); + descColor = RGB(72, 62, 72); + footerColor = RGB(48, 42, 50); + rarityText = _T("\u8bc5\u5492\uff1a\u4e0b\u6b21\u5347\u7ea7\u6240\u9700 EXP +25%"); + } + else if (hasSynthesisPath) + { + cardBorder = RGB(208, 74, 78); + footerColor = RGB(164, 58, 64); + rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u8fdb\u9636"); + } + else if (upgradeUiState.options[i].rarity == UPGRADE_RARITY_RARE) + { + cardBorder = RGB(218, 172, 72); + footerColor = RGB(150, 111, 40); + rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u73cd\u7a00"); + } + else if (upgradeUiState.options[i].rarity == UPGRADE_RARITY_UNCOMMON) + { + cardBorder = RGB(92, 152, 218); + footerColor = RGB(68, 112, 166); + rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u7f55\u89c1"); + } + + if (isMarked && !upgradeUiState.options[i].cursed) + { + cardFill = RGB(255, 226, 236); + } + + if (isSelected && !upgradeUiState.options[i].cursed) + { + cardFill = RGB(255, 235, 242); + } + + DrawPanelCardAlpha(cardRect, cardFill, cardBorder, 24, isSelected ? 238 : 226); + if (isSelected) + { + HPEN selectedPen = CreatePen(PS_SOLID, SS(3), cardBorder); + oldPen = (HPEN)SelectObject(hdc, selectedPen); + oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); + RoundRect(hdc, cardRect.left, cardRect.top, cardRect.right, cardRect.bottom, SS(24), SS(24)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(selectedPen); + } + + TCHAR levelText[32]; + _stprintf_s(levelText, _T("Lv.%d"), upgradeUiState.options[i].currentLevel + 1); + RECT levelRect = + { + cardRect.right - SS(88), + cardRect.top + SS(20), + cardRect.right - SS(20), + cardRect.top + SS(48) + }; + SelectObject(hdc, smallFont); + SetTextColor(hdc, footerColor); + if (isMarked) + { + DrawText(hdc, _T("[已选]"), -1, &levelRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + } + else + { + DrawText(hdc, levelText, -1, &levelRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + } + + if (hasSynthesisPath) + { + SelectObject(hdc, smallFont); + SetTextColor(hdc, upgradeUiState.options[i].cursed ? RGB(142, 78, 78) : RGB(116, 82, 104)); + RECT synthesisRect = + { + cardRect.left + SS(20), + cardRect.top + SS(18), + cardRect.right - SS(116), + cardRect.top + SS(40) + }; + DrawText(hdc, synthesisPath, -1, &synthesisRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + } + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, upgradeUiState.options[i].cursed ? RGB(130, 54, 54) : (isSelected ? titleColor : textColor)); + RECT nameRect = + { + cardRect.left + SS(20), + cardRect.top + (hasSynthesisPath ? SS(44) : SS(38)), + cardRect.right - SS(20), + cardRect.top + (hasSynthesisPath ? SS(76) : SS(70)) + }; + DrawText(hdc, upgradeUiState.options[i].name, -1, &nameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, bodyFont); + SetTextColor(hdc, descColor); + RECT descRect = + { + cardRect.left + SS(20), + cardRect.top + (hasSynthesisPath ? SS(84) : SS(78)), + cardRect.right - SS(20), + cardRect.bottom - SS(48) + }; + DrawText(hdc, upgradeUiState.options[i].description, -1, &descRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_END_ELLIPSIS); + + SelectObject(hdc, smallFont); + RECT footerRect = + { + cardRect.left + SS(20), + cardRect.bottom - SS(34), + cardRect.right - SS(20), + cardRect.bottom - SS(10) + }; + SetTextColor(hdc, footerColor); + DrawText( + hdc, + rarityText, + -1, + &footerRect, + DT_LEFT | DT_VCENTER | DT_SINGLELINE); + } + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT hintRect = + { + overlayRect.left + SS(30), + overlayRect.bottom - SS(52), + overlayRect.right - SS(30), + overlayRect.bottom - SS(18) + }; + DrawText( + hdc, + upgradeUiState.picksRemaining > 1 + ? _T("W/A/S/D 或方向键切换,Space 标记,Enter 确认已选强化") + : _T("W/A/S/D 或方向键切换,Enter 或 Space 确认"), + -1, + &hintRect, + DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + + if (currentScreen != SCREEN_MENU) + { + DrawBackButton(); + } + DrawMusicButton(); + + SelectObject(hdc, oldFont); + DeleteObject(titleFont); + DeleteObject(sectionFont); + DeleteObject(bodyFont); + DeleteObject(smallFont); +} +