C语言项目实践——贪吃蛇

引言:本篇博客中,我将会使用结构体,链表,WIN32 API等一系列知识完成C语言项目——贪吃蛇的实现。在观看此篇博客之前,请将这些知识所熟悉,不然可能会造成理解困难。

更多有关C语言的知识详解可前往个人主页:计信猫

目录

一,贪吃蛇项目的准备

1,三文件操作

2,贪吃蛇有关结构体的定义

3,改变窗口信息与隐藏光标

4,确定光标位置函数

 二,游戏开始(GameStart)

 1,欢迎界面的设计

2,游戏地图的创建

3,蛇的初始化

4,创建食物

​编辑

 三,游戏运行(GameRun)

 1,打印游戏帮助信息

​编辑2,游戏得分的打印与蛇的移动方向的判定

3,蛇的移动(SnakeMove)

4,检测下一个坐标是否为食物

Ⅰ,EatFood

Ⅱ,NoFood

5,判断蛇是否撞墙或者撞到自己

Ⅰ,KillByWall

Ⅱ,KillBySelf 

 四,游戏结束(GameEnd)

1,根据蛇的状态打印结束语

2,释放蛇的空间

五,游戏的优化设置

六,代码参考

1,test.c

2,  snake.c

3,  snake.h

一,贪吃蛇项目的准备

1,三文件操作

        在贪吃蛇项目中,我们会创建三个文件分别为:test.c,snake.c,snake.h。它们的作用分别为游戏函数的测试,游戏函数的实现,游戏函数的声明和结构体变量的定义。

2,贪吃蛇有关结构体的定义

        在snake.h中,我们将会定义与贪吃蛇有关的结构体。

        1,贪吃蛇身体的节点:

//定义蛇身体的节点
typedef struct snakenode
{//坐标int x;int y;struct snakenode* next;//下一个节点
}snakenode,*psnakenode;

        2,蛇的方向:

//定义方向
enum direction
{UP = 1,DOWN,LEFT,RIGHT
};

        3,蛇的状态:

//蛇的状态
enum game_state
{OK,//正常状态KILL_BY_WALL,//撞到墙KILL_BY_SELF,//撞到了自己END_NORMAL//正常结束游戏
};

        4,贪吃蛇游戏:

//贪吃蛇
typedef struct snake
{psnakenode _psnake;//蛇头节点psnakenode _pfood;//食物节点enum direction _dir;//蛇的方向enum game_state _sta;//游戏状态int _food_weight;//一个食物的分数int _score;//总成绩int _sleep_time;//蛇的速度
}snake,* psnake;

3,改变窗口信息与隐藏光标

        snake.c中,我们定义一个GameStart函数来包含开始游戏之前的窗口改变,光标隐藏,欢迎界面与地图打印函数

        在前文WIN32 API中我们已经对窗口信息修改和隐藏光标的函数进行过解释,所以我们这里直接使用:

void GameStart(psnake ps)
{//设置窗口大小,改变窗口名字system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取光标句柄CONSOLE_CURSOR_INFO cursorinfo;GetConsoleCursorInfo(houtput, &cursorinfo);//获取光标信息cursorinfo.bVisible = false;//隐藏鼠标SetConsoleCursorInfo(houtput, &cursorinfo);//设置光标状态
}

4,确定光标位置函数

        在欢迎界面与游戏界面中,我们需要将游戏开始信息与游戏信息等打印在指定的位置,这时候我们就需要有特定的函数来确定光标的位置,以此来完成指定位置信息的打印。所以我们如下设计确定光标位置setpos()函数。

void setpos(int x,int y)
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出句柄COORD pos = { x,y };//设定坐标SetConsoleCursorPosition(houtput, pos);//设定光标的坐标
}

 二,游戏开始(GameStart)

 1,欢迎界面的设计

       

        如图中的界面就是我们想要达到的欢迎界面效果,所以我们在GameStart()函数中设计一个新函数名为welcometogame()函数,函数内容如下:

void welcometogame()
{setpos(40, 14);wprintf(L"欢迎来到贪吃蛇游戏\n");setpos(42, 20);system("pause");system("cls");setpos(25, 14);wprintf(L"使用 ↑ . ↓ . ← . → 来进行方向移动,f3加速,f4减速\n");setpos(25, 15);wprintf(L"加速吃到食物可以增加得分\n");setpos(42, 20);system("pause");system("cls");
}

        首先我们使用setpos函数来确定光标的位置,再使用wprintf函数来打印内容,system(“pause”)语句则代表着暂停程序,按任意键表示继续程序,而system(“cls”)语句则代表着将当前控制台的页面全部清空,这样我们就打印出了我们的欢迎界面。

2,游戏地图的创建

        在贪吃蛇游戏中,我们将要创建如图所示的游戏地图:

        正如我们前一篇博客所讲到的,我们的控制台界面的各个地点都有着对应的坐标,如下:

        而我们所想要设计的地图的大小,就可以通过坐标的范围来圈定。所以我们的地图大小应该如下图坐标所示:

        有了坐标,我们就知道了地图的上边需要29个宽字符‘▢’,下边也是一样,而左右两边我们则可以使用setpos函数来移动光标进行左右两边边界的打印。所以我们在GameStart()函数中创建一个creatmap()函数,代码如下:

void creatmap()
{int i = 0;//上for (i = 0; i < 29; i++){wprintf(L"%lc", L'□');}//下setpos(0, 26);//移动光标以便打印下标for (i = 0; i < 29; i++){wprintf(L"%lc", L'□');}//左for (i = 1; i <= 25; i++){setpos(0, i);//移动光标wprintf(L"%lc", L'□');}//右for (i = 1; i <= 25; i++){setpos(56, i);//移动光标wprintf(L"%lc", L'□');}system("pause");//按任意键继续
}

        代码一走,则地图创建成功!

3,蛇的初始化

        我们将蛇想象成我们以前学到的链表,刚开始的蛇身长度为5个节点,所以我们便可以使用头插的办法来完成蛇身的初始化。

        但是,在初始化蛇身的时候,我们一定需要注意蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中, 另外⼀半在墙外的现象,坐标不好对齐。如下图所示:

        而对于蛇的身体,我们则使用宽字符‘●‘进行表示。为了方便以后更改蛇身体的坐标,墙和蛇的图标,于是我们选择在snake.h中使用define定义:

#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'

        最后,我们还不要忘记对蛇的结构体中的其他成员变量的初始化。所以在GameStart()函数中,我们继续定义一个InitSnake()函数来进行对蛇进行初始化。

void InitSnake(psnake ps)
{int i = 0;psnakenode cur = NULL;for (i = 0; i < 5; i++){cur = (psnakenode)malloc(sizeof(snakenode));//创建节点if (cur == NULL)//判断是否申请成功{perror("InitSnake()::malloc()");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->_food_weight = 10;ps->_score = 0;ps->_sleep_time = 200;//单位是毫秒ps->_sta = OK;getchar();//暂停程序观察效果,不需要观察效果后可将该语句删除
}

4,创建食物

        当蛇的初始化完成后,我们就需要完成食物的初始化。但在初始化食物的时候,我们需要注意到以下三点:

1,食物必须出现在墙体之内

2,食物的x坐标一定为2的倍数

3,食物坐标不可以与蛇身的坐标重复

        在该函数中,我们也给食物下定义:

#define FOOD L'※'

为了达到坐标随机生成的目的,我们将在主函数中使用srand((unsigned int)time);(需要包含<time.h>)。所以我们的创建食物函数CreatFood()函数的实现如下:

void CreatFood(psnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;//使横坐标在2~54之内y = rand() % 24 + 1;//使纵坐标在1~25之内} while (x % 2 != 0);//若生成的x为奇数,那么就重新生成psnakenode cur = ps->_psnake;//遍历链表,防止食物与蛇身重合while (cur){if (x == cur->x && y == cur->y)//如果食物的坐标与蛇身重合{goto again;//重新生成}cur = cur->next;}//打印食物标号psnakenode food = (psnakenode)malloc(sizeof(snakenode));if (food == NULL)//防止申请内存失败{perror("CreatFood()::malloc()");return;}food->x = x;food->y = y;food->next = NULL;setpos(food->x, food->y);wprintf(L"%lc", FOOD);ps->_pfood = food;getchar();//观察食物打印效果,之后若不需要观察则可以删除该语句
}

代码一走,食物生成成功!

 三,游戏运行(GameRun)

 1,打印游戏帮助信息

        在正式开始游戏时,我们需要在游戏地图之外打印一系列信息,以帮助用户了解游戏的玩法。所以我们在GameRun()函数中封装一个PrintHelpInfo()函数来进行游戏帮助信息的打印。

//打印游戏帮助信息
void PrintHelpInfo()
{//打印提⽰信息setpos(64, 15);printf("不能穿墙,不能咬到自己\n");setpos(64, 16);printf("⽤↑.↓.←.→分别控制蛇的移动\n");setpos(64, 17);printf("F3 为加速,F4 为减速\n");setpos(64, 18);printf("ESC :退出游戏.space:暂停游戏.");
}

        代码一走,效果如图:

2,游戏得分的打印与蛇的移动方向的判定

        首先,我们实现一个KEY_PRESS宏来判断某个按键是否被按下:

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

        所以,我们便可以在GameStart()函数中使用do-while循环来打印游戏的得分与了解按键按下的情况,以便控制蛇的移动方向。

//游戏的运行
void GameRun(psnake ps)
{//打印游戏帮助信息PrintHelpInfo();do{//打印游戏得分信息setpos(64, 10);printf("总得分:%d\n", ps->_score);setpos(64, 11);printf("当前一个食物的分数:%2d\n", ps->_food_weight);//判断按键以控制蛇的移动if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)//VK_UP为↑的虚拟键码,同时蛇的当前方向不可以向下{ps->_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP){ps->_dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_SPACE))//空格为暂停{Pause();//Pause函数执行暂停操作}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏ps->_sta = END_NORMAL;}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_sleep_time < 300){ps->_sleep_time += 30;ps->_food_weight -= 2;}}SnakeMove(ps);//蛇走一步Sleep(ps->_sleep_time);} while (ps->_sta == OK);
}

        其中Pause()函数如下:

//游戏暂停操作
void Pause()
{while (1){Sleep(200);//死循环休眠则视为暂停if (KEY_PRESS(VK_SPACE)){break;//再次按下空格则跳出循环}}
}

3,蛇的移动(SnakeMove)

        当我们在前面获取了按键按下的情况,即蛇的状态信息之后,我们就可以使用SnakeMove()函数来对蛇的移动进行控制。

        在该函数中,我们就可以再创建一个节点(pNextNode)表示蛇头即将到达的节点位置,而该新节点的位置与当前蛇头的坐标x,y有关:

        于是新节点的位置设置如下:

//蛇走一步
void SnakeMove(psnake ps)
{//先创建一个新节点,表示蛇头即将到达的位置psnakenode pNextNode = (psnakenode)malloc(sizeof(snakenode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y - 1;break;case DOWN:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y + 1;break;case LEFT:pNextNode->x = ps->_psnake->x-2;pNextNode->y = ps->_psnake->y;break;case RIGHT:pNextNode->x = ps->_psnake->x+2;pNextNode->y = ps->_psnake->y;break;}
}

4,检测下一个坐标是否为食物

        当我们的蛇头移动到下一个坐标的时候,就会产生两种情况:1,下一个坐标为食物。2,下一个坐标不为食物。两种情况应该不同方式来处理,所以我们在SnakeMove()函数中的switch循环之后再定义两个函数来处理不同情况:

//判断下一个坐标是否为食物
int NextIsFood(psnakenode pNextNode, psnake ps)
{return (pNextNode->x == ps->_pfood->x && pNextNode->y == ps->_pfood->y);
}
//检测下一个坐标是否为食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}

Ⅰ,EatFood

        该函数用于处理下一个坐标为食物的情况。该情况下,我们就可以直接将食物节点变为蛇的头节点,然后打印蛇,加分,最后再次创建食物:

//下一个坐标为食物
void EatFood(psnakenode pNextNode, psnake ps)
{//使用头插,将食物节点变为蛇头ps->_pfood->next = ps->_psnake;ps->_psnake = ps->_pfood;//释放掉pNextNodefree(pNextNode);pNextNode = NULL;//打印出新蛇psnakenode cur = ps->_psnake;while (cur){setpos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//加分ps->_score += ps->_food_weight;//再次创建食物CreatFood(ps);
}

Ⅱ,NoFood

        该函数用于处理下一个坐标不为食物的情况。该情况下,我们则需要做到的任务如下:

1,使用头插法将pNextNode变为新的蛇头
2,把最后一个坐标打印为两个空格
3,释放掉尾节点
4,将倒数第二个节点的next置空

        所以我们画图解释:

          于是我们使用cur遍历数组,当cur->next->nextNULL时,那么就证明cur来到了倒数第二个节点,此时我们就跳出while循环,之后将cur->next指向的节点处打印两个空格(以覆盖先前打印而留下的蛇身),再将尾节点释放掉,最后将cur->next置空。 

//下一个坐标不是食物
void NoFood(psnakenode pNextNode, psnake ps)
{//头插法pNextNode->next = ps->_psnake;ps->_psnake = pNextNode;//遍历链表psnakenode cur = ps->_psnake;while (cur->next->next){setpos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//出了循环,cur刚好停在倒数第二个节点,所以先打印两个空格setpos(cur->next->x, cur->next->y);printf("  ");//释放掉尾节点free(cur->next);//将倒数第二个节点的next置空cur->next = NULL;
}

        这样,一个贪吃蛇游戏就初步完成了。

5,判断蛇是否撞墙或者撞到自己

        在GameRUN()函数中,我们也需要检测蛇是否在行动的过程中撞到了墙或者撞到了自己。于是我们可以在设计两个函数KillByWall()函数KillBySelf()函数来进行判断。

Ⅰ,KillByWall

        关于蛇是否撞墙的判断十分简单。我们只需要判断蛇头的坐标是否与墙的x或者y坐标重合即可。若重合,则撞墙;反之,则未撞墙。

//检测是否撞墙
void KillByWall(psnake ps)
{if (ps->_psnake->x == 0 || ps->_psnake->x == 56 ||ps->_psnake->y == 0 || ps->_psnake->y == 26){ps->_sta = KILL_BY_WALL;}
}

Ⅱ,KillBySelf 

         在该函数中,我们只需要从蛇头之后的一个节点遍历蛇身,假如某段蛇身的坐标等于蛇头的坐标,那么就是撞到了自己;反之,则没有撞到自己。

//检测是否撞到自己
void KillBySelf(psnake ps)
{psnakenode cur = ps->_psnake->next;//从蛇头之后的一个节点开始遍历while (cur){if (cur->x == ps->_psnake->x && cur->y == ps->_psnake->y){ps->_sta = KILL_BY_SELF;break;//撞到了自己,退出循环}cur = cur->next;}
}

 四,游戏结束(GameEnd)

1,根据蛇的状态打印结束语

        有了前文所获取的蛇的结束状态,那我们就可以在GameEnd()函数中打印对应的结束语。

void GameEnd(psnake ps)
{setpos(24, 12);//设置光标位置,打印结束信息switch (ps->_sta){case END_NORMAL:printf("游戏正常结束\n");break;case KILL_BY_WALL:printf("您撞到了墙,游戏结束\n");break;case KILL_BY_SELF:printf("您撞到了自己,游戏结束\n");break;}
}

2,释放蛇的空间

        当然,当我们的游戏结束后,我们使用的蛇身节点也需要被释放,而释放方法非常简单,就是我们之前在链表操作函数中学到的销毁链表操作。

//游戏结束
void GameEnd(psnake ps)
{//释放蛇身psnakenode cur = ps->_psnake;while (cur){psnakenode del = cur;cur = cur->next;free(del);del = NULL;}
}

        如此,我们的贪吃蛇的主体函数就完成了!!

五,游戏的优化设置

        在这里,我们将实现游戏的多次使用和正常退出,以及项目结束的信息的位置的设置。这一切的操作,都在test.c中进行。

#include"snake.h"
void test()
{int ch = 0;do{snake ps = { 0 };GameStart(&ps);GameRun(&ps);GameEnd(&ps);setpos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();getchar();//清理\n} while (ch == 'Y' || ch == 'y');setpos(0, 27);
}
int main()
{srand((unsigned int)time);setlocale(LC_ALL, "");//设置本地化以打印宽字符test();return 0;
}

六,代码参考

1,test.c

#include"snake.h"
void test()
{int ch = 0;do{snake ps = { 0 };GameStart(&ps);GameRun(&ps);GameEnd(&ps);setpos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();getchar();//清理\n} while (ch == 'Y' || ch == 'y');setpos(0, 27);
}
int main()
{srand((unsigned int)time);setlocale(LC_ALL, "");//设置本地化以打印宽字符test();return 0;
}

2,  snake.c

#include"snake.h"
void setpos(int x,int y)
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出句柄COORD pos = { x,y };//设定坐标SetConsoleCursorPosition(houtput, pos);//设定光标的坐标
}
void welcometogame()
{setpos(40, 14);wprintf(L"欢迎来到贪吃蛇游戏\n");setpos(42, 20);system("pause");system("cls");setpos(25, 14);wprintf(L"使用 ↑ . ↓ . ← . → 来进行方向移动,f3加速,f4减速\n");setpos(25, 15);wprintf(L"加速吃到食物可以增加得分\n");setpos(42, 20);system("pause");system("cls");
}
void creatmap()
{int i = 0;//上for (i = 0; i < 29; i++){wprintf(L"%lc", L'□');}//下setpos(0, 26);//移动光标以便打印下标for (i = 0; i < 29; i++){wprintf(L"%lc", L'□');}//左for (i = 1; i <= 25; i++){setpos(0, i);//移动光标wprintf(L"%lc", L'□');}//右for (i = 1; i <= 25; i++){setpos(56, i);//移动光标wprintf(L"%lc", L'□');}
}
void InitSnake(psnake ps)
{int i = 0;psnakenode cur = NULL;for (i = 0; i < 5; i++){cur = (psnakenode)malloc(sizeof(snakenode));//创建节点if (cur == NULL)//判断是否申请成功{perror("InitSnake()::malloc()");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->_food_weight = 10;ps->_score = 0;ps->_sleep_time = 200;//单位是毫秒ps->_sta = OK;
}
void CreatFood(psnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;//使横坐标在2~54之内y = rand() % 24 + 1;//使纵坐标在1~25之内} while (x % 2 != 0);//若生成的x为奇数,那么就重新生成psnakenode cur = ps->_psnake;//遍历链表,防止食物与蛇身重合while (cur){if (x == cur->x && y == cur->y)//如果食物的坐标与蛇身重合{goto again;//重新生成}cur = cur->next;}//打印食物标号psnakenode food = (psnakenode)malloc(sizeof(snakenode));if (food == NULL)//防止申请内存失败{perror("CreatFood()::malloc()");return;}food->x = x;food->y = y;food->next = NULL;setpos(food->x, food->y);wprintf(L"%lc", FOOD);ps->_pfood = food;//getchar();//观察食物打印效果,之后若不需要观察则可以删除该语句
}
void GameStart(psnake ps)
{//设置窗口大小,改变窗口名字system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO cursorinfo;GetConsoleCursorInfo(houtput, &cursorinfo);//获取光标信息cursorinfo.bVisible = false;//隐藏鼠标SetConsoleCursorInfo(houtput, &cursorinfo);//设置光标状态//打印欢迎界面welcometogame();//打印地图creatmap();//初始化蛇InitSnake(ps);//创建食物CreatFood(ps);
}
//游戏暂停操作
void Pause()
{while (1){Sleep(200);//死循环休眠则视为暂停if (KEY_PRESS(VK_SPACE)){break;//再次按下空格则跳出循环}}
}
//打印游戏帮助信息
void PrintHelpInfo()
{//打印提⽰信息setpos(64, 15);printf("不能穿墙,不能咬到自己\n");setpos(64, 16);printf("用↑.↓.←.→分别控制蛇的移动\n");setpos(64, 17);printf("F3 为加速,F4 为减速\n");setpos(64, 18);printf("ESC :退出游戏.space:暂停游戏.");
}
//判断下一个坐标是否为食物
int NextIsFood(psnakenode pNextNode, psnake ps)
{return (pNextNode->x == ps->_pfood->x && pNextNode->y == ps->_pfood->y);
}
//下一个坐标为食物
void EatFood(psnakenode pNextNode, psnake ps)
{//使用头插,将食物节点变为蛇头ps->_pfood->next = ps->_psnake;ps->_psnake = ps->_pfood;//释放掉pNextNodefree(pNextNode);pNextNode = NULL;//打印出新蛇psnakenode cur = ps->_psnake;while (cur){setpos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//加分ps->_score += ps->_food_weight;//再次创建食物CreatFood(ps);
}
//下一个坐标不是食物
void NoFood(psnakenode pNextNode, psnake ps)
{//头插法pNextNode->next = ps->_psnake;ps->_psnake = pNextNode;//遍历链表psnakenode cur = ps->_psnake;while (cur->next->next){setpos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//出了循环,cur刚好停在倒数第二个节点,所以先打印两个空格setpos(cur->next->x, cur->next->y);printf("  ");//释放掉尾节点free(cur->next);//将倒数第二个节点的next置空cur->next = NULL;
}
//检测是否撞墙
void KillByWall(psnake ps)
{if (ps->_psnake->x == 0 || ps->_psnake->x == 56 ||ps->_psnake->y == 0 || ps->_psnake->y == 26){ps->_sta = KILL_BY_WALL;}
}
//检测是否撞到自己
void KillBySelf(psnake ps)
{psnakenode cur = ps->_psnake->next;//从蛇头之后的一个节点开始遍历while (cur){if (cur->x == ps->_psnake->x && cur->y == ps->_psnake->y){ps->_sta = KILL_BY_SELF;break;}cur = cur->next;}
}
//蛇走一步
void SnakeMove(psnake ps)
{//先创建一个新节点,表示蛇头即将到达的位置psnakenode pNextNode = (psnakenode)malloc(sizeof(snakenode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y - 1;break;case DOWN:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y + 1;break;case LEFT:pNextNode->x = ps->_psnake->x-2;pNextNode->y = ps->_psnake->y;break;case RIGHT:pNextNode->x = ps->_psnake->x+2;pNextNode->y = ps->_psnake->y;break;}//检测下一个坐标是否为食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}//检测是否撞墙KillByWall(ps);//检测是否撞到自己KillBySelf(ps);
}
//游戏的运行
void GameRun(psnake ps)
{//打印游戏帮助信息PrintHelpInfo();do{//打印游戏得分信息setpos(64, 10);printf("总得分:%d\n", ps->_score);setpos(64, 11);printf("当前一个食物的分数:%2d\n", ps->_food_weight);//判断按键以控制蛇的移动if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)//VK_UP为↑的虚拟键码,同时蛇的当前方向不可以向下{ps->_dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP){ps->_dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_SPACE))//空格为暂停{Pause();//Pause函数执行暂停操作}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏ps->_sta = END_NORMAL;}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_sleep_time < 300){ps->_sleep_time += 30;ps->_food_weight -= 2;}}SnakeMove(ps);//蛇走一步Sleep(ps->_sleep_time);} while (ps->_sta == OK);
}
//游戏结束
void GameEnd(psnake ps)
{setpos(24, 12);//设置光标位置,打印结束信息switch (ps->_sta){case END_NORMAL:printf("游戏正常结束\n");break;case KILL_BY_WALL:printf("您撞到了墙,游戏结束\n");break;case KILL_BY_SELF:printf("您撞到了自己,游戏结束\n");break;}//释放蛇身psnakenode cur = ps->_psnake;while (cur){psnakenode del = cur;cur = cur->next;free(del);del = NULL;}
}

3,  snake.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<stdbool.h>
#include<locale.h>
#include<time.h>
#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'※'
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//定义蛇身体的节点
typedef struct snakenode
{//坐标int x;int y;struct snakenode* next;//下一个节点
}snakenode,*psnakenode;
//定义方向
enum direction
{UP = 1,DOWN,LEFT,RIGHT
};
//蛇的状态
enum game_state
{OK,//正常状态KILL_BY_WALL,//撞到墙KILL_BY_SELF,//撞到了自己END_NORMAL//正常结束游戏
};
//贪吃蛇
typedef struct snake
{psnakenode _psnake;//蛇头节点psnakenode _pfood;//食物节点enum direction _dir;//蛇的方向enum game_state _sta;//游戏状态int _food_weight;//一个食物的分数int _score;//总成绩int _sleep_time;//蛇的速度
}snake,* psnake;
//设置光标位置
void setpos(int x, int y);
//开始游戏
void GameStart(psnake ps);
//打印欢迎界面
void welcometogame();
//设置光标坐标
void setpos(int x, int y);
//打印游戏地图
void creatmap();
//蛇的初始化
void InitSnake(psnake ps);
//创建食物
void CreatFood(psnake ps);
//游戏的运行
void GameRun(psnake ps);
//打印游戏帮助信息
void PrintHelpInfo();
//游戏暂停操作
void Pause();
//蛇走一步
void SnakeMove(psnake ps);
//判断下一个坐标是否为食物
int NextIsFood(psnakenode pNextNode, psnake ps);
//下一个坐标为食物
void EatFood(psnakenode pNextNode, psnake ps);
//下一个坐标不是食物
void NoFood(psnakenode pNextNode, psnake ps);
//检测是否撞墙
void KillByWall(psnake ps);
//检测是否撞到自己
void KillBySelf(psnake ps);
//游戏结束
void GameEnd(psnake ps);

        快去享受游戏吧!!! 

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

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

相关文章

【C++】explicit关键字详解(explicit关键字是什么? 为什么需要explicit关键字? 如何使用explicit 关键字)

目录 一、前言 二、explicit关键字是什么&#xff1f; 三、构造函数还具有类型转换的作用 &#x1f34e;单参构造函数 ✨引出 explicit 关键字 &#x1f34d;多参构造函数 ✨为什么需要explicit关键字&#xff1f; ✨怎么使用explicit关键字&#xff1f; 四、总结 五…

npx\pnpm 镜像过期解决方法

. // 1. 清空缓存 npm cache clean --force // 2. 关闭SSL验证 npm config set strict-ssl false // 3. 安装 到这里就可以正常使用npm命令安装需要的工具了。如( npm install -g cnpm )

虚拟机中使用LNMP模拟跨域并结合前端代码解决CORS跨域的简单示例

目录 一、首先&#xff0c;下载lnmp_soft.tar.gz压缩包 二、解压lnmp_soft.tar.gz和下载相关的依赖&#xff0c;插件 三、修改/usr/local/nginx/conf/nginx.conf配置文件 四、/usr/local/nginx/sbin/nginx命令启动nginx 五、在/usr/local/nginx/html目录下新建80.html&…

书生·浦语大模型实战营之微调 Llama 3 实践与教程 (XTuner 版)

书生浦语大模型实战营之微调 Llama 3 实践与教程 (XTuner 版) Llama 3 近期重磅发布,发布了 8B 和 70B 参数量的模型,XTuner 团队对 Llama 3 微调进行了光速支持!!!开源同时社区中涌现了 Llama3-XTuner-CN 手把手教大家使用 XTuner 微调 Llama 3 模型。 XTuner:http:/…

图深度学习——2.图的理论知识

1.图 1.1 图的定义 图是由节点&#xff08;顶点&#xff09;和边构成的数学结构。图用于表示对象之间的关系&#xff0c;其中节点表示对象&#xff0c;边表示对象之间的关系。 一个图&#xff0c;记为 G <V, E> &#xff0c;它包括以下两个要素&#xff1a; 1.节点&am…

第22天:安全开发-PHP应用留言板功能超全局变量数据库操作第三方插件引用

第二十二天 一、PHP留言板前后端功能实现 开发环境&#xff1a; DW PHPStorm PhpStudy Navicat Premium DW : HTML&JS&CSS开发 PHPStorm : 专业PHP开发IDE PhpStudy &#xff1a;Apache MYSQL环境 Navicat Premium: 全能数据库管理工具 二、数据库创建&架…

机器学习(三)之监督学习2

前言&#xff1a; 本专栏一直在更新机器学习的内容&#xff0c;欢迎点赞收藏哦&#xff01; 笔者水平有限&#xff0c;文中掺杂着自己的理解和感悟&#xff0c;如果有错误之处还请指出&#xff0c;可以在评论区一起探讨&#xff01; 1.支持向量机&#xff08;Support Vector Ma…

iTwin Capture Modeler-23中文版下载地址及安装教程

文章目录 一、iTwin Capture Modeler23中文版安装教程二、iTwin Capture Modeler23中文版下载地址一、iTwin Capture Modeler23中文版安装教程 1. 解压安装包。订阅专栏(可获取专栏内所有文章阅读权限与软件安装包)后,从文末获取安装包解压,如下所示: 2. 右击安装包,选择以…

【Web】HNCTF 2022 题解(全)

目录 Week1 Interesting_include 2048 easy_html What is Web Interesting_http easy_upload Week2 ez_SSTI easy_include ez_ssrf Canyource easy_unser easy_sql ohmywordpress Week3 ssssti Fun_php ez_phar QAQ_1inclu4e logjjjjlogjjjj …

图像哈希:Global+Local

文章信息 作者&#xff1a;梁小平&#xff0c;唐振军期刊&#xff1a;ACM Trans. Multimedia Comput. Commun. Appl&#xff08;三区&#xff09;题目&#xff1a;Robust Hashing via Global and Local Invariant Features for Image Copy Detection 目的、实验步骤及结论 目…

内网隧道技术总结

隧道技术解决的是网络通信问题&#xff0c;因为在内网环境下&#xff0c;我们不同的内网主机管理员会进行不同的网络配置&#xff0c;我们就需要使用不同的方式去控制我们的内网主机。隧道技术是一个后渗透的过程&#xff0c;是可以是我们已经取得了一定的权限&#xff0c;在这…

NLP任务全览:涵盖各类NLP自然语言处理任务及其面临的挑战

自然语言处理(Natural Language Processing, 简称NLP&#xff09;是计算机科学与语言学中关注于计算机与人类语言间转换的领域。NLP将非结构化文本数据转换为有意义的见解&#xff0c;促进人与机器之间的无缝通信&#xff0c;使计算机能够理解、解释和生成人类语言。人类等主要…

(四)openlayers加入矢量图层.json文件

openlayers加入矢量图层.json文件 &#xff08;1&#xff09;接上一章节&#xff0c;添加矢量图层.json文件。首先下载.json矢量图层文件。链接&#xff1a;JSON矢量图层文件 &#xff08;2&#xff09;导入相关的依赖&#xff0c;提前把你下载好的矢量文件放入assets文件夹下…

巧用波卡生态优势,Mythical Games 引领 Web3 游戏新航向

Polkadot 对创新、安全和治理的承诺为 Mythical Games 提供了极大的发展价值。这个链上生态不仅将支持 Mythical Games 成长发展&#xff0c;还将帮助其他 Mythos 合作伙伴来壮大建设项目。 —— Mythical Games 创始人兼首席执行官 John Linden 近期 Web3 游戏行业又有新动向&…

microk8s拉取pause镜像卡住

前几天嫌服务器上镜像太多占空间&#xff0c;全部删掉了&#xff0c;今天看到 microk8s 更新了 1.30 版本&#xff0c;果断更新&#xff0c;结果集群跑不起来了。 先通过 microk8s.kubectl get pods --all-namespaces 命令看看 pod 状态。 如上图可以看到&#xff0c;所有的业…

JS -关于对象相关介绍

在JS中&#xff0c;除去基本的数据类型&#xff0c;还有包含对象这种复合数据类型&#xff0c;他可以储存多个键值对&#xff0c;并且每个键都是唯一的&#xff0c;并且在对象中可以包含各种数据类型的值&#xff0c;包括其他对象&#xff0c;数组&#xff0c;函数等。对象是Ja…

LeetCode in Python 72. Edit Distance (编辑距离)

编辑距离的基本思想很直观&#xff0c;即不断比较两个单词每个位置的元素&#xff0c;若相同则比较下一个&#xff0c;若不同则需要考虑从插入、删除、替换三种方法中选择一个最优的策略。涉及最优策略笔者最先想到的即是动态规划的思想&#xff0c;将两个单词的位置对应放在矩…

Vue2 移动端(H5)项目封装弹窗组件

前言 因vant-ui的dialog组件没有自定义footer插槽 效果 参数配置 1、代码示例&#xff1a; <t-dialog :visible.sync"show" :title"title" submit"submit"></t-dialog>2、配置参数&#xff08;t-dialog Attributes&#xff09; 参…

IS62C256AL-45TLI功能参数介绍及如何优化性能

IS62C256AL-45TLI功能和参数介绍及如何优化性能-公司新闻-配芯易-深圳市亚泰盈科电子有限公司 产品品种:静态随机存取存储器 RoHS:是 存储容量:256 kbit 组织:32 k x 8 访问时刻:45 ns 接口类型:Parallel 电源电压-最大:5.5 V 电源电压-最小:4.5 V 电源电流—最大值:25 mA 最小…

JumpServer搭建堡垒机实战

文章目录 第一步、下载安装第二步、访问异常处理【1】docker方式拉取失败 JumpServer是运维人员可连接内部服务器上进行操作&#xff0c;支持Linux等操作系统的管理工具。 第一步、下载安装 curl -sSL https://resource.fit2cloud.com/jumpserver/jumpserver/releases/latest/…