当前位置: 首页 > news >正文

C/C++控制台贪吃蛇游戏的实现

在这里插入图片描述

在这里插入图片描述在这里插入图片描述

🚀欢迎互三👉:程序猿方梓燚 💎💎
🚀关注博主,后期持续更新系列文章
🚀如果有错误感谢请大家批评指出,及时修改
🚀感谢大家点赞👍收藏⭐评论✍

在这里插入图片描述

一、概述

本文对给定的贪吃蛇游戏代码进行详细分析。该游戏使用 C++语言编写,通过控制台界面实现了经典的贪吃蛇游戏玩法,包括登录、注册、游戏介绍、游戏操作和计分等功能。

二、功能模块分析

(一)基础模块

常量定义:
MAX:定义了蛇身最大长度为 100
UPDOWNLEFTRIGHT:分别代表蛇的上、下、左、右四个方向。
MOVING:表示蛇正在移动的状态。
STOP:表示蛇停止的状态。
全局变量:
hMain_Out:控制台输出句柄。
hMain_In:控制台输入句柄。
NewPos[MAX]:用于存储蛇身新位置的数组。
Food:食物的位置结构体。
Wall:墙壁的范围结构体。
countgradelevelamountspeed:分别用于记录蛇的移动步数、分数、难度等级、食物数量和移动速度。
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.h>

iostream:提供输入输出流的功能,用于在控制台进行输入输出操作。
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。
UPDOWNLEFTRIGHT:分别代表蛇的上、下、左、右四个方向,用整数表示方便在代码中进行判断和操作。
MOVINGSTOP:表示蛇的移动和停止状态。
hMain_OuthMain_In:分别是控制台输出句柄和输入句柄,用于在控制台进行输出和获取输入。
struct Pos:定义了一个表示位置的结构体,包含两个整数成员xy,分别表示横坐标和纵坐标。
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")清屏。
定义两个字符串变量usernamepassword,分别用于存储用户输入的用户名和密码。
提示用户输入用户名,并将输入存储在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类型的变量newUsernamenewPassword,分别用于存储用户输入的新用户名和新密码。
输出提示信息让用户输入新用户名,并使用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_OuthMain_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函数,并传入相应的方向常量(如UPDOWNLEFTRIGHT)以及蛇的结构体引用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语句根据传入的方向常量进行不同的操作。
例如,当dUP(向上)时,如果蛇当前的方向是向右(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循环将蛇的当前位置(存储在结构体bpos数组中)复制到临时数组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.Rightinfo.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_ty_t,分别是随机数范围与墙壁右边界和下边界的比例。
进入一个无限循环,在每次循环中生成随机坐标xy,并根据x_ty_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.Y3,表示在第三行显示信息。
使用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 cols=100 lines=30");system("title 贪吃蛇");basic();return 0;
}

在主函数中,首先使用system("mode con cols=100 lines=30")设置控制台的宽度为 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 cols=100 lines=30");system("title 贪吃蛇");basic();return 0;
}

六、技术亮点

随机生成食物位置:使用rand()函数和时间种子来生成随机数,确保食物位置的随机性。
碰撞检测:通过判断蛇头的位置是否与墙壁或食物的位置相同,以及蛇头是否与自身的其他部分重叠,来实现碰撞检测。
方向控制:根据用户按键输入来改变蛇的移动方向,实现了灵活的游戏操作。
难度提升:随着游戏的进行,食物数量增加,当达到一定数量时,提升游戏难度等级,加快蛇的移动速度。

七、改进建议

可以增加游戏音效,增强游戏的趣味性和沉浸感。
优化游戏界面,使用不同的字符或颜色来区分蛇身、食物和墙壁,提高游戏的视觉效果。
增加游戏模式选择,如单人模式、多人对战模式等,丰富游戏玩法。
对用户输入进行更严格的验证,防止输入错误导致程序异常。
可以将游戏数据保存到文件中,以便用户下次继续游戏。

九、特别鸣谢

特别感谢C++小盆友(ta的主页)以及阳了个阳C++(ta的主页)帮我测试游戏效果

相关文章:

C/C++控制台贪吃蛇游戏的实现

&#x1f680;欢迎互三&#x1f449;&#xff1a;程序猿方梓燚 &#x1f48e;&#x1f48e; &#x1f680;关注博主&#xff0c;后期持续更新系列文章 &#x1f680;如果有错误感谢请大家批评指出&#xff0c;及时修改 &#x1f680;感谢大家点赞&#x1f44d;收藏⭐评论✍ 一、…...

Linux 升级安装 Weblogic-补丁!

版本&#xff1a; RedHat 6.5 Weblogic 10.3.6.0 ----------------------------------------------------------------- 1.查看当前 weblogic 补丁版本 cd /weblogic/utils/bsu/ ./bsu.sh -prod_dir/weblogic/wlserver_10.3/ -statusapplied -verbose -view 2.卸载旧补丁…...

苍鹰来啦!快来看呀!NGO-BiTCN-BiGRU-Attention北方苍鹰算法优化多重双向深度学习回归预测

苍鹰来啦!快来看呀&#xff01;NGO-BiTCN-BiGRU-Attention北方苍鹰算法优化多重双向深度学习回归预测 目录 苍鹰来啦!快来看呀&#xff01;NGO-BiTCN-BiGRU-Attention北方苍鹰算法优化多重双向深度学习回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…...

关于WebSocket必知必会的知识点

什么是WebSocket WebSocket是一种网络传输协议&#xff0c;可以在单个TCP连接上进行全双工通信&#xff0c;位于OSI模型的应用层。 WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;服务器可以主动向客户端发送消息。在WebSocket API中&#xff0c;浏览器和…...

Go 1.19.4 Sort排序进阶-Day 12

1. 结构体&#xff08;切片&#xff09;排序 结构体返回的是切片。 之前学习了sort.Ints()和sort.Strings()&#xff0c;使用这两个sort库下面的方法&#xff0c;可以对int和strings进行排序。 那如果我要对自定义类型进行排序&#xff0c;怎么办&#xff0c;sort库没提供&…...

python-求距离(赛氪OJ)

[题目描述] 给你一个 1−>n 的排列&#xff0c;现在有一次机会可以交换两个数的位置&#xff0c;求交换后最小值和最大值之间的最大距离是多少&#xff1f;输入格式&#xff1a; 输入共两行。 第一行一个数 n 。 第二行 n 个数表示这个排列。输出格式&#xff1a; 输出一行一…...

《第二十一章 传感器与定位 - 传感器应用》

《第二十一章 传感器与定位 - 传感器应用》 在当今的移动应用开发中&#xff0c;充分利用设备的传感器能够为用户带来更加智能和便捷的体验。本章将重点探讨加速度传感器、方向传感器和光线传感器的应用。 一、传感器应用的重要性 随着智能手机和移动设备的普及&#xff0c;传感…...

Windows系统命令

Windows系统命令 Windows 系统中的命令行工具是指令式编程语言&#xff0c;可以用来执行各种任务、管理文件和目录、监控系统状态等。下面是一个 Windows 命令应用实例&#xff1a; 1. 文件操作 cd&#xff1a;用于改变当前目录。例如&#xff0c;cd Documents 将当前目录更…...

C语言函数递归

前言与概述 本文章将通过多个代码并赋予图示&#xff0c;详细讲解C语言函数递归的定义和函数递归的运算过程。 函数递归定义 程序调用自身的编程技巧称为递归。递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。它…...

【python数据分析11】——Pandas统计分析(分组聚合进行组内计算)

分组聚合进行组内计算 前言1、groupby方法拆分数据2、agg方法聚合数据3、apply方法聚合数据4、transform方法聚合数据5 小案例5.1 按照时间对菜品订单详情表进行拆分5.2 使用agg方法计算5.3 使用apply方法统计单日菜品销售数目 前言 依据某个或者几个字段对数据集进行分组&…...

高性能web服务器

目录 一、简介 &#xff08;一&#xff09;nginx-高性能的web服务端 &#xff08;二&#xff09;用户访问体验 二、I/O模型 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;网络I/O模型 &#xff08;三&#xff09;阻塞型 I/O 模型 &#xff08;四&#xf…...

微服务案例搭建

目录 一、案例搭建 1.数据库表 2.服务模块 二、具体代码实现如下&#xff1a; (1) 首先是大体框架为&#xff1a; &#xff08;2&#xff09;父模块中的pom文件配置 &#xff08;3&#xff09;shop_common模块&#xff0c;这个模块里面只需要配置pom.xml&#xff0c;与实体…...

SAP负库存

业务示例 在系统中&#xff0c;对于一些物料而言&#xff0c;不能立即将收到的交货输入为收货。如果要使发货无论如何都是可以过帐的&#xff0c;则需要允许这些物料的负库存。 负库存 发货数量大于预订数量时&#xff0c;过帐该发货就会出现负库存。如果由于组织原因&#…...

集团数字化转型方案(三)

集团数字化转型方案通过系统整合人工智能&#xff08;AI&#xff09;、大数据、云计算和物联网&#xff08;IoT&#xff09;技术&#xff0c;建立了一个全面智能化的业务管理平台&#xff0c;涵盖从业务流程自动化、数据驱动决策支持&#xff0c;到客户体验优化和供应链管理的各…...

ESP32智能设备:蓝牙音箱、AI语音助手、环境监测与调节以及智能控制,基于BLE与MQTT技术(代码详解)

本文将介绍如何实现一个功能丰富的ESP32项目&#xff0c;集成蓝牙音箱、AI语音助手、智能设备控制器、环境监测与调节等功能。通过本项目&#xff0c;您将学习到硬件设计、嵌入式编程、蓝牙技术、音频处理、人工智能与语音识别、物联网平台、数据分析及用户界面构建等技术。 一…...

web渗透测试 学习导图

web渗透学习路线 前言 一、web渗透测试是什么&#xff1f; Web渗透测试分为白盒测试和黑盒测试&#xff0c;白盒测试是指目标网站的源码等信息的情况下对其渗透&#xff0c;相当于代码分析审计。而黑盒测试则是在对该网站系统信息不知情的情况下渗透&#xff0c;以下所说的Web…...

WordPress禁止后台自定义功能

wordpress后台可以彻底禁止主题的自定义菜单功能&#xff0c;下面这段代码添加到functions.php文件中&#xff0c;后台外观菜单中的”自定义”就会消失不见了。 add_filter(map_meta_cap, function($caps, $cap){if($cap customize){return [do_not_allow];}return $caps; },…...

(六)Flink 窗口计算

窗口(Window)是处理无界流的关键所在。窗口可以将数据流装入大小有限的“桶”中,再对每个“桶”加以处理。 目录 时间概念 窗口类型 窗口划分 窗口的生命周期 Window Assigners 窗口函数 Triggers 窗口触发器 Evictor 数据剔除器 Allowed Lateness 旁路输出 时间…...

SQL 布尔盲注 (injection 第六关)

简介 SQL注入&#xff08;SQL Injection&#xff09;是一种常见的网络攻击方式&#xff0c;通过向SQL查询中插入恶意的SQL代码&#xff0c;攻击者可以操控数据库&#xff0c;SQL注入是一种代码注入攻击&#xff0c;其中攻击者将恶意的SQL代码插入到应用程序的输入字段中&am…...

OpenAI 重回巅峰:ChatGPT-4O 最新模型超越谷歌 Gemini 1.5,多项测试夺冠!

谷歌上周发布的Gemini 1.5 Pro模型&#xff0c;在LMSYS办的聊天机器人竞技场Chatbot Arena中获得第一名。但是&#xff0c;OpenAI迅速反应&#xff0c;推出了最新的chatgpt-4o-latest模型&#xff0c;重新夺回了冠军头衔。 chatgpt-4o-latest模型简介 OpenAI最近推出了名为gpt-…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用

前言&#xff1a;我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM&#xff08;Java Virtual Machine&#xff09;让"一次编写&#xff0c;到处运行"成为可能。这个软件层面的虚拟化让我着迷&#xff0c;但直到后来接触VMware和Doc…...