手把手教你实现贪吃蛇

前言

       在实现贪吃蛇前,我们需要熟练地掌握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;获取…

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…

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

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

Cronjob提权

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

SQLite数据库中JSON 函数和运算符(二十七)

返回&#xff1a;SQLite—系列文章目录 上一篇:维护SQLite的私有分支&#xff08;二十六&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 ​1. 概述 默认情况下&#xff0c;SQLite 支持 29 个函数和 2 个运算符 处理 JSON 值。还有两个表值函数可用于分解 JSON 字…

大模型应用开发基础

AGI 时代&#xff0c;AI 无处不在&#xff0c;形成新的社会分层&#xff1a; AI 使用者&#xff0c;使用别人开发的 AI 产品AI 产品开发者&#xff0c;设计和开发 AI 产品基础模型相关&#xff0c;训练基础大模型&#xff0c;或为大模型提供基础设施 越向下层&#xff0c;重要…

MEMENTO(备忘录)-- 对象行为型模式

意图&#xff1a; 在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 别名&#xff1a; Token 动机&#xff1a; 有时必要记录一个对象的内部状态。 适用性&#xff1a; a. 必…

【云计算】云数据中心网络(五):对等连接

《云网络》系列&#xff0c;共包含以下文章&#xff1a; 云网络是未来的网络基础设施云网络产品体系概述云数据中心网络&#xff08;一&#xff09;&#xff1a;VPC云数据中心网络&#xff08;二&#xff09;&#xff1a;弹性公网 IP云数据中心网络&#xff08;三&#xff09;…

跟TED演讲学英文:How AI could save (not destroy) education by Sal Khan

How AI could save (not destroy) education Link: How AI could save (not destroy) education Speaker: Sal Khan Date: April 2023 文章目录 How AI could save (not destroy) educationIntroductionVocabularyTranscriptSummary后记 Introduction Sal Khan, the founder…

遥瞻智慧:排水系统远程监控的卓越解决方案

遥瞻智慧&#xff1a;排水系统远程监控的卓越解决方案 在城市脉络的深层肌理中&#xff0c;排水系统犹如一条条隐秘的生命线&#xff0c;默默承载着城市的呼吸与律动。然而&#xff0c;如何以科技之眼&#xff0c;赋予这些无形网络以实时感知、精准调控的能力&#xff0c;使之…

网络管理实验三、SNMP协议工作原理验证与分析

1 实验概括 实验目的&#xff1a; 学习捕获SNMP报文&#xff0c;通过报文分析理解SNMP协议的工作过程。 实验内容&#xff1a; 1&#xff09; 使用snmputilg发送SNMP数据包; 使用wireshark抓包&#xff1b;使用netstat –an查看代理站TCP/UDP连接表&#xff1b; 2&#xff09;…