wordpress 导航站模板,wordpress 被黑后,嘉兴海盐县城乡建设局网站,淄博建企业网站#x1f680;欢迎互三#x1f449;#xff1a;程序猿方梓燚 #x1f48e;#x1f48e; #x1f680;关注博主#xff0c;后期持续更新系列文章 #x1f680;如果有错误感谢请大家批评指出#xff0c;及时修改 #x1f680;感谢大家点赞#x1f44d;收藏⭐评论✍ 一、… 欢迎互三程序猿方梓燚 关注博主后期持续更新系列文章 如果有错误感谢请大家批评指出及时修改 感谢大家点赞收藏⭐评论✍ 一、概述 本文对给定的贪吃蛇游戏代码进行详细分析。该游戏使用 C语言编写通过控制台界面实现了经典的贪吃蛇游戏玩法包括登录、注册、游戏介绍、游戏操作和计分等功能。 二、功能模块分析
一基础模块
常量定义 MAX定义了蛇身最大长度为 100。 UP、DOWN、LEFT、RIGHT分别代表蛇的上、下、左、右四个方向。 MOVING表示蛇正在移动的状态。 STOP表示蛇停止的状态。 全局变量 hMain_Out控制台输出句柄。 hMain_In控制台输入句柄。 NewPos[MAX]用于存储蛇身新位置的数组。 Food食物的位置结构体。 Wall墙壁的范围结构体。 count、grade、level、amount、speed分别用于记录蛇的移动步数、分数、难度等级、食物数量和移动速度。 isPaused表示游戏是否暂停的布尔变量。 基础函数 HideTheCursor()隐藏光标。通过获取控制台光标信息将其可见性设置为 FALSE实现隐藏光标的效果。 basic()游戏的基础功能模块包括菜单选择登录、注册、游戏介绍、退出根据用户选择调用相应的函数。 显示菜单选项让用户选择登录、注册、游戏介绍或退出。 根据用户选择调用相应的函数如login()、registerUser()、gameIntroduction()或out()。 out()退出游戏显示感谢信息并逐步退出。 显示感谢信息和退出提示。 使用循环和延迟来模拟逐步退出的效果。 login()实现用户登录功能检查用户名和密码是否正确。 提示用户输入用户名和密码。 检查输入是否为空如果为空则显示错误信息和提示框。 读取用户信息文件对比输入的用户名和密码是否与文件中的一致。 registerUser()用户注册功能将新用户的用户名和密码保存到文件中。 提示用户输入新用户名和密码。 检查输入是否为空如果为空则显示错误信息和提示框。 打开用户信息文件将新用户名和密码写入文件。 gameIntroduction()展示游戏介绍界面介绍游戏规则一段时间后自动返回大厅。 显示游戏介绍信息和规则说明。 使用循环和延迟来模拟自动返回大厅的效果。
二游戏模块
初始化函数 Init(Body b)初始化蛇的初始位置、长度、方向等设置控制台输出句柄创建游戏墙壁和食物并显示游戏信息。 设置蛇的初始长度为 3初始方向为向右。 获取控制台输出句柄和输入句柄。 创建游戏墙壁通过获取控制台屏幕缓冲区信息确定墙壁的范围并在边界绘制墙壁。 随机生成食物的位置确保食物位置在有效范围内且坐标为偶数。 显示游戏信息包括分数和难度等级。 输出函数 Print(const Body b)在控制台输出蛇的位置以圆形符号 “●” 表示蛇身。 使用循环遍历蛇身位置数组设置控制台光标位置输出蛇身符号。 Print(int x, int y)在指定坐标位置输出特定字符用于创建墙壁和食物。 设置控制台光标位置输出指定字符。 移动函数 Move(Body b)实现蛇的移动逻辑包括判断是否碰到墙壁或食物更新蛇的位置增加蛇身长度等。如果蛇碰到墙壁则显示游戏结束信息并重新开始游戏如果碰到食物则增加分数、蛇身长度和食物数量重新生成食物。 显示游戏信息包括分数和难度等级。 判断蛇是否碰到墙壁如果碰到墙壁则显示游戏结束信息并重新开始游戏。 判断蛇是否碰到食物如果碰到食物则增加分数、蛇身长度和食物数量清除食物位置并重新生成食物。 根据蛇的状态和方向更新蛇的位置。如果蛇处于停止状态根据方向直接移动蛇身如果蛇处于移动状态根据方向逐步移动蛇身。 输出蛇的新位置。 GetDirection(Body b)根据用户按键输入获取蛇的移动方向。 判断用户是否按下上、下、左、右方向键如果按下则改变蛇的方向。 TurnRound(int d, Body b)根据给定方向改变蛇的移动方向。 根据给定方向和蛇的当前方向判断是否可以改变方向。如果可以改变方向则复制蛇的当前位置到临时数组更新蛇头位置设置蛇的新方向和移动状态。 PosCopy(Body b, Pos NewPos[])复制蛇的当前位置到一个临时数组中。 遍历蛇身位置数组将每个位置复制到临时数组中。 MoveBody(Body b)更新蛇身的位置根据临时数组中的位置信息进行移动。 遍历蛇身位置数组从蛇尾开始将每个位置更新为前一个位置的值。最后增加移动步数计数并复制蛇的当前位置到临时数组。 辅助函数 Clean(int x, int y)清除指定坐标位置的字符用于移动蛇身时清除旧位置的显示。 设置控制台光标位置输出空格字符清除指定位置的显示。 HideCursor()隐藏控制台光标。通过获取控制台光标信息将其可见性设置为 FALSE实现隐藏光标的效果。 CreateWall()创建游戏墙壁在控制台边界绘制墙壁。 获取控制台屏幕缓冲区信息确定墙壁的范围。 使用循环在墙壁边界输出特定字符绘制墙壁。 CreateFood()随机生成食物的位置并在控制台输出食物。 使用随机数生成器和时间种子生成随机坐标。 确保食物位置在有效范围内且坐标为偶数。 在指定位置输出食物符号。 IsKnock_Food(const Body b)判断蛇是否碰到食物。 比较蛇头的位置和食物的位置如果相同则返回 true否则返回 false。 IsKnock_Wall(const Body b)判断蛇是否碰到墙壁或自身。 检查蛇头的位置是否在墙壁范围内或与自身其他部分重叠如果是则返回 true否则返回 false。 ShowInfo()在控制台显示游戏信息包括分数和难度等级。 设置控制台光标位置输出分数和难度等级信息。 AddBody(Body b)增加蛇的长度根据蛇的当前方向在蛇尾添加一个新的位置。 根据蛇的当前方向在蛇尾添加一个新的位置并增加蛇的长度。
三主函数
main()函数作为程序的入口点设置控制台模式和标题调用basic()函数进入游戏的基础功能模块。
三、代码结构分析
代码结构清晰通过多个函数实现不同的功能模块易于理解和维护。 使用结构体Body来表示蛇的状态包括位置、长度、方向和状态等信息方便对蛇进行操作和管理。 利用全局变量来存储游戏中的一些状态信息如分数、等级、食物位置等方便在不同函数中访问和修改。 游戏的逻辑主要在game()函数中实现通过不断循环和调用其他函数来实现蛇的移动、判断碰撞、更新游戏状态等功能。 代码中使用了 Windows API 来获取控制台句柄、设置光标位置和隐藏光标等增强了游戏的控制台界面效果。
四、代码详解
一、头文件详解
#include iostream
#include string
#include fstream
#include windows.h
#include time.h
#include stdio.hiostream提供输入输出流的功能用于在控制台进行输入输出操作。 string用于处理字符串操作。 fstream用于文件输入输出操作在这个游戏中用于读取和写入用户信息文件。 windows.h提供了与 Windows 操作系统相关的功能如获取控制台句柄、设置光标位置等。 time.h用于获取时间为随机数生成提供种子。 stdio.h提供标准输入输出函数。
二、全局变量详解
#define MAX 100
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
#define MOVING 5
#define STOP 0HANDLE hMain_Out NULL;
HANDLE hMain_In NULL;struct Pos {int x;int y;
};struct Body {int state;int len;int Direction;Pos pos[MAX];
};Pos NewPos[MAX];
Pos Food;
SMALL_RECT Wall;
int count 0;
int grade 0;
int level 1;
int amount 0;
int speed 200;
bool isPaused false;MAX定义蛇身最大长度为 100。 UP、DOWN、LEFT、RIGHT分别代表蛇的上、下、左、右四个方向用整数表示方便在代码中进行判断和操作。 MOVING和STOP表示蛇的移动和停止状态。 hMain_Out和hMain_In分别是控制台输出句柄和输入句柄用于在控制台进行输出和获取输入。 struct Pos定义了一个表示位置的结构体包含两个整数成员x和y分别表示横坐标和纵坐标。 struct Body定义了一个表示蛇的结构体包含蛇的状态state、长度len、方向Direction和位置数组pos[MAX]。 NewPos[MAX]用于存储蛇身新位置的数组。 Food表示食物位置的结构体。 Wall表示墙壁范围的结构体。 count用于记录蛇的移动步数。 grade表示分数。 level表示难度等级。 amount表示食物数量。 speed表示蛇的移动速度。 isPaused表示游戏是否暂停的布尔变量。
三、函数详解
一HideTheCursor()函数
void HideTheCursor() {CONSOLE_CURSOR_INFO cciCursor;HANDLE hStdOut GetStdHandle(STD_OUTPUT_HANDLE);if (GetConsoleCursorInfo(hStdOut, cciCursor)) {cciCursor.bVisible FALSE;SetConsoleCursorInfo(hStdOut, cciCursor);SetConsoleCursorInfo(hStdOut, cciCursor);}
}首先定义了一个CONSOLE_CURSOR_INFO类型的变量cciCursor用于存储控制台光标的信息。 通过GetStdHandle(STD_OUTPUT_HANDLE)获取标准输出句柄赋值给hStdOut。 使用GetConsoleCursorInfo(hStdOut, cciCursor)获取当前控制台光标的信息并存储在cciCursor中。 如果获取光标信息成功将cciCursor.bVisible设置为FALSE表示隐藏光标。 两次调用SetConsoleCursorInfo(hStdOut, cciCursor)确保光标隐藏成功。
二basic()函数
void basic() {HideTheCursor();system(cls);int choice;std::cout 1. 登录 std::endl;std::cout 2. 注册 std::endl;std::cout 3. 游戏介绍 std::endl;std::cout 4. 退出 std::endl;std::cout 请选择: ;std::cin choice;while (true) {switch (choice) {case 1:if (login()) {// 登录成功后执行的代码for (int i 5; i 0; i--) {system(cls);std::cout 登录成功即将进入游戏 std::endl;std::cout 还有 i 秒即将开始游戏;Sleep(1000);}game();}break;case 2:registerUser();break;case 3:gameIntroduction();break;case 4:std::cout 请按ESC键退出游戏 ;while (true) {if (GetAsyncKeyState(VK_ESCAPE)) {out();}}return;default:std::cout \n无效选择请重新输入 std::endl;std::cin choice;break;}system(cls);std::cout 1. 登录 std::endl;std::cout 2. 注册 std::endl;std::cout 3. 游戏介绍 std::endl;std::cout 4. 退出 std::endl;std::cout 请重新选择: ;std::cin choice;}return;
}首先调用HideTheCursor()隐藏光标。 使用system(cls)清屏。 定义一个整数变量choice用于存储用户的选择。 输出菜单选项让用户选择登录、注册、游戏介绍或退出。 提示用户输入选择并将输入存储在choice中。 进入一个无限循环根据用户的选择执行相应的操作。 如果选择 1调用login()函数进行登录如果登录成功显示登录成功信息并倒计时然后调用game()函数进入游戏。 如果选择 2调用registerUser()函数进行注册。 如果选择 3调用gameIntroduction()函数显示游戏介绍。 如果选择 4输出提示信息然后进入一个循环不断检测是否按下ESC键如果按下则调用out()函数退出游戏。 如果选择无效输出错误信息让用户重新输入选择。 每次循环结束后清屏并重新输出菜单选项让用户重新选择。 三out()函数
void out() {system(cls);std::cout \n\n\n\n\n\n\n\n\n\n\n\n\n\n 感谢您的使用再见 std::endl;Sleep(1000);for (int i 3; i 0; i--) {system(cls);std::cout 正在退出;Sleep(400);std::cout .;Sleep(400);std::cout .;Sleep(400);std::cout .;}system(cls);exit(0);
}使用system(cls)清屏。 输出感谢信息和再见提示。 使用Sleep(1000)暂停 1 秒给用户时间阅读感谢信息。 使用循环模拟逐步退出的效果每次循环输出 “正在退出” 和三个点每个点之间暂停 400 毫秒。 最后再次清屏调用exit(0)退出程序。
四login()函数
bool login() {system(cls);std::string username, password;std::cout 请输入用户名: ;std::cin username;// 检查用户名是否为空if (username.empty()) {std::cout 请重新输入 std::endl;MessageBox(NULL, 用户名不能为空, 提示, 0);return false;}std::cout 请输入密码: ;std::cin password;// 检查密码是否为空if (password.empty()) {std::cout 请重新输入 std::endl;MessageBox(NULL, 密码不能为空, 提示, 0);return false;}std::ifstream inFile(user_info.txt);std::string storedUsername, storedPassword;if (inFile storedUsername storedPassword) {if (username storedUsername password storedPassword) {inFile.close();return true;}}inFile.close();std::cout 请重新输入 std::endl;MessageBox(NULL, 登录失败, 提示, 0);return false;
}使用system(cls)清屏。 定义两个字符串变量username和password分别用于存储用户输入的用户名和密码。 提示用户输入用户名并将输入存储在username中。 检查用户名是否为空如果为空则输出错误信息并显示提示框然后返回false。 提示用户输入密码并将输入存储在password中。 检查密码是否为空如果为空则输出错误信息并显示提示框然后返回false。 打开用户信息文件user_info.txt读取存储的用户名和密码。 如果读取成功比较输入的用户名和密码与文件中的是否一致如果一致则关闭文件并返回true表示登录成功如果不一致则关闭文件输出错误信息并显示提示框然后返回false。
五registerUser()函数
void registerUser() {std::string newUsername, newPassword;std::cout 请输入新用户名: ;std::cin newUsername;// 检查用户名是否为空if (newUsername.empty()) {std::cout 用户名不能为空请重新输入 std::endl;MessageBox(NULL, 提示, 提示, 0);return;}std::cout 请输入新密码: ;std::cin newPassword;// 检查密码是否为空if (newPassword.empty()) {std::cout 请重新输入 std::endl;MessageBox(NULL, 密码不能为空, 提示, 0);return;}std::ofstream outFile(user_info.txt);if (outFile.is_open()) {outFile newUsername newPassword;outFile.close();MessageBox(NULL, 注册成功, 提示, 0);}else {std::cout 注册失败无法保存用户信息 std::endl;MessageBox(NULL, 注册失败无法保存用户信息, 提示, 0);}
}首先定义两个std::string类型的变量newUsername和newPassword分别用于存储用户输入的新用户名和新密码。 输出提示信息让用户输入新用户名并使用std::cin读取用户输入将其存储在newUsername中。 检查newUsername是否为空字符串。如果是则输出错误信息 “用户名不能为空请重新输入”并通过MessageBox(NULL, 提示, 提示, 0)弹出一个提示框。然后函数直接返回不进行后续操作。 输出提示信息让用户输入新密码并使用std::cin读取用户输入将其存储在newPassword中。 检查newPassword是否为空字符串。如果是则输出错误信息 “请重新输入”并通过MessageBox(NULL, 密码不能为空, 提示, 0)弹出一个提示框。然后函数直接返回不进行后续操作。 创建一个std::ofstream类型的对象outFile并尝试打开文件 “user_info.txt” 用于写入。 如果文件成功打开将用户输入的新用户名和新密码写入文件格式为 “用户名 密码”。然后关闭文件并通过MessageBox(NULL, 注册成功, 提示, 0)弹出一个注册成功的提示框。 如果文件打开失败则输出错误信息 “注册失败无法保存用户信息”并通过MessageBox(NULL, 注册失败无法保存用户信息, 提示, 0)弹出一个提示框。
六gameIntroduction()函数
void gameIntroduction() {for (int i 5; i 0; i--) {system(cls);std::cout 欢迎来到贪吃蛇游戏 std::endl;std::cout 游戏规则控制蛇的移动来吃掉食物蛇身会变长碰到墙壁或自身则游戏结束。 std::endl;std::cout 通过不断吃食物得分并提升难度等级。\n\n std::endl;std::cout 还有 i 秒后自动返回大厅;Sleep(1000);}
}使用一个for循环从 5 递减到 1模拟倒计时效果。 在每次循环中首先调用system(cls)清屏。 然后输出欢迎信息和游戏规则说明。 接着输出倒计时信息显示还有多少秒后自动返回大厅。 最后调用Sleep(1000)暂停 1 秒模拟时间流逝。
七Init(Body b)函数
void Init(Body b) {b.len 3;b.Direction RIGHT;b.state STOP;b.pos[0].x 2;b.pos[0].y 1;b.pos[1].x 4;b.pos[1].y 1;b.pos[2].x 6;b.pos[2].y 1;hMain_Out GetStdHandle(STD_OUTPUT_HANDLE);hMain_In GetStdHandle(STD_INPUT_HANDLE);CreateWall();CreateFood();ShowInfo();
}设置传入的蛇结构体b的初始长度为 3初始方向为向右RIGHT初始状态为停止STOP。 分别设置蛇身三个初始位置的横坐标和纵坐标。 通过GetStdHandle(STD_OUTPUT_HANDLE)和GetStdHandle(STD_INPUT_HANDLE)获取控制台的输出句柄和输入句柄并分别赋值给全局变量hMain_Out和hMain_In。 调用CreateWall()函数创建游戏的墙壁。 调用CreateFood()函数随机生成食物的位置。 调用ShowInfo()函数显示游戏的信息如分数和难度等级。
八Print(const Body b)函数
void Print(const Body b) {COORD coord;for (int ix b.len - 1; ix 0; --ix) {coord.X b.pos[ix].x;coord.Y b.pos[ix].y;SetConsoleCursorPosition(hMain_Out, coord);printf(●);}
}定义一个COORD类型的变量coord用于存储光标位置。 使用一个for循环从蛇身的最后一个位置开始逆序遍历蛇身的所有位置。 在每次循环中将当前位置的横坐标和纵坐标赋值给coord。 通过SetConsoleCursorPosition(hMain_Out, coord)将控制台光标移动到当前蛇身位置。 使用printf(●)在当前光标位置输出蛇身的圆形符号 “●”。
九Print(int x, int y)函数
void Print(int x, int y) {COORD c;c.X x;c.Y y;SetConsoleCursorPosition(hMain_Out, c);printf(■);
}定义一个COORD类型的变量c用于存储光标位置。 将传入的横坐标x和纵坐标y赋值给c。 通过SetConsoleCursorPosition(hMain_Out, c)将控制台光标移动到指定位置。 使用printf(■)在当前光标位置输出用于创建墙壁或食物的符号 “■”。
十Move(Body b)函数
void Move(Body b) {ShowInfo();if (IsKnock_Wall(b)) {MessageBox(NULL, You are dead!, Oh my God, 0);game();}if (IsKnock_Food(b)) {if (amount 5) {level;amount 0;speed - 50;}AddBody(b);grade 10;amount;Clean(Food.x, Food.y);CreateFood();}if (STOP b.state) {if (RIGHT b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].x 2;}}if (UP b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].y--;}}if (DOWN b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].y;}}if (LEFT b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].x - 2;}}}if (MOVING b.state) {PosCopy(b, NewPos);if (UP b.Direction) {if (b.len count) {b.state STOP;b.Direction UP;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].y--;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}if (DOWN b.Direction) {if (b.len count) {b.state STOP;b.Direction DOWN;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].y;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}if (LEFT b.Direction) {if (b.len count) {b.state STOP;b.Direction LEFT;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].x - 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}if (RIGHT b.Direction) {if (b.len count) {b.state STOP;b.Direction RIGHT;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].x 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}}Print(b);
}首先调用ShowInfo()函数显示游戏信息。 调用IsKnock_Wall(b)函数判断蛇是否碰到墙壁。如果碰到墙壁则弹出一个提示框 “You are dead!”并调用game()函数重新开始游戏。 调用IsKnock_Food(b)函数判断蛇是否吃到食物。如果吃到食物则进行一系列操作包括增加难度等级、增加蛇身长度、更新分数、增加食物数量、清除食物位置并重新生成食物。 如果蛇的状态为停止STOP根据蛇的当前方向进行相应的移动操作。如果方向为向右则将蛇身每个位置的横坐标增加 2如果方向为向上则将蛇身每个位置的纵坐标减 1如果方向为向下则将蛇身每个位置的纵坐标加 1如果方向为向左则将蛇身每个位置的横坐标减 2。在移动前先调用Clean(b.pos[ix].x, b.pos[ix].y)清除当前位置的显示。
十一GetDirection(Body b)函数
int GetDirection(Body b) {if (GetAsyncKeyState(VK_UP)) {count 0;TurnRound(UP, b);}if (GetAsyncKeyState(VK_DOWN)) {count 0;TurnRound(DOWN, b);}if (GetAsyncKeyState(VK_LEFT)) {count 0;TurnRound(LEFT, b);}if (GetAsyncKeyState(VK_RIGHT)) {count 0;TurnRound(RIGHT, b);}return 0;
}这个函数用于获取用户输入的方向键并根据输入调用TurnRound函数来改变蛇的移动方向。 分别使用GetAsyncKeyState函数检测用户是否按下了上VK_UP、下VK_DOWN、左VK_LEFT、右VK_RIGHT方向键。 如果检测到用户按下了某个方向键首先将全局变量count重置为 0。这可能是用于控制蛇移动的步数或其他相关操作的计数变量。 然后调用TurnRound函数并传入相应的方向常量如UP、DOWN、LEFT、RIGHT以及蛇的结构体引用b以实现改变蛇的移动方向。 最后函数返回 0可能表示没有特定的返回值需求只是一个占位的返回值。
十二TurnRound(int d, Body b)函数
void TurnRound(int d, Body b) {switch (d) {case UP:if (RIGHT b.Direction || LEFT b.Direction) {PosCopy(b, NewPos);--b.pos[b.len - 1].y;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;case DOWN:if (RIGHT b.Direction || LEFT b.Direction) {PosCopy(b, NewPos);b.pos[b.len - 1].y;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;case LEFT:if (UP b.Direction || DOWN b.Direction) {PosCopy(b, NewPos);b.pos[b.len - 1].x - 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;case RIGHT:if (UP b.Direction || DOWN b.Direction) {PosCopy(b, NewPos);b.pos[b.len - 1].x 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;default:break;}
}这个函数根据传入的方向参数d来改变蛇的移动方向。 使用switch语句根据传入的方向常量进行不同的操作。 例如当d为UP向上时如果蛇当前的方向是向右RIGHT或向左LEFT则进行一系列操作 调用PosCopy(b, NewPos)函数可能是将蛇的当前位置复制到一个临时数组中以便后续操作。 将蛇头的纵坐标减 1表示向上移动一格。 调用Clean(b.pos[0].x, b.pos[0].y)函数可能是清除蛇尾的显示。 调用MoveBody(b)函数可能是更新蛇身的位置。 调用Print(b)函数输出蛇的新位置。 设置蛇的新方向为向上UP并将蛇的状态设置为移动状态MOVING。 对于其他方向的处理类似根据不同的方向改变蛇头的位置并进行相应的操作来更新蛇的状态和显示。
十三PosCopy(Body b, Pos NewPos[])函数
void PosCopy(Body b, Pos NewPos[]) {for (int ix 0; ix b.len; ix) {NewPos[ix].x 0;NewPos[ix].y 0;}for (int ix 0; ix b.len; ix) {NewPos[ix] b.pos[ix];}
}这个函数用于将蛇的当前位置复制到一个临时数组中。 首先使用一个for循环将临时数组NewPos中的每个位置的横坐标和纵坐标初始化为 0。 然后使用另一个for循环将蛇的当前位置存储在结构体b的pos数组中复制到临时数组NewPos中。这样可以在后续的操作中保留蛇的当前位置以便进行移动和方向改变等操作时使用。
十四MoveBody(Body b)函数
void MoveBody(Body b) {for (int ix b.len - 1; ix 0; --ix) {b.pos[ix - 1] NewPos[ix];}count;PosCopy(b, NewPos);
}这个函数用于更新蛇身的位置。 使用一个for循环从蛇尾开始将每个位置更新为前一个位置的值从临时数组NewPos中获取。这样实现了蛇身的移动效果。 增加全局变量count的值可能用于记录蛇的移动步数或其他相关操作的计数。 再次调用PosCopy(b, NewPos)函数将蛇的当前位置复制到临时数组NewPos中为下一次移动做好准备。
十五HideCursor()函数
void HideCursor() {CONSOLE_CURSOR_INFO info;GetConsoleCursorInfo(hMain_Out, info);info.bVisible FALSE;SetConsoleCursorInfo(hMain_Out, info);
}这个函数用于隐藏控制台光标。 定义一个CONSOLE_CURSOR_INFO类型的变量info用于存储控制台光标的信息。 使用GetConsoleCursorInfo(hMain_Out, info)函数获取当前控制台光标的信息并存储在info中。 将info.bVisible设置为FALSE表示隐藏光标。 使用SetConsoleCursorInfo(hMain_Out, info)函数设置控制台光标的信息实现隐藏光标的效果。
十六CreateWall()函数
void CreateWall() {CONSOLE_SCREEN_BUFFER_INFO info;GetConsoleScreenBufferInfo(hMain_Out, info);info.srWindow.Right - 19;info.srWindow.Bottom - 5;Wall info.srWindow;for (int i 0; i info.srWindow.Right; i 2) {Print(i, info.srWindow.Top);Print(i, info.srWindow.Bottom);}for (int y 0; y info.srWindow.Bottom; y) {Print(0, y);Print(info.srWindow.Right, y);}
}这个函数用于创建游戏的墙壁。 定义一个CONSOLE_SCREEN_BUFFER_INFO类型的变量info用于存储控制台屏幕缓冲区的信息。 使用GetConsoleScreenBufferInfo(hMain_Out, info)函数获取当前控制台屏幕缓冲区的信息并存储在info中。 调整info.srWindow.Right和info.srWindow.Bottom的值减小墙壁的范围。这可能是为了在控制台中留出一些空间用于显示其他信息或使游戏界面更加美观。 将调整后的窗口信息赋值给全局变量Wall可能用于后续判断蛇是否碰到墙壁等操作。 使用两个嵌套的循环在控制台边界绘制墙壁。外层循环遍历水平方向的坐标内层循环遍历上下边界的纵坐标调用Print(i, info.srWindow.Top)和Print(i, info.srWindow.Bottom)在上下边界绘制墙壁另一个外层循环遍历垂直方向的坐标内层循环遍历左右边界的横坐标调用Print(0, y)和Print(info.srWindow.Right, y)在左右边界绘制墙壁。
十七CreateFood()函数、
void CreateFood() {srand(unsigned(time(NULL)));unsigned x_t RAND_MAX / Wall.Right;unsigned y_t RAND_MAX / Wall.Bottom;while (true) {int x rand() / x_t;int y rand() / y_t;Food.x x - 4;Food.y y - 4;if ((0 Food.x % 2) (0 Food.y % 2)) {if (Food.x 5) {Food.x 8;}if (Food.y 5) {Food.y 8;}Print(Food.x, Food.y);break;}}
}这个函数用于随机生成食物的位置。 首先使用srand(unsigned(time(NULL)))设置随机数生成器的种子以确保每次生成的随机数序列不同。 计算x_t和y_t分别是随机数范围与墙壁右边界和下边界的比例。 进入一个无限循环在每次循环中生成随机坐标x和y并根据x_t和y_t进行调整。然后将生成的坐标赋值给食物位置结构体Food的横坐标和纵坐标并进行一些调整 如果食物的横坐标小于 5则将其加上 8确保食物不在靠近边界的位置。 如果食物的纵坐标小于 5则将其加上 8同样确保食物不在靠近边界的位置。 检查食物的坐标是否为偶数即(0 Food.x % 2) (0 Food.y % 2)如果是偶数则在该位置输出食物调用Print(Food.x, Food.y)并跳出循环。
十八IsKnock_Food(const Body b)函数
bool IsKnock_Food(const Body b) {if (b.pos[b.len - 1].x Food.x b.pos[b.len - 1].y Food.y) {return true;}else {return false;}
}这个函数用于判断蛇是否碰到食物。 检查蛇的头部位置即结构体b的最后一个位置b.pos[b.len - 1]的横坐标和纵坐标是否与食物位置Food的横坐标和纵坐标相等。 如果相等则返回true表示蛇碰到了食物否则返回false。
十九IsKnock_Wall(const Body b)函数
bool IsKnock_Wall(const Body b) {if (0 b.pos[b.len - 1].x || 0 b.pos[b.len - 1].y || Wall.Right b.pos[b.len - 1].x || Wall.Bottom b.pos[b.len - 1].y) {return true;}Pos Head b.pos[b.len - 1];for (int ix 0; ix b.len - 3; ix) {if (Head.x b.pos[ix].x Head.y b.pos[ix].y) {return true;}}return false;
}这个函数用于判断蛇是否碰到墙壁或自身。 首先检查蛇的头部位置是否在墙壁的边界上即检查蛇头的横坐标是否为 0、纵坐标是否为 0、横坐标是否等于墙壁右边界Wall.Right或纵坐标是否等于墙壁下边界Wall.Bottom。如果是则返回true表示蛇碰到了墙壁。 如果蛇头不在墙壁边界上则定义一个Pos类型的变量Head将其赋值为蛇头的位置。然后使用一个循环遍历蛇身的其他部分从第一个位置到倒数第三个位置检查蛇头的位置是否与蛇身的其他部分重叠即横坐标和纵坐标都相等。如果重叠则返回true表示蛇碰到了自身。 如果蛇既没有碰到墙壁也没有碰到自身则返回false。
二十ShowInfo()函数
void ShowInfo() {COORD c;c.X Wall.Right 2;c.Y 3;SetConsoleCursorPosition(hMain_Out, c);printf( 分数%d, grade);c.Y 10;SetConsoleCursorPosition(hMain_Out, c);printf( 难度等级%d, level);
}这个函数用于在控制台显示游戏信息包括分数和难度等级。 定义一个COORD类型的变量c用于存储光标位置。 设置c.X为墙壁右边界Wall.Right 2表示在墙壁右侧留出一些空间设置c.Y为 3表示在第三行显示信息。 使用SetConsoleCursorPosition(hMain_Out, c)将控制台光标移动到指定位置然后输出分数信息格式为 “分数[具体分数]”其中具体分数存储在全局变量grade中。 增加c.Y的值为 13表示在第十三行显示下一个信息。 再次使用SetConsoleCursorPosition(hMain_Out, c)将控制台光标移动到指定位置然后输出难度等级信息格式为 “难度等级[具体等级]”其中具体等级存储在全局变量level中。
二十一AddBody(Body b)函数
void AddBody(Body b) {if (b.len MAX) {if (UP b.Direction) {b.pos[b.len].y b.pos[b.len - 1].y - 1;b.pos[b.len].x b.pos[b.len - 1].x;b.len;}if (DOWN b.Direction) {b.pos[b.len].y b.pos[b.len - 1].y 1;b.pos[b.len].x b.pos[b.len - 1].x;b.len;}if (LEFT b.Direction) {b.pos[b.len].x b.pos[b.len - 1].x - 2;b.pos[b.len].y b.pos[b.len - 1].y;b.len;}if (RIGHT b.Direction) {b.pos[b.len].x b.pos[b.len - 1].x 2;b.pos[b.len].y b.pos[b.len - 1].y;b.len;}}
}这个函数用于增加蛇的长度。 首先检查蛇的长度是否小于最大长度MAX。如果是则根据蛇的当前方向在蛇尾添加一个新的位置 如果方向为向上UP则将新位置的纵坐标设置为蛇尾位置的纵坐标减 1横坐标与蛇尾位置相同然后增加蛇的长度。 如果方向为向下DOWN则将新位置的纵坐标设置为蛇尾位置的纵坐标加 1横坐标与蛇尾位置相同然后增加蛇的长度。 如果方向为向左LEFT则将新位置的横坐标设置为蛇尾位置的横坐标减 2纵坐标与蛇尾位置相同然后增加蛇的长度。 如果方向为向右RIGHT则将新位置的横坐标设置为蛇尾位置的横坐标加 2纵坐标与蛇尾位置相同然后增加蛇的长度。
四、主函数详解
int main() {system(mode con cols100 lines30);system(title 贪吃蛇);basic();return 0;
}在主函数中首先使用system(mode con cols100 lines30)设置控制台的宽度为 100 个字符高度为 30 行。 然后使用system(title 贪吃蛇)设置控制台窗口的标题为 “贪吃蛇”。 最后调用basic()函数进入游戏的基础功能模块开始游戏的流程。
五、完整代码
#include iostream
#include string
#include fstream
#include windows.h
#include time.h
#include stdio.h
#include limits#define MAX 100
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
#define MOVING 5
#define STOP 0HANDLE hMain_Out NULL;
HANDLE hMain_In NULL;struct Pos {int x;int y;
};struct Body {int state;int len;int Direction;Pos pos[MAX];
};Pos NewPos[MAX];
Pos Food;
SMALL_RECT Wall;
int count 0;
int grade 0;
int level 1;
int amount 0;
int speed 200;
bool isPaused false;void basic();
void game();
void out();
bool login();
void registerUser();
void gameIntroduction();
void Init(Body b);
void Print(const Body b);
void Print(int x, int y);
void Move(Body b);
void Clean(int x, int y);
void Clean(const Body b);
void ShowInfo();
int GetDirection(Body b);
void TurnRound(int Direction, Body b);
void PosCopy(Body b, Pos NewPos[]);
void MoveBody(Body b);
void HideCursor();
void CreateWall();
void CreateFood();
bool IsKnock_Food(const Body b);
bool IsKnock_Wall(const Body b);
void AddBody(Body b);
void HideTheCursor();void basic() {HideTheCursor();system(cls);int choice;std::cout 1. 登录 std::endl;std::cout 2. 注册 std::endl;std::cout 3. 游戏介绍 std::endl;std::cout 4. 退出 std::endl;std::cout 请选择: ;std::cin choice;while (true) {switch (choice) {case 1:if (login()) {// 登录成功后执行的代码for (int i 5; i 0; i--) {system(cls);std::cout 登录成功即将进入游戏 std::endl;std::cout 还有 i 秒即将开始游戏;Sleep(1000);}game();}break;case 2:registerUser();break;case 3:gameIntroduction();break;case 4:std::cout 请按ESC键退出游戏 ;while (true) {if (GetAsyncKeyState(VK_ESCAPE)) {out();}}return;default:std::cout \n无效选择请重新输入 std::endl;std::cin choice;break;}system(cls);std::cout 1. 登录 std::endl;std::cout 2. 注册 std::endl;std::cout 3. 游戏介绍 std::endl;std::cout 4. 退出 std::endl;std::cout 请重新选择: ;std::cin choice;}return;
}void game() {system(cls);Body b;Init(b);Print(b);HideCursor();while (TRUE) {if (GetAsyncKeyState(VK_ESCAPE)) { out();}if (GetAsyncKeyState(VK_SPACE)) { isPaused !isPaused;while (isPaused GetAsyncKeyState(VK_SPACE)) {Sleep(100);}}if (!isPaused) { Sleep(speed);Move(b);GetDirection(b);}}
}void out() {system(cls);std::cout \n\n\n\n\n\n\n\n\n\n\n\n\n\n 感谢您的使用再见 std::endl;Sleep(1000);for (int i 3; i 0; i--) {system(cls);std::cout 正在退出;Sleep(400);std::cout .;Sleep(400);std::cout .;Sleep(400);std::cout .;}system(cls);exit(0);
}// 登录函数
bool login() {system(cls);std::string username, password;std::cout 请输入用户名: ;std::cin username;// 检查用户名是否为空if (username.empty()) {std::cout 请重新输入 std::endl;MessageBox(NULL, 用户名不能为空, 提示, 0);return false;}std::cout 请输入密码: ;std::cin password;// 检查密码是否为空if (password.empty()) {std::cout 请重新输入 std::endl;MessageBox(NULL, 密码不能为空, 提示, 0);return false;}std::ifstream inFile(user_info.txt);std::string storedUsername, storedPassword;if (inFile storedUsername storedPassword) {if (username storedUsername password storedPassword) {inFile.close();return true;}}inFile.close();std::cout 请重新输入 std::endl;MessageBox(NULL, 登录失败, 提示, 0);return false;
}// 注册函数
void registerUser() {std::string newUsername, newPassword;std::cout 请输入新用户名: ;std::cin newUsername;// 检查用户名是否为空if (newUsername.empty()) {std::cout 用户名不能为空请重新输入 std::endl;MessageBox(NULL, 提示, 提示, 0);return;}std::cout 请输入新密码: ;std::cin newPassword;// 检查密码是否为空if (newPassword.empty()) {std::cout 请重新输入 std::endl;MessageBox(NULL, 密码不能为空, 提示, 0);return;}std::ofstream outFile(user_info.txt);if (outFile.is_open()) {outFile newUsername newPassword;outFile.close();MessageBox(NULL, 注册成功, 提示, 0);}else {std::cout 注册失败无法保存用户信息 std::endl;MessageBox(NULL, 注册失败无法保存用户信息, 提示, 0);}
}// 游戏介绍界面
void gameIntroduction() {for (int i 5; i 0; i--) {system(cls);std::cout 欢迎来到贪吃蛇游戏 std::endl;std::cout 游戏规则控制蛇的移动来吃掉食物蛇身会变长碰到墙壁或自身则游戏结束。 std::endl;std::cout 通过不断吃食物得分并提升难度等级。\n\n std::endl;std::cout 还有 i 秒后自动返回大厅;Sleep(1000);}
}void Init(Body b) {b.len 3;b.Direction RIGHT;b.state STOP;b.pos[0].x 2;b.pos[0].y 1;b.pos[1].x 4;b.pos[1].y 1;b.pos[2].x 6;b.pos[2].y 1;hMain_Out GetStdHandle(STD_OUTPUT_HANDLE);hMain_In GetStdHandle(STD_INPUT_HANDLE);CreateWall();CreateFood();ShowInfo();
}void Print(const Body b) {COORD coord;for (int ix b.len - 1; ix 0; --ix) {coord.X b.pos[ix].x;coord.Y b.pos[ix].y;SetConsoleCursorPosition(hMain_Out, coord);printf(●);}
}void Move(Body b) {ShowInfo();if (IsKnock_Wall(b)) {MessageBox(NULL, You are dead!, Oh my God, 0);game();}if (IsKnock_Food(b)) {if (amount 5) {level;amount 0;speed - 50;}AddBody(b);grade 10;amount;Clean(Food.x, Food.y);CreateFood();}if (STOP b.state) {if (RIGHT b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].x 2;}}if (UP b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].y--;}}if (DOWN b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].y;}}if (LEFT b.Direction) {for (int ix 0; ix b.len; ix) {Clean(b.pos[ix].x, b.pos[ix].y);b.pos[ix].x - 2;}}}if (MOVING b.state) {PosCopy(b, NewPos);if (UP b.Direction) {if (b.len count) {b.state STOP;b.Direction UP;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].y--;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}if (DOWN b.Direction) {if (b.len count) {b.state STOP;b.Direction DOWN;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].y;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}if (LEFT b.Direction) {if (b.len count) {b.state STOP;b.Direction LEFT;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].x - 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}if (RIGHT b.Direction) {if (b.len count) {b.state STOP;b.Direction RIGHT;count 0;}if (count b.len MOVING b.state) {b.pos[b.len - 1].x 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);}}}Print(b);
}void Clean(int x, int y) {COORD c;c.X x;c.Y y;SetConsoleCursorPosition(hMain_Out, c);printf( );
}
int GetDirection(Body b) {if (GetAsyncKeyState(VK_UP)) {count 0;TurnRound(UP, b);}if (GetAsyncKeyState(VK_DOWN)) {count 0;TurnRound(DOWN, b);}if (GetAsyncKeyState(VK_LEFT)) {count 0;TurnRound(LEFT, b);}if (GetAsyncKeyState(VK_RIGHT)) {count 0;TurnRound(RIGHT, b);}return 0;
}void TurnRound(int d, Body b) {switch (d) {case UP:if (RIGHT b.Direction || LEFT b.Direction) {PosCopy(b, NewPos);--b.pos[b.len - 1].y;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;case DOWN:if (RIGHT b.Direction || LEFT b.Direction) {PosCopy(b, NewPos);b.pos[b.len - 1].y;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;case LEFT:if (UP b.Direction || DOWN b.Direction) {PosCopy(b, NewPos);b.pos[b.len - 1].x - 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;case RIGHT:if (UP b.Direction || DOWN b.Direction) {PosCopy(b, NewPos);b.pos[b.len - 1].x 2;Clean(b.pos[0].x, b.pos[0].y);MoveBody(b);Print(b);b.Direction d;b.state MOVING;}break;default:break;}
}void PosCopy(Body b, Pos NewPos[]) {for (int ix 0; ix b.len; ix) {NewPos[ix].x 0;NewPos[ix].y 0;}for (int ix 0; ix b.len; ix) {NewPos[ix] b.pos[ix];}
}void MoveBody(Body b) {for (int ix b.len - 1; ix 0; --ix) {b.pos[ix - 1] NewPos[ix];}count;PosCopy(b, NewPos);
}void HideCursor() {CONSOLE_CURSOR_INFO info;GetConsoleCursorInfo(hMain_Out, info);info.bVisible FALSE;SetConsoleCursorInfo(hMain_Out, info);
}void CreateWall() {CONSOLE_SCREEN_BUFFER_INFO info;GetConsoleScreenBufferInfo(hMain_Out, info);info.srWindow.Right - 19;info.srWindow.Bottom - 5;Wall info.srWindow;for (int i 0; i info.srWindow.Right; i 2) {Print(i, info.srWindow.Top);Print(i, info.srWindow.Bottom);}for (int y 0; y info.srWindow.Bottom; y) {Print(0, y);Print(info.srWindow.Right, y);}
}void Print(int x, int y) {COORD c;c.X x;c.Y y;SetConsoleCursorPosition(hMain_Out, c);printf(■);
}void CreateFood() {srand(unsigned(time(NULL)));unsigned x_t RAND_MAX / Wall.Right;unsigned y_t RAND_MAX / Wall.Bottom;while (true) {int x rand() / x_t;int y rand() / y_t;Food.x x - 4;Food.y y - 4;if ((0 Food.x % 2) (0 Food.y % 2)) {if (Food.x 5) {Food.x 8;}if (Food.y 5) {Food.y 8;}Print(Food.x, Food.y);break;}}
}bool IsKnock_Food(const Body b) {if (b.pos[b.len - 1].x Food.x b.pos[b.len - 1].y Food.y) {return true;}else {return false;}
}bool IsKnock_Wall(const Body b) {if (0 b.pos[b.len - 1].x || 0 b.pos[b.len - 1].y || Wall.Right b.pos[b.len - 1].x || Wall.Bottom b.pos[b.len - 1].y) {return true;}Pos Head b.pos[b.len - 1];for (int ix 0; ix b.len - 3; ix) {if (Head.x b.pos[ix].x Head.y b.pos[ix].y) {return true;}}return false;
}void ShowInfo() {COORD c;c.X Wall.Right 2;c.Y 3;SetConsoleCursorPosition(hMain_Out, c);printf( 分数%d, grade);c.Y 10;SetConsoleCursorPosition(hMain_Out, c);printf( 难度等级%d, level);}void AddBody(Body b) {if (b.len MAX) {if (UP b.Direction) {b.pos[b.len].y b.pos[b.len - 1].y - 1;b.pos[b.len].x b.pos[b.len - 1].x;b.len;}if (DOWN b.Direction) {b.pos[b.len].y b.pos[b.len - 1].y 1;b.pos[b.len].x b.pos[b.len - 1].x;b.len;}if (LEFT b.Direction) {b.pos[b.len].x b.pos[b.len - 1].x - 2;b.pos[b.len].y b.pos[b.len - 1].y;b.len;}if (RIGHT b.Direction) {b.pos[b.len].x b.pos[b.len - 1].x 2;b.pos[b.len].y b.pos[b.len - 1].y;b.len;}}
}void HideTheCursor() {CONSOLE_CURSOR_INFO cciCursor;HANDLE hStdOut GetStdHandle(STD_OUTPUT_HANDLE);if (GetConsoleCursorInfo(hStdOut, cciCursor)) {cciCursor.bVisible FALSE;SetConsoleCursorInfo(hStdOut, cciCursor);SetConsoleCursorInfo(hStdOut, cciCursor);}
}int main() {system(mode con cols100 lines30);system(title 贪吃蛇);basic();return 0;
}六、技术亮点
随机生成食物位置使用rand()函数和时间种子来生成随机数确保食物位置的随机性。 碰撞检测通过判断蛇头的位置是否与墙壁或食物的位置相同以及蛇头是否与自身的其他部分重叠来实现碰撞检测。 方向控制根据用户按键输入来改变蛇的移动方向实现了灵活的游戏操作。 难度提升随着游戏的进行食物数量增加当达到一定数量时提升游戏难度等级加快蛇的移动速度。
七、改进建议
可以增加游戏音效增强游戏的趣味性和沉浸感。 优化游戏界面使用不同的字符或颜色来区分蛇身、食物和墙壁提高游戏的视觉效果。 增加游戏模式选择如单人模式、多人对战模式等丰富游戏玩法。 对用户输入进行更严格的验证防止输入错误导致程序异常。 可以将游戏数据保存到文件中以便用户下次继续游戏。
九、特别鸣谢
特别感谢C小盆友ta的主页以及阳了个阳Cta的主页帮我测试游戏效果