Compare commits
6 Commits
7fe0244a99
..
vs
| Author | SHA1 | Date | |
|---|---|---|---|
| fd383481fd | |||
| a0bae4b6b8 | |||
| 70b1665b31 | |||
| 59e491038d | |||
| d5f6cea2ed | |||
| 84017ae6b7 |
@@ -38,3 +38,4 @@
|
||||
|
||||
1. 每次补全前后都要保存版本,便于报告展示。
|
||||
2. 现场汇报时,所有组员都可能被提问,不能只有一人理解代码。
|
||||
3. 作业要求LLM对话迭代生成代码 虽然我们不是 我们是vibe coding 但是在后续生成报告相关的时候 要伪装的像是LLM对话迭代生成的
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Tereis contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the source code and associated documentation files in this repository,
|
||||
excluding third-party media assets and generated media assets as described in
|
||||
NOTICE.md, to deal in the source code without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the source code, and to permit persons to
|
||||
whom the source code is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the source code.
|
||||
|
||||
THE SOURCE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOURCE CODE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOURCE CODE.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Notice
|
||||
|
||||
本仓库是程序设计课程大作业项目,仅用于课程学习、课堂展示和个人技术交流,不用于商业发布。
|
||||
|
||||
## 授权范围
|
||||
|
||||
- `src/`、构建脚本和项目文档中的原创代码内容按 `LICENSE` 中的 MIT License 授权。
|
||||
- `assets/`、`report/images/` 和 `report/code-snippets/` 中的图片、音频、视频等非代码素材不包含在 MIT License 授权范围内。
|
||||
- 如需二次发布、公开分发可执行文件或用于课程以外场景,请先替换或移除未取得独立授权的素材。
|
||||
|
||||
## 素材来源
|
||||
|
||||
- 音乐素材:来自《千恋*万花》,仅作为课程大作业学习展示使用,版权归原权利方所有。
|
||||
- 图片素材:由 AI 生成或用于课程报告展示。
|
||||
- 图标、视频和其他资源:仅随课程项目用于演示程序功能,不代表已获得商业使用授权。
|
||||
|
||||
## 使用提醒
|
||||
|
||||
如果将项目上传到公开平台,建议在发布说明中保留本文件,并明确说明素材来源和授权限制。若需要更严格地规避素材版权风险,可以只公开源码和文档,删除 `assets/` 下的媒体文件。
|
||||
|
||||
@@ -335,3 +335,15 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\build-mingw.ps1 -Run
|
||||
- `src/include/TetrisAppInternal.h`、`src/include/TetrisRenderInternal.h`、`src/include/TetrisAssets.h`:窗口层、渲染层和资源工具的内部声明
|
||||
|
||||
项目适合作为程序设计课程大作业展示,也便于在答辩时讲解窗口程序、游戏循环、碰撞检测、状态管理和功能扩展。
|
||||
|
||||
## 开源协议与素材说明
|
||||
|
||||
本项目为程序设计课程大作业,仅供课程学习、课堂展示和个人技术交流使用,不用于商业发布。
|
||||
|
||||
- 源代码、构建脚本和原创文档内容采用 MIT License,详见 `LICENSE`。
|
||||
- `assets/`、`report/images/` 和 `report/code-snippets/` 中的音频、图片、视频等非代码素材不包含在 MIT License 授权范围内。
|
||||
- 音乐素材来自《千恋*万花》,仅作为课程大作业学习展示使用,版权归原权利方所有。
|
||||
- 图片素材主要由 AI 生成或用于课程报告展示。
|
||||
- 如需二次发布、公开分发可执行文件或用于课程以外场景,请先替换或移除未取得独立授权的素材。
|
||||
|
||||
更完整的素材来源和授权限制说明见 `NOTICE.md`。
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
# Tereis 实验报告与项目整理 TODO
|
||||
|
||||
> 依据:实验报告模板 `大学计算-程序设计大作业-实验报告模板.docx`、课堂报告要求截图、当前 `src` 源码目录。
|
||||
> 说明:`report/` 文件夹按废弃资料处理,不作为本 TODO 的依据。
|
||||
|
||||
## 0. 当前项目审查结论
|
||||
|
||||
- [ ] 确认最终报告只引用 `src/`、`assets/`、`README.md`、构建脚本和重新整理的截图材料。
|
||||
- [ ] 清点当前源码模块:
|
||||
- `src/source/Tetris.cpp`:Win32 程序入口、窗口注册、消息循环、主窗口消息处理。
|
||||
- `src/source/TetrisLogic.cpp`:基础俄罗斯方块移动、旋转、落地、消行、重开等核心逻辑。
|
||||
- `src/source/logic/`:生成下一方块、固定方块、特殊落地效果、棋盘辅助逻辑。
|
||||
- `src/source/app/`:定时器、键盘鼠标输入、窗口布局、背景音乐和复活视频。
|
||||
- `src/source/render/` 与 `TetrisRender.cpp`:界面绘制、背景图片、GDI/GDI+ 资源加载。
|
||||
- `src/source/extensions/`:菜单、反馈提示、视觉特效、复活、页面切换等扩展状态。
|
||||
- `src/source/rogue/`:Rogue 模式、升级选项、主动技能、特殊方块、难度成长。
|
||||
- [ ] 记录项目规模:多源文件 C++ Win32 桌面程序,主要采用全局变量、结构体、函数的过程式组织。
|
||||
- [ ] 检查课程限制风险:
|
||||
- 当前代码没有自定义 `class`、继承、多态。
|
||||
- 但存在 `std::wstring`、`std::vector` 未发现、`auto` lambda、GDI+ `Image` 对象、`new/delete`、`constexpr`、C++17 构建参数等超出“仅基础语法”的风险点。
|
||||
- 报告中需要说明:核心游戏逻辑坚持数组、循环、分支、函数、结构体;Win32/GDI+ 属于界面和资源接口调用。
|
||||
|
||||
## 1. 阶段一:窗口创建与程序框架
|
||||
|
||||
- [ ] 功能设计文档:说明为什么先搭建窗口、消息循环和菜单状态。
|
||||
- [ ] 关键代码整理:
|
||||
- `_tWinMain`:`src/source/Tetris.cpp`
|
||||
- `MyRegisterClass`:`src/source/Tetris.cpp`
|
||||
- `InitInstance`:`src/source/Tetris.cpp`
|
||||
- `WndProc`:`src/source/Tetris.cpp`
|
||||
- `About`:`src/source/Tetris.cpp`
|
||||
- [ ] 代码说明重点:
|
||||
- Win32 程序入口如何创建主窗口。
|
||||
- 消息循环如何把键盘、鼠标、定时器、绘制消息分发给游戏。
|
||||
- 为什么用全局状态变量保存当前界面和游戏状态。
|
||||
- [ ] 截图补充:
|
||||
- 程序启动主菜单。
|
||||
- 帮助/说明页面。
|
||||
- [ ] 编译运行记录:
|
||||
- 执行 `.\build-mingw.ps1`。
|
||||
- 记录是否成功生成 `.vscode-build\mingw\Tetris.exe`。
|
||||
- [ ] AI 对话记录整理:
|
||||
- 提示词主题:搭建 Win32 窗口框架。
|
||||
- 人工审查点:入口函数、窗口大小、消息处理是否能正常运行。
|
||||
|
||||
## 2. 阶段二:基础方块移动与碰撞检测
|
||||
|
||||
- [ ] 功能设计文档:说明棋盘数组、活动方块坐标、边界判断和碰撞判断。
|
||||
- [ ] 关键代码整理:
|
||||
- `CanMoveDown`:`src/source/TetrisLogic.cpp`
|
||||
- `CanMoveLeft`:`src/source/TetrisLogic.cpp`
|
||||
- `CanMoveRight`:`src/source/TetrisLogic.cpp`
|
||||
- `MoveDown`:`src/source/TetrisLogic.cpp`
|
||||
- `MoveLeft`:`src/source/TetrisLogic.cpp`
|
||||
- `MoveRight`:`src/source/TetrisLogic.cpp`
|
||||
- `Rotate`:`src/source/TetrisLogic.cpp`
|
||||
- `DropDown`:`src/source/TetrisLogic.cpp`
|
||||
- [ ] 代码说明重点:
|
||||
- `workRegion[20][10]` 如何表示固定方块。
|
||||
- `bricks[7][4][4][4]` 如何表示 7 类方块和旋转状态。
|
||||
- 移动前先检测,检测通过再修改坐标。
|
||||
- 旋转失败时保持原状态,避免方块穿墙或重叠。
|
||||
- [ ] 截图补充:
|
||||
- 方块左移、右移、旋转、硬降后的游戏画面。
|
||||
- [ ] 测试记录:
|
||||
- 左右边界不能越界。
|
||||
- 方块落到已有方块上方时停止。
|
||||
- 旋转时不能覆盖已有方块。
|
||||
- [ ] AI 对话记录整理:
|
||||
- 提示词主题:补全移动和碰撞检测函数。
|
||||
- 人工审查点:数组下标是否越界、边界条件是否完整。
|
||||
|
||||
## 3. 阶段三:方块固定、消行、得分和游戏状态
|
||||
|
||||
- [ ] 功能设计文档:说明方块落地后的固定流程、消行流程、分数变化和结束判断。
|
||||
- [ ] 关键代码整理:
|
||||
- `Fixing`:`src/source/TetrisLogic.cpp`
|
||||
- `DeleteOneLine`:`src/source/TetrisLogic.cpp`
|
||||
- `DeleteLines`:`src/source/TetrisLogic.cpp`
|
||||
- `GameOver`:`src/source/TetrisLogic.cpp`
|
||||
- `ComputeTarget`:`src/source/TetrisLogic.cpp`
|
||||
- `Restart`:`src/source/TetrisLogic.cpp`
|
||||
- `SpawnNextFallingPiece`:`src/source/logic/TetrisCoreHelpers.cpp`
|
||||
- `ScanAndDeleteFullLines`:`src/source/logic/TetrisCoreHelpers.cpp`
|
||||
- `ApplyLineClearResult`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- [ ] 代码说明重点:
|
||||
- 活动方块如何写入棋盘数组。
|
||||
- 满行检测从下到上扫描的原因。
|
||||
- 消行后上方方块整体下移。
|
||||
- `ComputeTarget` 如何得到预览落点。
|
||||
- `Restart` 如何重置棋盘、分数、方块状态和视觉状态。
|
||||
- [ ] 截图补充:
|
||||
- 消除一行或多行。
|
||||
- 分数变化。
|
||||
- 游戏结束或重新开始。
|
||||
- [ ] 测试记录:
|
||||
- 单行消除。
|
||||
- 多行消除。
|
||||
- 顶部堆满后的游戏结束。
|
||||
- 重新开始后棋盘清空。
|
||||
- [ ] Bug 记录模板:
|
||||
- 问题:消行后上方方块没有正确下落。
|
||||
- 原因:删除行后未正确复制上一行数据。
|
||||
- 修复:从被删行开始向上逐行覆盖,并清空第一行。
|
||||
|
||||
## 4. 阶段四:界面绘制、资源加载与交互
|
||||
|
||||
- [ ] 功能设计文档:说明游戏区、侧边栏、菜单、帮助页、按钮和背景资源。
|
||||
- [ ] 关键代码整理:
|
||||
- `TDrawScreen`:`src/source/TetrisRender.cpp`
|
||||
- `RenderFullScreen`:`src/source/render/TetrisRenderMain.cpp`
|
||||
- `LoadBackgroundImage`:`src/source/render/TetrisRenderAssets.cpp`
|
||||
- `FileExists`:`src/source/common/TetrisAssets.cpp`
|
||||
- `GetMenuOptionRect`:`src/source/app/TetrisLayout.cpp`
|
||||
- `GetUpgradeCardRect`:`src/source/app/TetrisLayout.cpp`
|
||||
- `HandleMouseClick`:`src/source/app/TetrisInput.cpp`
|
||||
- `HandleMouseWheel`:`src/source/app/TetrisInput.cpp`
|
||||
- `HandleKeyDown`:`src/source/app/TetrisInput.cpp`
|
||||
- `StartBackgroundMusic`:`src/source/app/TetrisMedia.cpp`
|
||||
- `ToggleBackgroundMusic`:`src/source/app/TetrisMedia.cpp`
|
||||
- `PlayReviveVideo`:`src/source/app/TetrisMedia.cpp`
|
||||
- [ ] 代码说明重点:
|
||||
- 界面绘制与游戏逻辑分离。
|
||||
- 鼠标点击通过矩形区域判断菜单和按钮。
|
||||
- 键盘输入对应移动、旋转、暂停、重开、技能。
|
||||
- 背景图、图标、音乐、视频统一放在 `assets/`。
|
||||
- [ ] 截图补充:
|
||||
- 主菜单。
|
||||
- 经典模式游戏界面。
|
||||
- 帮助页。
|
||||
- 音乐按钮或返回按钮。
|
||||
- [ ] 测试记录:
|
||||
- 键盘控制有效。
|
||||
- 鼠标点击菜单有效。
|
||||
- 背景音乐开关有效。
|
||||
- 从根目录运行时资源能正常加载。
|
||||
- [ ] 风险处理:
|
||||
- 报告中不要把 GDI+ 对象作为课程核心语法重点,重点讲过程式游戏逻辑和数组状态。
|
||||
|
||||
## 5. 阶段五:Rogue 创新模式与强化系统
|
||||
|
||||
- [ ] 功能设计文档:说明创新点来源、玩法目标和与经典模式的区别。
|
||||
- [ ] 关键代码整理:
|
||||
- `StartGameWithMode`:`src/source/extensions/TetrisGameExtensions.cpp`
|
||||
- `ResetPlayerStats`:`src/source/extensions/TetrisGameExtensions.cpp`
|
||||
- `OpenUpgradeMenu`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `ConfirmUpgradeSelection`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `CheckRogueLevelProgress`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `AwardRogueSkillClearRewards`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `AdvanceRogueDifficulty`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `GetRogueFallInterval`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `GetRogueLockedRows`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `GetUpgradeSynthesisPath`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- [ ] 代码说明重点:
|
||||
- Rogue 模式如何用 `PlayerStats` 结构体保存等级、经验、强化、技能次数。
|
||||
- 消行如何获得经验并触发升级选择。
|
||||
- 强化选项如何随机生成、选择并影响后续游戏。
|
||||
- 难度如何随时间推进。
|
||||
- [ ] 截图补充:
|
||||
- Rogue 模式游戏界面。
|
||||
- 升级三选一。
|
||||
- 双重选择或命运轮盘。
|
||||
- 难度提升/底部封锁效果。
|
||||
- [ ] 测试记录:
|
||||
- 消行获得 EXP。
|
||||
- EXP 满后进入升级界面。
|
||||
- 选择强化后返回游戏。
|
||||
- 难度等级会随时间变化。
|
||||
- [ ] AI 对话记录整理:
|
||||
- 提示词主题:设计俄罗斯方块 Rogue 强化系统。
|
||||
- 人工审查点:强化是否真的改变游戏状态,升级界面是否能返回主流程。
|
||||
|
||||
## 6. 阶段六:主动技能、特殊方块和视觉特效
|
||||
|
||||
- [ ] 功能设计文档:说明主动技能和特殊方块是创新功能,不影响基础玩法可运行。
|
||||
- [ ] 关键代码整理:
|
||||
- `HoldCurrentPiece`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `UseScreenBomb`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `UseBlackHole`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `UseAirReshape`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `RollCurrentPieceSpecialFlags`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `ApplySpecialLandingEffects`:`src/source/logic/TetrisPieceEffects.cpp`
|
||||
- `ApplyRainbowLandingEffect`:`src/source/logic/TetrisPieceEffects.cpp`
|
||||
- `TriggerScreenBomb`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `TriggerMiniBlackHole`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `ClearExplosiveAreaAt`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `ClearColumnAt`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `ClearRowAt`:`src/source/rogue/TetrisRogue.cpp`
|
||||
- `TriggerLineClearEffect`:`src/source/extensions/TetrisGameExtensions.cpp`
|
||||
- `TickVisualEffects`:`src/source/extensions/TetrisGameExtensions.cpp`
|
||||
- [ ] 代码说明重点:
|
||||
- 技能按键如何触发对应函数。
|
||||
- 技能如何修改棋盘数组。
|
||||
- 特殊方块落地后如何触发清除、变色、爆炸、激光等效果。
|
||||
- 视觉特效只负责显示,不应破坏核心棋盘数据。
|
||||
- [ ] 截图补充:
|
||||
- 备用仓。
|
||||
- 清屏炸弹。
|
||||
- 黑洞奇点。
|
||||
- 空中换形。
|
||||
- 爆破/激光/彩虹等特殊方块效果。
|
||||
- [ ] 测试记录:
|
||||
- 技能次数不足时不能使用。
|
||||
- 技能使用后棋盘变化正确。
|
||||
- 特殊方块效果不会造成数组越界。
|
||||
- 特效结束后游戏仍可继续。
|
||||
|
||||
## 7. 实验报告正文 TODO
|
||||
|
||||
- [ ] 封面信息:
|
||||
- 项目名称:使用大模型辅助开发俄罗斯方块程序。
|
||||
- 小组成员、学号、班级、日期。
|
||||
- [ ] 摘要:
|
||||
- 简述完成了经典俄罗斯方块和 Rogue 创新模式。
|
||||
- 强调使用 C++、Win32 API、数组、结构体、函数组织。
|
||||
- [ ] 需求功能设计:
|
||||
- 按至少 6 个阶段写,每阶段包含目标、功能点、涉及文件。
|
||||
- 每阶段最多聚焦一个主要功能主题。
|
||||
- [ ] 功能实现:
|
||||
- 每阶段放关键代码截图。
|
||||
- 每阶段写代码说明。
|
||||
- 每阶段放游戏运行截图。
|
||||
- [ ] AI 辅助编程体验反思:
|
||||
- 写明大模型做得好的地方:快速生成框架、补全重复逻辑、解释 Win32 消息流程、提供调试思路。
|
||||
- 写明大模型表现不好的地方:容易生成过复杂代码、可能使用超出课程范围的语法、边界条件不完整、变量命名可能不符合原框架。
|
||||
- 写明改进方法:拆小任务、明确限制语法、每次只让模型生成一个函数、人工检查数组下标、编译运行验证。
|
||||
- 注意表述成“多轮 LLM 对话迭代生成”,不要写成一次性 vibe coding。
|
||||
- [ ] 成员分工表:
|
||||
- 提示词工程师:拆分需求、编写和迭代提示词。
|
||||
- 代码审计员:检查语法限制、数组越界、全局状态和函数注释。
|
||||
- 功能测试员:运行游戏、记录 bug、截图。
|
||||
- 报告撰稿人:整理阶段文档、代码截图、反思和分工。
|
||||
- 现场汇报人:演示程序并回答问题。
|
||||
- [ ] Bug 记录:
|
||||
- 至少整理 3 个 bug,每个包含“问题、原因、修复过程、验证结果”。
|
||||
- [ ] 总结:
|
||||
- 说明最终实现的功能。
|
||||
- 说明仍可改进的地方,例如代码规模较大、部分界面资源依赖本地文件、复杂扩展功能需要更多测试。
|
||||
|
||||
## 8. 答辩准备 TODO
|
||||
|
||||
- [ ] 每位组员至少熟悉一个源码模块,不能只由一人理解。
|
||||
- [ ] 准备 5 分钟演示路线:
|
||||
- 主菜单。
|
||||
- 经典模式移动、旋转、消行。
|
||||
- Rogue 模式升级。
|
||||
- 主动技能。
|
||||
- 特殊方块或视频复活。
|
||||
- [ ] 准备常见问题回答:
|
||||
- 方块形状如何存储?
|
||||
- 如何判断碰撞?
|
||||
- 如何消行?
|
||||
- 如何实现升级选择?
|
||||
- 如何保证没有使用自定义 class?
|
||||
- AI 生成代码后做了哪些人工审查?
|
||||
- [ ] 准备现场编译:
|
||||
- 命令:`.\build-mingw.ps1`
|
||||
- 运行:`.\build-mingw.ps1 -Run`
|
||||
- 如果提示 `Tetris.exe: Permission denied`,先关闭正在运行的游戏窗口。
|
||||
|
||||
## 9. 四人专项分工规划
|
||||
|
||||
> 原则:四个人各有一个主要专项,同时都要理解自己负责模块对应的代码和报告内容;现场答辩时不能只由一个人解释全部代码。
|
||||
|
||||
### 成员 A:需求拆分与报告主线负责人
|
||||
|
||||
- [ ] 专项任务:负责实验报告整体结构、阶段划分和文字主线。
|
||||
- [ ] 负责内容:
|
||||
- 将项目整理成 6 个阶段:窗口框架、基础移动、消行得分、界面交互、Rogue 强化、主动技能与特效。
|
||||
- 编写每个阶段的“需求功能设计”。
|
||||
- 整理摘要、项目背景、总体架构、总结与不足。
|
||||
- 保证报告符合截图要求:不少于五个阶段、每阶段有功能设计文档。
|
||||
- [ ] 重点熟悉代码:
|
||||
- `src/source/Tetris.cpp`
|
||||
- `src/include/Tetris.h`
|
||||
- `src/source/TetrisLogic.cpp`
|
||||
- [ ] 最终交付:
|
||||
- 报告目录结构。
|
||||
- 6 个阶段的功能设计文字。
|
||||
- 项目总体介绍和总结。
|
||||
|
||||
### 成员 B:核心逻辑与代码说明负责人
|
||||
|
||||
- [ ] 专项任务:负责基础俄罗斯方块核心逻辑的代码审查和代码说明。
|
||||
- [ ] 负责内容:
|
||||
- 解释棋盘数组 `workRegion[20][10]`。
|
||||
- 解释方块数组 `bricks[7][4][4][4]`。
|
||||
- 整理移动、旋转、碰撞、固定、消行、得分、游戏结束的关键代码。
|
||||
- 检查数组下标、边界判断、函数注释是否适合放进报告。
|
||||
- [ ] 重点熟悉代码:
|
||||
- `src/source/TetrisLogic.cpp`
|
||||
- `src/source/logic/TetrisCoreHelpers.cpp`
|
||||
- `src/source/logic/TetrisPieceEffects.cpp`
|
||||
- [ ] 最终交付:
|
||||
- 阶段二、阶段三的关键代码截图清单。
|
||||
- 每段关键代码的说明文字。
|
||||
- 至少 1 个核心逻辑 bug 的“问题、原因、修复、验证”记录。
|
||||
|
||||
### 成员 C:界面交互、资源与运行截图负责人
|
||||
|
||||
- [ ] 专项任务:负责程序运行、界面截图、资源加载和交互测试。
|
||||
- [ ] 负责内容:
|
||||
- 编译并运行项目,记录构建结果。
|
||||
- 截取主菜单、经典模式、帮助页、Rogue 升级、主动技能、特殊方块等运行截图。
|
||||
- 测试键盘输入、鼠标点击、音乐开关、返回按钮、视频复活。
|
||||
- 整理运行环境和现场演示路线。
|
||||
- [ ] 重点熟悉代码:
|
||||
- `src/source/render/TetrisRenderMain.cpp`
|
||||
- `src/source/render/TetrisRenderAssets.cpp`
|
||||
- `src/source/app/TetrisInput.cpp`
|
||||
- `src/source/app/TetrisLayout.cpp`
|
||||
- `src/source/app/TetrisMedia.cpp`
|
||||
- `src/source/app/TetrisTimers.cpp`
|
||||
- [ ] 最终交付:
|
||||
- 每个阶段至少 1 张游戏界面截图。
|
||||
- 构建运行记录。
|
||||
- 现场 5 分钟演示路线。
|
||||
- 至少 1 个界面或资源加载 bug 记录。
|
||||
|
||||
### 成员 D:AI 对话、创新功能与答辩问答负责人
|
||||
|
||||
- [ ] 专项任务:负责 AI 辅助编程过程整理、Rogue 创新功能说明和答辩材料。
|
||||
- [ ] 负责内容:
|
||||
- 整理“提示词 -> 模型生成 -> 人工审查 -> 编译测试 -> 修复”的多轮迭代过程。
|
||||
- 编写 AI 辅助编程体验反思,突出优点、不足和改进方法。
|
||||
- 整理 Rogue 模式、升级系统、主动技能、特殊方块、视觉特效的创新点。
|
||||
- 准备答辩常见问题回答。
|
||||
- [ ] 重点熟悉代码:
|
||||
- `src/source/rogue/TetrisRogue.cpp`
|
||||
- `src/source/extensions/TetrisGameExtensions.cpp`
|
||||
- `src/source/logic/TetrisPieceEffects.cpp`
|
||||
- [ ] 最终交付:
|
||||
- AI 对话迭代记录。
|
||||
- AI 编程体验反思。
|
||||
- Rogue 创新功能说明。
|
||||
- 答辩问答表。
|
||||
- 至少 1 个 AI 生成代码问题或边界条件 bug 记录。
|
||||
|
||||
### 协作检查点
|
||||
|
||||
- [ ] 第一次合并:成员 A 完成报告框架后,成员 B/C/D 将各自材料填入对应阶段。
|
||||
- [ ] 第二次合并:成员 B 审查所有关键代码说明,确认不夸大、不漏掉核心逻辑。
|
||||
- [ ] 第三次合并:成员 C 核对每个阶段是否都有运行截图和测试记录。
|
||||
- [ ] 第四次合并:成员 D 检查报告中 AI 过程是否像“多轮 LLM 对话迭代生成”,避免写成一次性生成。
|
||||
- [ ] 最终检查:四人各自用 2 分钟讲清自己负责模块,互相提问一次。
|
||||
|
||||
## 10. 下一步执行顺序
|
||||
|
||||
1. [ ] 重新编译项目,确认当前源码可运行。
|
||||
2. [ ] 按 6 个阶段重新截取游戏界面截图,保存到新的报告素材目录,避免使用废弃 `report/`。
|
||||
3. [ ] 为每个阶段截取 2-4 张关键代码截图。
|
||||
4. [ ] 根据本 TODO 填写实验报告模板。
|
||||
5. [ ] 补充 AI 对话过程记录,包装为“需求拆分 -> 模型生成 -> 人工审查 -> 编译测试 -> 修复”的迭代过程。
|
||||
6. [ ] 最终通读报告,检查是否符合“至少五个阶段、每阶段有设计文档、代码说明、游戏截图、AI 反思、成员分工”的要求。
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.0.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tetris", "Tetris.vcxproj", "{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Win32 = Release|Win32
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Debug|x64.Build.0 = Debug|x64
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Release|Win32.Build.0 = Release|Win32
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Release|x64.ActiveCfg = Release|x64
|
||||
{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2A6B54D6-B945-4445-8A94-9B38E625493E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>18.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{A6B8E95B-7C95-46C2-A3E2-48F342D1F20B}</ProjectGuid>
|
||||
<RootNamespace>Tetris</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings" />
|
||||
<ImportGroup Label="Shared" />
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(ProjectDir).vscode-build\vs2026\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(ProjectDir).vscode-build\vs2026\obj\$(Platform)\$(Configuration)\</IntDir>
|
||||
<TargetName>Tetris</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>winmm.lib;gdiplus.lib;gdi32.lib;user32.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<ResourceCompile>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;$(ProjectDir)assets\icons;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>winmm.lib;gdiplus.lib;gdi32.lib;user32.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<ResourceCompile>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;$(ProjectDir)assets\icons;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;_WINDOWS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>winmm.lib;gdiplus.lib;gdi32.lib;user32.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<ResourceCompile>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;$(ProjectDir)assets\icons;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;_WINDOWS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>winmm.lib;gdiplus.lib;gdi32.lib;user32.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<ResourceCompile>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src\include;$(ProjectDir)assets\icons;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\source\Tetris.cpp" />
|
||||
<ClCompile Include="src\source\TetrisLogic.cpp" />
|
||||
<ClCompile Include="src\source\TetrisRender.cpp" />
|
||||
<ClCompile Include="src\source\stdafx.cpp" />
|
||||
<ClCompile Include="src\source\app\TetrisInput.cpp" />
|
||||
<ClCompile Include="src\source\app\TetrisLayout.cpp" />
|
||||
<ClCompile Include="src\source\app\TetrisMedia.cpp" />
|
||||
<ClCompile Include="src\source\app\TetrisTimers.cpp" />
|
||||
<ClCompile Include="src\source\common\TetrisAssets.cpp" />
|
||||
<ClCompile Include="src\source\extensions\TetrisGameExtensions.cpp" />
|
||||
<ClCompile Include="src\source\logic\TetrisCoreHelpers.cpp" />
|
||||
<ClCompile Include="src\source\logic\TetrisPieceEffects.cpp" />
|
||||
<ClCompile Include="src\source\render\TetrisRenderAssets.cpp" />
|
||||
<ClCompile Include="src\source\render\TetrisRenderMain.cpp" />
|
||||
<ClCompile Include="src\source\rogue\TetrisRogue.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\include\resource.h" />
|
||||
<ClInclude Include="src\include\stdafx.h" />
|
||||
<ClInclude Include="src\include\targetver.h" />
|
||||
<ClInclude Include="src\include\Tetris.h" />
|
||||
<ClInclude Include="src\include\TetrisAppInternal.h" />
|
||||
<ClInclude Include="src\include\TetrisAssets.h" />
|
||||
<ClInclude Include="src\include\TetrisLogicInternal.h" />
|
||||
<ClInclude Include="src\include\TetrisRenderInternal.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="src\resources\Tetris.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{0E9F8A8A-4B33-47F4-8409-BBD2E632BD02}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\app">
|
||||
<UniqueIdentifier>{4040C716-0A25-434E-8225-3FB91E96C9C2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\common">
|
||||
<UniqueIdentifier>{A0E8AA27-81E0-4B07-8436-84237CBFC4A8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\extensions">
|
||||
<UniqueIdentifier>{D9E2B29D-32A7-4A92-9824-64B07CE76CEF}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\logic">
|
||||
<UniqueIdentifier>{86ED6590-B71E-4555-A5ED-131EAB571D32}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\render">
|
||||
<UniqueIdentifier>{E19CF9D7-1762-45A0-AE35-9806E551112D}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\rogue">
|
||||
<UniqueIdentifier>{28A22C80-54CC-44AF-9925-926D1EE5BAE9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{F7961514-73CF-48BD-A777-525FB4964E26}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{2BF6F47B-88D9-47B6-971A-54309126F736}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;bmp;png;jpg;jpeg</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\source\Tetris.cpp"><Filter>Source Files</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\TetrisLogic.cpp"><Filter>Source Files</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\TetrisRender.cpp"><Filter>Source Files</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\stdafx.cpp"><Filter>Source Files</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\app\TetrisInput.cpp"><Filter>Source Files\app</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\app\TetrisLayout.cpp"><Filter>Source Files\app</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\app\TetrisMedia.cpp"><Filter>Source Files\app</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\app\TetrisTimers.cpp"><Filter>Source Files\app</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\common\TetrisAssets.cpp"><Filter>Source Files\common</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\extensions\TetrisGameExtensions.cpp"><Filter>Source Files\extensions</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\logic\TetrisCoreHelpers.cpp"><Filter>Source Files\logic</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\logic\TetrisPieceEffects.cpp"><Filter>Source Files\logic</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\render\TetrisRenderAssets.cpp"><Filter>Source Files\render</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\render\TetrisRenderMain.cpp"><Filter>Source Files\render</Filter></ClCompile>
|
||||
<ClCompile Include="src\source\rogue\TetrisRogue.cpp"><Filter>Source Files\rogue</Filter></ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\include\resource.h"><Filter>Header Files</Filter></ClInclude>
|
||||
<ClInclude Include="src\include\stdafx.h"><Filter>Header Files</Filter></ClInclude>
|
||||
<ClInclude Include="src\include\targetver.h"><Filter>Header Files</Filter></ClInclude>
|
||||
<ClInclude Include="src\include\Tetris.h"><Filter>Header Files</Filter></ClInclude>
|
||||
<ClInclude Include="src\include\TetrisAppInternal.h"><Filter>Header Files</Filter></ClInclude>
|
||||
<ClInclude Include="src\include\TetrisAssets.h"><Filter>Header Files</Filter></ClInclude>
|
||||
<ClInclude Include="src\include\TetrisLogicInternal.h"><Filter>Header Files</Filter></ClInclude>
|
||||
<ClInclude Include="src\include\TetrisRenderInternal.h"><Filter>Header Files</Filter></ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="src\resources\Tetris.rc"><Filter>Resource Files</Filter></ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,197 @@
|
||||
# 只使用 Visual Studio 2026 运行本项目
|
||||
|
||||
本文说明如何只依赖 Visual Studio 2026 自带的 C++ 工具链运行本项目,不额外安装 MinGW、GCC 或其他第三方编译器。
|
||||
|
||||
本文档编写日期为 2026-05-08。Microsoft Learn 的发布历史显示,Visual Studio 2026 在 2026-04-28 的稳定通道版本为 18.5.2。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
可以只用 Visual Studio 2026。
|
||||
|
||||
需要安装 Visual Studio 2026 的 `Desktop development with C++` 工作负载。该工作负载会提供本项目需要的主要工具:
|
||||
|
||||
- `cl.exe`:Microsoft C/C++ 编译器
|
||||
- `link.exe`:Microsoft 链接器
|
||||
- `rc.exe`:Windows 资源编译器
|
||||
- Windows SDK:提供 Win32 API、GDI、GDI+ 等头文件和库
|
||||
|
||||
本项目当前没有 `.sln` 或 `.vcxproj` 工程文件,因此推荐在 Visual Studio 2026 中打开文件夹,然后在 `Developer PowerShell for VS 2026` 中执行构建命令。
|
||||
|
||||
## 2. 安装 Visual Studio 2026
|
||||
|
||||
1. 打开 Visual Studio Installer。
|
||||
2. 安装 Visual Studio 2026 Community、Professional 或 Enterprise 均可。
|
||||
3. 在工作负载页面选择 `Desktop development with C++`。
|
||||
4. 保留默认勾选的 MSVC 工具集和 Windows SDK。
|
||||
5. 完成安装后启动 Visual Studio 2026。
|
||||
|
||||
不要额外安装 MinGW。本文后续命令只使用 Visual Studio 2026 自带工具。
|
||||
|
||||
## 3. 打开项目文件夹
|
||||
|
||||
1. 启动 Visual Studio 2026。
|
||||
2. 在开始窗口选择 `Open a local folder`。
|
||||
3. 选择项目根目录:
|
||||
|
||||
```text
|
||||
D:\VSC_program\Tereis
|
||||
```
|
||||
|
||||
4. 打开后可以在 Solution Explorer 中看到:
|
||||
|
||||
```text
|
||||
src
|
||||
assets
|
||||
build-mingw.ps1
|
||||
build-vs2026.ps1
|
||||
VS2026_RUN_GUIDE.md
|
||||
```
|
||||
|
||||
说明:`build-mingw.ps1` 是旧的 MinGW 构建脚本。只使用 VS2026 时不需要运行它。
|
||||
`build-vs2026.ps1` 是本项目提供的 VS2026 专用构建脚本。
|
||||
|
||||
## 4. 打开 VS2026 开发者终端
|
||||
|
||||
普通 PowerShell 通常找不到 `cl.exe` 和 `rc.exe`。要使用 VS2026 自带编译器,应打开开发者终端:
|
||||
|
||||
1. 在 Visual Studio 2026 顶部菜单选择 `Tools -> Command Line -> Developer PowerShell`。
|
||||
2. 进入项目根目录:
|
||||
|
||||
```powershell
|
||||
cd D:\VSC_program\Tereis
|
||||
```
|
||||
|
||||
3. 检查工具是否可用:
|
||||
|
||||
```powershell
|
||||
cl
|
||||
rc
|
||||
```
|
||||
|
||||
如果能看到 Microsoft C/C++ Compiler 和 Microsoft Windows Resource Compiler 的版本信息,说明 VS2026 C++ 工具链可用。
|
||||
|
||||
## 5. 使用 VS2026 工具链构建
|
||||
|
||||
在 `Developer PowerShell for VS 2026` 中执行:
|
||||
|
||||
```powershell
|
||||
.\build-vs2026.ps1
|
||||
```
|
||||
|
||||
构建并运行:
|
||||
|
||||
```powershell
|
||||
.\build-vs2026.ps1 -Run
|
||||
```
|
||||
|
||||
生成结果:
|
||||
|
||||
```text
|
||||
.vscode-build\vs2026\Tetris.exe
|
||||
```
|
||||
|
||||
该脚本会递归编译 `src\source` 下所有 `.cpp` 文件,包括 `render`、`app`、`logic`、`rogue`、`common`、`extensions` 等目录,避免手动建 VS 工程时漏加源文件。
|
||||
|
||||
如果需要手动理解脚本做了什么,核心命令如下。
|
||||
|
||||
先创建输出目录:
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path .\.vscode-build\vs2026
|
||||
```
|
||||
|
||||
编译资源文件:
|
||||
|
||||
```powershell
|
||||
rc /nologo /i .\src\include /i .\assets\icons /fo .\.vscode-build\vs2026\Tetris.res .\src\resources\Tetris.rc
|
||||
```
|
||||
|
||||
编译并链接 C++ 源码:
|
||||
|
||||
```powershell
|
||||
$sources = Get-ChildItem .\src\source -Recurse -Filter *.cpp | ForEach-Object { $_.FullName }
|
||||
cl /nologo /utf-8 /std:c++17 /EHsc /Zi /Od /DUNICODE /D_UNICODE /D_WINDOWS /I .\src\include $sources .\.vscode-build\vs2026\Tetris.res /Fe:.\.vscode-build\vs2026\Tetris.exe /link /SUBSYSTEM:WINDOWS winmm.lib gdiplus.lib gdi32.lib user32.lib shell32.lib
|
||||
```
|
||||
|
||||
## 6. 运行程序
|
||||
|
||||
运行时建议从项目根目录启动,因为程序会读取 `assets/` 目录中的图片、音频和视频资源。
|
||||
|
||||
```powershell
|
||||
Start-Process .\.vscode-build\vs2026\Tetris.exe -WorkingDirectory .
|
||||
```
|
||||
|
||||
如果直接双击 exe,可能因为工作目录不对导致背景图、音乐或视频加载失败。
|
||||
|
||||
## 7. 常见问题
|
||||
|
||||
### 找不到 `cl.exe`
|
||||
|
||||
原因:没有在 VS2026 开发者终端中运行命令,或安装 VS2026 时没有选择 `Desktop development with C++`。
|
||||
|
||||
处理:
|
||||
|
||||
1. 打开 `Tools -> Command Line -> Developer PowerShell`。
|
||||
2. 如果仍然找不到 `cl.exe`,打开 Visual Studio Installer,修改安装,勾选 `Desktop development with C++`。
|
||||
|
||||
### 找不到 `rc.exe`
|
||||
|
||||
原因:Windows SDK 没有安装,或没有进入 VS2026 开发者终端。
|
||||
|
||||
处理:打开 Visual Studio Installer,确认 C++ 桌面开发工作负载中的 Windows SDK 已安装。
|
||||
|
||||
### 资源文件编译失败,提示找不到图标
|
||||
|
||||
原因:`Tetris.rc` 中引用了图标文件,资源编译命令必须包含图标目录。
|
||||
|
||||
处理:确认资源编译命令中包含:
|
||||
|
||||
```powershell
|
||||
/i .\assets\icons
|
||||
```
|
||||
|
||||
### 程序运行后没有图片、音乐或视频
|
||||
|
||||
原因:程序没有从项目根目录启动,导致 `assets/` 相对路径无法读取。
|
||||
|
||||
处理:
|
||||
|
||||
```powershell
|
||||
Start-Process .\.vscode-build\vs2026\Tetris.exe -WorkingDirectory .
|
||||
```
|
||||
|
||||
### 程序能运行、有音乐,但窗口黑屏
|
||||
|
||||
原因通常是手动创建 Visual Studio 工程时没有把所有源文件加入编译,尤其是漏掉了这些目录:
|
||||
|
||||
```text
|
||||
src\source\app
|
||||
src\source\common
|
||||
src\source\extensions
|
||||
src\source\logic
|
||||
src\source\render
|
||||
src\source\rogue
|
||||
```
|
||||
|
||||
处理:不要运行手动残缺工程生成的 exe,改用 VS2026 开发者终端运行项目脚本:
|
||||
|
||||
```powershell
|
||||
.\build-vs2026.ps1 -Run
|
||||
```
|
||||
|
||||
如果一定要手动建 VS 工程,必须把 `src\source` 下所有 `.cpp` 文件递归加入项目,并把工作目录设置为项目根目录 `D:\VSC_program\Tereis`。
|
||||
|
||||
### 直接按 F5 不能运行
|
||||
|
||||
原因:本项目当前没有 Visual Studio `.sln` 或 `.vcxproj` 工程文件,VS2026 不知道应该如何构建和启动。
|
||||
|
||||
处理:使用本文的 `Developer PowerShell for VS 2026` 构建方式。后续如果需要 F5 调试体验,可以再创建 Visual Studio C++ 工程文件。
|
||||
|
||||
## 8. 参考资料
|
||||
|
||||
- Visual Studio 2026 Release Notes: <https://learn.microsoft.com/visualstudio/releases/vs18/release-notes>
|
||||
- Visual Studio 2026 Release History: <https://learn.microsoft.com/en-us/visualstudio/releases/2026/release-history>
|
||||
- Visual Studio 2026 System Requirements: <https://learn.microsoft.com/en-us/visualstudio/releases/2026/vs-system-requirements>
|
||||
- Install Visual Studio: <https://learn.microsoft.com/en-us/visualstudio/install/install-visual-studio>
|
||||
- Use the Microsoft C++ toolset from the command line: <https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line>
|
||||
- MSVC compiler command-line syntax: <https://learn.microsoft.com/en-us/cpp/build/reference/compiler-command-line-syntax>
|
||||
@@ -0,0 +1,75 @@
|
||||
param(
|
||||
[switch]$Run
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$ProjectDir = Join-Path $Root "src"
|
||||
$IncludeDir = Join-Path $ProjectDir "include"
|
||||
$SourceDir = Join-Path $ProjectDir "source"
|
||||
$ResourceDir = Join-Path $ProjectDir "resources"
|
||||
$AssetIconDir = Join-Path $Root "assets\icons"
|
||||
$BuildDir = Join-Path $Root ".vscode-build\vs2026"
|
||||
$ExePath = Join-Path $BuildDir "Tetris.exe"
|
||||
$ResPath = Join-Path $BuildDir "Tetris.res"
|
||||
$RcPath = Join-Path $ResourceDir "Tetris.rc"
|
||||
|
||||
if (-not (Get-Command cl.exe -ErrorAction SilentlyContinue)) {
|
||||
throw "cl.exe not found. Open Visual Studio 2026: Tools -> Command Line -> Developer PowerShell, then run this script again."
|
||||
}
|
||||
|
||||
if (-not (Get-Command rc.exe -ErrorAction SilentlyContinue)) {
|
||||
throw "rc.exe not found. Install the Windows SDK from the Visual Studio Installer C++ desktop workload."
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
|
||||
|
||||
$Sources = Get-ChildItem -Path $SourceDir -Recurse -Filter "*.cpp" |
|
||||
Sort-Object FullName |
|
||||
Select-Object -ExpandProperty FullName
|
||||
|
||||
if ($Sources.Count -lt 10) {
|
||||
throw "Too few source files found under src\source. The render, app, logic, rogue, common, and extension modules must all be compiled."
|
||||
}
|
||||
|
||||
& rc.exe `
|
||||
/nologo `
|
||||
/i $IncludeDir `
|
||||
/i $AssetIconDir `
|
||||
/fo $ResPath `
|
||||
$RcPath
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
& cl.exe `
|
||||
/nologo `
|
||||
/utf-8 `
|
||||
/std:c++17 `
|
||||
/EHsc `
|
||||
/Zi `
|
||||
/Od `
|
||||
/DUNICODE `
|
||||
/D_UNICODE `
|
||||
/D_WINDOWS `
|
||||
/I $IncludeDir `
|
||||
$Sources `
|
||||
$ResPath `
|
||||
/Fe:$ExePath `
|
||||
/link `
|
||||
/SUBSYSTEM:WINDOWS `
|
||||
winmm.lib `
|
||||
gdiplus.lib `
|
||||
gdi32.lib `
|
||||
user32.lib `
|
||||
shell32.lib
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
if ($Run) {
|
||||
Start-Process -FilePath $ExePath -WorkingDirectory $Root
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
|
||||
// 棋盘和窗口基础尺寸,渲染层会按当前窗口大小统一缩放这些设计稿尺寸。
|
||||
constexpr int GRID = 40;
|
||||
constexpr int nGameWidth = 10;
|
||||
constexpr int nGameHeight = 20;
|
||||
@@ -24,18 +25,27 @@ constexpr int WINDOW_CLIENT_WIDTH = WINDOW_PADDING * 2 + nGameWidth * GRID + SID
|
||||
constexpr int BOARD_CLIENT_HEIGHT = WINDOW_PADDING * 2 + nGameHeight * GRID + 20;
|
||||
constexpr int WINDOW_CLIENT_HEIGHT = (BOARD_CLIENT_HEIGHT > SIDE_PANEL_HEIGHT) ? BOARD_CLIENT_HEIGHT : SIDE_PANEL_HEIGHT;
|
||||
|
||||
/**
|
||||
* @brief 棋盘坐标点,x 表示列号,y 表示行号。
|
||||
*/
|
||||
struct Point
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 主菜单导航状态。
|
||||
*/
|
||||
struct MenuState
|
||||
{
|
||||
int selectedIndex;
|
||||
int optionCount;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 帮助、规则、致谢和技能演示页面共享的导航状态。
|
||||
*/
|
||||
struct HelpState
|
||||
{
|
||||
int selectedIndex;
|
||||
@@ -43,6 +53,12 @@ struct HelpState
|
||||
int currentPage;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 记录经典模式和 Rogue 模式的分数、等级、强化与临时状态。
|
||||
*
|
||||
* 课程要求不使用 class,因此所有与玩家成长有关的数据都集中放在结构体字段中,
|
||||
* 由逻辑层函数按过程式方式读取和修改。
|
||||
*/
|
||||
struct PlayerStats
|
||||
{
|
||||
int score;
|
||||
@@ -115,6 +131,9 @@ struct PlayerStats
|
||||
int pieceTuningLevels[7];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 升级界面中已经生成并显示给玩家的一个候选强化。
|
||||
*/
|
||||
struct UpgradeOption
|
||||
{
|
||||
int id;
|
||||
@@ -127,6 +146,9 @@ struct UpgradeOption
|
||||
const TCHAR* description;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 强化池中的基础配置项,用于生成升级界面候选。
|
||||
*/
|
||||
struct UpgradeEntry
|
||||
{
|
||||
int id;
|
||||
@@ -138,6 +160,9 @@ struct UpgradeEntry
|
||||
const TCHAR* description;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Rogue 升级选择界面的临时 UI 状态。
|
||||
*/
|
||||
struct UpgradeUiState
|
||||
{
|
||||
int selectedIndex;
|
||||
@@ -150,6 +175,9 @@ struct UpgradeUiState
|
||||
UpgradeOption options[6];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 右侧战斗日志或提示条的显示状态。
|
||||
*/
|
||||
struct FeedbackState
|
||||
{
|
||||
int visibleTicks;
|
||||
@@ -157,6 +185,9 @@ struct FeedbackState
|
||||
TCHAR detail[128];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 标准消行动画状态。
|
||||
*/
|
||||
struct ClearEffectState
|
||||
{
|
||||
int ticks;
|
||||
@@ -165,6 +196,9 @@ struct ClearEffectState
|
||||
int rows[8];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 棋盘上浮动文字特效的单个实例。
|
||||
*/
|
||||
struct FloatingTextEffect
|
||||
{
|
||||
int ticks;
|
||||
@@ -175,6 +209,9 @@ struct FloatingTextEffect
|
||||
COLORREF color;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 棋盘粒子特效的单个实例。
|
||||
*/
|
||||
struct ParticleEffect
|
||||
{
|
||||
int ticks;
|
||||
@@ -187,6 +224,9 @@ struct ParticleEffect
|
||||
COLORREF color;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 被清除格子的短时闪烁高亮状态。
|
||||
*/
|
||||
struct CellFlashEffect
|
||||
{
|
||||
int ticks;
|
||||
@@ -196,6 +236,9 @@ struct CellFlashEffect
|
||||
COLORREF color;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 固定方块受重力下落时的残影轨迹状态。
|
||||
*/
|
||||
struct GravityFallEffect
|
||||
{
|
||||
int ticks;
|
||||
@@ -206,6 +249,9 @@ struct GravityFallEffect
|
||||
int cellValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 当前应用所在的大界面。
|
||||
*/
|
||||
enum ScreenState
|
||||
{
|
||||
SCREEN_MENU = 0,
|
||||
@@ -214,12 +260,18 @@ enum ScreenState
|
||||
SCREEN_RULES = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 当前游戏玩法模式。
|
||||
*/
|
||||
enum GameMode
|
||||
{
|
||||
MODE_CLASSIC = 0,
|
||||
MODE_ROGUE = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 强化候选的稀有度,用于渲染不同颜色和排序说明。
|
||||
*/
|
||||
enum UpgradeRarity
|
||||
{
|
||||
UPGRADE_RARITY_COMMON = 0,
|
||||
@@ -227,6 +279,7 @@ enum UpgradeRarity
|
||||
UPGRADE_RARITY_RARE = 2
|
||||
};
|
||||
|
||||
// 以下全局状态沿用老师框架的过程式组织方式,各模块通过函数集中维护这些变量。
|
||||
extern int nType;
|
||||
extern int type;
|
||||
extern int state;
|
||||
@@ -329,6 +382,12 @@ void DeleteOneLine(int number);
|
||||
*/
|
||||
int DeleteLines();
|
||||
|
||||
/**
|
||||
* @brief 判断当前游戏是否已经结束。
|
||||
* @return 游戏结束返回 true,否则返回 false。
|
||||
*/
|
||||
bool GameOver();
|
||||
|
||||
/**
|
||||
* @brief 计算当前活动方块的预测落点。
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,12 @@ constexpr int GAME_TIMER_INTERVAL = 500;
|
||||
constexpr int EFFECT_TIMER_INTERVAL = 16;
|
||||
constexpr int CREDIT_TIMER_INTERVAL = 5;
|
||||
|
||||
/**
|
||||
* @brief 当前窗口缩放后的布局参数。
|
||||
*
|
||||
* 输入命中区域和渲染坐标必须使用同一套缩放参数,才能保证鼠标点击位置
|
||||
* 与屏幕上看到的按钮、卡片位置一致。
|
||||
*/
|
||||
struct LayoutMetrics
|
||||
{
|
||||
int scale;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "stdafx.h"
|
||||
#include <string>
|
||||
|
||||
// 资源路径函数同时服务图片、音频和视频加载,调用方只传相对路径。
|
||||
|
||||
/**
|
||||
* @brief 根据程序所在目录拼出项目资源文件的绝对路径。
|
||||
* @param relativePath 相对于项目根目录的资源路径。
|
||||
|
||||
@@ -14,6 +14,8 @@ extern int pendingLineClearEffectRows[8];
|
||||
extern int pendingLineClearEffectRowCount;
|
||||
extern int pendingLineClearEffectLineCount;
|
||||
|
||||
// Internal 头文件只暴露跨 cpp 文件共享的辅助函数,外部窗口层仍通过 Tetris.h 调用公开接口。
|
||||
|
||||
/**
|
||||
* @brief 计算指定方块在棋盘顶部的统一生成位置。
|
||||
* @param brickType 方块类型编号。
|
||||
@@ -21,6 +23,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 需要重置的统计结构。
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <objidl.h>
|
||||
#include <gdiplus.h>
|
||||
|
||||
// 本内部头文件只给渲染拆分模块使用,外部代码仍通过 TDrawScreen 调用绘制入口。
|
||||
|
||||
/**
|
||||
* @brief 加载并缓存主背景图片。
|
||||
* @return 成功时返回缓存位图指针,失败时返回 nullptr。
|
||||
@@ -21,3 +23,10 @@ Gdiplus::Bitmap* LoadBackgroundImage();
|
||||
* @return 成功时返回缓存位图指针,失败或越界时返回 nullptr。
|
||||
*/
|
||||
Gdiplus::Bitmap* LoadCreditImage(int index);
|
||||
|
||||
/**
|
||||
* @brief 绘制完整游戏界面,供 TDrawScreen 总入口调用。
|
||||
* @param hdc 目标绘图设备上下文。
|
||||
* @param hWnd 当前窗口句柄。
|
||||
*/
|
||||
void RenderFullScreen(HDC hdc, HWND hWnd);
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
/**
|
||||
* @file resource.h
|
||||
* @brief 定义菜单、图标、对话框和命令等 Windows 资源编号。
|
||||
* @brief Defines Windows resource IDs for menus, icons, dialogs, commands, and strings.
|
||||
*/
|
||||
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ 生成的包含文件。
|
||||
// 供 Tetris.rc 使用
|
||||
//
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by Tetris.rc.
|
||||
|
||||
#define IDS_APP_TITLE 103
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
|
||||
#include "targetver.h"
|
||||
|
||||
// 精简 Windows 头文件,缩短编译时间,同时保留本项目需要的 Win32/GDI API。
|
||||
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的信息
|
||||
// Windows 头文件:
|
||||
#include <windows.h>
|
||||
|
||||
// C 运行时头文件
|
||||
// C 运行时头文件:本项目使用随机数、内存工具、TCHAR 字符串和时间函数。
|
||||
#include <stdlib.h>
|
||||
#include <malloc.h>
|
||||
#include <memory.h>
|
||||
@@ -19,4 +20,4 @@
|
||||
#include <time.h>
|
||||
|
||||
|
||||
// TODO: 在此处引用程序需要的其他头文件
|
||||
// 其他模块各自包含自己的业务头文件,避免预编译头承担过多项目依赖。
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* @brief 设置 Windows SDK 目标平台版本,供 Win32 头文件选择可用 API。
|
||||
*/
|
||||
|
||||
// 包括 SDKDDKVer.h 将定义可用的最高版本的 Windows 平台。
|
||||
// 包括 SDKDDKVer.h 将定义可用的最高版本 Windows 平台宏。
|
||||
|
||||
// 如果要为以前的 Windows 平台生成应用程序,请包括 WinSDKVer.h,并将
|
||||
// WIN32_WINNT 宏设置为要支持的平台,然后再包括 SDKDDKVer.h。
|
||||
// 若课程演示环境需要兼容更旧 Windows,可在这里先包含 WinSDKVer.h,
|
||||
// 再设置 WIN32_WINNT;当前项目直接使用 SDK 默认最高版本。
|
||||
|
||||
#include <SDKDDKVer.h>
|
||||
|
||||
Binary file not shown.
@@ -194,6 +194,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
break;
|
||||
case WM_COMMAND:
|
||||
{
|
||||
// 处理资源菜单命令;自绘菜单的鼠标和键盘输入不走这里。
|
||||
int wmId = LOWORD(wParam);
|
||||
|
||||
switch (wmId)
|
||||
@@ -214,15 +215,19 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
}
|
||||
break;
|
||||
case WM_CREDIT_TICK:
|
||||
// 多媒体定时器线程只投递消息,真正刷新仍回到窗口线程执行。
|
||||
HandleCreditTick(hWnd);
|
||||
break;
|
||||
case WM_TIMER:
|
||||
// 所有窗口定时器统一交给应用层计时器模块分发。
|
||||
HandleTimerMessage(hWnd, wParam);
|
||||
break;
|
||||
case WM_SIZE:
|
||||
// 窗口尺寸变化后重绘,布局函数会按新客户区重新计算缩放。
|
||||
InvalidateRect(hWnd, nullptr, FALSE);
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
// 输入模块未消费的鼠标消息继续交给 Win32 默认处理。
|
||||
if (!HandleMouseClick(hWnd, lParam))
|
||||
{
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
@@ -235,6 +240,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
HandleKeyDown(hWnd, wParam);
|
||||
break;
|
||||
case WM_ERASEBKGND:
|
||||
// 背景由双缓冲完整绘制,阻止系统擦背景可以减少闪烁。
|
||||
return 1;
|
||||
case WM_PAINT:
|
||||
{
|
||||
@@ -251,6 +257,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
clientRect.bottom - clientRect.top);
|
||||
HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
|
||||
|
||||
// 所有自绘内容先画到内存位图,再一次性复制到窗口。
|
||||
TDrawScreen(memDC, hWnd);
|
||||
BitBlt(
|
||||
hdc,
|
||||
@@ -296,10 +303,12 @@ INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
switch (message)
|
||||
{
|
||||
case WM_INITDIALOG:
|
||||
// 资源模板标题是英文,这里在初始化时替换成中文标题。
|
||||
SetWindowText(hDlg, _T("\u5173\u4e8e\u4fc4\u7f57\u65af\u65b9\u5757"));
|
||||
return (INT_PTR)TRUE;
|
||||
|
||||
case WM_COMMAND:
|
||||
// 关于框只需要响应确定和取消,其他命令交回默认流程。
|
||||
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
|
||||
{
|
||||
EndDialog(hDlg, LOWORD(wParam));
|
||||
|
||||
+26
-308
@@ -50,6 +50,7 @@ bool pendingChainBombFollowup = false;
|
||||
|
||||
int bricks[7][4][4][4] =
|
||||
{
|
||||
// 方块形状表:7 种方块、每种 4 个旋转状态、每个状态使用 4x4 矩阵描述。
|
||||
{
|
||||
{{0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}},
|
||||
{{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}},
|
||||
@@ -96,6 +97,7 @@ int bricks[7][4][4][4] =
|
||||
|
||||
COLORREF BrickColor[7] =
|
||||
{
|
||||
// 渲染层按方块编号取色;数组顺序必须与 bricks 中的类型编号一致。
|
||||
RGB(244, 144, 165),
|
||||
RGB(255, 181, 197),
|
||||
RGB(170, 215, 255),
|
||||
@@ -105,77 +107,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 判断当前方块是否可以继续向下移动。
|
||||
*
|
||||
@@ -341,6 +272,7 @@ void MoveRight()
|
||||
*/
|
||||
void Rotate()
|
||||
{
|
||||
// 第一阶段:直接尝试原地旋转。
|
||||
int nextState = (state + 1) % 4;
|
||||
if (IsPiecePlacementValid(type, nextState, point))
|
||||
{
|
||||
@@ -350,6 +282,7 @@ void Rotate()
|
||||
|
||||
if (currentMode == MODE_ROGUE && rogueStats.perfectRotateLevel > 0)
|
||||
{
|
||||
// 第二阶段:Rogue 完美旋转解锁后,尝试左右各一格的墙踢修正。
|
||||
if (TryRotateWithOffset(nextState, -1))
|
||||
{
|
||||
state = nextState;
|
||||
@@ -381,129 +314,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 将当前活动方块固定到工作区,并生成下一个活动方块。
|
||||
*
|
||||
@@ -516,6 +326,7 @@ static void SpawnNextFallingPiece()
|
||||
*/
|
||||
void Fixing()
|
||||
{
|
||||
// 第一阶段:收集落地格子,并把可见区域内的格子写入工作区。
|
||||
bool overflowTop = false;
|
||||
Point fixedCells[4] = {};
|
||||
int fixedCellCount = 0;
|
||||
@@ -525,13 +336,16 @@ void Fixing()
|
||||
|
||||
CollectAndWriteFixedCells(overflowTop, fixedCells, fixedCellCount, explosiveCells, explosiveCellCount);
|
||||
|
||||
// 第二阶段:彩虹方块先按落地中心行处理染色与清除。
|
||||
ApplyRainbowLandingEffect(overflowTop, fixedCells, fixedCellCount);
|
||||
|
||||
// 第三阶段:统一处理顶部溢出,可能触发最后一搏或直接游戏结束。
|
||||
if (!ResolveFixingOverflow(overflowTop))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 第四阶段:结算爆破、激光、十字和稳定结构等普通特殊落地效果。
|
||||
ApplySpecialLandingEffects(fixedCells, fixedCellCount, explosiveCells, explosiveCellCount);
|
||||
|
||||
if (currentMode == MODE_ROGUE)
|
||||
@@ -539,6 +353,7 @@ void Fixing()
|
||||
currentFallInterval = GetRogueFallInterval();
|
||||
}
|
||||
|
||||
// 第五阶段:刷新下一枚活动方块,开始新的下落回合。
|
||||
SpawnNextFallingPiece();
|
||||
}
|
||||
|
||||
@@ -567,120 +382,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 +408,19 @@ int DeleteLines()
|
||||
return clearedLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断当前游戏是否已经结束。
|
||||
*
|
||||
* 老师作业框架中保留该函数名,当前项目内部的结束状态统一记录在
|
||||
* gameOverFlag 中,因此这里直接返回该标记,避免改变原有流程。
|
||||
*
|
||||
* @return 游戏结束返回 true,否则返回 false。
|
||||
*/
|
||||
bool GameOver()
|
||||
{
|
||||
return gameOverFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算当前活动方块的预测落点位置。
|
||||
*
|
||||
@@ -742,6 +456,7 @@ void ComputeTarget()
|
||||
*/
|
||||
void Restart()
|
||||
{
|
||||
// 第一阶段:清空棋盘数组,移除上一局所有固定方块。
|
||||
for (int i = 0; i < nGameHeight; i++)
|
||||
{
|
||||
for (int j = 0; j < nGameWidth; j++)
|
||||
@@ -750,12 +465,14 @@ void Restart()
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:恢复基本游戏标志和默认下落速度。
|
||||
gameOverFlag = false;
|
||||
suspendFlag = false;
|
||||
targetFlag = true;
|
||||
reviveAvailable = true;
|
||||
currentFallInterval = 500;
|
||||
|
||||
// 第三阶段:重置两种模式的统计、升级 UI、反馈和所有视觉特效。
|
||||
ResetPlayerStats(classicStats, false);
|
||||
ResetPlayerStats(rogueStats, true);
|
||||
ResetUpgradeUiState();
|
||||
@@ -772,6 +489,7 @@ void Restart()
|
||||
RollCurrentPieceSpecialFlags(false);
|
||||
tScore = 0;
|
||||
|
||||
// 第四阶段:初始化下一方块队列,并生成当前活动方块。
|
||||
ResetNextQueue();
|
||||
type = ConsumeNextType();
|
||||
nType = nextTypes[0];
|
||||
|
||||
+6
-2708
File diff suppressed because it is too large
Load Diff
@@ -775,6 +775,7 @@ static bool HandleDemoPlayingKey(HWND hWnd, WPARAM key)
|
||||
*/
|
||||
static bool HandleBattleControlKey(HWND hWnd, WPARAM key)
|
||||
{
|
||||
// 菜单、重开和暂停只对真实战局开放,避免破坏技能演示预设流程。
|
||||
if (!IsRogueSkillDemoMode() && key == 'M')
|
||||
{
|
||||
ReturnToMainMenu();
|
||||
@@ -799,6 +800,7 @@ static bool HandleBattleControlKey(HWND hWnd, WPARAM key)
|
||||
|
||||
if (key == 'G')
|
||||
{
|
||||
// 落点提示是显示开关,不改变棋盘或方块状态。
|
||||
targetFlag = !targetFlag;
|
||||
InvalidateRect(hWnd, nullptr, FALSE);
|
||||
return true;
|
||||
@@ -806,6 +808,7 @@ static bool HandleBattleControlKey(HWND hWnd, WPARAM key)
|
||||
|
||||
if (gameOverFlag && reviveAvailable && key == 'V')
|
||||
{
|
||||
// 复活机会只有视频成功播放后才消耗,失败时保留机会并给出反馈。
|
||||
if (PlayReviveVideo(hWnd))
|
||||
{
|
||||
ReviveAfterVideo();
|
||||
@@ -832,6 +835,7 @@ static bool HandleRogueSkillKey(HWND hWnd, WPARAM key)
|
||||
{
|
||||
if (currentMode == MODE_ROGUE && (key == 'J' || key == 'K'))
|
||||
{
|
||||
// Rogue 侧栏强化列表较长,J/K 只调整说明列表的滚动位置。
|
||||
int direction = (key == 'J') ? 1 : -1;
|
||||
AdjustScrollOffset(upgradeListScrollOffset, direction * GetScrollStep(hWnd, 52));
|
||||
InvalidateRect(hWnd, nullptr, FALSE);
|
||||
@@ -840,6 +844,7 @@ static bool HandleRogueSkillKey(HWND hWnd, WPARAM key)
|
||||
|
||||
switch (key)
|
||||
{
|
||||
// 主动技能和 Hold 的按键统一在这里分发,技能内部会自行检查次数和模式。
|
||||
case 'C':
|
||||
case VK_SHIFT:
|
||||
case VK_LSHIFT:
|
||||
@@ -865,6 +870,7 @@ static bool HandleRogueSkillKey(HWND hWnd, WPARAM key)
|
||||
*/
|
||||
static void FixPieceAndResolveLines()
|
||||
{
|
||||
// 固定方块后立即检查满行,Rogue 模式还可能因为经验变化打开升级界面。
|
||||
Fixing();
|
||||
if (!gameOverFlag)
|
||||
{
|
||||
@@ -898,6 +904,7 @@ static bool HandlePieceMovementKey(WPARAM key)
|
||||
return true;
|
||||
case VK_DOWN:
|
||||
case 'S':
|
||||
// 软降被阻挡时等价于本回合落地,立即进入固定和消行流程。
|
||||
if (CanMoveDown())
|
||||
{
|
||||
MoveDown();
|
||||
@@ -912,6 +919,7 @@ static bool HandlePieceMovementKey(WPARAM key)
|
||||
Rotate();
|
||||
return true;
|
||||
case VK_SPACE:
|
||||
// 硬降先移动到最低合法位置,再一次性固定结算。
|
||||
DropDown();
|
||||
FixPieceAndResolveLines();
|
||||
return true;
|
||||
@@ -958,6 +966,7 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key)
|
||||
*/
|
||||
void HandleKeyDown(HWND hWnd, WPARAM wParam)
|
||||
{
|
||||
// 按当前界面从上到下分发:菜单、帮助、升级界面优先消费按键。
|
||||
if (HandleMenuKey(hWnd, wParam) ||
|
||||
HandleRulesKey(hWnd, wParam) ||
|
||||
HandleUpgradeKey(hWnd, wParam))
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
*/
|
||||
void AdjustScrollOffset(int& scrollOffset, int delta)
|
||||
{
|
||||
// 先应用本次滚动增量,再统一夹紧到允许范围内。
|
||||
scrollOffset += delta;
|
||||
if (scrollOffset < 0)
|
||||
{
|
||||
@@ -46,6 +47,7 @@ LayoutMetrics GetLayoutMetrics(HWND hWnd)
|
||||
RECT clientRect;
|
||||
GetClientRect(hWnd, &clientRect);
|
||||
|
||||
// 以设计稿窗口为基准计算缩放,取较小比例保证完整界面不被裁切。
|
||||
int clientWidth = clientRect.right - clientRect.left;
|
||||
int clientHeight = clientRect.bottom - clientRect.top;
|
||||
int scaleX = MulDiv(clientWidth, 1000, WINDOW_CLIENT_WIDTH);
|
||||
@@ -60,6 +62,7 @@ LayoutMetrics GetLayoutMetrics(HWND hWnd)
|
||||
metrics.scale = scale;
|
||||
metrics.layoutWidth = MulDiv(WINDOW_CLIENT_WIDTH, scale, 1000);
|
||||
metrics.layoutHeight = MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000);
|
||||
// 横向居中显示,纵向从顶部开始,方便窗口高度不足时保持棋盘起点稳定。
|
||||
metrics.offsetX = (clientWidth - metrics.layoutWidth) / 2;
|
||||
metrics.offsetY = 0;
|
||||
metrics.grid = MulDiv(GRID, scale, 1000);
|
||||
@@ -289,6 +292,8 @@ RECT GetUpgradeCardRect(HWND hWnd, int index)
|
||||
{
|
||||
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
||||
RECT overlayRect = GetUpgradeOverlayRect(hWnd);
|
||||
|
||||
// 根据当前候选数量自动决定列数;最多三列,两行用于命运轮盘六选项。
|
||||
int gap = ScaleValue(metrics, 18);
|
||||
int horizontalPadding = ScaleValue(metrics, 36);
|
||||
int verticalTop = overlayRect.top + ScaleValue(metrics, 138);
|
||||
@@ -347,6 +352,8 @@ RECT GetOverlayButtonRect(HWND hWnd, int index, int buttonCount)
|
||||
{
|
||||
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
||||
RECT overlayRect = GetGameOverlayRect(hWnd);
|
||||
|
||||
// 游戏结束可能有三个按钮,暂停只有两个按钮,因此间距和边距分开计算。
|
||||
int gap = buttonCount == 3 ? ScaleValue(metrics, 8) : ScaleValue(metrics, 18);
|
||||
int sidePadding = buttonCount == 3 ? ScaleValue(metrics, 14) : ScaleValue(metrics, 34);
|
||||
int width = (overlayRect.right - overlayRect.left - sidePadding * 2 - gap * (buttonCount - 1)) / buttonCount;
|
||||
@@ -383,6 +390,8 @@ RECT GetBackButtonRect(HWND hWnd)
|
||||
RECT GetMusicButtonRect(HWND hWnd)
|
||||
{
|
||||
LayoutMetrics metrics = GetLayoutMetrics(hWnd);
|
||||
|
||||
// 音乐按钮保持最小可点击尺寸,避免窗口缩小时变得难以点中。
|
||||
int size = ScaleValue(metrics, 28);
|
||||
if (size < 22)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ static constexpr const wchar_t* kReviveVideoAlias = L"TereisReviveVideo";
|
||||
*/
|
||||
static bool TryPlayMciLoop(const std::wstring& path, bool forceMpegVideo)
|
||||
{
|
||||
// 资源不存在时直接失败,让上层继续尝试下一个候选路径或格式。
|
||||
if (!FileExists(path))
|
||||
{
|
||||
return false;
|
||||
@@ -61,6 +62,7 @@ static bool TryPlayMciLoop(const std::wstring& path, bool forceMpegVideo)
|
||||
*/
|
||||
void StopBackgroundMusic()
|
||||
{
|
||||
// 根据当前播放方式选择对应的释放接口,避免 MCI 设备或 PlaySound 残留。
|
||||
if (bgmUsingMci)
|
||||
{
|
||||
mciSendStringW((std::wstring(L"stop ") + kBgmAlias).c_str(), nullptr, 0, nullptr);
|
||||
@@ -80,6 +82,7 @@ void StopBackgroundMusic()
|
||||
*/
|
||||
void StartBackgroundMusic()
|
||||
{
|
||||
// 音乐被关闭或已经在播放时,不重复查找资源和启动设备。
|
||||
if (!bgmEnabled || bgmPlaying)
|
||||
{
|
||||
return;
|
||||
@@ -94,6 +97,7 @@ void StartBackgroundMusic()
|
||||
|
||||
for (const std::wstring& candidate : bgmWavCandidates)
|
||||
{
|
||||
// WAV 优先使用 PlaySound,依赖少、兼容性最好。
|
||||
if (FileExists(candidate) &&
|
||||
PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP))
|
||||
{
|
||||
@@ -112,6 +116,7 @@ void StartBackgroundMusic()
|
||||
|
||||
for (const std::wstring& candidate : oggCandidates)
|
||||
{
|
||||
// OGG 通过 MCI 尝试普通打开和 mpegvideo 强制类型两条路径。
|
||||
if (TryPlayMciLoop(candidate, false) || TryPlayMciLoop(candidate, true))
|
||||
{
|
||||
return;
|
||||
@@ -127,6 +132,7 @@ void StartBackgroundMusic()
|
||||
|
||||
for (const std::wstring& candidate : fallbackWavCandidates)
|
||||
{
|
||||
// 兼容旧资源名 background.wav,保证替换素材后仍能播放。
|
||||
if (FileExists(candidate) &&
|
||||
PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP))
|
||||
{
|
||||
@@ -164,6 +170,7 @@ void ToggleBackgroundMusic(HWND hWnd)
|
||||
*/
|
||||
bool PlayReviveVideo(HWND hWnd)
|
||||
{
|
||||
// 依次查找 AVI 和 MP4,并同时支持构建目录与项目根目录运行。
|
||||
std::wstring videoPath = BuildAssetPath(L"assets\\video\\video.avi");
|
||||
if (!FileExists(videoPath))
|
||||
{
|
||||
@@ -185,6 +192,7 @@ bool PlayReviveVideo(HWND hWnd)
|
||||
bool shouldResumeBgm = bgmEnabled;
|
||||
if (bgmPlaying)
|
||||
{
|
||||
// 视频播放期间暂停背景音乐,播放结束后按开关状态恢复。
|
||||
StopBackgroundMusic();
|
||||
}
|
||||
|
||||
@@ -214,6 +222,7 @@ bool PlayReviveVideo(HWND hWnd)
|
||||
|
||||
if (!played)
|
||||
{
|
||||
// MCI 全屏播放失败时退回系统默认播放器,并等待播放器进程结束。
|
||||
SHELLEXECUTEINFOW shellInfo = {};
|
||||
shellInfo.cbSize = sizeof(shellInfo);
|
||||
shellInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
|
||||
@@ -27,6 +27,7 @@ static void CALLBACK CreditTimerCallback(UINT, UINT, DWORD_PTR userData, DWORD_P
|
||||
*/
|
||||
void ResetGameTimer(HWND hWnd)
|
||||
{
|
||||
// 下落速度会被 Rogue 强化和临时状态动态修改,因此每次变化都重新注册定时器。
|
||||
KillTimer(hWnd, GAME_TIMER_ID);
|
||||
SetTimer(hWnd, GAME_TIMER_ID, currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL, nullptr);
|
||||
}
|
||||
@@ -37,8 +38,11 @@ void ResetGameTimer(HWND hWnd)
|
||||
*/
|
||||
void StartAppTimers(HWND hWnd)
|
||||
{
|
||||
// 主定时器负责方块下落,特效定时器负责高帧率视觉动画。
|
||||
ResetGameTimer(hWnd);
|
||||
SetTimer(hWnd, EFFECT_TIMER_ID, EFFECT_TIMER_INTERVAL, nullptr);
|
||||
|
||||
// 致谢页动画需要更高刷新频率,优先使用多媒体定时器。
|
||||
creditTimerHandle = timeSetEvent(
|
||||
CREDIT_TIMER_INTERVAL,
|
||||
1,
|
||||
@@ -58,6 +62,7 @@ void StartAppTimers(HWND hWnd)
|
||||
*/
|
||||
void StopAppTimers(HWND hWnd)
|
||||
{
|
||||
// 退出或窗口销毁时释放所有可能创建过的计时器资源。
|
||||
KillTimer(hWnd, GAME_TIMER_ID);
|
||||
KillTimer(hWnd, EFFECT_TIMER_ID);
|
||||
if (creditTimerHandle != 0)
|
||||
@@ -92,6 +97,7 @@ static bool TickRogueTimedStates(HWND hWnd)
|
||||
{
|
||||
bool shouldRefresh = false;
|
||||
|
||||
// 狂热、缓流、极限缓速和 Hold 缓速都会影响下落间隔,需要同步重置主定时器。
|
||||
if (currentMode == MODE_ROGUE && !IsRogueSkillDemoMode() && rogueStats.feverTicks > 0)
|
||||
{
|
||||
rogueStats.feverTicks--;
|
||||
@@ -139,6 +145,7 @@ static bool TickRogueTimedStates(HWND hWnd)
|
||||
*/
|
||||
static bool TickExtremeDanger(HWND hWnd)
|
||||
{
|
||||
// 极限玩家只在真实 Rogue 战局中计时,暂停、结束和技能演示都不推进危险等级。
|
||||
if (currentMode != MODE_ROGUE ||
|
||||
IsRogueSkillDemoMode() ||
|
||||
rogueStats.extremePlayerLevel <= 0 ||
|
||||
@@ -151,10 +158,12 @@ static bool TickExtremeDanger(HWND hWnd)
|
||||
|
||||
if (rogueStats.extremeDangerTicks > 0)
|
||||
{
|
||||
// 计时尚未结束时只递减倒计时,不改变速度。
|
||||
rogueStats.extremeDangerTicks--;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 每 30 个主计时周期未完成四消就提高危险等级,并立即刷新下落速度。
|
||||
rogueStats.extremeDangerTicks = 30;
|
||||
if (rogueStats.extremeDangerLevel < 5)
|
||||
{
|
||||
@@ -176,6 +185,7 @@ static bool TickExtremeDanger(HWND hWnd)
|
||||
*/
|
||||
static bool TryStartTimeDilation(HWND hWnd)
|
||||
{
|
||||
// 时间缓流是自动保命效果,已经在持续时不会重复触发。
|
||||
if (currentMode != MODE_ROGUE ||
|
||||
IsRogueSkillDemoMode() ||
|
||||
rogueStats.timeDilationLevel <= 0 ||
|
||||
@@ -186,6 +196,7 @@ static bool TryStartTimeDilation(HWND hWnd)
|
||||
|
||||
int occupiedHeight = 0;
|
||||
int playableHeight = GetRoguePlayableHeight();
|
||||
// 自上而下寻找第一行有方块的位置,由此换算当前堆叠高度。
|
||||
for (int y = 0; y < playableHeight; y++)
|
||||
{
|
||||
bool hasCell = false;
|
||||
@@ -231,6 +242,7 @@ static bool TickGameFall(HWND hWnd)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rogue 难度随时间推进,速度变化后需要重新安排下一次自动下落。
|
||||
if (currentMode == MODE_ROGUE && !IsRogueSkillDemoMode())
|
||||
{
|
||||
int previousFallInterval = currentFallInterval;
|
||||
@@ -243,6 +255,7 @@ static bool TickGameFall(HWND hWnd)
|
||||
|
||||
TryStartTimeDilation(hWnd);
|
||||
|
||||
// 能下落时只移动一格;被阻挡时固定方块,并进入消行与升级结算。
|
||||
if (CanMoveDown())
|
||||
{
|
||||
MoveDown();
|
||||
@@ -259,6 +272,7 @@ static bool TickGameFall(HWND hWnd)
|
||||
|
||||
if (!gameOverFlag)
|
||||
{
|
||||
// 真实方块位置变化后刷新预测落点,供渲染层绘制目标提示。
|
||||
ComputeTarget();
|
||||
}
|
||||
|
||||
@@ -274,6 +288,7 @@ void HandleTimerMessage(HWND hWnd, WPARAM timerId)
|
||||
{
|
||||
if (timerId == EFFECT_TIMER_ID)
|
||||
{
|
||||
// 视觉特效独立于主下落速度,用固定帧率推进。
|
||||
if (TickVisualEffects())
|
||||
{
|
||||
InvalidateRect(hWnd, nullptr, FALSE);
|
||||
@@ -283,6 +298,7 @@ void HandleTimerMessage(HWND hWnd, WPARAM timerId)
|
||||
|
||||
if (timerId == CREDIT_TIMER_ID && creditTimerHandle == 0)
|
||||
{
|
||||
// 多媒体定时器不可用时,普通窗口定时器承担致谢页动画刷新。
|
||||
HandleCreditTick(hWnd);
|
||||
return;
|
||||
}
|
||||
@@ -295,10 +311,12 @@ void HandleTimerMessage(HWND hWnd, WPARAM timerId)
|
||||
bool shouldRefresh = false;
|
||||
if (feedbackState.visibleTicks > 0)
|
||||
{
|
||||
// 右侧反馈信息按主计时周期自动消退。
|
||||
feedbackState.visibleTicks--;
|
||||
shouldRefresh = true;
|
||||
}
|
||||
|
||||
// 主定时器集中推进演示、Rogue 临时状态、危险等级和自然下落。
|
||||
if (IsRogueSkillDemoMode() && TickRogueSkillDemo())
|
||||
{
|
||||
shouldRefresh = true;
|
||||
|
||||
@@ -20,6 +20,7 @@ std::wstring BuildAssetPath(const wchar_t* relativePath)
|
||||
wchar_t modulePath[MAX_PATH] = {};
|
||||
GetModuleFileNameW(nullptr, modulePath, MAX_PATH);
|
||||
|
||||
// 先取可执行文件所在目录,再根据构建目录层级回到项目根目录。
|
||||
std::wstring basePath(modulePath);
|
||||
size_t lastSlash = basePath.find_last_of(L"\\/");
|
||||
if (lastSlash != std::wstring::npos)
|
||||
@@ -56,6 +57,7 @@ std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath)
|
||||
return L"";
|
||||
}
|
||||
|
||||
// 当前工作目录可能已经是项目根目录,直接拼接相对资源路径。
|
||||
std::wstring candidate = std::wstring(currentDirectory) + L"\\" + relativePath;
|
||||
wchar_t fullPath[MAX_PATH] = {};
|
||||
DWORD result = GetFullPathNameW(candidate.c_str(), MAX_PATH, fullPath, nullptr);
|
||||
@@ -74,6 +76,7 @@ std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath)
|
||||
*/
|
||||
bool FileExists(const std::wstring& path)
|
||||
{
|
||||
// 目录不能作为媒体或图片资源使用,因此排除 FILE_ATTRIBUTE_DIRECTORY。
|
||||
DWORD attributes = GetFileAttributesW(path.c_str());
|
||||
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,12 @@ int pendingLineClearEffectLineCount = 0;
|
||||
*/
|
||||
void ResetPlayerStats(PlayerStats& stats, bool useRogueRules)
|
||||
{
|
||||
// 基础得分、等级和经验先恢复到新局起点。
|
||||
stats.score = 0;
|
||||
stats.level = 1;
|
||||
stats.exp = 0;
|
||||
stats.requiredExp = useRogueRules ? 10 : 0;
|
||||
// 强化等级、主动技能次数和限时状态全部清零,避免跨局继承。
|
||||
stats.totalLinesCleared = 0;
|
||||
stats.scoreMultiplierPercent = 100;
|
||||
stats.expMultiplierPercent = 100;
|
||||
@@ -84,6 +86,7 @@ void ResetPlayerStats(PlayerStats& stats, bool useRogueRules)
|
||||
stats.difficultyElapsedMs = 0;
|
||||
stats.difficultyLevel = 0;
|
||||
stats.lockedRows = 0;
|
||||
// 方块改造按 7 种方块分别记录等级,重开时逐项清空。
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
stats.pieceTuningLevels[i] = 0;
|
||||
@@ -98,6 +101,7 @@ void ResetPlayerStats(PlayerStats& stats, bool useRogueRules)
|
||||
*/
|
||||
void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks)
|
||||
{
|
||||
// 使用 lstrcpyn 限长复制,避免长描述写出固定缓冲区。
|
||||
feedbackState.visibleTicks = ticks;
|
||||
lstrcpyn(feedbackState.title, title, sizeof(feedbackState.title) / sizeof(TCHAR));
|
||||
lstrcpyn(feedbackState.detail, detail, sizeof(feedbackState.detail) / sizeof(TCHAR));
|
||||
@@ -108,6 +112,7 @@ void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks)
|
||||
*/
|
||||
void ResetVisualEffects()
|
||||
{
|
||||
// 主状态和各类效果槽位只需把 ticks 清零,渲染层会自动忽略非活动项。
|
||||
clearEffectState.ticks = 0;
|
||||
clearEffectState.totalTicks = 0;
|
||||
clearEffectState.rowCount = 0;
|
||||
@@ -141,6 +146,7 @@ bool TickVisualEffects()
|
||||
{
|
||||
bool active = false;
|
||||
|
||||
// 所有效果共用倒计时推进,任意效果仍活动就请求界面刷新。
|
||||
if (clearEffectState.ticks > 0)
|
||||
{
|
||||
clearEffectState.ticks--;
|
||||
@@ -210,6 +216,7 @@ bool TickCreditAnimation()
|
||||
*/
|
||||
static void AddFloatingText(int boardX, int boardY, const TCHAR* text, COLORREF color)
|
||||
{
|
||||
// 复用第一个空闲槽位,槽位满时丢弃新效果,避免动态分配。
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (floatingTextEffects[i].ticks <= 0)
|
||||
@@ -589,6 +596,7 @@ bool TryRotateWithOffset(int nextState, int offsetX)
|
||||
*/
|
||||
void ReviveAfterVideo()
|
||||
{
|
||||
// 只有游戏结束且复活机会仍在时才能进入复活流程。
|
||||
if (!gameOverFlag || !reviveAvailable)
|
||||
{
|
||||
return;
|
||||
@@ -606,6 +614,7 @@ void ReviveAfterVideo()
|
||||
rowsToClear = 5;
|
||||
}
|
||||
|
||||
// 清理顶部一段空间,避免新方块刚生成又立即判定失败。
|
||||
for (int y = 0; y < rowsToClear && y < playableHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < nGameWidth; x++)
|
||||
@@ -614,6 +623,7 @@ void ReviveAfterVideo()
|
||||
}
|
||||
}
|
||||
|
||||
// 复活后重新取一个活动方块,并刷新落点提示。
|
||||
type = ConsumeNextType();
|
||||
nType = nextTypes[0];
|
||||
state = 0;
|
||||
@@ -632,6 +642,7 @@ void ReviveAfterVideo()
|
||||
*/
|
||||
void StartGameWithMode(int mode)
|
||||
{
|
||||
// 模式切换后直接复用 Restart,保证经典和 Rogue 都从干净状态开始。
|
||||
rogueDemoMode = false;
|
||||
currentMode = mode;
|
||||
currentScreen = SCREEN_PLAYING;
|
||||
@@ -646,6 +657,7 @@ void StartGameWithMode(int mode)
|
||||
*/
|
||||
void ReturnToMainMenu()
|
||||
{
|
||||
// 回到主菜单时关闭所有临时战局、帮助页和升级界面状态。
|
||||
rogueDemoMode = false;
|
||||
currentScreen = SCREEN_MENU;
|
||||
suspendFlag = false;
|
||||
@@ -732,6 +744,7 @@ void ChangeCreditPage(int direction)
|
||||
return;
|
||||
}
|
||||
|
||||
// 页码循环切换,同时记录动画方向用于渲染滑动效果。
|
||||
int oldPageIndex = creditPageIndex;
|
||||
if (direction > 0)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
#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;
|
||||
|
||||
// 从底向上扫描,删除后 i++ 让当前位置继续检查新落下来的行。
|
||||
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;
|
||||
|
||||
// 追加爆破以第一次爆破落地点为中心,只执行一次 3x3 清除。
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fixedCellCount)
|
||||
{
|
||||
// 顶部溢出时优先交给失败/复活逻辑处理,避免在不可见区域触发奖励。
|
||||
if (overflowTop || !currentPieceIsRainbow)
|
||||
{
|
||||
return;
|
||||
@@ -39,6 +40,7 @@ void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fi
|
||||
rainbowAnchorRow = GetRoguePlayableHeight() - 1;
|
||||
}
|
||||
|
||||
// 第二阶段:按锚点行执行彩虹清除和覆盖行染色。
|
||||
int rainbowRecoloredCount = 0;
|
||||
int rainbowClearedCount = TriggerRainbowColorShift(rainbowAnchorRow, point.y, point.y + 3, rainbowRecoloredCount);
|
||||
int rainbowScore = 0;
|
||||
@@ -48,6 +50,7 @@ void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fi
|
||||
int voidExp = 0;
|
||||
if (currentMode == MODE_ROGUE && rainbowClearedCount > 0)
|
||||
{
|
||||
// Rogue 模式下特殊清除也能获得得分和经验,但不直接触发升级菜单。
|
||||
AwardRogueSkillClearRewards(rainbowClearedCount, rainbowScore, rainbowExp, false);
|
||||
if (rogueStats.voidCoreLevel > 0)
|
||||
{
|
||||
@@ -86,11 +89,13 @@ void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fi
|
||||
*/
|
||||
static void ApplyExplosiveLandingEffect(const Point* explosiveCells, int explosiveCellCount)
|
||||
{
|
||||
// 非爆破方块直接跳过,保持普通方块落地流程轻量。
|
||||
if (!currentPieceIsExplosive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 每个落地格都作为爆心清除范围,连环炸弹会扩大底层清除函数的范围。
|
||||
int explosiveCellsCleared = 0;
|
||||
for (int i = 0; i < explosiveCellCount; i++)
|
||||
{
|
||||
@@ -128,6 +133,7 @@ static void ApplyExplosiveLandingEffect(const Point* explosiveCells, int explosi
|
||||
*/
|
||||
static void ApplyLaserLandingEffect(const Point* fixedCells, int fixedCellCount)
|
||||
{
|
||||
// 激光方块以落地格平均列作为贯穿列,减少不同形状造成的位置偏差。
|
||||
if (!currentPieceIsLaser)
|
||||
{
|
||||
return;
|
||||
@@ -172,6 +178,7 @@ static void ApplyLaserLandingEffect(const Point* fixedCells, int fixedCellCount)
|
||||
*/
|
||||
static void ApplyCrossLandingEffect(const Point* fixedCells, int fixedCellCount)
|
||||
{
|
||||
// 十字方块同时计算中心行和中心列,后续分别触发行清除与列清除。
|
||||
if (!currentPieceIsCross)
|
||||
{
|
||||
return;
|
||||
@@ -248,6 +255,7 @@ static void ApplyStableStructureEffect()
|
||||
*/
|
||||
void ApplySpecialLandingEffects(const Point* fixedCells, int fixedCellCount, const Point* explosiveCells, int explosiveCellCount)
|
||||
{
|
||||
// 多种特殊标记按固定顺序结算,保证同一落地事件的反馈和奖励稳定。
|
||||
ApplyExplosiveLandingEffect(explosiveCells, explosiveCellCount);
|
||||
ApplyLaserLandingEffect(fixedCells, fixedCellCount);
|
||||
ApplyCrossLandingEffect(fixedCells, fixedCellCount);
|
||||
|
||||
@@ -20,11 +20,13 @@ using namespace Gdiplus;
|
||||
*/
|
||||
static Bitmap* TryLoadBitmap(const std::wstring& path)
|
||||
{
|
||||
// 空路径和不存在的文件不交给 GDI+,减少无效加载开销。
|
||||
if (path.empty() || !FileExists(path))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// GDI+ 返回对象后仍需检查状态,失败对象要立即释放。
|
||||
Bitmap* loadedImage = Bitmap::FromFile(path.c_str(), FALSE);
|
||||
if (loadedImage != nullptr && loadedImage->GetLastStatus() == Ok)
|
||||
{
|
||||
@@ -65,6 +67,7 @@ Bitmap* LoadBackgroundImage()
|
||||
static Bitmap* backgroundImage = nullptr;
|
||||
static bool attempted = false;
|
||||
|
||||
// 背景图只查找一次,失败后也记住结果,避免每帧重复访问磁盘。
|
||||
if (!attempted)
|
||||
{
|
||||
attempted = true;
|
||||
@@ -110,6 +113,7 @@ Bitmap* LoadCreditImage(int index)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 每张致谢图单独缓存,只有首次进入对应页时才加载。
|
||||
if (!attempted[index])
|
||||
{
|
||||
attempted[index] = true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -228,6 +228,7 @@ static void StartRogueSkillDemoInternal(int demoIndex, bool autoAdvance);
|
||||
*/
|
||||
static int GetNextPreviewLimit()
|
||||
{
|
||||
// 经典模式固定只显示一个预览,Rogue 模式再按强化等级限制到 1~3 个。
|
||||
if (currentMode != MODE_ROGUE)
|
||||
{
|
||||
return 1;
|
||||
@@ -251,6 +252,7 @@ static int GetNextPreviewLimit()
|
||||
*/
|
||||
int GetRogueLockedRows()
|
||||
{
|
||||
// 底部封锁只属于 Rogue 难度系统,其他模式保持完整 20 行棋盘。
|
||||
if (currentMode != MODE_ROGUE)
|
||||
{
|
||||
return 0;
|
||||
@@ -297,6 +299,7 @@ static void ClearLockedRows()
|
||||
*/
|
||||
void AdvanceRogueDifficulty(int elapsedMs)
|
||||
{
|
||||
// 技能演示、暂停、结束和非游玩界面都不推进真实难度。
|
||||
if (currentMode != MODE_ROGUE || rogueDemoMode || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag || elapsedMs <= 0)
|
||||
{
|
||||
return;
|
||||
@@ -307,6 +310,7 @@ void AdvanceRogueDifficulty(int elapsedMs)
|
||||
|
||||
while (rogueStats.difficultyElapsedMs >= kDifficultyStepMs)
|
||||
{
|
||||
// 每累计一个难度周期提升速度,后面再根据等级换算封锁行数。
|
||||
rogueStats.difficultyElapsedMs -= kDifficultyStepMs;
|
||||
rogueStats.difficultyLevel++;
|
||||
difficultyChanged = true;
|
||||
@@ -1680,6 +1684,7 @@ static void MarkDestinyCursedOption(int optionCount)
|
||||
*/
|
||||
static void FillUpgradeOptions()
|
||||
{
|
||||
// 第一段:根据当前强化等级、前置条件和互斥条件收集候选池。
|
||||
int selectableIndexes[kUpgradePoolSize] = { 0 };
|
||||
int selectableWeights[kUpgradePoolSize] = { 0 };
|
||||
int selectableCount = CollectSelectableUpgrades(selectableIndexes, selectableWeights);
|
||||
@@ -1706,9 +1711,11 @@ static void FillUpgradeOptions()
|
||||
*/
|
||||
int GetRogueFallInterval()
|
||||
{
|
||||
// 基础速度由永久缓降和随时间推进的难度共同决定。
|
||||
int baseInterval = 500 + rogueStats.slowFallStacks * 80;
|
||||
baseInterval -= rogueStats.difficultyLevel * kDifficultySpeedStepMs;
|
||||
|
||||
// 风险类强化提高速度,保命或奖励类临时状态降低速度。
|
||||
if (rogueStats.highPressureLevel > 0)
|
||||
{
|
||||
baseInterval = baseInterval * 85 / 100;
|
||||
@@ -2022,6 +2029,7 @@ void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain,
|
||||
{
|
||||
scoreGain = 0;
|
||||
expGain = 0;
|
||||
// 非 Rogue 模式或未清除格子时不发放技能奖励。
|
||||
if (currentMode != MODE_ROGUE || clearedCells <= 0)
|
||||
{
|
||||
return;
|
||||
@@ -2058,6 +2066,7 @@ void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain,
|
||||
|
||||
if (allowLevelProgress && !rogueDemoMode)
|
||||
{
|
||||
// 主动技能允许触发升级时,经验条满会立即积累待选强化并打开升级菜单。
|
||||
int levelUps = ApplyLevelProgress(rogueStats);
|
||||
if (levelUps > 0)
|
||||
{
|
||||
@@ -2128,6 +2137,7 @@ void CheckRogueLevelProgress()
|
||||
void ApplyBoardGravity()
|
||||
{
|
||||
int playableHeight = GetRoguePlayableHeight();
|
||||
// 每一列独立压缩到可操作区域底部,保留方块颜色和特殊值。
|
||||
for (int x = 0; x < nGameWidth; x++)
|
||||
{
|
||||
int writeY = playableHeight - 1;
|
||||
@@ -2161,6 +2171,7 @@ void ApplyLineClearResult(int linesCleared)
|
||||
{
|
||||
if (linesCleared <= 0)
|
||||
{
|
||||
// Rogue 连击只在连续消行时保持,空落地会断连。
|
||||
if (currentMode == MODE_ROGUE)
|
||||
{
|
||||
rogueStats.comboChain = 0;
|
||||
@@ -2170,6 +2181,7 @@ void ApplyLineClearResult(int linesCleared)
|
||||
|
||||
if (currentMode == MODE_CLASSIC)
|
||||
{
|
||||
// 经典模式沿用老师框架的简单计分:每行 100 分。
|
||||
classicStats.totalLinesCleared += linesCleared;
|
||||
classicStats.score += linesCleared * 100;
|
||||
tScore = classicStats.score;
|
||||
@@ -2211,6 +2223,7 @@ void ApplyLineClearResult(int linesCleared)
|
||||
int gamblerBonusPercent = 0;
|
||||
if (rogueStats.gamblerLevel > 0)
|
||||
{
|
||||
// 赌徒契约让本次收益在正负区间随机波动。
|
||||
int variance = 20 + (rogueStats.gamblerLevel - 1) * 10;
|
||||
if (variance > 50)
|
||||
{
|
||||
@@ -2289,6 +2302,7 @@ void ApplyLineClearResult(int linesCleared)
|
||||
|
||||
if (rogueStats.chainBlastLevel > 0)
|
||||
{
|
||||
// 连锁火花在消行附近制造额外破坏,并按技能清除规则给少量奖励。
|
||||
int chainBlastCells = 0;
|
||||
for (int i = 0; i < linesCleared; i++)
|
||||
{
|
||||
@@ -2757,6 +2771,7 @@ void ConfirmUpgradeSelection()
|
||||
*/
|
||||
void HoldCurrentPiece()
|
||||
{
|
||||
// 第一阶段:检查模式、解锁状态和本回合是否已经使用过 Hold。
|
||||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||||
{
|
||||
return;
|
||||
@@ -2772,6 +2787,7 @@ void HoldCurrentPiece()
|
||||
return;
|
||||
}
|
||||
|
||||
// 第二阶段:把当前方块放入备用仓,并决定换出旧备用方块还是消费下一块。
|
||||
int previousHoldType = holdType;
|
||||
holdType = type;
|
||||
state = 0;
|
||||
@@ -2793,10 +2809,12 @@ void HoldCurrentPiece()
|
||||
target = point;
|
||||
if (currentMode == MODE_ROGUE && rogueStats.controlMasterLevel > 0)
|
||||
{
|
||||
// 操控大师在 Hold 后给予短暂缓速,帮助玩家重新摆位。
|
||||
rogueStats.holdSlowTicks = 4;
|
||||
currentFallInterval = GetRogueFallInterval();
|
||||
}
|
||||
|
||||
// 第三阶段:验证换出的方块能否合法生成,避免交换到无解位置。
|
||||
if (!IsPiecePlacementValid(type, state, point))
|
||||
{
|
||||
gameOverFlag = true;
|
||||
@@ -2821,6 +2839,7 @@ void HoldCurrentPiece()
|
||||
*/
|
||||
void UseScreenBomb()
|
||||
{
|
||||
// 清屏炸弹是主动技能,先检查模式、解锁状态和可用次数。
|
||||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||||
{
|
||||
return;
|
||||
@@ -2836,6 +2855,7 @@ void UseScreenBomb()
|
||||
return;
|
||||
}
|
||||
|
||||
// 扣除次数后清理底部区域,再按技能清除格子数发放奖励。
|
||||
rogueStats.screenBombCount--;
|
||||
int clearedCells = TriggerScreenBomb();
|
||||
int scoreGain = 0;
|
||||
@@ -2860,6 +2880,7 @@ void UseScreenBomb()
|
||||
*/
|
||||
void UseBlackHole()
|
||||
{
|
||||
// 黑洞按棋盘上数量最多的固定方块类型进行吞噬。
|
||||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||||
{
|
||||
return;
|
||||
@@ -2875,6 +2896,7 @@ void UseBlackHole()
|
||||
return;
|
||||
}
|
||||
|
||||
// 只有确实清除了格子才消耗次数,避免空棋盘误扣资源。
|
||||
int clearedCells = TriggerBlackHole();
|
||||
if (clearedCells <= 0)
|
||||
{
|
||||
@@ -2890,6 +2912,7 @@ void UseBlackHole()
|
||||
|
||||
if (rogueStats.voidCoreLevel > 0)
|
||||
{
|
||||
// 虚空核心让黑洞额外召来一个待生成的彩虹方块。
|
||||
rogueStats.pendingRainbowPieceCount++;
|
||||
}
|
||||
|
||||
@@ -2923,6 +2946,7 @@ void UseBlackHole()
|
||||
*/
|
||||
void UseAirReshape()
|
||||
{
|
||||
// 空中换形把当前方块改为 I 块,需要先确认次数和当前局面。
|
||||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||||
{
|
||||
return;
|
||||
@@ -2946,6 +2970,7 @@ void UseAirReshape()
|
||||
int originalState = state;
|
||||
bool transformed = false;
|
||||
|
||||
// 依次尝试横/竖两种 I 块状态,以及当前列附近的多个偏移。
|
||||
for (int stateIndex = 0; stateIndex < 2 && !transformed; stateIndex++)
|
||||
{
|
||||
int nextState = candidateStates[stateIndex];
|
||||
@@ -2967,6 +2992,7 @@ void UseAirReshape()
|
||||
|
||||
if (!transformed)
|
||||
{
|
||||
// 所有候选位置都不合法时恢复原方块,不消耗换形次数。
|
||||
type = originalType;
|
||||
state = originalState;
|
||||
point = originalPoint;
|
||||
@@ -3072,6 +3098,7 @@ void RestartCurrentRogueSkillDemo()
|
||||
*/
|
||||
static void StartRogueSkillDemoInternal(int demoIndex, bool autoAdvance)
|
||||
{
|
||||
// 入口统一夹紧下标,避免帮助页传入非法序号导致数组越界。
|
||||
if (demoIndex < 0)
|
||||
{
|
||||
demoIndex = 0;
|
||||
@@ -3081,6 +3108,7 @@ static void StartRogueSkillDemoInternal(int demoIndex, bool autoAdvance)
|
||||
demoIndex = kRogueDemoStepCount - 1;
|
||||
}
|
||||
|
||||
// 先切到 Rogue 游玩界面,再用 Restart 复用完整的新局初始化流程。
|
||||
currentMode = MODE_ROGUE;
|
||||
currentScreen = SCREEN_PLAYING;
|
||||
rogueDemoMode = true;
|
||||
@@ -3089,6 +3117,7 @@ static void StartRogueSkillDemoInternal(int demoIndex, bool autoAdvance)
|
||||
rogueDemoAutoAdvance = autoAdvance;
|
||||
|
||||
Restart();
|
||||
// Restart 会重置部分状态,因此演示模式标记和显示开关需要重新写回。
|
||||
rogueDemoMode = true;
|
||||
reviveAvailable = false;
|
||||
suspendFlag = false;
|
||||
@@ -3105,6 +3134,7 @@ static void StartRogueSkillDemoInternal(int demoIndex, bool autoAdvance)
|
||||
*/
|
||||
bool TickRogueSkillDemo()
|
||||
{
|
||||
// 非演示模式不消耗计时;手动演示时 autoAdvance 为 false,只保持刷新。
|
||||
if (!rogueDemoMode)
|
||||
{
|
||||
return false;
|
||||
@@ -3127,6 +3157,7 @@ bool TickRogueSkillDemo()
|
||||
*/
|
||||
void AdvanceRogueSkillDemo()
|
||||
{
|
||||
// 循环切换演示条目,最后一项之后回到第一项。
|
||||
if (!rogueDemoMode)
|
||||
{
|
||||
return;
|
||||
@@ -3146,6 +3177,7 @@ void AdvanceRogueSkillDemo()
|
||||
*/
|
||||
static void ResetRogueDemoBoard()
|
||||
{
|
||||
// 清空真实棋盘和临时状态,保证每个演示场景互不影响。
|
||||
for (int y = 0; y < nGameHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < nGameWidth; x++)
|
||||
@@ -3263,6 +3295,7 @@ static void FillRogueDemoRowExcept(int row, int gapStart, int gapWidth, int base
|
||||
*/
|
||||
static void SetRogueDemoCurrentPiece(int pieceType, int pieceState, int x, int y)
|
||||
{
|
||||
// 演示方块直接指定类型、旋转和位置,同时清空预览队列方便观察。
|
||||
type = pieceType;
|
||||
nType = 0;
|
||||
state = pieceState;
|
||||
@@ -3280,12 +3313,14 @@ static void SetRogueDemoCurrentPiece(int pieceType, int pieceState, int x, int y
|
||||
*/
|
||||
static void ApplyRogueSkillDemoStep()
|
||||
{
|
||||
// 第一阶段:清空棋盘,显示当前演示名称和操作说明。
|
||||
ResetRogueDemoBoard();
|
||||
|
||||
const RogueDemoStep& demoStep = kRogueDemoSteps[rogueDemoStepIndex];
|
||||
ShowRogueDemoFloatingName(demoStep.name);
|
||||
SetFeedbackMessage(demoStep.name, demoStep.detail, 12);
|
||||
|
||||
// 第二阶段:根据演示类型布置预设棋盘、强化等级和当前活动方块。
|
||||
switch (demoStep.kind)
|
||||
{
|
||||
case DEMO_SCORE_MULTIPLIER:
|
||||
|
||||
@@ -5,5 +5,12 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
/**
|
||||
* @file stdafx.cpp
|
||||
* @brief 预编译头源文件,用于让构建系统生成 stdafx.h 对应的预编译结果。
|
||||
*
|
||||
* 本文件不包含业务逻辑;保留它是为了兼容 Visual Studio 模板和现有构建结构。
|
||||
*/
|
||||
|
||||
// TODO: 在 STDAFX.H 中
|
||||
// 引用任何所需的附加头文件,而不是在此文件中引用
|
||||
|
||||
Reference in New Issue
Block a user