“饕餮之路:贪吃蛇编程精粹“

目录

前言

1.写代码前的预准备

1.1修改控制台

1.2相关WinAPI32知识学习

1.2.1WinAPI32介绍

1.2.2 mode 命令

1.2.3 title 命令

1.2.4COORD

1.2.5GetStdHandle

1.2.6对控制台光标进行设置

1.2.6.1GetConsolrCursorInfo

1.2.6.2 CONSOLE_CURSOR_INFO

1.2.6.3SetConsolrCursorInfo

1.2.6.4修改光标大小和可见度代码示例

1.2.7设置光标出现的位置

1.2.7.1SetConsoleCursorPosition

1.2.7.2代码示例

1.2.7.3封装函数

1.2.8GetAsyncKeyState

1.2.9如何修改颜色

1.2.10如何修改控制台背景颜色

1.3将C语言库本地化

1.3.1简单说一下为什么要本地化

1.3.2本地化后打印字符和字符串

1.4控制台窗口的横纵坐标比

2.游戏整体的逻辑框架

3.GameStart()

3.1设置控制台大小和名字

3.2隐藏光标

3.3打印游戏界面和操作介绍

3.4打印游戏地图

3.5创建蛇

3.6创建食物

4.GameRun()

4.1怎么判断游戏一直在运行

4.2打印当前得分

4.3调整蛇的运动方向以及相关按键操作

4.4从当前这个位置到下一个位置的间隔时间

4.5移动到下一个位置SnakeMove()

4.5.1存储下一个位置的坐标

4.5.2判断下一个节点是否是食物NextIsFood()

4.5.3吃掉食物EatFood()

4.5.4下一个位置不是食物

4.5.5判断是否撞到墙

4.5.6判断是否撞到自己

5.GameEnd()

6.程序源代码

6.1代码一

6.2代码二


前言

贪吃蛇是一款很经典的小游戏,也是大家童年里的记忆,当你是一个计算机专业相关的学生,学完了C语言一定和我一样也想自己做一个小游戏在同学面前炫耀一番,证明一波自己的N13之处,接下来看完这篇文章,我相信你可以实现这波N13

效果展示

1.写代码前的预准备

1.1修改控制台

鱼哥使用的编译器是VS2022,但我们在运行程序时,大多数人和我一样弹出的是下面这个界面

这个叫做控制台窗口,我们首先需要修改一下控制台窗口,大家可以按以下操作,修改控制台窗口

当你按照步骤修改,最后再运行一遍程序,出现第六步的窗口时,就算修改成功了

1.2相关WinAPI32知识学习

1.2.1WinAPI32介绍

Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应用程序达到开启 视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称API函数。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接

1.2.2 mode 命令

mode命令用来控制控制台窗口的大小

cols代表例,lines代表行,这些在控制台执行的命令,我们调用C语言中的system函数来执行

1.2.3 title 命令

除了控制控制台的大小,我们还想修改控制台的名字又该怎么做呢?

同样是借助system函数来对控制台进行操作

int main()
{system("title 贪吃蛇");return 0;
}

当你运行上面的代码时,你会发现控制台的名字好像并没有被修改,这是因为当你运行时程序就已经结束了,所以看不见。

当我们加上下面这行代码时,你就可以看见控制台的名字被修改为贪吃蛇了

int main()
{system("title 贪吃蛇");system("pause");return 0;
}

       system(“pause”)是暂停的意思,等待用户输入信息,不然控制台一闪而过,你来不及看见执行结果

       在使用system函数时需要包含头文件stdlib.h

1.2.4COORD

COORD是Windows API中定义的⼀个结构体,表示⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系 (0,0)的原点位于缓冲区的顶部左侧单元格。

控制台可以看成是一个坐标平面,后面我们可以通过这个坐标将内容打印到对应的位置

给大家你看一下COORD这个结构体长啥样

typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;

X,Y分别代表横坐标和纵坐标

赋值操作如下:

COORD pos={1,2};

pos的类型是COORD,坐标是(1,2)

1.2.5GetStdHandle

GetStdHandle是⼀个Windows API函数。它用于从⼀个特定的标准设备(标准输入、标准输出或标准错误)中取得⼀个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。

类型声明:

HANDLE WINAPI GetStdHandle(_In_ DWORD nStdHandle
);

参数:

含义
STD_INPUT_HANDLE((DWORD)-10)标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台。
STD_OUTPUT_HANDLE((DWORD)-11)标准输出设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$
STD_ERROR_HANDLE((DWORD)-12)标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
或者
HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);

1.2.6对控制台光标进行设置

1.2.6.1GetConsolrCursorInfo

检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息

BOOL WINAPI GetConsoleCursorInfo(_In_  HANDLE               hConsoleOutput,_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
1.2.6.2 CONSOLE_CURSOR_INFO

CONSOLE_CURSOR_INFO是一个存储光标大小可见度的结构体

typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

 dwsize表示光标的大小,数值位1到100,表示的是百分比

bVisible表示的是光标的可见度,默认是true,隐藏光标需要将bVisible赋值为false

1.2.6.3SetConsolrCursorInfo

设置指定控制台屏幕缓冲区的光标的大小和可见性。

BOOL WINAPI SetConsoleCursorInfo(HANDLE hConsoleOutput,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

1.2.6.4修改光标大小和可见度代码示例

1.修改光标大小

int main()
{
//获取一个输出相关的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定义一个有光标属性的结构体CONSOLE_CURSOR_INFO CONCURINFO;
//检索这个光标结构体GetConsoleCursorInfo(houtput, &CONCURINFO);
//对结构体进行赋值	CONCURINFO.dwSize = 50;
//设置光标的属性SetConsoleCursorInfo(houtput, &CONCURINFO);return 0;
}

 以下是修改前后的效果

2.修改光标的可见度

#include<Windows.h>
#include<stdbool.h>
int main()
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CONCURINFO;GetConsoleCursorInfo(houtput, &CONCURINFO);
//将光标的可见度修改为不可见CONCURINFO.bVisible = false;
//设置光标属性SetConsoleCursorInfo(houtput, &CONCURINFO);return 0;
}

 在使用WinAPI32的函数时需要包含Windows.h这个头文件,使用false和true时要包含stdbool.h这个头文件,下面是光标隐藏的效果图

1.2.7设置光标出现的位置

1.2.7.1SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD pos
);
1.2.7.2代码示例
int main()
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { 20,20 };SetConsoleCursorPosition(houtput, pos);return 0;
}

 效果展示:

1.2.7.3封装函数

想一想,每次需要修改光标的位置,都需要重新写一遍上面的代码,使代码的可读性和简便性下降,增加了代码的绒余性,我们不妨把设置光标的位置分装成一个函数,提高代码的可读性

void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);
}

1.2.8GetAsyncKeyState

GetAsyncKeyState是一个获取按键情况的函数

SHORT GetAsyncKeyState(int vKey
);

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。 GetAsyncKeyState 的返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。 如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

怎么检测按键是否被按过呢?

一个宏就可以解决

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

将GetAsyncKeyState()的返回值按位与上一个1,结果为1则说明按过VK这个键,为0则表示没有按过VK这个键

虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

1.2.9如何修改颜色

void color(int c)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
}

直接调用上面这个函数

鱼哥给大家整理了各种颜色对应的数值

数值颜色数值颜色数值颜色
0黑色1蓝色2绿色
3湖蓝色4红色5紫色
6黄色7白色8灰色
9淡蓝色10淡绿色11淡浅绿色
12淡红色13淡紫色14淡黄色
15亮白色16大于15,恢复默认的颜色

代码示例:

1.2.10如何修改控制台背景颜色

1.3将C语言库本地化

1.3.1简单说一下为什么要本地化

C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。也就是不适合一些非英语的国家,后来为了使C语眼适应国际化,C语眼的标准中不断入了国际化的支持。比如:加入了宽字符的类型 wchar_t 和宽字符的输入和输出函数,加入了头文件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。如果不本地化,我们就无法显示出地图和蛇。

1.首先包含头文件locale.h

2.在主函数第一行写上setlocale(LC_ALL, "");即完成了本地化

1.3.2本地化后打印字符和字符串

int main()
{setlocale(LC_ALL, "");wchar_t ch = L'鱼';wprintf(L"%lc", ch);
//先定义一个字符后打印wprintf(L"%lc", L'哥');
//直接打印wprintf(L"%ls", L"好帅");
//打印字符串	
return 0;
}

在定义和打印字符或字符串时,注意添加L

1.4控制台窗口的横纵坐标比

控制台的横纵坐标比是1:2

也就是两个横坐标的长度等于一个纵坐标的长度

每一个坐标不是数学模型上的一个点,而是一个长二宽一的矩形,每一个小矩形可装下一个一个字节的字符,而我们使用的汉字和一些符号是宽字符,也就是两个字节的大小,在控制台所呈现的就是一个正方形的面积大小

2.游戏整体的逻辑框架

test.c文件

#include"Snake.h"
void test()
{
char ch=0;
do{
//创建贪吃蛇
Snake snake = { 0 };
//开始游戏
GameStart(&snake);
//运行游戏
GameRun(&snake);
//结束游戏
GameEnd(&snake);
//将光标移动到指定位置
SetPos(20, 15);
printf("再来一局吗?(Y/N):");
//接受Y或N
ch = getchar();
}while(ch==Y||ch==y);
//考虑到大小写
}

Snake.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#include<stdbool.h>
#include<stdlib.h>#define Pos_X 20
#define Pos_Y 9
#define Wall L'◆'//墙体的符号
#define Body L'●'//蛇身每一节的符号
#define Food L'☆'//食物的符号//蛇的方向
enum DIRECTION
{UP=1,向上DOWN,向下LEFT,向左RIGHT向右
};//蛇的运动状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL//正常退出
};//蛇身节点
typedef struct SnakeNode
{int x;//在控制台上的横坐标int y;//在控制台上的纵坐标struct SnakeNode* next;//指向下一个节点的指针
}SnakeNode,*pSnakeNode;//重命名结构体和重命名结构体指针//贪吃蛇完整的信息
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物的指针enum DIRECTION _dir;//方向enum GAME_STATUS _status;//状态int _food_weight;//食物分数int _score;//总分数int _sleep_time;//休息时间
}Snake,*pSnake;//重命名结构体和重命名结构体指针//开始游戏
void GameStart(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//游戏善后
void GameEnd(pSnake ps);

Snake.c

1.开始游戏

//开始游戏
void GameStart(pSnake ps)
{system("mode con cols=110 lines=35");//设置窗口大小system("title 贪吃蛇");//设置项目名字HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出流的句柄CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//隐藏控制台光标SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态//1.打印环境界面//2.功能介绍WelcomeToGame();//3.绘制地图CreatMap();//4.创建蛇InitSnake(ps);//5.创建食物CreatFood(ps);//6.设置蛇的相关信息
}

2.运行游戏

void GameRun(pSnake ps)
{PrintfHelpInfo();//打印提示信息do {SetPos(70, 6);printf("总分数:%d", ps->_score);SetPos(70, 8);printf("当前食物的分数:%2d", ps->_food_weight);if (KEY_PRESS(VK_UP) && ps->_dir != DOWN){ps->_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP){ps->_dir = DOWN;}else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){//退出ps->_status = END_NORMAL;break;}else if (KEY_PRESS(VK_F3)){//F3加速if (ps->_sleep_time >= 80){ps->_sleep_time -= 10;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//F4减速if (ps->_food_weight >= 2){ps->_food_weight -= 2;ps->_sleep_time += 10;}}Sleep(ps->_sleep_time);SnakeMove(ps);//蛇走一步的状态} while (ps->_status==OK);
}

3.游戏结束

void GameEnd(pSnake ps)
{switch (ps->_status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞到自己了\n");break;case KILL_BY_WALL:printf("您撞到墙了\n");break;}//释放蛇身链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

接下来我们主要实现Snake.c的内容,也会简单分析test.c和Snake.h

3.GameStart()

3.1设置控制台大小和名字

void GameStart(pSnake ps)
{system("mode con cols=100 lines=30");system("title 贪吃蛇");
}

注意:设置宽度时需要将宽度设置为偶数,因为我们蛇和墙体还有食物均为宽字符,方便后期对这三者的坐标进行设置,你也可以按自己的想法来,可以通过自己的设计让界面美观一点

控制台大小和名字设置完记得自己调试检查一下是不是自己想要的结果,方便即使调整一下

3.2隐藏光标

HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CURINFO;
GetConsoleCursorInfo(houtput, &CURINFO);
CURINFO.bVisible = false;
SetConsoleCursorInfo(houtput, &CURINFO);

这是一个固定操作,不做过多解释

注意在检索和设置函数那里CURINFO前加上&操作符

3.3打印游戏界面和操作介绍

1.首先我们开始游戏进入一个欢迎来到贪吃蛇的界面

2.直接输出“欢迎来到贪吃蛇”,由于光标在(0,0)这个位置,打印出来肯定不美观,所以我们要将光标移动到控制台界面的中间,然后再进行打印

这里我们就用到上面1.2.7.3的封装函数(记得在Snake.h声明,在Snake.c里实现)

void WelcomeToSnake()
{SetPos(40, 13);wprintf(L"欢迎来到贪吃蛇小游戏");SetPos(40, 25);system("pause");system("cls");SetPos(25, 13);wprintf(L"游戏规则:");SetPos(40, 10);wprintf(L"↑↓←→控制贪吃蛇移动");SetPos(40, 12);wprintf(L"F3加速,F4减速");SetPos(40, 14);wprintf(L"加速食物的分数更高,减速食物的分数会降低");SetPos(40, 16);wprintf(L"撞墙或撞到自己游戏均会结束");SetPos(40, 25);system("pause");system("cls");
}

1.system("pause")是防止程序一下就结束,看不到我们想看到的画面(也可用getchar()替用)

2.system("cls")起到清屏的作用,当打印完一个界面的内容,如果不清屏进入下一个界面,就会将上一个界面的内容遗留下来

下面是一个效果图

如果觉得这个界面字体颜色太枯燥,自己想个性化,换成其他颜色,我们就可以使用1.2.9的那个封装函数就行(同样也是记得声明)

修改控制台背景颜色参考1.2.10

注意:修改输出内容的颜色时,调用void color(int c)时,color下面逻辑下面的在控制台输出的内容会全部修改为这个颜色,想要修改其他内容的颜色,只需要在指定内容的前面加上color函数,并传相应颜色的参数就行了

3.4打印游戏地图

我们打印一个空心矩形将贪吃蛇围住,这个矩形就是我们的地图

void GreatMap()
{//上墙SetPos(0, 0);for (int i = 0; i < 50; i++){wprintf(L"%lc", Wall);}//下墙SetPos(0, 26);for (int i = 0; i < 50; i++){wprintf(L"%lc", Wall);}//左墙for (int i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%lc", Wall);}//右墙for (int i = 1; i < 26; i++){SetPos(98, i);wprintf(L"%lc", Wall);}
}

这是鱼哥自己设计的一个边框,要注意打印右墙时光标的位置,不要弄错了

下面是我的一个效果图

3.5创建蛇

主要思路,创建一个链表,链表的每个节点存储在该节点的坐标和下一个节点的地址

void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;//定义一个指针,方便后面两个节点之间的连接for (i; i < 5; i++)//初始化时我们设置蛇有五个节点{cur = (pSnakeNode)malloc(sizeof(SnakeNode));//向内存申请一个节点,由cur指向if (cur == NULL)//如果申请失败,那么cur将指向空指针{perror("InitSnake fail");//申请失败我们就将失败原因打印出来,一般都不会申请失败的return;//直接返回}cur->x = Pos_X + i * 2;//Pos_X是在Snake.h里面定义的宏,也就是蛇的尾部节点的横坐标cur->y = Pos_Y ;//Pos_Y是尾部节点的纵坐标cur->next = NULL;//每申请一个节点我们就将他的下一个节点置为空if (ps->pSnakeHead == NULL)//如果蛇头的节点为空,说明这条蛇一个节点也没有,我们现在申请的是第一个节点,我们就将蛇头指向第一个节点{ps->pSnakeHead = cur;}else//如果这条蛇已经有节点了,我们对这条蛇进行头插{cur->next = ps->pSnakeHead;//将新节点的下一个指针指向当前蛇头ps->pSnakeHead = cur;//将cur指向的节点作为蛇头}}cur = ps->pSnakeHead;//用cur从蛇头向蛇尾遍历一遍链表SetPos(cur->x, cur->y);//找到蛇头节点的坐标wprintf(L"%lc", Head);//打印蛇头cur = cur->next;while (cur){SetPos(cur->x, cur->y);//找到相应节点的坐标wprintf(L"%lc", Body);//在对应节点打印蛇身cur = cur->next;}
//将整条蛇的信息设置一下ps->_dir = RIGHT;ps->_Food_weight = 10;ps->_score = 0;ps->_state = OK;ps->_Sleep_time = 400;//用来控制蛇移动的速度
}

 对应的Pos_X,Pos_Y,Head,Body我们在Snake.h里面用#define定义

#define Wall L'卍'
#define Body L'○'
#define Head L'●'
#define Pos_X 48
#define Pos_Y 8

3.6创建食物

创建食物我们是需要食物在控制台窗口并且是在我们的地图里面随机生成的,且食物不能与蛇身重合,不能与墙体重合

void GreatFood(pSnake ps)
{int x = 0;int y = 0;again:do {x = rand() % 97 + 2;//随机生成坐标y = rand() % 28 + 1;//因为我们的地图是30行100列的,除去墙体还剩28行96列,且第一列第二列为墙体,第一行也为墙体,所以x要加2,y要加一} while (x % 2 != 0);//我们在游戏里面的每个图形都是宽字符,所以横坐标必须为2的倍数,不是2的倍数就重新生成pSnakeNode cur = ps->pSnakeHead;//定义一个指针,遍历一遍蛇身,看生成的食物的坐标是否和蛇身重合while (cur){if (cur->x == x && cur->y == y)//如果生成的食物的坐标和蛇身重合,那么用goto跳回到again的位置,重新生成随机数{goto again;}cur = cur->next;}cur = (pSnakeNode)malloc(sizeof(SnakeNode));//如果食物的坐标可用,我们就申请一个节点if (cur == NULL){perror("malloc PSnakeNode fail");//节点申请失败,打印一下原因return;}cur->x = x;设置一下食物的坐标cur->y = y;cur->next = NULL;color(1);//设置食物的颜色SetPos(cur->x, cur->y);wprintf(L"%lc", Food);//打印食物在屏幕上ps->pFood = cur;将食物节点的信息存放进蛇的信息里面
}

补充:随机数的生成需要包含time.h和stdlib.h两个头文件,在test.c文件中加上srand((unsigned)time(NULL));这是生成随机种子用的

4.GameRun()

运行游戏这一步是整个游戏最难的一部分,它使用了各种各样的函数嵌套,不过没关系,鱼哥会带领大家一步一步攻破难关的

4.1怎么判断游戏一直在运行

怎么判断游戏一直在运行呢?

我们之前创建了一个结构体,在结构体里面有一个枚举类型叫enum STATE,当这个类型为OK的时候,就说明游戏还在运行,是不是只要判断游戏的状态是不是OK就可以了

判断之前是不是得先让游戏运行起来

想运行再判断就会用到do while循环

do{
//游戏的运行逻辑
}while (ps->_status==OK);

这是游戏运行的整体结构

4.2打印当前得分

这部分很简单,只需要将光标设置在自己觉得美观的位置,然后将得分打印出来就行了

color(6);
SetPos(10, 28);
printf("总分:");
color(7);
printf("%d", ps->_score);
color(6);
SetPos(60, 28);
printf("当前食物的分值为:");
color(7);
printf("%d", ps->_Food_weight);

大家根据自己的审美设计

4.3调整蛇的运动方向以及相关按键操作

我们设计的这个游戏主要涉及8个按键,↑↓←→以及space(空格),Esc3,F4

↑↓←→操作贪吃蛇移动,space暂停游戏,Esc退出游戏,F3加速,F4减速

判断对应按键是否按过我们调用1.2.8的宏即可

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)

vk是对应的键位的虚拟键码,不做过多解释,具体参考1.2.8

if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
{ps->_dir = UP;//向上走,除获取向上走的指令外,当前的运动状态还不能为向下
}
else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)//另外三个移动键同理
{ps->_dir = DOWN;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
{ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_SPACE))//空格暂停键下一段代码会给大家介绍
{//暂停Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{//退出ps->_state = END_NORMAL;//退出只需要将当前蛇的游戏状态修改为END_NORMAL即可break;
}
else if (KEY_PRESS(VK_F3))
{//F3加速if (ps->_Sleep_time >= 80)//加速和减速主要就是控制屏幕休息的时间{ps->_Sleep_time -= 10;ps->_Food_weight += 2;//休息时间减少,单个食物的分数增加}
}
else if (KEY_PRESS(VK_F4))//休息时间有一个下限,食物的分数也有一个下限
{//F4减速if (ps->_Food_weight >= 2){ps->_Food_weight -= 2;ps->_Sleep_time+= 10;}

Pause()函数的实现

void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}

Pause()函数的实现很简单,就是使用循环无限让屏幕休息,直到遇到下一个空格键的按下,打破循环,结束暂停

4.4从当前这个位置到下一个位置的间隔时间

间隔时间也就相当于移动速度

间隔时间我们用pSnake->_Sleep_time来存放的

Sleep(ps->_Sleep_time);

这样休息一下就可以了

4.5移动到下一个位置SnakeMove()

4.5.1存储下一个位置的坐标

pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{perror("malloc pnext fail");return;
}
switch (ps->_dir)
{
case UP:pnext->x = ps->pSnakeHead->x;pnext->y = ps->pSnakeHead->y - 1;break;
case DOWN:pnext->x = ps->pSnakeHead->x;pnext->y = ps->pSnakeHead->y + 1;break;
case RIGHT:pnext->x = ps->pSnakeHead->x + 2;pnext->y = ps->pSnakeHead->y;break;
case LEFT:pnext->x = ps->pSnakeHead->x - 2;pnext->y = ps->pSnakeHead->y;break;
}

思路很简单,根据当前蛇的运动状态,判断下一个位置的坐标应该是多少,申请一个节点存储数据

4.5.2判断下一个节点是否是食物NextIsFood()

下一个位置是不是食物,我们已经找出了下一个位置的坐标,将下一个位置的坐标与食物的坐标作比较,相同则是,否则不是

int NextIsFood(pSnakeNode pn, pSnake ps)
{return ((pn->x == ps->pFood->x) && (pn->y == ps->pFood->y));
}

如果是返回非0,否则返回0

4.5.3吃掉食物EatFood()

吃掉食物,蛇的身体会变长,就相当于将食物的节点头插到贪吃蛇里,头插完后将新的蛇身打印出来,将原来蛇身存储的食物的数据清理一下,然后创建下一个食物节点

void EatFood(pSnakeNode pn, pSnake ps)
{color(1);//由于之前修改过颜色,所以要将颜色修改为最初蛇的颜色pn->next = ps->pSnakeHead;ps->pSnakeHead = pn;pSnakeNode cur = ps->pSnakeHead;SetPos(cur->x, cur->y);wprintf(L"%lc", Head);//由于蛇头和蛇身不一样,所以要单独打印cur=cur->next;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", Body);//循环打印蛇身
cur=cur->next;}ps->_score += ps->_Food_weight;free(ps->pFood);//释放掉当前食物节点GreatFood(ps);//创建一个新的食物节点
}

4.5.4下一个位置不是食物

如果下一个节点不是食物,我们的蛇身长度肯定是不能变的,而且蛇头要指向下一个节点

void NOFood(pSnakeNode pn, pSnake ps)
{color(2);//修改蛇的颜色pn->next = ps->pSnakeHead;ps->pSnakeHead = pn;
pSnakeNode pcur = ps->pSnakeHead;
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", Head);
pcur = pcur->next;while (pcur->next->next){SetPos(pcur->x, pcur->y);wprintf(L"%lc", Body);pcur = pcur->next;}SetPos(pcur->next->x, pcur->next->y);printf("  ");//最后一个节点打印为空格,注意是两个空格free(pcur->next);pcur->next = NULL;
}

4.5.5判断是否撞到墙

void KillByWall(pSnake ps)
{if (ps->pSnakeHead->x == 0 || ps->pSnakeHead->x == 98|| ps->pSnakeHead->y == 0 || ps->pSnakeHead->y == 26){ps->_dir = KILL_BY_WALL;}
}

判断蛇头坐标是否与墙的坐标相同就可判断是否撞墙

4.5.6判断是否撞到自己

void KillBySelf(pSnake ps)
{pSnakeNode pcur = ps->pSnakeHead->next;//定义一个指针遍历一遍蛇身,看蛇身坐标是否和蛇头坐标相同while (pcur){if (pcur->x == ps->pSnakeHead->x && pcur->y == ps->pSnakeHead->y){ps->_state = KILL_BY_SELF;break;}pcur = pcur->next;}
}

5.GameEnd()

void GameEnd(pSnake ps)
{switch (ps->_state){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞到自己了\n");break;case KILL_BY_WALL:printf("您撞到墙了\n");break;}//释放蛇身链表pSnakeNode cur = ps->pSnakeHead;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

最后进行善后工作,将打印怎么退出的,和释放链表

6.程序源代码

6.1代码一

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"void test()
{int ch = 0;do {Snake snake = { 0 };//游戏开始GameStart(&snake);游戏运行GameRun(&snake);游戏结束GameEnd(&snake);SetPos(40, 15);printf("是否再玩一局?(Y/N)");ch=getchar();} while (ch == 'Y' || ch == 'y');SetPos(0, 29);
}int main()
{setlocale(LC_ALL, "");srand((unsigned)time(NULL));test();getchar();return 0;
}

Snake.c

#include"Snake.h"
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//设置颜色
void color(int c)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
}//将光标移动到指定位置
void SetPos(short x, short y)
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x,y };SetConsoleCursorPosition(houtput, pos);
}void WelcomeToSnake()
{color(2);SetPos(40, 13);wprintf(L"欢迎来到贪吃蛇小游戏");SetPos(40, 25);system("pause");system("cls");SetPos(25, 13);wprintf(L"游戏规则:");SetPos(40, 10);wprintf(L"↑↓←→控制贪吃蛇移动");SetPos(40, 12);wprintf(L"F3加速,F4减速");SetPos(40, 14);wprintf(L"加速食物的分数更高,减速食物的分数会降低");SetPos(40, 16);wprintf(L"撞墙或撞到自己游戏均会结束");SetPos(40, 25);system("pause");system("cls");
}void GreatMap()
{//上墙SetPos(0, 0);for (int i = 0; i < 50; i++){wprintf(L"%lc", Wall);}//下墙SetPos(0, 26);for (int i = 0; i < 50; i++){wprintf(L"%lc", Wall);}//左墙for (int i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%lc", Wall);}//右墙for (int i = 1; i < 26; i++){SetPos(98, i);wprintf(L"%lc", Wall);}
}//创建蛇
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake fail");return;}cur->x = Pos_X + i * 2;cur->y = Pos_Y ;cur->next = NULL;if (ps->pSnakeHead == NULL){ps->pSnakeHead = cur;}else{cur->next = ps->pSnakeHead;ps->pSnakeHead = cur;}}cur = ps->pSnakeHead;SetPos(cur->x, cur->y);wprintf(L"%lc", Head);cur = cur->next;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", Body);cur = cur->next;}ps->_dir = RIGHT;ps->_Food_weight = 10;ps->_score = 0;ps->_state = OK;ps->_Sleep_time = 300;
}//创建食物节点
void GreatFood(pSnake ps)
{int x = 0;int y = 0;again:do {x = rand() % 97 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);pSnakeNode cur = ps->pSnakeHead;while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("malloc PSnakeNode fail");return;}cur->x = x;cur->y = y;cur->next = NULL;color(1);SetPos(cur->x, cur->y);wprintf(L"%lc", Food);ps->pFood = cur;
}//暂停
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}int NextIsFood(pSnakeNode pn, pSnake ps)
{return ((pn->x == ps->pFood->x) && (pn->y == ps->pFood->y));
}void EatFood(pSnakeNode pn, pSnake ps)
{color(1);pn->next = ps->pSnakeHead;ps->pSnakeHead = pn;pSnakeNode cur = ps->pSnakeHead;SetPos(cur->x, cur->y);wprintf(L"%lc", Head);cur = cur->next;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", Body);cur = cur->next;}ps->_score += ps->_Food_weight;free(ps->pFood);GreatFood(ps);
}void NOFood(pSnakeNode pn, pSnake ps)
{color(2);pn->next = ps->pSnakeHead;ps->pSnakeHead = pn;
pSnakeNode pcur = ps->pSnakeHead;
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", Head);
pcur = pcur->next;while (pcur->next->next){SetPos(pcur->x, pcur->y);wprintf(L"%lc", Body);pcur = pcur->next;}SetPos(pcur->next->x, pcur->next->y);printf("  ");free(pcur->next);pcur->next = NULL;
}//检查是否撞墙
void KillByWall(pSnake ps)
{if (ps->pSnakeHead->x == 0 || ps->pSnakeHead->x == 98|| ps->pSnakeHead->y == 0 || ps->pSnakeHead->y == 25){ps->_state= KILL_BY_WALL;}
}//检查是否撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode pcur = ps->pSnakeHead->next;while (pcur){if (pcur->x == ps->pSnakeHead->x && pcur->y == ps->pSnakeHead->y){ps->_state = KILL_BY_SELF;break;}pcur = pcur->next;}
}void SnakeMove(pSnake ps)
{pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pnext == NULL){perror("malloc pnext fail");return;}switch (ps->_dir){case UP:pnext->x = ps->pSnakeHead->x;pnext->y = ps->pSnakeHead->y - 1;break;case DOWN:pnext->x = ps->pSnakeHead->x;pnext->y = ps->pSnakeHead->y + 1;break;case RIGHT:pnext->x = ps->pSnakeHead->x + 2;pnext->y = ps->pSnakeHead->y;break;case LEFT:pnext->x = ps->pSnakeHead->x - 2;pnext->y = ps->pSnakeHead->y;break;}if (NextIsFood(pnext,ps)){EatFood(pnext,ps);}else{NOFood(pnext,ps);}KillBySelf(ps);KillByWall(ps);
}void GameRun(pSnake ps)
{do {
color(6);
SetPos(10, 28);
printf("总分:");
color(7);
printf("%d", ps->_score);
color(6);
SetPos(60, 28);
printf("当前食物的分值为:");
color(7);
printf("%d", ps->_Food_weight);
if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
{ps->_dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
{ps->_dir = DOWN;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
{ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_SPACE))
{//暂停Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{//退出ps->_state = END_NORMAL;break;
}
else if (KEY_PRESS(VK_F3))
{//F3加速if (ps->_Sleep_time >= 80){ps->_Sleep_time -= 10;ps->_Food_weight += 2;}
}
else if (KEY_PRESS(VK_F4))
{//F4减速if (ps->_Food_weight >= 2){ps->_Food_weight -= 2;ps->_Sleep_time+= 10;}
}
Sleep(ps->_Sleep_time);
SnakeMove(ps);} while (ps->_state == OK);
}void GameStart(pSnake ps)
{system("mode con cols=100 lines=30");system("title 贪吃蛇");HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CURINFO;GetConsoleCursorInfo(houtput, &CURINFO);CURINFO.bVisible = false;SetConsoleCursorInfo(houtput, &CURINFO);WelcomeToSnake();GreatMap();InitSnake(ps);GreatFood(ps);
}void GameEnd(pSnake ps)
{switch (ps->_state){case END_NORMAL:SetPos(40, 14);printf("您主动退出游戏\n");break;case KILL_BY_SELF:SetPos(40, 14);printf("您撞到自己了\n");break;case KILL_BY_WALL:SetPos(40, 14);printf("您撞到墙了\n");break;}//释放蛇身链表pSnakeNode cur = ps->pSnakeHead;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

Snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#include<stdbool.h>
#include<stdlib.h>#define Wall L'卍'
#define Body L'○'
#define Head L'●'
#define Food L'★'
#define Pos_X 48
#define Pos_Y 8enum DIRECTION
{UP = 1,DOWN,RIGHT,LEFT
};enum STATE
{OK,KILL_BY_SELF,KILL_BY_WALL,END_NORMAL
};typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode,*pSnakeNode;typedef struct Snake
{enum STATE _state;enum DIRECTION _dir;int _Food_weight;int _score;int _Sleep_time;pSnakeNode pSnakeHead;pSnakeNode pFood;
}Snake,*pSnake;void GameStart(pSnake ps);
//欢迎界面和游戏规则介绍界面
void WelcomeToSnake();
// 设置颜色
void color(int c);
//将光标移动到指定位置
void SetPos(short x, short y);
//创造地图
void GreatMap();
//创建蛇
void InitSnake(pSnake ps);
//创建食物
void GreatFood(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//移动到下一步
void SnakeMove(pSnake ps);
//下一个位置是不是食物
int NextIsFood(pSnakeNode pn, pSnake ps);
void GameEnd(pSnake ps);
void EatFood(pSnakeNode pn, pSnake ps);
void NOFood(pSnakeNode pn, pSnake ps);
void KillByWall(pSnake ps);
void KillBySelf(pSnake ps);

6.2代码二

test.c

#include"snake.h"void test()
{int ch = 0;do {//创建贪吃蛇Snake snake = { 0 };//初始化游戏// 0.隐藏光标//1.打印环境界面//2.功能介绍//3.绘制地图//4.创建蛇//5.创建食物//6.设置蛇的相关信息GameStart(&snake);运行游戏GameRun(&snake);结束游戏GameEnd(&snake);SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();} while (ch == 'Y' || ch == 'y');SetPos(0,35);
}int main()
{//设置适配本地环境setlocale(LC_ALL, "");srand((unsigned)time(NULL));test();getchar();return 0;
}

Snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#include<stdbool.h>
#include<stdlib.h>#define Pos_X 20
#define Pos_Y 9
#define Wall L'◆'
#define Body L'●'
#define Food L'☆'//蛇的方向
enum DIRECTION
{UP=1,DOWN,LEFT,RIGHT
};//蛇的运动状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL//正常退出
};//蛇身节点
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode,*pSnakeNode;typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物的指针enum DIRECTION _dir;//方向enum GAME_STATUS _status;//状态int _food_weight;//食物分数int _score;//总分数int _sleep_time;//休息时间
}Snake,*pSnake;//开始游戏
void GameStart(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//游戏善后
void GameEnd(pSnake ps);//地图打印
void CreatMap();
//创建蛇
void InitSnake(pSnake ps);
//创建食物
void CreatFood(pSnake ps);//蛇的移动
void SnakeMove(pSnake ps);
//下一个不是食物
void NOFood(pSnakeNode pn, pSnake ps);
//检查蛇是否撞墙
void KillByWall(pSnake ps);
//检测是否撞到自己
void KillBySelf(pSnake ps);
//吃食物
void EatFood(pSnakeNode pn, pSnake ps);//定义光标位置
void SetPos(short x, short y);
//欢迎界面
void WelcomeToGame();
//颜色设置
void color(int c);

Snake.c

#include"snake.h"//颜色设置
void color(int c)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
}void SetPos(short x,short y)
{//获取句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x,y };SetConsoleCursorPosition(houtput, pos);
}
//游戏界面
void WelcomeToGame()
{ color(1);SetPos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏");SetPos(40, 17);system("pause");system("cls");SetPos(35, 12);wprintf(L"↑↓←→控制方向,F3加速,F4减速\n");SetPos(40, 14);wprintf(L"加速可以获得额外分数\n");SetPos(45, 16);wprintf(L"减速会扣分");SetPos(40, 20);system("pause");system("cls");
}//地图打印
void CreatMap()
{color(2);//上墙int i = 0;for (i; i < 29; i++){wprintf(L"%lc", Wall);}//下SetPos(0, 28);for (i=0; i < 29; i++){wprintf(L"%lc", Wall);}//左for (int i = 1; i <= 27; i++){SetPos(0, i);wprintf(L"%lc", Wall);}//右for (int i = 1; i <= 27; i++){SetPos(56, i);wprintf(L"%lc", Wall);}}void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():fail");return;}cur->next = NULL;cur->x = Pos_X + 2 * i;cur->y = Pos_Y;if (ps->_pSnake == NULL){ps->_pSnake = cur;}else {cur->next = ps->_pSnake;ps->_pSnake = cur;}}cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", Body);cur = cur->next;}ps->_dir = RIGHT;ps->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;//单位是毫秒ps->_status = OK;
}void CreatFood(pSnake ps)
{int x = 0;int y = 0;again:do {x = rand() % 53 + 2;y = rand() % 27 + 1;} while (x % 2 != 0);//x必须为2的倍数//x和y不能与蛇身冲突pSnakeNode cur = ps->_pSnake;while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//创建食物的节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("Creat Food fail");return;}pFood->x = x;pFood->y = y;//pFood->next = NULL;SetPos(x, y);wprintf(L"%lc", Food);ps->_pFood = pFood;
}void PrintfHelpInfo()
{SetPos(70, 10);wprintf(L"不能撞墙,不能咬到自己");SetPos(70, 12);wprintf(L"↑↓←→控制方向,F3加速,F4减速");SetPos(70, 14);wprintf(L"加速可以获得额外分数");SetPos(70, 16);wprintf(L"减速会扣分");SetPos(70, 18);wprintf(L"按ESC退出游戏,按空格暂停游戏");
}#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//暂停
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}//判断下一个节点是否有食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}//吃食物
void EatFood(pSnakeNode pn,pSnake ps)
{/*ps->_pSnake->next = ps->_pSnake;ps->_pSnake = ps->_pFood;free(pn);pn = NULL;*/pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode pcur = ps->_pSnake;while (pcur){SetPos(pcur->x, pcur->y);wprintf(L"%lc", Body);pcur = pcur->next;}ps->_score += ps->_food_weight;free(ps->_pFood);CreatFood(ps);
}//下一个位置不是食物
void NOFood(pSnakeNode pn, pSnake ps)
{pn->next = ps->_pSnake;ps->_pSnake = pn;
pSnakeNode pcur = ps->_pSnake;while (pcur->next->next){SetPos(pcur->x, pcur->y);wprintf(L"%lc", Body);pcur = pcur->next;}SetPos(pcur->next->x, pcur->next->y);printf("  ");free(pcur->next);pcur->next = NULL;
}//检测是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56|| ps->_pSnake->y == 0 || ps->_pSnake->y == 28){ps->_status = KILL_BY_WALL;}
}//检测是否撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode pcur = ps->_pSnake->next;while (pcur){if (pcur->x == ps->_pSnake->x && pcur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;break;}pcur = pcur->next;}
}//蛇的移动
void SnakeMove(pSnake ps)
{pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pnext == NULL){perror("pnext malloc fail");return;}switch (ps->_dir){case UP:pnext->x = ps->_pSnake->x;pnext->y = ps->_pSnake->y - 1;break;case DOWN:pnext->x = ps->_pSnake->x;pnext->y = ps->_pSnake->y + 1;break;case RIGHT:pnext->x = ps->_pSnake->x+2;pnext->y = ps->_pSnake->y;break;case LEFT:pnext->x = ps->_pSnake->x-2;pnext->y = ps->_pSnake->y;break;}if (NextIsFood(pnext, ps)){EatFood(pnext, ps);}else {NOFood(pnext, ps);}//检查是否撞墙KillByWall(ps);//检查是否撞到自己KillBySelf(ps);
}void GameRun(pSnake ps)
{PrintfHelpInfo();//打印提示信息do {SetPos(70, 6);printf("总分数:%d", ps->_score);SetPos(70, 8);printf("当前食物的分数:%2d", ps->_food_weight);if (KEY_PRESS(VK_UP) && ps->_dir != DOWN){ps->_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP){ps->_dir = DOWN;}else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){//退出ps->_status = END_NORMAL;break;}else if (KEY_PRESS(VK_F3)){//F3加速if (ps->_sleep_time >= 80){ps->_sleep_time -= 10;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//F4减速if (ps->_food_weight >= 2){ps->_food_weight -= 2;ps->_sleep_time += 10;}}Sleep(ps->_sleep_time);SnakeMove(ps);//蛇走一步的状态} while (ps->_status==OK);
}void GameEnd(pSnake ps)
{SetPos(20, 14);switch (ps->_status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞到自己了\n");break;case KILL_BY_WALL:printf("您撞到墙了\n");break;}//释放蛇身链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}void GameStart(pSnake ps)
{system("mode con cols=110 lines=35");//设置窗口大小system("title 贪吃蛇");//设置项目名字HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输入流的句柄CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//隐藏控制台光标SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态//1.打印环境界面//2.功能介绍WelcomeToGame();//3.绘制地图CreatMap();//4.创建蛇InitSnake(ps);//5.创建食物CreatFood(ps);//6.设置蛇的相关信息
}

最后祝大家学有所成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/1708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vos3000外呼系统客户端无法安装如何解决?

如果 VOS3000 外呼系统客户端无法安装&#xff0c;可以尝试以下解决方法&#xff1a; 检查系统要求&#xff1a; 确保你的计算机满足 VOS3000 外呼系统客户端的系统要求&#xff0c;包括操作系统版本、内存、处理器等。如果系统不符合要求&#xff0c;可能会导致安装失败或者运…

c语言中,数组取地址的书写格式

数组取地址 为了更好的区分数组取地址时的情况&#xff0c;我们建立两个数组&#xff0c;arr1一维数组和arr2二维数组&#xff0c;用printf函数来打印出每个例子arr1和arr2的地址&#xff0c;这样可以更加直观的区分出来。 首先我们看到第一组打印&#xff0c;可以看到若是直接…

Qt图片等资源管理

Qt的图片等资源管理通常有两种方式 1&#xff0c;直接将图标和一些配置文件打包在可执行程序中 添加qrc文件&#xff0c;可使用qtcreator直接添加 右键选中工程 点击选择即可。 然后添加文件。我这个例子是添加了Image文件夹下的图片资源 使用的时候&#xff0c;可以在代码…

TCP/IP协议(二)

一、TCP-选项 1.简介 在TCP/IP报文中&#xff0c;固定头部下边就是 "选项"部分。 (1)TCP头部的选项部分是TCP为了适应复杂的网络环境和更好的服务应用层而进行设计的 (2)大多数的TCP选项部分出现在TCP连接建立阶段 2.构成 2.1 最大报文传输段 最大报文传输段(Ma…

Java面试八股之简述Servlet体系结构

简述Servlet体系结构 Servlet是Java Web开发中的核心组件&#xff0c;用于接收和响应HTTP请求&#xff0c;生成动态内容。它具有平台无关性、协议无关性和动态内容生成能力&#xff0c;遵循明确的生命周期。尽管现代Web开发中更多使用高级框架&#xff0c;但Servlet作为基础&a…

【SAP ME 18】SAP ME创建开发组件ear

1、说明 SC开发组件ear项目是所有sap me二次开发项目的编译入和部署入口,通过ear可以有效的针对子项目的编译和部署 2、创建开发组件

【MATLAB源码-第190期】基于matlab的32QAM系统相位偏移估计EOS算法仿真,对比补偿前后的星座图误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 引言 M-QAM调制技术的重要性 现代通信系统追求的是更高的数据传输速率和更有效的频谱利用率。M-QAM调制技术&#xff0c;作为一种高效的调制方案&#xff0c;能够通过在相同的带宽条件下传输更多的数据位来满足这一需求…

云打印怎么下单?网上云打印下单教程来了!

近些年来&#xff0c;随着移动互联网的发展&#xff0c;云打印也越来越火热。如今有越来越多的用户选择云打印服务。但是现在仍有很多不知道如何下单。那么云打印怎么下单呢&#xff1f;今天小易就来和大家介绍一下网上云打印的下单教程。 云打印怎么下单&#xff1f;网上云打印…

MYSQL45道练习题---持续更新中

来源&#xff1a; Mysql_45道练习题 - 简书 共四张表&#xff1a; ①、course表&#xff1a; CId&#xff1a;课程id Cname&#xff1a;课程名称 TId&#xff1a;老师id ②、student学生表&#xff1a; SId&#xff1a;学生id Sname&#xff1a;…

【Linux】进程的程序地址空间①

目录 前言&#xff1a; 1.什么是地址空间 区域划分 页表&#xff1a; 2.为什么要有地址空间 2.1 进程与内存解耦合 2.2安全 3.凭什么说进程具有独立性&#xff1a; 4.用地址空间解释一下申请内存 前言&#xff1a; 在C语言中&#xff0c;我们说我们将内存分为&#xff0c;栈区…

vue3组件之间的传参

1、父传子 defineProps 父组件 <script setup>import { reactive } from vue;import Children from ./children.vue;const parentProps reactive({name:zhangsan,age:20})</script><template><div>这是父组件</div><div>子组件:<Chil…

探索大型语言模型(LLM)在人类性格个性评估(MBTI)中的前景与应用

1.概述 大型语言模型&#xff08;LLM&#xff09;如ChatGPT在各个领域的应用确实越来越广泛&#xff0c;它们利用庞大的数据集进行训练&#xff0c;以模拟人类的语言理解和生成能力。这些模型在提供信息、解答问题、辅助决策等方面表现出了强大的能力&#xff0c;但它们并不具…

AI大模型之路 第二篇: Word2Vec介绍

你好&#xff0c;我是郭震 今天我来总结大模型第二篇&#xff0c;word2vec&#xff0c;它是大模型的根基&#xff0c;一切NLP都会用到它。 Word2Vec Word2Vec 是一种流行的自然语言处理&#xff08;NLP&#xff09;工具&#xff0c;它通过将词汇表中的每个单词转换成一个独特的…

优先级队列(概念理解/底层模拟/时间复杂度分析)

目录 1.概念理解 2.优先级队列的底层模拟 2.1堆的概念 2.2优先队列的模拟实现 2.2.1把Heap类定义好 2.2.2初始化堆 2.2.3创建大堆 1.思路 以此二叉树为例&#xff1a; 图文理解&#xff1a; 2.思路转化为代码 2.2.4堆操作之offer&#xff08;进队列&#xff09; 1…

机器学习-10-基于paddle实现神经网络

文章目录 总结参考本门课程的目标机器学习定义第一步&#xff1a;数据准备第二步&#xff1a;定义网络第三步&#xff1a;训练网络第四步&#xff1a;测试训练好的网络 总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍基于paddle实现神经网络。 参考 MNIST 训练_副…

【Node.js】01 —— fs模块全解析

&#x1f525;【Node.js】 fs模块全解析 &#x1f4e2; 引言 在Node.js开发中&#xff0c;fs模块犹如一把万能钥匙&#xff0c;解锁着整个文件系统的操作。从读取文件、写入文件、检查状态到目录管理&#xff0c;无所不能。接下来&#xff0c;我们将逐一揭开fs模块中最常用的那…

vue ant form validate如何对数组下的表单校验

问题 使用Ant Design Vue校验表单时&#xff0c;通过validateFields&#xff0c;但是如何一个数组内部的校验呢&#xff1f; 效果图&#xff1a; 实现方式&#xff1a; 通过 v-for 循环渲染:name"[]"实现&#xff0c;我们直接看代码。 <template><a-for…

Spring Boot中JUnit 4与JUnit 5的如何共存

文章目录 前言一、先上答案二、稍微深入了解2.1 maven-surefire-plugin是什么2.2 JUnit4和JUnit5有什么区别2.2.1 不同的注解2.2.2 架构 前言 在maven项目中&#xff0c;生成单测时是否有这样的疑问&#xff1a;该选JUnit4还是JUnit5&#xff1f;在执行 mvn test 命令时有没有…

三、SpringBoot整合MyBatis

本章节主要描述MyBatis的整合&#xff0c;以及使用mybatis-generator-maven-plugin生成代码骨架&#xff0c;源码&#xff1a; jun/learn-springboot - Gitee.com 一、首先建数据库 本示例用的是MySQL8.0.23&#xff0c;建表t_goods、t_orders&#xff0c;略... 二、goods模块…

Java | Leetcode Java题解之第36题有效的数独

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isValidSudoku(char[][] board) {int[][] rows new int[9][9];int[][] columns new int[9][9];int[][][] subboxes new int[3][3][9];for (int i 0; i < 9; i) {for (int j 0; j < 9; j) {char …