一.贪吃蛇
贪吃蛇是一款简单而富有乐趣的游戏,它的规则易于理解,但挑战性也很高。它已经成为经典的游戏之一,并且在不同的平台上一直受到人们的喜爱和回忆。
二.贪吃蛇的功能
-
游戏控制:玩家可以使用键盘输入设备来控制蛇的移动方向。
-
蛇的移动:蛇头会根据玩家的输入方向进行移动,而蛇身会随着蛇头的移动而延长,形成一条越来越长的蛇。
-
食物生成:游戏界面会随机生成食物,玩家控制的蛇头需要吃掉这些食物,每吃到一块食物,蛇的身体就会增长一节。
-
碰撞检测:游戏会检测蛇头是否与自身的身体或者墙壁碰撞,如果碰撞则游戏结束。
-
分数计算:游戏会记录玩家吃到的食物数量,并根据数量进行得分计算,通常吃到的食物越多,得分越高。
-
难度递增:随着蛇身越来越长,游戏的难度也会逐渐增加,因为玩家需要更小心地避免碰撞
-
双人游戏:贪吃蛇支持双人游玩,两位玩家可以相互竞争。
三.贪吃蛇项目的实现
1.游戏前的准备
1.1游戏的状态
enum state
枚举类型定义了游戏的状态,包括以下常量:ok
:正常状态,表示游戏进行中。by_wall
:撞墙状态,表示蛇撞到了地图的边界。by_body
:撞到蛇的身体状态,表示蛇头撞到了蛇身。by_self
:蛇咬到自己状态,表示蛇头咬到了自己的身体。by_end
:游戏结束状态。by_headpush
:蛇头相互碰撞状态,表示两个玩家的蛇头相互碰撞。
enum state
{
ok = 1, // 游戏状态:正常状态
by_wall, // 游戏状态:撞墙
by_body, // 游戏状态:撞到蛇的身体
by_self, // 游戏状态:蛇咬到自己
by_end, // 游戏状态:游戏结束
by_headpush // 游戏状态:蛇头相互碰撞
};
1.2蛇的移动方向
enum direction
枚举类型定义了蛇的移动方向,包括以下常量:up
:表示向上移动。down
:表示向下移动。left
:表示向左移动。right
:表示向右移动。
enum direction
{
up = 1, // 方向:上
down, // 方向:下
left, // 方向:左
right // 方向:右
};
1.3蛇的节点
struct SnakeNode
结构体用于表示蛇的一个节点,包括以下成员:x
:节点的横坐标。y
:节点的纵坐标。next
:指向下一个节点的指针。
typedef struct SnakeNode
{
int x; // 蛇节点的横坐标
int y; // 蛇节点的纵坐标
struct SnakeNode* next; // 指向下一个蛇节点的指针
} SnakeNode, * pSnakeNode;
1.4食物的位置
struct Food
结构体用于表示食物的位置,包括以下成员:x
:食物的横坐标。y
:食物的纵坐标。
typedef struct Food
{
int x; // 食物的横坐标
int y; // 食物的纵坐标
} Food, * pFood;
1.5整个贪吃蛇游戏
struct Snake
结构体用于表示蛇,包括以下成员:_snake
:蛇的头节点指针。_food
:食物指针。_score
:蛇的得分。dir
:蛇的移动方向。sta
:当前游戏状态。_weight
:奖励。_sleeptime
:游戏循环每次暂停的时间间隔。
typedef struct Snake
{
pSnakeNode _snake; // 蛇的头节点指针
pFood _food; // 食物指针
int _score; // 蛇的得分
enum direction dir; // 蛇的移动方向
enum state sta; // 当前游戏状态
int _weight; // 奖励
int _sleeptime; // 游戏循环每次暂停的时间间隔
} Snake, * pSnake;
2.游戏开始
2.1本地化设置
使用setlocale
函数设置本地化环境为当前系统默认的环境。因为环境的差异,导致我们的中文的宽字符无法被识别,所以要本地化设置。
int main()
{
setlocale(LC_ALL, ""); // 设置本地化环境为当前系统默认的环境
test(); // 调用test函数进行测试
return 0; // 返回0表示程序正常结束
}
2.2 实现贪吃蛇的基本流程
test
函数中,通过srand
函数将当前时间作为随机数种子。然后使用do-while
循环进行游戏的测试和循环控制。在循环内部,首先使用malloc
动态分配了两个Snake
结构体的内存,并将其指针赋值给snake1
和snake2
。接着调用gamestart
函数开始游戏,传入snake1
和snake2
作为参数;然后调用gamerun
函数进行游戏运行,同样传入snake1
和snake2
作为参数;最后调用gameend
函数结束游戏并释放内存,同样传入snake1
和snake2
作为参数。之后,使用system("cls")
清空控制台屏幕,然后在指定位置打印提示信息。接下来,使用getchar
函数获取一个字符并赋值给变量ch
,再使用getchar
读取多余的换行符。最后,判断ch
是否为字符'y'
或'Y'
,如果是则继续进行下一轮游戏。
#include "snake.h" // 引入自定义的头文件snake.h
void test()
{
srand((unsigned int)time(NULL)); // 使用当前时间作为随机数种子
int ch;
do {
pSnake snake1 = (pSnake)malloc(sizeof(Snake)); // 动态分配一个Snake结构体的内存,并将其指针赋给snake1
pSnake snake2 = (pSnake)malloc(sizeof(Snake)); // 动态分配一个Snake结构体的内存,并将其指针赋给snake2
gamestart(snake1, snake2); // 调用gamestart函数开始游戏,传入snake1和snake2作为参数
gamerun(snake1, snake2); // 调用gamerun函数进行游戏运行,传入snake1和snake2作为参数
gameend(snake1, snake2); // 调用gameend函数结束游戏,释放内存,传入snake1和snake2作为参数
system("cls"); // 清空控制台屏幕
setpos(46, 15); // 设置光标位置为(46, 15)
printf("选择Y/N");
setpos(50, 16); // 设置光标位置为(50, 16)
ch = getchar(); // 从标准输入中获取一个字符,赋值给ch
getchar(); // 读取多余的换行符
} while (ch == 'y' || ch == 'Y'); // 如果输入的字符是'y'或'Y',则继续进行下一轮游戏
}
2.3设置光标
该函数接受两个整型参数x
和y
,分别表示光标的横坐标和纵坐标。在函数内部,首先调用GetStdHandle
函数获取标准输出句柄,该句柄用于操作控制台窗口。然后创建一个COORD
结构体变量coord
,并将x
赋值给coord.X
,将y
赋值给coord.Y
,即设置光标的位置。最后,调用SetConsoleCursorPosition
函数将控制台光标位置设置为指定的坐标。这样,在调用setpos
函数时,控制台光标会移动到指定的位置。
void setpos(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
COORD coord;
coord.X = x; // 设置光标的横坐标为x
coord.Y = y; // 设置光标的纵坐标为y
SetConsoleCursorPosition(handle, coord); // 设置控制台光标位置为指定的坐标
}
2.4游戏的初始化
该函数接受两个参数,snake1
和snake2
,分别表示两条蛇的指针。函数开始使用断言assert
,确保snake1
和snake2
不为NULL
,以保证后续操作的正确性。然后依次调用以下函数:
welcome
:显示游戏欢迎信息,可能是在控制台输出一些欢迎文本。creatmap
:创建游戏地图,可能是在控制台上绘制一个游戏地图的界面。initsnake
:初始化蛇的身体,可能是将蛇的初始位置和长度设置为默认值。creatsnake
:生成两条蛇的初始位置,可能是将两条蛇放置在地图的指定位置。creatfood
:生成食物的位置,以供两条蛇争夺,可能是将食物随机放置在地图的空闲位置。gameinfo
:显示游戏信息,可能是在控制台上显示当前游戏的状态、得分等信息。
void gamestart(pSnake snake1, pSnake snake2)
{
assert(snake1 && snake2); // 使用断言确保snake1和snake2不为NULL
welcome(); // 调用welcome函数,显示游戏欢迎信息
creatmap(); // 调用creatmap函数,创建游戏地图
initsnake(snake1); // 调用initsnake函数,初始化snake1蛇身
initsnake(snake2); // 调用initsnake函数,初始化snake2蛇身
creatsnake(snake1, snake2);// 调用creatsnake函数,生成snake1和snake2的初始位置
creatfood(snake1, snake2);// 调用creatfood函数,生成食物的位置,以供snake1和snake2争夺
creatfood(snake2, snake1);// 调用creatfood函数,生成食物的位置,以供snake2和snake1争夺
gameinfo(snake1, snake2); // 调用gameinfo函数,显示游戏信息
}
2.4.1欢迎信息
首先调用system
函数设置控制台窗口的大小为100列,30行,使用命令mode con cols=100 lines=30
实现。然后调用system
函数设置控制台窗口的标题为"贪吃蛇",使用命令title 贪吃蛇
实现。接下来,获取标准输出句柄,并获取控制台光标信息。将光标的可见性设置为false
,即不可见,然后再将修改后的光标信息设置回控制台。然后使用setpos
函数设置光标位置为(40, 10),在该位置打印欢迎信息。接着,使用setpos
函数设置光标位置为(40, 20),调用system("pause")
暂停程序的执行,等待用户按下任意键。然后使用system("cls")
清空控制台屏幕。接下来,使用setpos
函数设置光标位置为(38, 10),然后依次打印游戏规则的内容。再次使用setpos
函数设置光标位置为(40, 20),使用system("pause")
暂停程序的执行,等待用户按下任意键。最后,使用system("cls")
清空控制台屏幕。
void welcome()
{
system("mode con cols=100 lines=30"); // 设置控制台窗口的大小为100列,30行
system("title 贪吃蛇"); // 设置控制台窗口的标题为"贪吃蛇"
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
CONSOLE_CURSOR_INFO cursor;
GetConsoleCursorInfo(handle, &cursor); // 获取控制台光标信息
cursor.bVisible = false; // 将光标设置为不