手把手教你实现贪吃蛇

前言

       在实现贪吃蛇前,我们需要熟练地掌握C语言知识,对初阶数据结构中的链表有一定的掌握,并且我们还会使用到Win 32 API 的知识,下面我会对需要使用到的API接口函数进行解释。最终的代码我放在后面,有需要的可以自取。

准备阶段

       首先我使用的是VS2022版本,在使用VS2022运行贪吃蛇前我们需要进行一些设置,因为我们的贪吃蛇可以设置窗口大小和窗口名字。
运行窗口如下:

如果你是win11操作系统的话:
设置好这两个:

如果你不是Win11的操作系统,可以将Windows控制台主机设计成由Windows决定。

这里的背景颜色和字体颜色都可以自己去调整。

游戏窗口设置

title

       这个命令是用来设置窗口名字的

title 贪吃蛇

这里我们会使用到system函数来实现。

	system("title 贪吃蛇");

这样就设置好窗口的名字

窗口大小

mode con cols=100 lines=30

这个命令是用来设置几行几列的,这里注意了,x的两个格子相当于y的一个格子的大小

	system("mode con cols=100 lines=30");

如果觉得自己的窗口比例小,我们可以调节字体的大小来设置窗口的比例~~

本地化配置

       由于很多特殊字符是占用两个字节的(这些字符又被称为宽字符),为了能打印出特殊字符,我们需要进行本地化设置。

	setlocale(LC_ALL, "");

这个命令就是自动适配好本地化,需要的头文件是<locale.h>

光标设置

为了能在任意位置上进行输出,我们需要获取光标的位置,然后进行打印,这里我们可以封装一个函数,用来定位光标的位置,方便后续指定位置的输出。

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

这里解释一下里面的函数:

COORD这个函数是用来接收坐标的。
GetStdHandle 是用来接收不同的设备,就是你想操控那个设备,这里由三个参数,大家有兴趣可以去微软的官方文档去阅读,我们需要控制输出设备(就是屏幕),所以使用STD_OUTPUT_HANDLE这个参数
SetConsoleCursorPosition这个函数可以设置光标的位置,需要两个参数,一个就是上面的句柄和坐标。

游戏的实现

游戏准备阶段

游戏开始时,我们要初始化游戏,设置好界面大小,打印欢迎界面,打印按键功能界面,打印游戏界面(墙体,蛇,事物,右侧提示面板)等等操作,这时候我们需要对蛇这个对象进行设置,需要使用到结构体,蛇需要x、y两个坐标,我这里使用到的是单链表的结构,所以蛇的结构体如下:

//创建蛇的结构体
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode;

然后就是这个游戏需要的参数,首先就是蛇,我们需要一个蛇头的指针来找到这条蛇,然后是蛇的方向,速度,总得分,食物的分数,食物的位置,还有蛇的状态(是否死亡),于是我们对这个游戏对象进行结构体的封装:

//创建贪吃蛇对象的结构体
typedef struct SnakeInfo
{SnakeNode* snake_head;//蛇头的位置int total_score;//总得分int food_score;//食物分数int snake_speed;//蛇的速度SnakeNode* food;//食物的位置int snake_diretion;//蛇的方向int snake_state;//蛇的状态
}SnakeInfo;

这里不定义食物的结构体是因为食物也是需要x、y的坐标值,所以我们可以借用上面蛇的结构体~~

由于蛇的位置和状态可以一一列举出来,我们就使用枚举来定义,这样也方便我们后面的使用和操作:

//蛇的方向
typedef enum Diretion
{UP = 1,DOWN,LEFT,RIGHT
}Dit;//蛇的状态
typedef enum SnakeState
{OK,KILL_BY_ITSELF,KILL_BY_WALL,END_NORMAL
}State;

为了方便打印字符,我们可以宏定义一下这些字符

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

初始化游戏

//初始化游戏
void GameStart(SnakeInfo* sp)
{//设置界面大小system("mode con cols=100 lines=30");system("title 贪吃蛇");//获取句柄HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//获取光标信息CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);CursorInfo.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 //打印欢迎界面Welcome();//打印游戏界面CreatMap(sp);//设置好初始游戏参数sp->food_score = 10;sp->snake_speed = 200;sp->total_score = 0;sp->snake_diretion = RIGHT;//开始向右移动sp->snake_state = OK;}

在开始前,光标是会闪烁的,所以为了防止光标对玩家产生影响,我们需要把光标隐藏起来,就是如下代码:

	//获取光标信息CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);CursorInfo.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 

GetConsoleCursorInfo这个函数是获取光标信息的,CursorInfo.bVisible这里设置成false就可以把光标的能见度调成0,最后使用这个函数SetConsoleCursorInfo来设置光标的能见度。

打印欢迎界面

这里就不多说了,记得设置好光标的位置,然后进行打印信息即可。

//打印欢迎界面
void Welcome()
{SetPos(40, 12);printf("欢迎来到贪吃蛇小游戏");SetPos(42, 18);system("pause");//清理界面system("cls");//打印按键功能信息SetPos(8, 12);printf("↑  ↓  ←  → 分别控制蛇的上下左右移动, F3为加速,F4为减速, Esc 退出, Space 暂停");SetPos(35, 15);printf("加速后的食物分数会更高!!!");SetPos(40, 20);system("pause");system("cls");
}
游戏地图设置

下面是我们需要编写的函数:

void CreatMap(SnakeInfo* sp)
{//打印墙体CreatWall();//打印右侧面板信息PrintInfo();//初始化蛇的身体sp->snake_head = NULL;InitSnake(&(sp->snake_head));//打印蛇的身体PrintSnake(sp->snake_head);//打印食物PrintFood(sp);}
打印墙体和右侧面板信息

我们需要打印墙体和旁边的信息,值得注意的是窗口的坐标分布:

x的两格相当于y的一格,宽字符是占两个字符的,所以一个宽字符是两个x轴的格子加一个y轴的格子。都是从0开始的~~

//打印墙体
void CreatWall()
{int i = 0;//上墙体for (i = 0; i < 30; i++){wprintf(L"%lc", WALL);}//下墙体SetPos(0, 24);for (i = 0; i < 30; i++){wprintf(L"%lc", WALL);}//左墙体for (i = 1; i <= 23; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右墙体for (i = 1; i <= 23; i++){SetPos(58, i);wprintf(L"%lc", WALL);}
}
//打印右侧面板信息
void PrintInfo()
{SetPos(64, 8);printf("总得分:0    食物分数:10");SetPos(64, 12);printf("↑  ↓  ←  → 分别控制蛇移动");SetPos(65, 14);printf("F3为加速,F4为减速");SetPos(65, 15);printf("Esc 退出  Space 暂停");SetPos(65, 18);printf("加速后的食物分数会更高!!!");
}
初始化蛇的身体

这里我设置了5个节点进行尾插操作,为了便捷,我还写了一个创建节点的函数:

SnakeNode* CreatSnakeNewNode()
{SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));if (newnode == NULL){perror("CreatSnakeNewNode()::malloc()");}newnode->x = newnode->y = 0;newnode->next = NULL;return newnode;
}//初始化蛇的身体
void InitSnake(SnakeNode** sp)
{//五个节点if (*sp == NULL)//空链表{*sp = CreatSnakeNewNode();(*sp)->y = 4;(*sp)->x = 30;}SnakeNode* pcur = *sp;//尾插for (int i = 1; i <= 4; i++){SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));newnode->y = 4;newnode->x = 30 - 2 * i;pcur->next = newnode;pcur = pcur->next;}pcur->next = NULL;
}
打印蛇的身体与食物
//打印蛇的身体
void PrintSnake(SnakeNode* sp)
{SnakeNode* pcur = sp;while (pcur){SetPos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->next;}
}//打印食物
void PrintFood(SnakeInfo* sp)
{//设置食物的位置int x, y;again:x = 2 * (rand() % 28 + 1);y = rand() % 23 + 1;//不能和蛇的身体重叠SnakeNode* pcur = sp->snake_head;while (pcur){if (pcur->x == x && pcur->y == y){goto again;}pcur = pcur->next;}SnakeNode* newfood = CreatSnakeNewNode();sp->food = newfood;sp->food->x = x;sp->food->y = y;sp->food->next = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);
}

这里需要注意食物的创建,为了使食物随机出现,我们可以使用随机数的知识来创建,我们只需要使用一次随机数的种子,所以我在主函数写了下面一行代码:

//设置随机数种子
srand((unsigned int)time(NULL));

然后就是随机数的判断,不能与蛇身重合,不能超过墙体!!!

游戏的运行

我们需要从玩家的键盘里获取玩家按了什么键,然后做出相关操作,这时候我们需要使用到另外一个API接口函数:

GetAsyncKeyState(VK)
GetAsyncKeyState 的返回值是short类型,在上⼀次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

这时候我们可以写一个宏来便于判断什么键被按过:

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

首先我们要从键盘上进行获取哪些键被按过,然后进行修改游戏参数,由于蛇是由移动速度的,这时候我们可以通过Sleep来调整需要休眠几秒,以此来达到速度的效果。

蛇的移动也是由讲究的,当蛇正在向上移动,玩家按了向下的键是不可以让蛇进行向下移动,同理,向左向右和向下移动也是如此。
如果玩家按了加速键和减速键,我们也要对加速的上限和减速的下限进行限制,不可能一直加速,因为睡眠时间不能为负数,这里大家按自己的喜好进行调整即可。
游戏在进行的时候,就是蛇没有死亡游戏才能进行,所以这里用个da while循环,如果玩家主动退出游戏或者蛇死亡就会跳出游戏,循环的条件就是我们上面结构体中蛇的状态是不是正常~~

//游戏运行
void GameRun(SnakeInfo* sp)
{do{SetPos(64, 8);printf("总得分:%2d   食物分数:%2d", sp->total_score, sp->food_score);if (KEY_PRESS(VK_UP) && sp->snake_diretion != DOWN){//上移sp->snake_diretion = UP;}else if (KEY_PRESS(VK_DOWN) && sp->snake_diretion != UP){//下移sp->snake_diretion = DOWN;}else if(KEY_PRESS(VK_RIGHT) && sp->snake_diretion != LEFT){//右移sp->snake_diretion = RIGHT;}else if (KEY_PRESS(VK_LEFT) && sp->snake_diretion != RIGHT){//左移sp->snake_diretion = LEFT;}else if (KEY_PRESS(VK_SPACE)){//暂停Stop();}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏sp->snake_state = END_NORMAL;}else if (KEY_PRESS(VK_F3)){//加速//每次加速二十//可以加速到100msif (sp->snake_speed > 100){sp->snake_speed -= 20;sp->food_score += 2;}}else if (KEY_PRESS(VK_F4)){//减速//每次减速二十//可以减速到280msif (sp->snake_speed < 280){sp->snake_speed += 20;sp->food_score -= 2;}}//睡眠时间Sleep(sp->snake_speed);//蛇的移动SnakeMove(sp);} while (sp->snake_state == OK);//打印死亡或者结束信息if (sp->snake_state == END_NORMAL){SetPos(30, 10);printf("您退出了游戏!!!");}else if (sp->snake_state == KILL_BY_WALL){SetPos(30, 10);printf("您的蛇被墙撞到了!!!");}else{SetPos(30, 10);printf("您的蛇撞到了自己!!!");}
}

游戏暂停

游戏暂停很简单,我们写一个死循环的睡眠,当玩家再次按下空格键,就跳出循环。

//暂停
void Stop()
{while (1){Sleep(200);//再次按下空格跳出睡眠if (KEY_PRESS(VK_SPACE)){break;}}
}

蛇的移动

这里的蛇每次移动都是走一步,所以y的坐标每次改变1,x就是每次改变2

这里为了方便,我就直接创建一个新结点进行头插,如果没有吃到食物,最后一个节点就打印成空格(一定要打印空格,因为上一步的时候就打印好蛇的最后一个节点,所以不打印空格,就无法覆盖上一次的节点圆形,造成视觉影响)然后释放空间。如果吃到食物,就直接打印,所有节点保留,我这里设计的是每吃到一个食物蛇就会增加一个节点的长度。
在进行判断是否吃到食物的之前,我们需要对新结点的坐标进行赋值,大家要注意x,y的赋值,别搞错了~~

//蛇的移动
void SnakeMove(SnakeInfo* sp)
{//头插新节点SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));newnode->next = sp->snake_head;sp->snake_head = newnode;switch (sp->snake_diretion){case UP:sp->snake_head->y = sp->snake_head->next->y - 1;sp->snake_head->x = sp->snake_head->next->x;break;case DOWN:sp->snake_head->y = sp->snake_head->next->y + 1;sp->snake_head->x = sp->snake_head->next->x;break;case LEFT:sp->snake_head->x = sp->snake_head->next->x - 2;//注意x轴sp->snake_head->y = sp->snake_head->next->y;break;case RIGHT:sp->snake_head->x = sp->snake_head->next->x + 2;sp->snake_head->y = sp->snake_head->next->y;break;}//判断新状态if (EatFood(sp)){PrintSnake(sp->snake_head);sp->total_score += sp->food_score;//释放食物的节点free(sp->food);//打印新的食物PrintFood(sp);}else {NoFood(sp);kill_by_wall(sp);kill_by_itself(sp);}
}

头插和赋值完后我们就来判断是否吃到食物~~当食物的坐标和蛇头的坐标是一样的时候,我们需要释放食物的空间,然后打印新的食物出来。

//吃到食物
//蛇身体增加一节
int EatFood(SnakeInfo* sp)
{if ((sp->food->x == sp->snake_head->x) && (sp->food->y == sp->snake_head->y)){//吃到返回return 1;}//没有吃到返回0return 0;
}

如果没有吃到食物,我们先打印蛇身的信息,一定要注意最后一个节点打印成空格,然后再释放掉最后一个节点,没有吃到食物,蛇身长度不增加,然后再判断是否撞墙或者撞到自己~~

//没有吃到食物
void NoFood(SnakeInfo* sp)
{SnakeNode* pcur = sp->snake_head;SnakeNode* prev = sp->snake_head;while (pcur->next){prev = pcur;SetPos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->next;}SnakeNode* del = pcur;SetPos(del->x, del->y);printf("  ");free(del);del = NULL;prev->next = NULL;
}

是否撞墙

//是否被墙撞死
void kill_by_wall(SnakeInfo* sp)
{if (sp->snake_head->x == 0 || sp->snake_head->x == 58 || sp->snake_head->y == 0 || sp->snake_head->y == 24){sp->snake_state = KILL_BY_WALL;}
}

是否撞到自己

//是否撞到自己
void kill_by_itself(SnakeInfo* sp)
{SnakeNode* pcur = sp->snake_head->next;while (pcur){if (sp->snake_head->x == pcur->x && sp->snake_head->y == pcur->y){sp->snake_state = KILL_BY_ITSELF;break;}pcur = pcur->next;}
}

如果在GameRun这个函数走出了循环,我们需要打印一下游戏结束状态的信息。

//打印死亡或者结束信息
if (sp->snake_state == END_NORMAL)
{SetPos(30, 10);printf("您退出了游戏!!!");
}
else if (sp->snake_state == KILL_BY_WALL)
{SetPos(30, 10);printf("您的蛇被墙撞到了!!!");
}
else
{SetPos(30, 10);printf("您的蛇撞到了自己!!!");
}

游戏的结束善后工作

在游戏结束的时候,我们需要释放掉我们申请的空间,避免内存泄漏~~

//游戏结束
void GameEnd(SnakeInfo* sp)
{//销毁蛇节点SnakeNode* pcur = sp->snake_head;while (pcur){SnakeNode* next = pcur->next;free(pcur);pcur = next;}sp->snake_head = NULL;//销毁食物节点free(sp->food);sp->food = NULL;
}

最后如果你觉得还想添加一个小设置,就是玩家还要不要再来一把,可以写一个do while循环~~

void test()
{int n = 0;do {system("cls");//初始化游戏//设置好界面大小//打印欢迎界面//打印按键功能界面//打印游戏界面(墙体,蛇,事物,右侧提示面板)SnakeInfo snake = { 0 };GameStart(&snake);//游戏运行GameRun(&snake);//游戏结束GameEnd(&snake);SetPos(10, 15);printf("是否再来一局?是输入非0,不需要请输入0:");scanf("%d", &n);} while (n);SetPos(0, 26);
}

如果你不想写,我们可以美化一下游戏,把程序的终止信息打印在最下面,需要设置一下光标位置,所以最后我还写了一行光标的定位代码。
就是最下面这一行的信息:

最终的代码

Sanke.h

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <stdbool.h>
#include <time.h>#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )//创建蛇的结构体
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode;//创建贪吃蛇对象的结构体
typedef struct SnakeInfo
{SnakeNode* snake_head;//蛇头的位置int total_score;//总得分int food_score;//事物分数int snake_speed;//蛇的速度SnakeNode* food;//食物的位置int snake_diretion;//蛇的方向int snake_state;//蛇的状态
}SnakeInfo;//蛇的方向
typedef enum Diretion
{UP = 1,DOWN,LEFT,RIGHT
}Dit;//蛇的状态
typedef enum SnakeState
{OK,KILL_BY_ITSELF,KILL_BY_WALL,END_NORMAL
}State;//初始化游戏
void GameStart(SnakeInfo* sp);//初始化蛇的身体
void InitSnake(SnakeNode** sp);//打印蛇的身体
void PrintSnake(SnakeNode* sp);//打印食物
void PrintFood(SnakeInfo* sp);//游戏运行
void GameRun(SnakeInfo* sp);//蛇的移动
void SnakeMove(SnakeInfo* sp);//暂停
void Stop();//吃到食物
//吃到返回 没有吃到返回0
int EatFood(SnakeInfo* sp);//没有吃到食物
void NoFood(SnakeInfo* sp);//是否被墙撞死
void kill_by_wall(SnakeInfo* sp);//是否撞到自己
void kill_by_itself(SnakeInfo* sp);//游戏结束
void GameEnd(SnakeInfo* sp);//设置光标位置
void SetPos(short x, short y);

Sanke.c

#include "Snake.h"//设置光标位置
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);
}//打印欢迎界面
void Welcome()
{SetPos(40, 12);printf("欢迎来到贪吃蛇小游戏");SetPos(42, 18);system("pause");//清理界面system("cls");//打印按键功能信息SetPos(8, 12);printf("↑  ↓  ←  → 分别控制蛇的上下左右移动, F3为加速,F4为减速, Esc 退出, Space 暂停");SetPos(35, 15);printf("加速后的食物分数会更高!!!");SetPos(40, 20);system("pause");system("cls");
}//打印墙体
void CreatWall()
{int i = 0;//上墙体for (i = 0; i < 30; i++){wprintf(L"%lc", WALL);}//下墙体SetPos(0, 24);for (i = 0; i < 30; i++){wprintf(L"%lc", WALL);}//左墙体for (i = 1; i <= 23; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右墙体for (i = 1; i <= 23; i++){SetPos(58, i);wprintf(L"%lc", WALL);}
}//打印右侧面板信息
void PrintInfo()
{SetPos(64, 8);printf("总得分:0    食物分数:10");SetPos(64, 12);printf("↑  ↓  ←  → 分别控制蛇移动");SetPos(65, 14);printf("F3为加速,F4为减速");SetPos(65, 15);printf("Esc 退出  Space 暂停");SetPos(65, 18);printf("加速后的食物分数会更高!!!");
}//创建蛇的节点
SnakeNode* CreatSnakeNewNode()
{SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));if (newnode == NULL){perror("CreatSnakeNewNode()::malloc()");}newnode->x = newnode->y = 0;newnode->next = NULL;return newnode;
}//初始化蛇的身体
void InitSnake(SnakeNode** sp)
{//五个节点if (*sp == NULL)//空链表{*sp = CreatSnakeNewNode();(*sp)->y = 4;(*sp)->x = 30;}SnakeNode* pcur = *sp;//尾插for (int i = 1; i <= 4; i++){SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));newnode->y = 4;newnode->x = 30 - 2 * i;pcur->next = newnode;pcur = pcur->next;}pcur->next = NULL;
}//打印蛇的身体
void PrintSnake(SnakeNode* sp)
{SnakeNode* pcur = sp;while (pcur){SetPos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->next;}
}//打印食物
void PrintFood(SnakeInfo* sp)
{//设置食物的位置int x, y;again:x = 2 * (rand() % 28 + 1);y = rand() % 23 + 1;//不能和蛇的身体重叠SnakeNode* pcur = sp->snake_head;while (pcur){if (pcur->x == x && pcur->y == y){goto again;}pcur = pcur->next;}SnakeNode* newfood = CreatSnakeNewNode();sp->food = newfood;sp->food->x = x;sp->food->y = y;sp->food->next = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);
}void CreatMap(SnakeInfo* sp)
{//打印墙体CreatWall();//打印右侧面板信息PrintInfo();//初始化蛇的身体sp->snake_head = NULL;InitSnake(&(sp->snake_head));//打印蛇的身体PrintSnake(sp->snake_head);//打印食物PrintFood(sp);}//初始化游戏
void GameStart(SnakeInfo* sp)
{//设置界面大小system("mode con cols=100 lines=30");system("title 贪吃蛇");//获取句柄HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//获取光标信息CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);CursorInfo.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 //打印欢迎界面Welcome();//打印游戏界面CreatMap(sp);//设置好初始游戏参数sp->food_score = 10;sp->snake_speed = 200;sp->total_score = 0;sp->snake_diretion = RIGHT;//开始向右移动sp->snake_state = OK;}//暂停
void Stop()
{while (1){Sleep(200);//再次按下空格跳出睡眠if (KEY_PRESS(VK_SPACE)){break;}}
}//吃到食物
//蛇身体增加一节
int EatFood(SnakeInfo* sp)
{if ((sp->food->x == sp->snake_head->x) && (sp->food->y == sp->snake_head->y)){//吃到返回return 1;}//没有吃到返回0return 0;
}//没有吃到食物
void NoFood(SnakeInfo* sp)
{SnakeNode* pcur = sp->snake_head;SnakeNode* prev = sp->snake_head;while (pcur->next){prev = pcur;SetPos(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->next;}SnakeNode* del = pcur;SetPos(del->x, del->y);printf("  ");free(del);del = NULL;prev->next = NULL;
}//是否被墙撞死
void kill_by_wall(SnakeInfo* sp)
{if (sp->snake_head->x == 0 || sp->snake_head->x == 58 || sp->snake_head->y == 0 || sp->snake_head->y == 24){sp->snake_state = KILL_BY_WALL;}
}//是否撞到自己
void kill_by_itself(SnakeInfo* sp)
{SnakeNode* pcur = sp->snake_head->next;while (pcur){if (sp->snake_head->x == pcur->x && sp->snake_head->y == pcur->y){sp->snake_state = KILL_BY_ITSELF;break;}pcur = pcur->next;}
}//蛇的移动
void SnakeMove(SnakeInfo* sp)
{//头插新节点SnakeNode* newnode = (SnakeNode*)malloc(sizeof(SnakeNode));newnode->next = sp->snake_head;sp->snake_head = newnode;switch (sp->snake_diretion){case UP:sp->snake_head->y = sp->snake_head->next->y - 1;sp->snake_head->x = sp->snake_head->next->x;break;case DOWN:sp->snake_head->y = sp->snake_head->next->y + 1;sp->snake_head->x = sp->snake_head->next->x;break;case LEFT:sp->snake_head->x = sp->snake_head->next->x - 2;//注意x轴sp->snake_head->y = sp->snake_head->next->y;break;case RIGHT:sp->snake_head->x = sp->snake_head->next->x + 2;sp->snake_head->y = sp->snake_head->next->y;break;}//判断新状态if (EatFood(sp)){PrintSnake(sp->snake_head);sp->total_score += sp->food_score;//释放食物的节点free(sp->food);//打印新的食物PrintFood(sp);}else {NoFood(sp);kill_by_wall(sp);kill_by_itself(sp);}
}//游戏运行
void GameRun(SnakeInfo* sp)
{do{SetPos(64, 8);printf("总得分:%2d   食物分数:%2d", sp->total_score, sp->food_score);if (KEY_PRESS(VK_UP) && sp->snake_diretion != DOWN){//上移sp->snake_diretion = UP;}else if (KEY_PRESS(VK_DOWN) && sp->snake_diretion != UP){//下移sp->snake_diretion = DOWN;}else if(KEY_PRESS(VK_RIGHT) && sp->snake_diretion != LEFT){//右移sp->snake_diretion = RIGHT;}else if (KEY_PRESS(VK_LEFT) && sp->snake_diretion != RIGHT){//左移sp->snake_diretion = LEFT;}else if (KEY_PRESS(VK_SPACE)){//暂停Stop();}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏sp->snake_state = END_NORMAL;}else if (KEY_PRESS(VK_F3)){//加速//每次加速二十//可以加速到100msif (sp->snake_speed > 100){sp->snake_speed -= 20;sp->food_score += 2;}}else if (KEY_PRESS(VK_F4)){//减速//每次减速二十//可以减速到300msif (sp->snake_speed < 280){sp->snake_speed += 20;sp->food_score -= 2;}}//睡眠时间Sleep(sp->snake_speed);//蛇的移动SnakeMove(sp);} while (sp->snake_state == OK);//打印死亡或者结束信息if (sp->snake_state == END_NORMAL){SetPos(30, 10);printf("您退出了游戏!!!");}else if (sp->snake_state == KILL_BY_WALL){SetPos(30, 10);printf("您的蛇被墙撞到了!!!");}else{SetPos(30, 10);printf("您的蛇撞到了自己!!!");}
}//游戏结束
void GameEnd(SnakeInfo* sp)
{//销毁蛇节点SnakeNode* pcur = sp->snake_head;while (pcur){SnakeNode* next = pcur->next;free(pcur);pcur = next;}sp->snake_head = NULL;//销毁食物节点free(sp->food);sp->food = NULL;
}

test.c

#include <locale.h>
#include "Snake.h"void test()
{int n = 0;do {system("cls");//初始化游戏//设置好界面大小//打印欢迎界面//打印按键功能界面//打印游戏界面(墙体,蛇,事物,右侧提示面板)SnakeInfo snake = { 0 };GameStart(&snake);//游戏运行GameRun(&snake);//游戏结束GameEnd(&snake);SetPos(10, 15);printf("是否再来一局?是输入非0,不需要请输入0:");scanf("%d", &n);} while (n);SetPos(0, 26);
}int main()
{//配置好本地化环境,支持宽字符输出setlocale(LC_ALL, "");//设置随机数种子srand((unsigned int)time(NULL));//游戏测试test();return 0;
}

C语言进阶到此完结撒花!!!
如果有机会学习C++我会回来继续更新的!!!
祝老铁们每天快乐!!!知识倍增!!!我们顶峰相见!!!

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

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

相关文章

探索C语言数据结构:利用顺序表完成通讯录的实现

在好久之前我就已经学习过顺序表&#xff0c;但是在前几天再次温习顺序表的时候&#xff0c;我惊奇的发现顺序编表可以完成我们日常使用的通讯录的功能&#xff0c;那么今天就来好好通过博客总结一下通讯录如何完成吧。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留…

OpenHarmony其他工具类—lua

简介 Lua是一种功能强大、高效、轻量级、可嵌入的脚本语言。 支持过程编程、面向对象编程、函数编程、数据驱动编程和数据描述。 下载安装 直接在OpenHarmony-SIG仓中搜索lua并下载。 使用说明 以OpenHarmony 3.1 Beta的rk3568版本为例 将下载的lua库代码存在以下路径&#…

Java Web3-2 - tomcat

https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Tomcat_架构解析.md https://zhuanlan.zhihu.com/p/40249834 早期&#xff0c;web技术主要用于浏览静态页面 时间发展&#xff0c;用户已经不满足于仅浏览静态页面。用户需要一些交互操作&#xff0c;获取…

服务器有哪些特性?

服务器是计算机的一种&#xff0c;但是和普通的计算机是不同的&#xff0c;服务器比普通计算机的运行速度更快、负载能力更高&#xff0c;可以在网络中为其它客户机或是大型设备提供计算或者是应用服务&#xff0c;服务器有着高速的CPU运算能力、能够进行长时间的运行有着更好的…

STM32G431RBT6之时钟树配置与生成工程

默认大家都下载了蓝桥杯嵌入式资源包了哈. 首先,打开cubumx,修改RCC与SYS. 打开并观察原理图,发现晶振是24Mhz. 第一步,打开Clock Configuration. 第二步,修改晶振为原理图相对应的24Mhz. 第三步,切换到HSE. 第四步,切换到PLLCLK. 第五步,设置HCLK为80Mhz(15届真题要求为8…

洛谷P1057 [NOIP2008 普及组] 传球游戏

#include<iostream> using namespace std; int n;// n个人传球游戏 默认开始球在编号为1的位置 int m;// 传递m次球 int main(){cin>>n>>m;// 动态转方程&#xff1a;// 球传递到编号为k人的手中// 种类总数 传递到k-1编号种类总数 传递到k1编号种类总数//…

wsl2 Ubuntu子系统内存只有一半的解决办法

物理机的内存是64G&#xff0c;在wsl2安装完Ubuntu20.04后&#xff0c;输入命令&#xff1a; free -g 发现只有32G&#xff0c;原因是默认只能获得物理机一半的内存&#xff1a; WSL 中的高级设置配置 | Microsoft Learn 因此可手动修改为与物理机同等大小&#xff1a; 1&a…

后端开发面经系列 -- 哔哩哔哩C++后端一面

B站C后端开发一面 公众号&#xff1a;阿Q技术站 来源&#xff1a;https://www.nowcoder.com/discuss/550638808786661376 1、MySQL默认16KB的页大小会不会有什么问题&#xff1f;为什么使用16KB作为页面的默认大小&#xff1f; MySQL默认的页大小&#xff08;或称为数据页、表…

【数据库】数据库为什么比电子表格快

为了更深入地了解SQL数据库用于加速查询的特定算法和机制&#xff0c;让我们关注索引和查询优化。与在CSV等平面文件中进行线性搜索相比&#xff0c;这些基本方面使数据库中的查询速度更快。 1。索引 数据库中的索引有点类似于书籍中的索引&#xff0c;它允许你快速定位特定的…

再拓信创版图-Smartbi Insight V11与东方国信CirroData数据库完成兼容适配认证

近日&#xff0c;思迈特商业智能与数据分析软件 [简称&#xff1a;Smartbi Insight] V11与北京东方国信科技股份有限公司 &#xff08;以下简称东方国信&#xff09;CirroData-OLAP分布式数据库V2.14.1完成兼容性测试。经双方严格测试&#xff0c;两款产品能够达到通用兼容性要…

i18next serverSideTranslations 的使用

i18next 是一个流行的国际化&#xff08;i18n&#xff09;库&#xff0c;它可以帮助应用程序中实现多语言支持。next-i18next 是 Next.js 中与 i18next 集成的官方插件&#xff0c;它提供了一种简单的方式来在 Next.js 应用程序中实现国际化。 serverSideTranslations 是 next…

PyTorch中的常见乘法运算(*、@、Mul、Matmul)

哈达玛积&#xff1a;torch.mul()、torch.dot()、* 两个相同尺寸的张量相乘&#xff0c;然后对应元素的相乘就是哈达玛积&#xff0c;这种乘法要求参与运算的矩阵唯独相同&#xff0c;运算结果还是一个相同维度的矩阵。在这个运算中&#xff0c;torch.mul()和*以及torch.dot()…

Cronjob提权

参考&#xff1a; https://redpomelo.xyz/archives/1699953656909 前言 提权为该靶机的精髓&#xff0c;Cronjob通常以root特权运行。如果我们可以成功篡改cronjob中 定义的任何脚本或二进制文件&#xff0c;那么我们可以以root特权执行任意 代码。 什么是Cronjob&#xf…

JS - 分支结构、循环结构

关于JavaScript中的分支结构和循环结构&#xff0c;其实和其他编程语言区别也不是很大&#xff0c;只是js对这两种结构进行了相应的扩充&#xff0c;当然本质上并没有变化&#xff0c;本篇就是一篇记录博主在学习前端路上的总结和敲过的demo&#xff0c;实际上水份很大&#xf…

Jmeter 性能-死锁问题定位+分析

1、环境搭建 ①准备脚本&#xff0c;执行压测 ②用Jstack 打印日志 jstack 112759 >dead.log ③下载日志到本地 sz dead.log 2、问题定位 ①打开dead.log&#xff0c;搜索deadlock ②查看死锁的线程 ③查看死锁位置 3、问题分析 ①下载死锁的类文件 Sz CaseControlle…

使用iMazing对iPhone有影响吗 为什么iPhone都会装iMazing来管理 苹果手机imazing安装

随着科技的迅速发展&#xff0c;智能手机已成为我们日常生活中不可或缺的一部分&#xff0c;iPhone手机占据了智能手机市场的大部分&#xff0c;也有着庞大的用户基础。随着时代的发展&#xff0c;用户对于更高级的设备管理工具的需求也随之增加。iMazing作为一款强大的设备管理…

美业连锁门店收银系统源码-如何查看收款门店对应的加盟商?

美业管理系统源码 博弈美业SaaS系统 连锁多门店美业收银系统源码 多门店管理 / 会员管理 / 预约管理 / 排班管理 / 商品管理 / 促销活动 PC管理后台、手机APP、iPad APP、微信小程序 第一步&#xff1a; 登录pc管理后端 第二步&#xff1a; 进入企业组织管理-门店管理&a…

pandas/python 一个实战小案例

上次写坦克游戏的时候&#xff0c;接触了一点pandas&#xff0c;当时只是简单了解了一下如何遍历行和列并获取值来替换图片&#xff0c;想更多了解pandas。正好有一些数据需要筛选&#xff0c;试试能不能用通过代码实现。虽然总的来说不复杂&#xff0c;但由于原始数据在命名、…

ai写作强大,ai写作哪个软件最好用?

在当今数字化时代&#xff0c;ai技术的发展正以惊人的速度改变着我们的生活和工作方式。其中&#xff0c;ai写作作为一项令人瞩目的创新&#xff0c;展示了强大的文本生成能力。然而&#xff0c;随着各种ai写作软件的涌现&#xff0c;人们不禁困惑&#xff1a;哪个软件才是最好…

js删除对象中值为null的属性

需求&#xff1a;在做编辑操作的时候&#xff0c;后端不需要值为null的数据&#xff0c;所以默认把编辑中值为null的数据传给他会编辑失败&#xff0c;所以前端做个筛选就行了 let obj {id: 1,name: "翠花",sex: 18,hobby: null,age: null,};// 使用Object.entries(…