diff --git a/Rogue_Tetris_功能设计文档.md b/Rogue_Tetris_功能设计文档.md deleted file mode 100644 index 28d18e7..0000000 --- a/Rogue_Tetris_功能设计文档.md +++ /dev/null @@ -1,624 +0,0 @@ -# 《Rogue Tetris》功能设计文档 - -## 1. 项目概述 - -本项目基于经典俄罗斯方块玩法,加入类肉鸽成长系统。玩家通过消除方块获得积分与经验,达到一定条件后触发升级,从随机出现的强化选项中选择一项,形成不同流派与策略组合。 - -游戏核心循环为: - -```text -下落方块 → 摆放 → 消行 → 获得积分/经验 → 升级 → 选择强化 → 提升能力 → 继续挑战 -``` - -项目目标是在保留俄罗斯方块基础规则的同时,增强随机性、成长性和可重复游玩价值。 - ---- - -## 2. 基础玩法设计 - -### 2.1 棋盘设计 - -游戏区域采用标准俄罗斯方块棋盘: - -```cpp -const int BOARD_WIDTH = 10; -const int BOARD_HEIGHT = 20; -``` - -棋盘使用二维数组或 `vector>` 存储。 - -```cpp -int board[BOARD_HEIGHT][BOARD_WIDTH]; -``` - -其中: - -| 数值 | 含义 | -|---|---| -| 0 | 空格 | -| 1-7 | 不同类型的普通方块 | -| 8 | 特殊方块,例如爆破方块 | - -### 2.2 方块类型 - -游戏包含七种经典方块: - -| 类型 | 说明 | -|---|---| -| I | 长条方块 | -| O | 方形方块 | -| T | T 型方块 | -| S | S 型方块 | -| Z | Z 型方块 | -| J | J 型方块 | -| L | L 型方块 | - -### 2.3 玩家操作 - -| 按键 | 功能 | -|---|---| -| A / ← | 左移 | -| D / → | 右移 | -| W / ↑ | 旋转 | -| S / ↓ | 软降 | -| 空格 | 硬降 | -| C | 暂存方块 | -| P | 暂停 | - ---- - -## 3. 肉鸽成长系统设计 - -### 3.1 经验获取 - -玩家通过消行获得经验。 - -| 消除行数 | 获得经验 | -|---|---| -| 1 行 | 10 EXP | -| 2 行 | 25 EXP | -| 3 行 | 45 EXP | -| 4 行 | 80 EXP | - -### 3.2 升级规则 - -玩家经验达到当前等级需求后升级。 - -```cpp -requiredExp = 100 + level * 50; -``` - -升级时: - -1. 游戏暂停; -2. 随机生成若干强化选项; -3. 玩家选择其中一个; -4. 应用强化效果; -5. 游戏继续。 - -### 3.3 升级流程示例 - -```cpp -void levelUp() { - player.level++; - player.exp -= player.requiredExp; - player.requiredExp = 100 + player.level * 50; - - vector choices = upgradeSystem.getRandomUpgrades(player.upgradeChoiceCount); - int selected = showUpgradeChoices(choices); - upgradeSystem.applyUpgrade(choices[selected], player, board); -} -``` - ---- - -## 4. 强化系统设计 - -### 4.1 强化分类 - -| 类型 | 说明 | -|---|---| -| 得分强化 | 提高积分收益 | -| 操作强化 | 改善玩家操作体验 | -| 消行强化 | 增强消行效果 | -| 生存强化 | 降低游戏失败风险 | -| 特殊强化 | 改变游戏规则 | - ---- - -## 5. 强化内容设计 - -### 5.1 得分类强化 - -#### 1. 积分倍率 - -效果:所有得分提高 20%。 - -```cpp -player.scoreMultiplier += 0.2f; -``` - -可重复获得。 - -#### 2. 连击奖励 - -效果:连续消行时获得额外积分。 - -```cpp -player.comboBonus += 50; -``` - -当玩家连续多个方块都产生消行时,触发连击奖励。 - -#### 3. 四消强化 - -效果:一次消除 4 行时额外获得 300 分。 - -```cpp -if (linesCleared == 4) { - score += 300; -} -``` - ---- - -### 5.2 操作类强化 - -#### 4. 慢速下落 - -效果:方块自然下落速度降低 10%。 - -```cpp -player.fallSpeed *= 0.9f; -``` - -#### 5. 额外预览 - -效果:增加下一个方块预览数量。 - -```cpp -player.previewCount += 1; -``` - -#### 6. 暂存槽 - -效果:解锁 Hold 功能,允许玩家暂存当前方块。 - -```cpp -player.canHold = true; -``` - ---- - -### 5.3 消行类强化 - -#### 7. 清扫者 - -效果:每累计消除 10 行,自动清除最底部一行。 - -```cpp -if (player.totalLinesCleared % 10 == 0) { - board.clearBottomLine(); -} -``` - -#### 8. 爆破方块 - -效果:每隔一定数量的方块,生成一个特殊爆破方块。爆破方块落地后清除周围 3x3 区域。 - -```cpp -void explode(int x, int y) { - for (int dy = -1; dy <= 1; dy++) { - for (int dx = -1; dx <= 1; dx++) { - board[y + dy][x + dx] = 0; - } - } -} -``` - -#### 9. 经验强化 - -效果:消行获得经验提高 25%。 - -```cpp -player.expMultiplier += 0.25f; -``` - ---- - -### 5.4 生存类强化 - -#### 10. 最后一搏 - -效果:当玩家即将失败时,自动清除底部 3 行。每局只能触发一次。 - -```cpp -player.hasRevive = true; -``` - -#### 11. 减压 - -效果:每次升级后,自动清除当前最高的一行障碍。 - -```cpp -board.clearHighestOccupiedLine(); -``` - -#### 12. 稳定结构 - -效果:方块锁定后,有小概率自动填补附近空洞。 - -```cpp -if (rand() % 100 < 10) { - board.fillNearbyHole(); -} -``` - ---- - -### 5.5 特殊类强化 - -#### 13. 双倍成长 - -效果:之后每次升级出现 4 个选项,而不是 3 个。 - -```cpp -player.upgradeChoiceCount = 4; -``` - -#### 14. 赌徒 - -效果:选择强化时有概率获得双倍效果,也有概率无效果。 - -```cpp -int chance = rand() % 100; -if (chance < 30) { - applyUpgradeTwice(); -} else if (chance < 50) { - noEffect(); -} -``` - -#### 15. 方块改造 - -效果:降低某一种方块的出现概率,例如减少 Z 方块出现概率。 - -```cpp -blockWeight[BlockType::Z] -= 1; -``` - ---- - -## 6. 数据结构设计 - -### 6.1 方块结构 - -```cpp -struct Tetromino { - int type; - int rotation; - int x; - int y; - vector> shape; -}; -``` - -### 6.2 玩家状态 - -```cpp -struct PlayerStats { - int score = 0; - int level = 1; - int exp = 0; - int requiredExp = 100; - - int totalLinesCleared = 0; - int combo = 0; - int comboBonus = 0; - - float scoreMultiplier = 1.0f; - float expMultiplier = 1.0f; - float fallSpeed = 1.0f; - - bool canHold = false; - bool hasRevive = false; - - int previewCount = 1; - int upgradeChoiceCount = 3; -}; -``` - -### 6.3 强化结构 - -```cpp -enum class UpgradeType { - Score, - Control, - Clear, - Survival, - Special -}; - -struct Upgrade { - string name; - string description; - UpgradeType type; - int level; -}; -``` - ---- - -## 7. 计分系统设计 - -### 7.1 基础得分 - -| 消除行数 | 基础分 | -|---|---| -| 1 行 | 100 | -| 2 行 | 300 | -| 3 行 | 500 | -| 4 行 | 800 | - -### 7.2 得分计算 - -```cpp -int calculateScore(int linesCleared) { - int baseScore = 0; - - switch (linesCleared) { - case 1: baseScore = 100; break; - case 2: baseScore = 300; break; - case 3: baseScore = 500; break; - case 4: baseScore = 800; break; - default: return 0; - } - - int finalScore = static_cast(baseScore * player.scoreMultiplier); - - if (player.combo > 1) { - finalScore += player.combo * player.comboBonus; - } - - return finalScore; -} -``` - ---- - -## 8. 强化随机池设计 - -### 8.1 强化池初始化 - -```cpp -vector upgradePool; - -void initUpgradePool() { - upgradePool.push_back({ - "积分倍率", - "所有得分提高20%", - UpgradeType::Score, - 1 - }); - - upgradePool.push_back({ - "慢速下落", - "方块下落速度降低10%", - UpgradeType::Control, - 1 - }); - - upgradePool.push_back({ - "最后一搏", - "失败时自动清除底部3行,每局一次", - UpgradeType::Survival, - 1 - }); -} -``` - -### 8.2 随机抽取强化 - -```cpp -vector getRandomUpgrades(int count) { - vector result; - vector pool = upgradePool; - - random_shuffle(pool.begin(), pool.end()); - - for (int i = 0; i < count && i < pool.size(); i++) { - result.push_back(pool[i]); - } - - return result; -} -``` - ---- - -## 9. 主要类设计 - -### 9.1 Game 类 - -```cpp -class Game { -private: - Board board; - Tetromino currentBlock; - PlayerStats player; - UpgradeSystem upgradeSystem; - -public: - void init(); - void update(); - void render(); - void handleInput(); - void checkGameOver(); -}; -``` - -### 9.2 Board 类 - -```cpp -class Board { -private: - int grid[20][10]; - -public: - bool checkCollision(Tetromino block); - void lockBlock(Tetromino block); - int clearLines(); - void clearBottomLine(); - void clearHighestOccupiedLine(); - bool isGameOver(); -}; -``` - -### 9.3 UpgradeSystem 类 - -```cpp -class UpgradeSystem { -private: - vector upgradePool; - -public: - void init(); - vector getRandomUpgrades(int count); - void applyUpgrade(Upgrade upgrade, PlayerStats& player, Board& board); -}; -``` - ---- - -## 10. 游戏主流程 - -```text -初始化游戏数据 -↓ -生成当前方块和预览方块 -↓ -进入游戏主循环 -↓ -读取玩家输入 -↓ -更新方块位置 -↓ -检测碰撞 -↓ -无法下落时锁定方块 -↓ -检测并消除完整行 -↓ -计算得分和经验 -↓ -判断是否升级 -↓ -若升级,进入强化选择界面 -↓ -应用强化效果 -↓ -继续游戏 -↓ -判断是否失败 -↓ -游戏结束,显示最终得分 -``` - ---- - -## 11. 界面设计 - -### 11.1 主界面示意 - -```text -+--------------------+----------------+ -| | Score: 1200 | -| | Level: 3 | -| | EXP: 40/250 | -| | | -| 游戏棋盘 | Next: | -| | [T] [L] [I] | -| | | -| | Upgrades: | -| | 积分倍率 Lv2 | -+--------------------+----------------+ -``` - -### 11.2 升级界面示意 - -```text -请选择一个强化: - -[1] 积分倍率 -所有得分提高20% - -[2] 慢速下落 -方块下落速度降低10% - -[3] 最后一搏 -失败时自动清除底部3行,每局一次 -``` - ---- - -## 12. 模块划分 - -| 模块 | 功能 | -|---|---| -| Game | 控制游戏主循环 | -| Board | 管理棋盘数据 | -| Tetromino | 管理方块形状、旋转、移动 | -| Renderer | 负责界面绘制 | -| InputManager | 处理键盘输入 | -| ScoreSystem | 计算分数与经验 | -| UpgradeSystem | 管理强化池、升级、强化应用 | -| PlayerStats | 保存玩家成长数据 | - ---- - -## 13. 开发优先级 - -| 优先级 | 内容 | -|---|---| -| P0 | 实现基础俄罗斯方块逻辑 | -| P0 | 实现消行和计分 | -| P1 | 实现经验和升级系统 | -| P1 | 实现随机强化三选一 | -| P1 | 实现 5 到 8 个基础强化 | -| P2 | 实现特殊方块,例如爆破方块 | -| P2 | 实现 Hold、预览数量等操作强化 | -| P3 | 增加稀有度、成就、存档等扩展内容 | - ---- - -## 14. 技术实现建议 - -| 技术 | 用途 | -|---|---| -| C++ | 主体开发语言 | -| STL vector | 存储棋盘、方块、强化池 | -| enum class | 表示方块类型和强化类型 | -| class | 进行模块化封装 | -| random | 实现随机强化和随机方块 | -| SFML / SDL / Qt | 可选,用于图形界面 | - -如果课程要求较基础,可以先完成控制台版本,再根据时间增加图形界面。 - ---- - -## 15. 创新点总结 - -本项目的主要创新点包括: - -1. 在传统俄罗斯方块中加入经验和等级系统; -2. 将消行行为与角色成长绑定; -3. 每次升级随机提供强化选项; -4. 强化之间可以叠加,形成不同构筑路线; -5. 加入生存类和特殊类强化,提高策略空间; -6. 每局强化组合不同,提升重复游玩价值。 - ---- - -## 16. 结论 - -《Rogue Tetris》在经典俄罗斯方块的基础上引入类肉鸽升级机制,使游戏从单纯的消行挑战扩展为具有成长路线、随机选择和策略构筑的玩法。玩家每局都能通过不同强化组合形成不同体验,既保留了俄罗斯方块的核心规则,也体现了创新性,适合作为 C++ 课程大作业项目。 diff --git a/TODO.md b/TODO.md deleted file mode 100644 index b61bb3a..0000000 --- a/TODO.md +++ /dev/null @@ -1,176 +0,0 @@ -# Rogue Tetris TODO List - -更新时间:2026-04-25 - -当前开发主线: - -1. 整理 UI -2. 补充美术资源 -3. 调整数值 - -本清单按这个顺序组织,后续默认先做表现层,再补资源,最后做平衡。结构重构类工作只保留为支撑项,不再作为当前第一优先级。 - -## 当前已确认状态 - -- [x] 项目可以通过 `build-mingw.ps1` 正常编译 -- [x] 已有主菜单、经典模式、Rogue 模式、规则页 -- [x] 已有 Rogue HUD、等级、EXP、升级暂停、三选一升级界面 -- [x] 已有较完整的强化池、特殊方块、主动技能与反馈提示 -- [x] 已将 Rogue 主要逻辑拆到独立文件,便于后续继续整理表现层 - -## 第一阶段:整理 UI - -目标:先把现有界面统一干净,信息层级明确,布局稳定,操作反馈清楚。 - -### 1. 主菜单与规则页 - -- [ ] 统一主菜单、规则页、游戏内面板的视觉语言 -- [ ] 调整标题、正文、提示文案的字号层级 -- [ ] 检查菜单卡片间距、对齐和高亮态,避免现在的“能用但不够整” -- [ ] 规则页重新梳理信息分组 - 建议分成:基础操作、Rogue 机制、主动技能、当前版本说明 -- [ ] 精简过长文案,减少一屏内的信息拥挤 - -### 2. 游戏内 HUD - -- [ ] 统一右侧面板的留白、卡片高度和分区顺序 -- [ ] 明确 HUD 的核心信息优先级 - 建议顺序:模式 / 分数 / 等级 EXP / 当前强化摘要 / Hold / Next / 按键提示 -- [ ] 检查经典模式 HUD,避免与 Rogue HUD 风格割裂太大 -- [ ] 压缩强化摘要显示,避免后期强化太多时面板失控 -- [ ] 检查提示文本换行和截断,尤其是中文长句 - -### 3. 升级选择界面 - -- [ ] 统一升级界面中卡片的标题、分类、描述、等级标签布局 -- [ ] 明确三种状态的视觉差异 - - [ ] 普通态 - - [ ] 当前选中态 - - [ ] 诅咒态 -- [ ] 补一个真正的“不可选/不显示”处理策略,避免后续扩展时 UI 语义混乱 -- [ ] 调整升级卡片高度和内部间距,减少文本拥挤 -- [ ] 检查 3 项 / 5 项升级时的布局一致性 -- [ ] 检查升级结束返回游戏时的反馈是否足够清楚 - -### 4. 浮层与反馈 - -- [ ] 统一暂停、失败、升级三类覆盖层的边框、底色和标题样式 -- [ ] 调整反馈浮层的位置、尺寸和显示时间 -- [ ] 统一提示文案格式 - 建议统一成:事件标题 + 核心收益/变化 -- [ ] 检查连续触发多种效果时,反馈是否被覆盖得过快 - -### 5. 当前阶段完成标准 - -- [ ] 主菜单、规则页、游戏 HUD、升级界面视觉风格一致 -- [ ] 中文排版、字号、留白、对齐达到可继续细化的基线 -- [ ] 玩家在不看代码的情况下能读懂主要状态和操作反馈 - -## 第二阶段:补充美术资源 - -目标:把现在代码里硬绘制的占位 UI,逐步替换为统一命名、统一风格的占位资源。 - -### 1. 建立资源规范 - -- [ ] 新建 `assets/ui/` 目录 -- [ ] 统一占位资源命名 - 例如: - - [ ] `placeholder_panel_*` - - [ ] `placeholder_button_*` - - [ ] `placeholder_upgrade_icon_*` - - [ ] `placeholder_overlay_*` -- [ ] 明确哪些资源继续用 GDI 绘制,哪些改成资源加载 - -### 2. 先补最需要的资源 - -- [ ] 主菜单卡片背景 / 边框占位资源 -- [ ] 规则页分区标题装饰 -- [ ] HUD 面板边框与卡片底图 -- [ ] 升级卡片图标占位资源 -- [ ] 升级卡片背景与诅咒态装饰 -- [ ] 浮层弹窗底图 -- [ ] 按钮 / 键位提示底图 - -### 3. 背景与氛围资源 - -- [ ] 评估是否接入现有 `background.bmp` -- [ ] 如果保留纯色 / 渐变背景,则补一版更统一的背景方案 -- [ ] 评估 `background.wav` 是否接入 -- [ ] 如果暂时不用音频,就在文档里明确标记为“资源已存在但未接入” - -### 4. 资源接入与替换 - -- [ ] 给升级卡片图标留出稳定的资源映射方式 -- [ ] 给菜单 / HUD / 浮层补最小资源加载接口 -- [ ] 避免资源路径和业务逻辑耦合过深 -- [ ] 替换时保证没有资源也能回退到当前占位绘制 - -### 5. 当前阶段完成标准 - -- [ ] UI 主要元素已经有统一的占位资源体系 -- [ ] `assets` 目录不再只是零散图标和背景图 -- [ ] 后续替换正式美术时不需要重写界面结构 - -## 第三阶段:调整数值 - -目标:在界面和资源相对稳定后,再系统调整 Rogue 的节奏、强度和成长曲线。 - -### 1. 等级与成长节奏 - -- [ ] 调整等级需求曲线 -- [ ] 调整 EXP 获取速度 -- [ ] 调整升级频率,避免前期过快、后期过慢 - -### 2. 掉落与操作节奏 - -- [ ] 调整 Rogue 基础下落速度 -- [ ] 检查 `slow_fall`、`time_dilation`、`fever`、`high_pressure`、`extreme_player` 的叠加结果 -- [ ] 避免速度效果叠加后出现明显失控或无感 - -### 3. 强化池权重 - -- [ ] 调整基础成长类强化的出现频率 -- [ ] 调整主动技能类强化的出现频率 -- [ ] 调整进化类强化的前置条件和权重 -- [ ] 减少明显无效或当前局收益过低的选项 -- [ ] 检查 3 选 1 / 5 选 2 时的整体体验 - -### 4. 特殊机制强度 - -- [ ] 调整爆破、激光、十字、彩虹方块的触发频率 -- [ ] 调整黑洞、清屏炸弹、空中换形的获得与使用强度 -- [ ] 调整最后一搏、终末清场的保命收益 -- [ ] 检查特殊机制是否破坏基础俄罗斯方块节奏 - -### 5. 回归检查 - -- [ ] 经典模式基础体验不受 Rogue 调整影响 -- [ ] Rogue 模式升级、选择、恢复流程稳定 -- [ ] 特殊方块落地后结算顺序正确 -- [ ] 主动技能在暂停、升级、失败状态下不会误触发 -- [ ] 多次升级、多选升级、诅咒升级组合下状态不乱 - -### 6. 当前阶段完成标准 - -- [ ] Rogue 模式的升级节奏稳定 -- [ ] 强化不再明显偏科或频繁出废选项 -- [ ] 特殊机制保留爽感,但不压垮基础玩法 - -## 配套支持项 - -这些事项不是当前主线,但在推进 UI / 资源 / 数值时需要同步维护。 - -- [ ] `README.md` 按当前版本重写 -- [ ] 补一份简短人工回归清单文档 -- [ ] 继续维持 Rogue 逻辑分文件管理,避免重新塞回单文件 -- [ ] 如果后续 UI 改动开始受零散全局变量影响,再启动 `PieceState` 抽取 - -## 暂不优先处理 - -- [ ] 现在先不要继续大规模新增强化种类 -- [ ] 现在先不要优先做深度结构重构 -- [ ] 现在先不要先调数值再返工 UI - -## 一句话总结 - -接下来先把界面整理顺,再把占位资源补齐,最后在稳定表现层上做数值和平衡。 diff --git a/assets/video/video.avi b/assets/video/video.avi new file mode 100644 index 0000000..81d8683 Binary files /dev/null and b/assets/video/video.avi differ diff --git a/list.md b/report/list.md similarity index 100% rename from list.md rename to report/list.md diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 5f7ded8..bb9ee10 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -202,6 +202,7 @@ extern bool gameOverFlag; extern bool suspendFlag; extern bool targetFlag; extern bool bgmEnabled; +extern bool reviveAvailable; extern int workRegion[20][10]; extern Point point; extern Point target; @@ -241,6 +242,8 @@ void ComputeTarget(); void Restart(); void StartGameWithMode(int mode); void ReturnToMainMenu(); +void ReviveAfterVideo(); +void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks); void OpenRulesScreen(); void OpenUpgradeMenu(); void ConfirmUpgradeSelection(); diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index 3a04a57..29cbf3b 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "Tetris.h" #include +#include #define MAX_LOADSTRING 100 #define GAME_TIMER_ID 1 @@ -16,11 +17,17 @@ bool bgmEnabled = true; static bool bgmPlaying = false; static bool bgmUsingMci = false; static constexpr const wchar_t* kBgmAlias = L"TereisBgm"; +static constexpr const wchar_t* kReviveVideoAlias = L"TereisReviveVideo"; ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); +static std::wstring BuildAssetPath(const wchar_t* relativePath); +static std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath); +static bool FileExists(const std::wstring& path); +static void StopBackgroundMusic(); +static void StartBackgroundMusic(); static void ResetGameTimer(HWND hWnd) { @@ -28,6 +35,90 @@ static void ResetGameTimer(HWND hWnd) SetTimer(hWnd, GAME_TIMER_ID, currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL, nullptr); } +static bool PlayReviveVideo(HWND hWnd) +{ + std::wstring videoPath = BuildAssetPath(L"assets\\video\\video.avi"); + if (!FileExists(videoPath)) + { + videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.avi"); + } + if (!FileExists(videoPath)) + { + videoPath = BuildAssetPath(L"assets\\video\\video.mp4"); + } + if (!FileExists(videoPath)) + { + videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.mp4"); + } + if (!FileExists(videoPath)) + { + return false; + } + + bool shouldResumeBgm = bgmEnabled; + if (bgmPlaying) + { + StopBackgroundMusic(); + } + + auto tryPlayWithMci = [&](bool forceMpegVideo) -> bool + { + mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr); + + std::wstring openCommand = L"open \"" + videoPath + L"\" "; + if (forceMpegVideo) + { + openCommand += L"type mpegvideo "; + } + openCommand += L"alias "; + openCommand += kReviveVideoAlias; + + if (mciSendStringW(openCommand.c_str(), nullptr, 0, hWnd) != 0) + { + return false; + } + + std::wstring playCommand = std::wstring(L"play ") + kReviveVideoAlias + L" fullscreen wait"; + MCIERROR playResult = mciSendStringW(playCommand.c_str(), nullptr, 0, hWnd); + mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr); + return playResult == 0; + }; + + bool played = tryPlayWithMci(true); + if (!played) + { + played = tryPlayWithMci(false); + } + + if (!played) + { + SHELLEXECUTEINFOW shellInfo = {}; + shellInfo.cbSize = sizeof(shellInfo); + shellInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + shellInfo.hwnd = hWnd; + shellInfo.lpVerb = L"open"; + shellInfo.lpFile = videoPath.c_str(); + shellInfo.nShow = SW_SHOWNORMAL; + + if (ShellExecuteExW(&shellInfo)) + { + if (shellInfo.hProcess != nullptr) + { + WaitForSingleObject(shellInfo.hProcess, INFINITE); + CloseHandle(shellInfo.hProcess); + } + played = true; + } + } + + if (shouldResumeBgm) + { + StartBackgroundMusic(); + } + + return played; +} + static std::wstring BuildAssetPath(const wchar_t* relativePath) { wchar_t modulePath[MAX_PATH] = {}; @@ -729,6 +820,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; } + if (gameOverFlag && reviveAvailable && wParam == 'V') + { + if (PlayReviveVideo(hWnd)) + { + ReviveAfterVideo(); + ResetGameTimer(hWnd); + } + else + { + SetFeedbackMessage(_T("视频播放失败"), _T("无法打开复活视频,复活机会未消耗。"), 14); + } + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + if (gameOverFlag || suspendFlag) { break; diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 4b563d7..4bd9591 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -9,6 +9,7 @@ int tScore = 0; bool gameOverFlag = false; bool suspendFlag = false; bool targetFlag = false; +bool reviveAvailable = false; int workRegion[20][10] = { 0 }; Point point = { 0, 0 }; Point target = { 0, 0 }; @@ -1160,6 +1161,7 @@ void Restart() gameOverFlag = false; suspendFlag = false; targetFlag = true; + reviveAvailable = true; currentFallInterval = 500; ResetPlayerStats(classicStats, false); @@ -1194,6 +1196,45 @@ void Restart() ComputeTarget(); } +void ReviveAfterVideo() +{ + if (!gameOverFlag || !reviveAvailable) + { + return; + } + + reviveAvailable = false; + gameOverFlag = false; + suspendFlag = false; + currentScreen = SCREEN_PLAYING; + + int playableHeight = GetRoguePlayableHeight(); + int rowsToClear = playableHeight / 3; + if (rowsToClear < 5) + { + rowsToClear = 5; + } + + for (int y = 0; y < rowsToClear && y < playableHeight; y++) + { + for (int x = 0; x < nGameWidth; x++) + { + workRegion[y][x] = 0; + } + } + + type = ConsumeNextType(); + nType = nextTypes[0]; + state = 0; + holdUsedThisTurn = false; + RollCurrentPieceSpecialFlags(true); + point = GetSpawnPoint(type); + target = point; + ComputeTarget(); + + SetFeedbackMessage(_T("复活成功"), _T("已清理顶部空间,本局复活机会已用完。"), 14); +} + void StartGameWithMode(int mode) { currentMode = mode; diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 6a126e3..858bb3e 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -1663,7 +1663,14 @@ void TDrawScreen(HDC hdc, HWND hWnd) { DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, bodyFont); - DrawText(hdc, _T("按 R 重新挑战\r\n或按 M 返回菜单"), -1, &tipRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); + DrawText( + hdc, + reviveAvailable + ? _T("按 V 看视频复活(仅一次)\r\n按 R 重新挑战 或按 M 返回菜单") + : _T("按 R 重新挑战\r\n或按 M 返回菜单"), + -1, + &tipRect, + DT_CENTER | DT_VCENTER | DT_WORDBREAK); } }