手把手教你实现贪吃蛇

news/2024/9/24 5:25:23/

前言

       在实现贪吃蛇前,我们需要熟练地掌握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.ppmy.cn/news/1426648.html

相关文章

怎么看自己是不是公网IP?

当我们需要进行网络连接或者网络配置的时候&#xff0c;经常会遇到需要知道自己是否拥有公网IP的情况。公网IP是全球唯一的IP地址&#xff0c;在互联网上可直接访问和被访问&#xff0c;而私有IP则是在本地网络中使用&#xff0c;无法从互联网上直接访问。我们将介绍如何查看自…

springboot+java照相馆预约管理系统ssm

框架&#xff1a;ssm/springboot都有 jdk版本&#xff1a;1.8 及以上 ide工具&#xff1a;IDEA 或者eclipse 数据库: mysql 编程语言: java 前端&#xff1a;layuibootstrapjsp 详细技术&#xff1a;HTMLCSSJSjspspringmvcmybatisMYSQLMAVENtomcat 开发工具 IntelliJ IDEA: 一…

【SpringBoot】springboot的启动初步理解

springboot的启动初步理解 我们会发现开发一个Spring Boot&#xff0c;都会有一个注解SpringBootApplication和一个类定义SpringApplication.run&#xff0c;点击源码可以查看到如下代码&#xff1a; Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Document…

Modelsim与Verilog入门

0.什么是Modelsim&#xff1f; Modelsim是一个支持多语言的仿真环境&#xff0c;比如我知道的Verilog和VHDL语言都可以在里边使用&#xff0c;这俩都是硬件描述语言&#xff1b; 即就是个软件&#xff0c;你可以用Verilog语言来写代码&#xff0c;然后编译&#xff0c;仿真出…

MATLAB设置变量

您可以通过简单的方式分配变量。例如&#xff0c; 示例 x 3 %定义x并用值初始化它 MATLAB将执行上述语句并返回以下结果- x 3 它创建一个名为x的1乘1矩阵&#xff0c;并将值3存储在其元素中。再举一个实例&#xff0c; 示例 x sqrt(16) %定义x并用表达式初始化它 MATLAB将…

Opencv | 基于ndarray的基本操作

这里写目录标题 一. Opencv 基于ndarray的基本操作1. 浅拷贝2. np.copy ( ) 深拷贝3. 堆叠3.1 np.vstack ( ) 垂直方向堆叠3.2 np.hstack ( ) 水平方向堆叠 4. numpy创建图像5 np.transpose ( ) 更改维度顺序6. cv.resize ( ) 放大缩小7. np.clip ( ) 一. Opencv 基于ndarray的…

Pandas介绍与Series创建

1.Pandas介绍 Pandas 是基于 NumPy 的一种工具&#xff0c;该工具是为解决数据分析任务而创建的&#xff0c;Pandas 提供了大量能使我们快速便捷地处理数据的功能 Pandas 与出色的 Jupyter 工具包和其他库相结合&#xff0c;Python 中用于进行数据分析的环境在性能、生产率和协…

Linux网络编程--网络传输

Linux网络编程--网络传输 Linux网络编程TCP/IP网络模型网络通信的过程局域网通信跨网络通信&#xff1a;问题总结&#xff1a; Linux网络编程 TCP/IP网络模型 发送方&#xff08;包装&#xff09;&#xff1a; 应用层&#xff1a;HTTP HTTPS SSH等 —> 包含数据&#xff0…