C语言综合练习6:制作贪吃蛇

news/2025/2/14 4:10:51/

1 初始化界面

因为还没学QT,我们就使用终端界面替代。
这里我们假设界面中没有障碍物,我们只需要设定界面的高宽就行,这是蛇的移动范围,我们可以写两个宏来规定界面的高宽
新建一个snake.c的文件

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>#define WIDE  60
#define HIGH  20void init_ui()
{for (int i = 0; i < HIGH; i++){for (int j = 0; j < WIDE; j++){printf("#");}printf("\n");}
}

新建一个名为main.c的文件,作为测试用,内容如下:

int main() {init_ui();return 0;
}

输出
在这里插入图片描述

2 初始化状态

蛇分为蛇头和蛇身,假设最开始的时候,蛇的长度只有两节,一节是蛇头,一节是蛇身。
要把蛇打印到界面上,那么先知道蛇头和蛇身的坐标,这里我们定义一个结构体来保存蛇的每一节的坐标

typedef struct _position
{int x;int y;
}POSITION;

x和y的增长方向如下图所示
在这里插入图片描述

任意时刻,布局中除了有蛇,还有食物,我们可以把蛇和食物都放进同一结构体里

typedef struct _status
{POSITION list[WIDE * HIGH];		//蛇的最大长度为WIDE * HIGH,数组的每个元素都是POSITION类型int snake_size;					//蛇的长度POSITION food_position;			//食物位置
}STATUS;

现在要定义一个生成食物的函数,因为它是在界面中随机产生,所以我们需要使用随机化函数

void generate_food(STATUS* status)
{srand(time(NULL));			//设置随机种子//初始化食物status->food_position.x = rand() % WIDE;status->food_position.y = rand() % HIGH;
}

现在我们可以初始化状态了

void init_status(STATUS* status) {//蛇长status->snake_size = 2;//蛇头status->list[0].x = WIDE / 2;status->list[0].y = HIGH / 2;//蛇身status->list[1].x = WIDE / 2 + 1;status->list[1].y = HIGH / 2;//初始化食物位置generate_food(status);
}

3 设置光标位置

在Windows.h文件中,定义了一个名为COORD的类型,内容如下:

typedef struct _COORD {SHORT X;SHORT Y;
} COORD;

这个类型的变量可以设置光标位置

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<Windows.h>
int main() {COORD coord;//行号和列号都是从0开始coord.X = 5;			//第6列coord.Y = 10;			//第11行init_ui();//设置光标在第11行、第6列SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);//在光标位置打印指定字符串printf("12345");system("pause");return 0;
}

控制台输出
在这里插入图片描述

4 将状态显示

有了COORD,我们在打印食物和蛇的时候就能轻松很多。因为光标的位置经常要设置,所以我们可以在状态结构体中插入一个COORD类型的成员变量,新的结构体如下:

typedef struct _status
{POSITION list[WIDE * HIGH];		//蛇的最大长度为WIDE * HIGH,数组的每个元素都是POSITION类型int snake_size;					//蛇的长度POSITION food_position;			//食物位置COORD coord;					//便于设置光标
}STATUS;

我们建立一个显示函数,把蛇和食物打印出来

void show_ui(STATUS* status)
{//显示食物status->coord.X = status->food_position.x;status->coord.Y = status->food_position.y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("#");//显示蛇for (int i = 0; i < status->snake_size; i++){status->coord.X = status->list[i].x;status->coord.Y = status->list[i].y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);if (0 == i) printf("@");	//打印蛇头elseprintf("*");	//打印蛇身}
}

测试函数如下:

int main() {STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);show_ui(status);system("pause");return 0;
}

输出
在这里插入图片描述

5 根据蛇的方向更新蛇的位置

蛇是移动的,并且会长大的,所以我们需要及时更新蛇的位置。

为了能够更新谁的位置,我们需要一对变量来规定蛇头移动的方向,可以在状态结构体中增加两个变量

typedef struct _status
{POSITION list[WIDE * HIGH];		//蛇的最大长度为WIDE * HIGH,数组的每个元素都是POSITION类型int snake_size;					//蛇的长度POSITION food_position;			//食物位置COORD coord;int dx, dy;						//蛇头移动方向
}STATUS;

相应地,需要修改状态初始化函数:

void init_status(STATUS* status) {//蛇长status->snake_size = 2;//蛇头status->list[0].x = WIDE / 2;status->list[0].y = HIGH / 2;//蛇身status->list[1].x = WIDE / 2 + 1;status->list[1].y = HIGH / 2;//蛇头移动方向status->dx = -1;status->dy = 0;//初始化食物位置generate_food(status);
}

此时,我们可以根据dx和dy更新蛇的位置了

void move_snake(STATUS* status)
{//更新蛇身的坐标for (int i = status->snake_size - 1; i >= 1; i--){//数组status->list的每一个元素都是结构体变量,因此可以直接赋值status->list[i] = status->list[i - 1];	}//更新蛇头的坐标status->list[0].x += status->dx;status->list[0].y += status->dy;
}

测试代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<Windows.h>
int main()
{STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);while (1){system("cls");			//清屏show_ui(status);Sleep(300);				//睡眠300ms(Windows系统中)move_snake(status);		//更新蛇的位置}system("pause");return 0;
}

这里必须先清屏后显示,否则清屏后延迟300ms,导致看到的屏幕一直是清屏状态,这里有时间可以自己实验一下。

好了,我们的贪吃蛇终于能跑了,但由于我还不知道如何在这里插入gif动图,所以这里就不贴输出了

6 从键盘获得按键信息

既然是游戏,必然需要通过键盘输入获得信息,可以使用下面这段代码从键盘获取信息,当按下键盘时,进入while循环,松开后退出循环

//判断是否按下按键
#include <conio.h>
char  key;
while (_kbhit()) //判断是否按下按键,按下不等于0 
{key = _getch();
}

上面的程序需要放在循环里面,因为程序一瞬间就执行完了,while循环不会停下来等你

我们可以测试一下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include <conio.h>
int main()
{char  key;int is_break = 0;while (1){while (_kbhit()) //判断是否按下按键,按下不等于0 {key = _getch();is_break = 1;break;}if (is_break)break;}printf("%c\n", key);return 0;
}

7 使用键盘控制蛇前进的方向

有了_kbhit()_getch(),现在就能用键盘控制蛇的方向了,写一个来实现键盘控制方向

void control_snake(STATUS* status)
{char  key = 0;		//这里必须初始化,因为可能不会进入while循环中,导致key未赋值,从而在switch语句中报错while (_kbhit())	//判断是否按下按键,按下不等于0 {key = _getch();}//使用wsad分别控制上下左右,其它按键无效switch (key){case 'a':status->dx = -1;status->dy = 0;break;case 'w':status->dx = 0;status->dy = -1;break;case 's':status->dx = 0;status->dy = 1;break;case 'd':status->dx = 1;status->dy = 0;break;}
}

测试程序如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<Windows.h>
int main()
{STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);while (1){system("cls");			//清屏show_ui(status);Sleep(300);				//睡眠300ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向move_snake(status);		//更新蛇的位置}system("pause");return 0;
}

我们终于可以控制蛇前进的方向了,但这个程序还是有bug的,因为我们这个贪吃蛇居然还能掉头,所以必须修改control_snake,使其不能掉头

void control_snake(STATUS* status)
{char  key = 0;		//这里必须初始化,因为可能不会进入while循环中,导致key未赋值,从而在switch语句中报错while (_kbhit())	//判断是否按下按键,按下不等于0 {key = _getch();}//使用wsad分别控制上下左右,其它按键无效switch (key){case 'a':if (1 == status->dx && 0 == status->dy)		//防止出现调头break;else{status->dx = -1;status->dy = 0;break;}case 'w':if (1 == status->dy) //status->dy和status->dx中,有且只有一个0,因此只需要判断一个break;else{status->dx = 0;status->dy = -1;break;}case 's':if (-1 == status->dy)break;else{status->dx = 0;status->dy = 1;break;}case 'd':if (-1 == status->dx)break;else{status->dx = 1;status->dy = 0;break;}}
}

测试程序同上,这里不再赘述

8 游戏得分

既然是游戏,就有评价标准,贪吃蛇通过吃了多少个食物来衡量得分。我们需要在状态结构体定义中加入分数变量

typedef struct _status
{POSITION list[WIDE * HIGH];		//蛇的最大长度为WIDE * HIGH,数组的每个元素都是POSITION类型int snake_size;					//蛇的长度POSITION food_position;			//食物位置COORD coord;int dx, dy;						//蛇头移动方向int score;						//游戏得分
}STATUS;

相应的也要修改状态初始化函数

void init_status(STATUS* status) {//蛇长status->snake_size = 2;//蛇头status->list[0].x = WIDE / 2;status->list[0].y = HIGH / 2;//蛇身status->list[1].x = WIDE / 2 + 1;status->list[1].y = HIGH / 2;//蛇头移动方向status->dx = -1;status->dy = 0;//游戏得分status->score = 0;//初始化食物位置generate_food(status);
}

9 检测蛇是否碰到墙

检测碰到墙,可以通过蛇头是否超出边界来判断,这里我们定义一个检测越界的函数

int is_out_range(STATUS* status)
{int ret;if (status->list[0].x >= 0 && status->list[0].x < WIDE &&status->list[0].y >= 0 && status->list[0].y < HIGH)ret = 0;elseret = 1;return ret;
}

注意,因为食物的位置,横纵坐标都有可能是0,因此0不能判定为越界,所以要取>=0

测试代码

int main() {STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);while (1){system("cls");			//清屏show_ui(status);		Sleep(300);				//睡眠300ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向move_snake(status);		//更新蛇的位置if (is_out_range(status))break;}printf("游戏结束,得分为%d\n", status->score);system("pause");return 0;
}

输出
在这里插入图片描述

现在可以检测越界,并在游戏结束后计算得分,但打印得分的位置有点尴尬,显示完蛇身之后,光标就在蛇最后一节的右边,于是就在这个位置上继续打印。

对测试代码进行如下修改:

int main() {STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);while (1){system("cls");			//清屏show_ui(status);		Sleep(300);				//睡眠300ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向move_snake(status);		//更新蛇的位置if (is_out_range(status))	//判断蛇头是否越界break;}//重新设定光标位置,方面打印得分status->coord.X = 5;status->coord.Y = HIGH + 1;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("游戏结束,得分为%d\n", status->score);system("pause");return 0;
}

输出
在这里插入图片描述

10 检测蛇是否吃到食物

这里只需要判断蛇头坐标是否和食物坐标重合,如果是则吃到食物,否则没迟到

void eat_food(STATUS* status)
{if (status->list[0].x == status->food_position.x &&status->list[0].y == status->food_position.y){status->snake_size++;			//蛇身增长status->score += 10;			//分数增加generate_food(status);			//重新生成一个食物}
}

这里蛇身增长之后,无需考虑增长的那一节的坐标,只需要更新status->snake_size就行,因为在move_snake函数中,存在下面这一段代码

	//更新蛇身的坐标for (int i = status->snake_size - 1; i >= 1; i--){//数组status->list的每一个元素都是结构体变量,因此可以直接赋值status->list[i] = status->list[i - 1];	}

新增的那一节,会在第一轮循环的时候得到原先最后一节的坐标,后面的循环,会使原来的每一节得到前一节的坐标,从而使蛇增长。

另外,我们这里还有个bug,因为生成的食物位置是随机的,有可能生成的位置在蛇身上,因此需要对生成的食物位置进行判断,如果在蛇身上则需要重新生成。

改进后的生成食物代码如下:

void generate_food(STATUS* status)
{srand(time(NULL));			//设置随机种子//初始化食物status->food_position.x = rand() % WIDE;status->food_position.y = rand() % HIGH;int in_snake = 1;while (in_snake){for (int i = 0; i < status->snake_size; i++){if (status->food_position.x == status->list[i].x &&status->food_position.y == status->list[i].y){in_snake = 1;break;}in_snake = 0;}//如果 in_snake==1,表示循环是中途退出的,意味着生成的事物在蛇身上,因此要重新生成食物//如果 in_snake==0,表示循环是正常退出的,此时in_snake不再满足while循环的条件if (in_snake){//重新生成食物status->food_position.x = rand() % WIDE;status->food_position.y = rand() % HIGH;}}
}

下面是测试函数

int main() {STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);while (1){system("cls");			//清屏show_ui(status);Sleep(300);				//睡眠300ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向eat_food(status);		//判断蛇是否吃到食物move_snake(status);		//更新蛇的位置if (is_out_range(status))	//判断蛇头是否越界break;}//重新设定光标位置,方面打印得分status->coord.X = 5;status->coord.Y = HIGH + 1;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("游戏结束,得分为%d\n", status->score);system("pause");return 0;
}

输出
在这里插入图片描述

好了,现在的贪吃蛇可以吃到食物了。

11 检测蛇是否咬到自己

这个只需要判断蛇头的坐标是否和蛇身的某一节坐标相等即可。

int is_eat_body(STATUS* status)
{int ret;for (int i = 1; i < status->snake_size; i++){if (status->list[0].x == status->list[i].x && status->list[0].y == status->list[i].y){ret = 1;break;}elseret = 0;}return ret;
}

测试代码:

int main() {STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);while (1){system("cls");			//清屏show_ui(status);Sleep(300);				//睡眠300ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向eat_food(status);		//判断蛇是否吃到食物move_snake(status);		//更新蛇的位置if (is_out_range(status))	//判断蛇头是否越界break;if (is_eat_body(status))	//判断是否咬到自己break;}//重新设定光标位置,方面打印得分status->coord.X = 5;status->coord.Y = HIGH + 1;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("游戏结束,得分为%d\n", status->score);system("pause");return 0;
}

这里需要注意的是,if (is_eat_body(status))需要在move_snake(status);后面,假如在move_snake(status);的前面,则是判断上一轮循环中,所更新得到的蛇的位置(即上一轮循环中move_snake的结果),并且此时已经显示把蛇吃到自己的结果显示出来了(蛇头被蛇身覆盖,因为蛇身在蛇头之后打印),这个有时间可以自己去尝试一下。

结果:
在这里插入图片描述

12 隐藏控制台光标

前面的程序,蛇最后一节的右边,还有一个光标,影响蛇的美观
在这里插入图片描述
接下来我们把它去掉。
可以将以下代码放置于main函数的开头,实现光标的隐藏:

//隐藏控制台光标
CONSOLE_CURSOR_INFO  cci;
cci.dwSize = sizeof(cci);
cci.bVisible = FALSE;
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);

为了使main函数精简,将上面的代码段封装成函数

void hide_cur()
{//隐藏控制台光标CONSOLE_CURSOR_INFO  cci;cci.dwSize = sizeof(cci);cci.bVisible = FALSE;SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);
}

测试函数变成下面的形式:

13 建墙

前面的程序,我们是看不到左边界和下边界的,只有撞墙了才知道
现在我们写一个函数来建墙

void init_wall()
{for (int i = 0; i <= HIGH; i++){for (int j = 0; j <= WIDE; j++){if (i == HIGH || j == WIDE)printf("+");elseprintf(" ");}printf("\n");}
}

测试代码如下:

int main() {//隐藏控制台光标hide_cur();STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);while (1){system("cls");			//清屏init_wall();			//显示边界show_ui(status);Sleep(300);				//睡眠300ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向eat_food(status);		//判断蛇是否吃到食物move_snake(status);		//更新蛇的位置if (is_out_range(status))	//判断蛇头是否越界break;if (is_eat_body(status))	//判断是否咬到自己break;}//重新设定光标位置,方面打印得分status->coord.X = 5;status->coord.Y = HIGH + 1;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("游戏结束,得分为%d\n", status->score);system("pause");return 0;
}

效果很好,但是墙总是一闪一闪的,晃眼,因为程序每隔300ms就清屏一次。如果把清屏函数去掉,并且把init_wall();放到while循环外面,那么将导致蛇的轨迹一直留在屏幕上。

解决这个问题,只需要在show_ui函数中,在上一轮蛇尾的位置打印空格键即可,以下是修改后的show_ui函数

void show_ui(STATUS* status)
{//显示食物status->coord.X = status->food_position.x;status->coord.Y = status->food_position.y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("#");//显示蛇for (int i = 0; i < status->snake_size; i++){status->coord.X = status->list[i].x;status->coord.Y = status->list[i].y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);if (0 == i) printf("@");	//打印蛇头elseprintf("*");	//打印蛇身}//蛇尾打印空格,防止显示轨迹status->coord.X = status->list[status->snake_size].x;status->coord.Y = status->list[status->snake_size].y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf(" ");
}

最后的测试代码如下:

int main() {//隐藏控制台光标hide_cur();STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);init_wall();			//显示边界while (1){//system("cls");			//清屏show_ui(status);Sleep(300);				//睡眠300ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向eat_food(status);		//判断蛇是否吃到食物move_snake(status);		//更新蛇的位置if (is_out_range(status))	//判断蛇头是否越界break;if (is_eat_body(status))	//判断是否咬到自己break;}//重新设定光标位置,方面打印得分status->coord.X = 5;status->coord.Y = HIGH + 1;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("游戏结束,得分为%d\n", status->score);system("pause");return 0;
}

输出
在这里插入图片描述
蛇只有在向左移动的时候,轨迹才能去除,原因是下面这段程序并不是在上一个循环中的蛇尾位置上打印空格,而是在一个随机的位置上打印空格(因为status->list[status->snake_size].xstatus->list[status->snake_size].y就是随机值,可以通过debug看到),之所以在想左的时候有效,是因为光标的重新定位不成功(由于是随机值,无法实现定位),于是光标仍然在蛇的最后一节的右边位置,因此能去掉轨迹,但向其他方向就不行了。

	//蛇尾打印空格,防止显示轨迹status->coord.X = status->list[status->snake_size].x;status->coord.Y = status->list[status->snake_size].y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf(" ");

我们需要在状态结构体中,新增一个变量来保存蛇尾位置

typedef struct _status
{POSITION list[WIDE * HIGH];		//蛇的最大长度为WIDE * HIGH,数组的每个元素都是POSITION类型int snake_size;					//蛇的长度POSITION food_position;			//食物位置COORD coord;int dx, dy;						//蛇头移动方向int score;						//游戏得分POSITION tail;					//上一拍(即上一轮循环)的蛇尾位置
}STATUS;

初始化函数是否变无所谓

void init_status(STATUS* status) {//蛇长status->snake_size = 2;//蛇头status->list[0].x = WIDE / 2;status->list[0].y = HIGH / 2;//蛇身status->list[1].x = WIDE / 2 + 1;status->list[1].y = HIGH / 2;//蛇头移动方向status->dx = -1;status->dy = 0;//游戏得分status->score = 0;//蛇尾status->tail = status->list[1];//初始化食物位置generate_food(status);
}

更新蛇位置的函数要变

void move_snake(STATUS* status)
{//记录移动前的蛇尾位置status->tail = status->list[status->snake_size - 1];//更新蛇身的坐标for (int i = status->snake_size - 1; i >= 1; i--){//数组status->list的每一个元素都是结构体变量,因此可以直接赋值status->list[i] = status->list[i - 1];	}//更新蛇头的坐标status->list[0].x += status->dx;status->list[0].y += status->dy;
}

当蛇身增长时,status->list[status->snake_size - 1]虽然是蛇尾,但其坐标却是随机值,因为需要在后面的“更新蛇身的坐标”之后,新的蛇尾才有坐标,不过却不影响,原因稍后会讲。

最后是修改show_ui函数

void show_ui(STATUS* status)
{//显示食物status->coord.X = status->food_position.x;status->coord.Y = status->food_position.y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("#");//显示蛇for (int i = 0; i < status->snake_size; i++){status->coord.X = status->list[i].x;status->coord.Y = status->list[i].y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);if (0 == i) printf("@");	//打印蛇头elseprintf("*");	//打印蛇身}//蛇尾打印空格,防止显示轨迹status->coord.X = status->tail.x;status->coord.Y = status->tail.y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf(" ");
}

有一种可能,就是在刚刚吃完食物,status->snake_size增长,这种情况下,move_snake函数中status->list[status->snake_size - 1]虽然是蛇尾,但其坐标并未赋值,或者说,此时蛇尾的坐标还是随机值,因为需要在后面的“更新蛇身的坐标”之后,蛇尾才有坐标。不过由于status->tail得到的是随机的坐标,使得show_ui函数中光标重定位失败,进而上一轮的蛇尾位置没能打印出空格,而是保留了#,但由于蛇身本身增长,上一轮蛇尾的位置,本轮依然是蛇尾的位置,因此仍然需要打印#,阴差阳错导致结果正确。

输出
在这里插入图片描述

至此,我们实现了贪吃蛇的基本功能了。

14 总结

贪吃蛇游戏除了main函数外,我们还写了12个函数,其中很多函数都不是一步到位,而是慢慢完善,这也符合软件工程的特点,循序渐进。我们之前写的快译通也是如此,先实现一个简单的,然后再实现复杂的。
贪吃蛇的最终版整体程序如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<conio.h>
#define WIDE  60
#define HIGH  20typedef struct _position
{int x;int y;
}POSITION;typedef struct _status
{POSITION list[WIDE * HIGH];		//蛇的最大长度为WIDE * HIGH,数组的每个元素都是POSITION类型int snake_size;					//蛇的长度POSITION food_position;			//食物位置COORD coord;int dx, dy;						//蛇头移动方向int score;						//游戏得分POSITION tail;					//上一拍(即上一轮循环)的蛇尾位置
}STATUS;void init_ui()
{for (int i = 0; i < HIGH; i++){for (int j = 0; j < WIDE; j++){printf("#");}printf("\n");}
}void generate_food(STATUS* status)
{srand(time(NULL));			//设置随机种子//初始化食物status->food_position.x = rand() % WIDE;status->food_position.y = rand() % HIGH;int in_snake = 1;while (in_snake){for (int i = 0; i < status->snake_size; i++){if (status->food_position.x == status->list[i].x &&status->food_position.y == status->list[i].y){in_snake = 1;break;}in_snake = 0;}//如果 in_snake==1,表示循环是中途退出的,意味着生成的事物在蛇身上,因此要重新生成食物//如果 in_snake==0,表示循环是正常退出的,此时in_snake不再满足while循环的条件if (in_snake){//重新生成食物status->food_position.x = rand() % WIDE;status->food_position.y = rand() % HIGH;}}
}void init_status(STATUS* status) {//蛇长status->snake_size = 2;//蛇头status->list[0].x = WIDE / 2;status->list[0].y = HIGH / 2;//蛇身status->list[1].x = WIDE / 2 + 1;status->list[1].y = HIGH / 2;//蛇头移动方向status->dx = -1;status->dy = 0;//游戏得分status->score = 0;//蛇尾status->tail = status->list[1];//初始化食物位置generate_food(status);
}void show_ui(STATUS* status)
{//显示食物status->coord.X = status->food_position.x;status->coord.Y = status->food_position.y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("#");//显示蛇for (int i = 0; i < status->snake_size; i++){status->coord.X = status->list[i].x;status->coord.Y = status->list[i].y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);if (0 == i) printf("@");	//打印蛇头elseprintf("*");	//打印蛇身}//蛇尾打印空格,防止显示轨迹status->coord.X = status->tail.x;status->coord.Y = status->tail.y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf(" ");
}void move_snake(STATUS* status)
{//记录移动前的蛇尾位置status->tail = status->list[status->snake_size - 1];//更新蛇身的坐标for (int i = status->snake_size - 1; i >= 1; i--){//数组status->list的每一个元素都是结构体变量,因此可以直接赋值status->list[i] = status->list[i - 1];	}//更新蛇头的坐标status->list[0].x += status->dx;status->list[0].y += status->dy;
}void control_snake(STATUS* status)
{char  key = 0;		//这里必须初始化,因为可能不会进入while循环中,导致key未赋值,从而在switch语句中报错while (_kbhit())	//判断是否按下按键,按下不等于0 {key = _getch();}//使用wsad分别控制上下左右,其它按键无效switch (key){case 'a':if (1 == status->dx && 0 == status->dy)		//防止出现调头break;else{status->dx = -1;status->dy = 0;break;}case 'w':if (1 == status->dy) //status->dy和status->dx中,有且只有一个0,因此只需要判断一个break;else{status->dx = 0;status->dy = -1;break;}case 's':if (-1 == status->dy)break;else{status->dx = 0;status->dy = 1;break;}case 'd':if (-1 == status->dx)break;else{status->dx = 1;status->dy = 0;break;}}
}void start_game(STATUS* status)
{//蛇的前进方向}int is_out_range(STATUS* status)
{int ret;if (status->list[0].x >= 0 && status->list[0].x < WIDE &&status->list[0].y >= 0 && status->list[0].y < HIGH)ret = 0;elseret = 1;return ret;
}int is_eat_body(STATUS* status)
{int ret;for (int i = 1; i < status->snake_size; i++){if (status->list[0].x == status->list[i].x && status->list[0].y == status->list[i].y){ret = 1;break;}elseret = 0;}return ret;
}void eat_food(STATUS* status)
{if (status->list[0].x == status->food_position.x &&status->list[0].y == status->food_position.y){status->snake_size++;			//蛇身增长status->score += 10;			//分数增加generate_food(status);			//重新生成一个食物}
}void hide_cur()
{//隐藏控制台光标CONSOLE_CURSOR_INFO  cci;cci.dwSize = sizeof(cci);cci.bVisible = FALSE;SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);
}void init_wall()
{for (int i = 0; i <= HIGH; i++){for (int j = 0; j <= WIDE; j++){if (i == HIGH || j == WIDE)printf("+");elseprintf(" ");}printf("\n");}
}int main() {//隐藏控制台光标hide_cur();STATUS* status = (STATUS*)malloc(sizeof(STATUS));init_status(status);init_wall();			//显示边界while (1){show_ui(status);Sleep(200);				//睡眠200ms(Windows系统中)control_snake(status);	//键盘控制蛇的方向eat_food(status);		//判断蛇是否吃到食物move_snake(status);		//更新蛇的位置if (is_out_range(status))	//判断蛇头是否越界break;if (is_eat_body(status))	//判断是否咬到自己break;}//重新设定光标位置,方面打印得分status->coord.X = 5;status->coord.Y = HIGH + 1;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), status->coord);printf("游戏结束,得分为%d\n", status->score);system("pause");return 0;
}

http://www.ppmy.cn/news/586052.html

相关文章

嵌入式开发课程简介

最近几年&#xff0c;几乎所有的IT企业对应届毕业生都有抱怨&#xff1a;动手能力太差&#xff0c;编程水平低下。大学期间&#xff0c;老师授课以理论为主&#xff0c;学生缺少各种实践。这造成很多学生就业难&#xff1b;学生找不到工作&#xff0c;而企业招不到合适的人&…

C语言学习之路(基础篇)—— 文件操作(下)

说明&#xff1a;该篇博客是博主一字一码编写的&#xff0c;实属不易&#xff0c;请尊重原创&#xff0c;谢谢大家&#xff01; 文件的随机读写 1) fseek 表头文件&#xff1a;#include <stdio.h>定义函数&#xff1a;int fseek(FILE *stream, long offset, int whenc…

Stable Diffusion 对图像进行风格化

风格化是基于现有图像转换成另一种风格的操作方法&#xff0c;通常应用于img2img中&#xff0c;将文字提示中特定的新风格应用于原图像上进行修改。在这个过程中并非使用随机的潜在状态&#xff0c;而是采用原始图像去编码初始潜在状态。在此基础上通过加入少量的随机性&#x…

RHEL 9 新特性及技术演示

OpenSSH&#xff1a;新增禁止 root 的密码登录 Cockpit&#xff1a;RHEL 的 Web 控制台 DNF-3&#xff1a;软件安装方法 NetworkManager&#xff1a;网络管理的主要组件 Nftables&#xff1a;默认的用户空间防火墙 WireGuard&#xff1a;快速、安全的 VPN 隧道&#xff08…

什么是Dos攻击?

①DOS攻击&#xff1a; DOS&#xff1a;中文名称是拒绝服务&#xff0c;一切能引起DOS行为的攻击都被称为dos攻击。该攻击的效果是使得计算机或网络无法提供正常的服务。常见的DOS攻击有针对计算机网络带宽和连通性的攻击。 DOS是单机于单机之间的攻击。 DOS攻击的原理&#…

DOS和CMD的区别

DOS和CMD的区别 DOS&#xff08;disk operating system&#xff09;磁盘操作系统&#xff0c;是一个顶层系统&#xff0c;在Windows GUI&#xff08;图形界面&#xff09;出来以前就是人和机器交互的工具&#xff0c;当时人和机器交互就只能通过命令而不能使用鼠标&#xff0c;…

cmd和dos的区别

你在windows操作系统里进的DOS(即输入 CMD 进命令提示符)不是纯DOS,只是为方便某些需求而建立的, 而纯DOS本身就是一种操作系统.(两者的区别:比如你可以在纯DOS下删除你的 windows系统, 但在你所说的"命令提示符"里却不能,因为你不可能"在房子里面拆房子吧?&qu…

在计算机中dos代表什么意思,dos是什么意思?怎么进DOS命令的方法

dos怎么进入?DOS是Disk Operation System(磁盘操作系统)的简称。dos是一个基于磁盘管理的操作系统&#xff0c;是一种命令行形式的&#xff0c;需要输入命令的形式才能把指令传给计算机&#xff0c;让计算机实现操作的。经常在修理电脑时会需要进入dos&#xff0c;但是很多朋友…