添加看视频复活

This commit is contained in:
2026-04-26 14:19:27 +08:00
parent aa3d4d3945
commit 96ab175877
8 changed files with 158 additions and 801 deletions
-624
View File
@@ -1,624 +0,0 @@
# 《Rogue Tetris》功能设计文档
## 1. 项目概述
本项目基于经典俄罗斯方块玩法,加入类肉鸽成长系统。玩家通过消除方块获得积分与经验,达到一定条件后触发升级,从随机出现的强化选项中选择一项,形成不同流派与策略组合。
游戏核心循环为:
```text
下落方块 → 摆放 → 消行 → 获得积分/经验 → 升级 → 选择强化 → 提升能力 → 继续挑战
```
项目目标是在保留俄罗斯方块基础规则的同时,增强随机性、成长性和可重复游玩价值。
---
## 2. 基础玩法设计
### 2.1 棋盘设计
游戏区域采用标准俄罗斯方块棋盘:
```cpp
const int BOARD_WIDTH = 10;
const int BOARD_HEIGHT = 20;
```
棋盘使用二维数组或 `vector<vector<int>>` 存储。
```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<Upgrade> 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<vector<int>> 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<int>(baseScore * player.scoreMultiplier);
if (player.combo > 1) {
finalScore += player.combo * player.comboBonus;
}
return finalScore;
}
```
---
## 8. 强化随机池设计
### 8.1 强化池初始化
```cpp
vector<Upgrade> 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<Upgrade> getRandomUpgrades(int count) {
vector<Upgrade> result;
vector<Upgrade> 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<Upgrade> upgradePool;
public:
void init();
vector<Upgrade> 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++ 课程大作业项目。
-176
View File
@@ -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
## 一句话总结
接下来先把界面整理顺,再把占位资源补齐,最后在稳定表现层上做数值和平衡。
Binary file not shown.
View File
+3
View File
@@ -202,6 +202,7 @@ extern bool gameOverFlag;
extern bool suspendFlag; extern bool suspendFlag;
extern bool targetFlag; extern bool targetFlag;
extern bool bgmEnabled; extern bool bgmEnabled;
extern bool reviveAvailable;
extern int workRegion[20][10]; extern int workRegion[20][10];
extern Point point; extern Point point;
extern Point target; extern Point target;
@@ -241,6 +242,8 @@ void ComputeTarget();
void Restart(); void Restart();
void StartGameWithMode(int mode); void StartGameWithMode(int mode);
void ReturnToMainMenu(); void ReturnToMainMenu();
void ReviveAfterVideo();
void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks);
void OpenRulesScreen(); void OpenRulesScreen();
void OpenUpgradeMenu(); void OpenUpgradeMenu();
void ConfirmUpgradeSelection(); void ConfirmUpgradeSelection();
+106
View File
@@ -1,6 +1,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "Tetris.h" #include "Tetris.h"
#include <string> #include <string>
#include <shellapi.h>
#define MAX_LOADSTRING 100 #define MAX_LOADSTRING 100
#define GAME_TIMER_ID 1 #define GAME_TIMER_ID 1
@@ -16,11 +17,17 @@ bool bgmEnabled = true;
static bool bgmPlaying = false; static bool bgmPlaying = false;
static bool bgmUsingMci = false; static bool bgmUsingMci = false;
static constexpr const wchar_t* kBgmAlias = L"TereisBgm"; static constexpr const wchar_t* kBgmAlias = L"TereisBgm";
static constexpr const wchar_t* kReviveVideoAlias = L"TereisReviveVideo";
ATOM MyRegisterClass(HINSTANCE hInstance); ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int); BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(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) 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); 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) static std::wstring BuildAssetPath(const wchar_t* relativePath)
{ {
wchar_t modulePath[MAX_PATH] = {}; wchar_t modulePath[MAX_PATH] = {};
@@ -729,6 +820,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
break; 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) if (gameOverFlag || suspendFlag)
{ {
break; break;
+41
View File
@@ -9,6 +9,7 @@ int tScore = 0;
bool gameOverFlag = false; bool gameOverFlag = false;
bool suspendFlag = false; bool suspendFlag = false;
bool targetFlag = false; bool targetFlag = false;
bool reviveAvailable = false;
int workRegion[20][10] = { 0 }; int workRegion[20][10] = { 0 };
Point point = { 0, 0 }; Point point = { 0, 0 };
Point target = { 0, 0 }; Point target = { 0, 0 };
@@ -1160,6 +1161,7 @@ void Restart()
gameOverFlag = false; gameOverFlag = false;
suspendFlag = false; suspendFlag = false;
targetFlag = true; targetFlag = true;
reviveAvailable = true;
currentFallInterval = 500; currentFallInterval = 500;
ResetPlayerStats(classicStats, false); ResetPlayerStats(classicStats, false);
@@ -1194,6 +1196,45 @@ void Restart()
ComputeTarget(); 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) void StartGameWithMode(int mode)
{ {
currentMode = mode; currentMode = mode;
+8 -1
View File
@@ -1663,7 +1663,14 @@ void TDrawScreen(HDC hdc, HWND hWnd)
{ {
DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SelectObject(hdc, bodyFont); 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);
} }
} }