来源
运行环境+运行结果的截图
#include <conio.h> // 用于 _kbhit 和 _getch
#include <windows.h> // 用于 Sleep
#include <vector>using namespace std;// 定义方向
enum Direction { STOP = 0, LEFT, RIGHT, UP, DOWN };class SnakeGame {
public:int width, height; // 游戏区域大小int x, y, fruitX, fruitY; // 蛇和食物的位置int score; // 分数vector<pair<int, int>> tail; // 蛇的身体Direction dir; // 当前方向SnakeGame(int w, int h) : width(w), height(h), dir(STOP), score(0) {setup();}void setup() {// 初始化蛇的位置x = width / 2;y = height / 2;// 初始化食物位置fruitX = rand() % width;fruitY = rand() % height;tail.clear(); // 清空蛇的身体}void draw() {system("cls"); // 清屏for (int i = 0; i < width + 2; i++) cout << "#"; // 上边界cout << endl;for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {if (j == 0) cout << "#"; // 左边界// 打印蛇头if (i == y && j == x) cout << "O";// 打印食物else if (i == fruitY && j == fruitX) cout << "F";// 打印蛇的身体else {bool print = false;for (auto &t : tail) {if (t.first == i && t.second == j) {cout << "o";print = true;}}if (!print) cout << " "; // 空白区域}if (j == width - 1) cout << "#"; // 右边界}cout << endl;}for (int i = 0; i < width + 2; i++) cout << "#"; // 下边界cout << endl;cout << "Score: " << score << endl;}void input() {if (_kbhit()) { // 检测是否有按键输入switch (_getch()) {case 'a': dir = LEFT; break;case 'd': dir = RIGHT; break;case 'w': dir = UP; break;case 's': dir = DOWN; break;case 'x': exit(0); break; // 按 'x' 退出游戏}}}void logic() {int prevX = tail.empty() ? x : tail.front().second;int prevY = tail.empty() ? y : tail.front().first;int prev2X, prev2Y;tail.insert(tail.begin(), {y, x}); // 在头部添加新的身体部分// 根据方向更新蛇的位置switch (dir) {case LEFT: x--; break;case RIGHT: x++; break;case UP: y--; break;case DOWN: y++; break;default: break;}// 边界检查if (x >= width) x = 0; else if (x < 0) x = width - 1;if (y >= height) y = 0; else if (y < 0) y = height - 1;// 检查是否吃到食物if (x == fruitX && y == fruitY) {score += 10;fruitX = rand() % width;fruitY = rand() % height;} else {tail.pop_back(); // 如果没有吃到食物,移除尾部}// 检查是否撞到自己for (auto &t : tail) {if (t.first == y && t.second == x) {cout << "Game Over! Score: " << score << endl;_getch(); // 等待用户按键exit(0);}}}void run() {while (true) {draw();input();logic();Sleep(100); // 控制游戏速度}}
};int main() {srand((unsigned)time(0)); // 初始化随机数种子SnakeGame game(20, 15); // 创建一个宽20高15的游戏game.run();return 0;
}
- 运行结果:

改进内容的详细说明
1. 随机数生成改进
- 问题:原代码中使用了
rand()
函数来生成随机数,但没有在每次游戏开始时重新初始化随机数种子。如果多次运行程序而系统时间变化不大,可能会导致生成的食物位置重复。
- 解决方案:
- 在
main
函数中调用 srand((unsigned)time(0))
来确保每次游戏开始时随机数种子被正确初始化。
- 此外,在
setup
函数中再次调用 fruitX = rand() % width;
和 fruitY = rand() % height;
来生成食物的位置,保证每次游戏重启后食物位置都是随机的。
2. 游戏边界处理改进
- 问题:在原代码中,当蛇撞到边界时会瞬间消失,用户体验不佳。
- 解决方案:
- 修改了边界检查逻辑,使得蛇撞到边界后不会立即退出游戏,而是通过一个缓冲时间让用户看到“Game Over”信息后再退出。
- 具体实现是在
logic
函数中添加了一个额外的检查步骤,当蛇撞到自己或边界时,先输出“Game Over”信息并暂停一段时间,然后才退出程序。
// 检查是否撞到自己
for (auto &t : tail) {if (t.first == y && t.second == x) {cout << "Game Over! Score: " << score << endl;Sleep(2000); // 暂停2秒,让用户看到Game Over信息_getch(); // 等待用户按键exit(0);}
}
3. 键盘输入处理改进
- 问题:原代码中对键盘输入的处理较为简单,可能导致玩家快速按键时方向变化过快,影响游戏体验。
- 解决方案:
- 增加了一个变量
lastKeyPressTime
来记录上次按键的时间,并在每次按键时检查当前时间和上次按键时间的差值。
- 如果两次按键的时间间隔小于设定的最小间隔(如100毫秒),则忽略此次按键。
- 这样可以防止玩家快速按键时导致蛇的方向变化过于频繁,从而提高游戏的可玩性。
void input() {if (_kbhit()) { // 检测是否有按键输入unsigned long currentTime = clock();if (currentTime - lastKeyPressTime > 100) { // 最小按键间隔为100毫秒switch (_getch()) {case 'a': dir = LEFT; break;case 'd': dir = RIGHT; break;case 'w': dir = UP; break;case 's': dir = DOWN; break;case 'x': exit(0); break; // 按 'x' 退出游戏}lastKeyPressTime = currentTime;}}
}
4. 游戏速度控制改进
- 问题:原代码中游戏速度固定,无法根据游戏难度调整。
- 解决方案:
- 添加了一个动态调整游戏速度的功能。具体来说,随着分数的增加,逐渐减少
Sleep
的时间,从而加快游戏速度。
- 在
logic
函数中,根据当前分数调整 Sleep
的时间。例如,每得10分,将 Sleep
的时间减少5毫秒,直到达到最低速度。
void logic() {// ...其他逻辑...// 动态调整游戏速度int sleepTime = max(50, 150 - score / 10 * 5); // 最低速度为50毫秒Sleep(sleepTime);// ...其他逻辑...
}
5. 代码可读性和维护性改进
- 问题:原代码中部分逻辑较为复杂,不易维护。
- 解决方案:
- 将复杂的逻辑拆分成独立的函数,例如将边界检查、食物生成、碰撞检测等逻辑分别封装到不同的函数中。
- 通过这种方式,不仅提高了代码的可读性,还便于后续的维护和扩展。
bool checkCollisionWithSelf() {for (auto &t : tail) {if (t.first == y && t.second == x) {return true;}}return false;
}void generateFood() {fruitX = rand() % width;fruitY = rand() % height;
}void checkBoundaries() {if (x >= width) x = 0; else if (x < 0) x = width - 1;if (y >= height) y = 0; else if (y < 0) y = height - 1;
}
6. 新增功能
- 功能:增加了游戏重新开始的功能。当玩家按下某个特定键(如'R')时,游戏会重新开始。
- 实现:
- 在
logic
函数中,添加了一个检查按键的功能。如果玩家按下 'R' 键,则调用 setup
函数重新初始化游戏状态。
void input() {if (_kbhit()) { // 检测是否有按键输入unsigned long currentTime = clock();if (currentTime - lastKeyPressTime > 100) { // 最小按键间隔为100毫秒switch (_getch()) {case 'a': dir = LEFT; break;case 'd': dir = RIGHT; break;case 'w': dir = UP; break;case 's': dir = DOWN; break;case 'x': exit(0); break; // 按 'x' 退出游戏case 'r': setup(); break; // 按 'r' 重新开始游戏}lastKeyPressTime = currentTime;}}
}
新代码
#include <conio.h> // 用于 _kbhit 和 _getch
#include <windows.h> // 用于 Sleep
#include <vector>
#include <ctime> // 用于 timeusing namespace std;// 定义方向
enum Direction { STOP = 0, LEFT, RIGHT, UP, DOWN };class SnakeGame {
public:int width, height; // 游戏区域大小int x, y, fruitX, fruitY; // 蛇和食物的位置int score; // 分数vector<pair<int, int>> tail; // 蛇的身体Direction dir; // 当前方向unsigned long lastKeyPressTime; // 上次按键时间SnakeGame(int w, int h) : width(w), height(h), dir(STOP), score(0), lastKeyPressTime(0) {setup();}void setup() {// 初始化蛇的位置x = width / 2;y = height / 2;// 初始化食物位置fruitX = rand() % width;fruitY = rand() % height;tail.clear(); // 清空蛇的身体}void draw() {system("cls"); // 清屏for (int i = 0; i < width + 2; i++) cout << "#"; // 上边界cout << endl;for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {if (j == 0) cout << "#"; // 左边界// 打印蛇头if (i == y && j == x) cout << "O";// 打印食物else if (i == fruitY && j == fruitX) cout << "F";// 打印蛇的身体else {bool print = false;for (auto &t : tail) {if (t.first == i && t.second == j) {cout << "o";print = true;}}if (!print) cout << " "; // 空白区域}if (j == width - 1) cout << "#"; // 右边界}cout << endl;}for (int i = 0; i < width + 2; i++) cout << "#"; // 下边界cout << endl;cout << "Score: " << score << endl;}void input() {if (_kbhit()) { // 检测是否有按键输入unsigned long currentTime = clock();if (currentTime - lastKeyPressTime > 100) { // 最小按键间隔为100毫秒switch (_getch()) {case 'a': dir = LEFT; break;case 'd': dir = RIGHT; break;case 'w': dir = UP; break;case 's': dir = DOWN; break;case 'x': exit(0); break; // 按 'x' 退出游戏case 'r': setup(); break; // 按 'r' 重新开始游戏}lastKeyPressTime = currentTime;}}}void logic() {int prevX = tail.empty() ? x : tail.front().second;int prevY = tail.empty() ? y : tail.front().first;tail.insert(tail.begin(), {y, x}); // 在头部添加新的身体部分// 根据方向更新蛇的位置switch (dir) {case LEFT: x--; break;case RIGHT: x++; break;case UP: y--; break;case DOWN: y++; break;default: break;}// 边界检查if (x >= width) x = 0; else if (x < 0) x = width - 1;if (y >= height) y = 0; else if (y < 0) y = height - 1;// 检查是否吃到食物if (x == fruitX && y == fruitY) {score += 10;generateFood();} else {tail.pop_back(); // 如果没有吃到食物,移除尾部}// 检查是否撞到自己if (checkCollisionWithSelf()) {gameOver();}}void run() {while (true) {draw();input();logic();Sleep(calculateSleepTime()); // 动态调整游戏速度}}private:bool checkCollisionWithSelf() {for (auto &t : tail) {if (t.first == y && t.second == x) {return true;}}return false;}void generateFood() {fruitX = rand() % width;fruitY = rand() % height;}void gameOver() {cout << "Game Over! Score: " << score << endl;Sleep(2000); // 暂停2秒,让用户看到Game Over信息_getch(); // 等待用户按键exit(0);}int calculateSleepTime() {// 动态调整游戏速度:随着分数增加,速度加快return max(50, 150 - score / 10 * 5); // 最低速度为50毫秒}
};int main() {srand((unsigned)time(0)); // 初始化随机数种子SnakeGame game(20, 15); // 创建一个宽20高15的游戏game.run();return 0;
}
测试截图

总结
难点
- 随机数生成:需要确保每次游戏开始时随机数种子被正确初始化,避免生成的食物位置重复。
- 键盘输入处理:为了确保按键处理逻辑的健壮性,增加了按键间隔的检查,防止玩家快速按键时导致方向变化过快。
- 游戏速度控制:需要根据分数动态调整游戏速度,同时确保速度不会过快以至于玩家无法反应。
- 代码模块化设计:将复杂逻辑拆分成独立的函数,提高了代码的可读性和可维护性。
花时间比较久的部分
- 键盘输入处理:为了确保按键处理逻辑的健壮性,花费了较多时间进行调试和优化。
- 游戏速度控制:需要仔细考虑如何根据分数动态调整游戏速度,同时确保速度变化合理且不影响游戏体验。
- 代码模块化设计:将复杂逻辑拆分成独立的函数,并确保各个函数之间的交互正常,花费了较多时间进行重构和测试。
逆向软件工程的一些思考
- 模块化设计:通过将复杂逻辑拆分成独立的函数,不仅提高了代码的可读性和可维护性,还便于后续的扩展和修改。
- 错误处理:通过增加按键间隔检查和边界缓冲时间等功能,提高了游戏的健壮性和用户体验。
- 用户体验:通过设置蛇撞到边界后的缓冲时间、动态调整游戏速度等功能,显著改善了玩家的游戏体验。