【贪吃蛇:C语言实现】
文章目录
- 前言
- 1.了解Win32API相关知识
- 1.1什么是Win32API
- 1.2设置控制台的大小、名称
- 1.3控制台上的光标
- 1.4 GetStdHandle(获得控制台信息)
- 1.5 SetConsoleCursorPosition(设置光标位置)
- 1.6 GetConsoleCursorInfo(获得光标信息)
- 1.7 SetConsoleCursorInfo(设置光标信息)
- 1.8 GetAsyncKeyState(获取按键信息)
- 2.游戏设计与分析
- 2.1地图设计
- 2.2数据结构的设计
- 2.2.1蛇身的设计
- 2.2.2蛇的方向
- 2.2.3游戏的状态
- 2.2.4管理整条蛇
- 2.3.游戏流程
- 3. 功能的具体实现
- 游戏开始前
- 3.1 初始化蛇
- 3.2 创建食物
- 游戏运行
- 3.3 检测按键
- 3.4 蛇移动
- 3.5 判断节点是否是食物
- 3.6 吃食物
- 3.7 不吃食物
- 3.8 撞墙
- 3.9 撞到自己
- 3.10 游戏结束
- 3.11 main函数
- 4.参考代码
- 4.1 main
- 4.2 Snake.h
- 4.3 Snake.c

前言
彻底学习完C语言之后,为巩固所学,我们来实现一个贪吃蛇小游戏。
该游戏涉及到的C语言的知识有:函数、枚举、结构体、动态内存管理、预处理指令。
另外,还包含数据结构(链表)以及Win32API相关知识。
先看一下游戏效果吧
目前该游戏还有很多可以改进的地方,由于蛇的移动使用的是Sleep函数,看起来有点卡顿,而且按键会有一点延迟。
代码仓库链接
1.了解Win32API相关知识
1.1什么是Win32API
Win32 API也就是Microsoft Windows32位平台的应用程序编程接口。调用各种接口,可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。
1.2设置控制台的大小、名称
设置大小和名称的命令为:
- mode con cols=列 lines=行
- tiele 名称
如果想在C语言中设置窗口,需要使用system函数。
1.3控制台上的光标
COORD 是Windows API中定义的⼀个结构体,表示⼀个字符在控制台屏幕上的坐标,坐标系(0,0)的原点位于缓冲区的左侧顶部的单元格。使用需要window.h的头文件
COORD类型的声明:
typedef struct _COORD {SHORT X;//横坐标SHORT Y;//纵坐标
} COOR, *PCOORD;
1.4 GetStdHandle(获得控制台信息)
GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使用这个句柄可以操作设备(控制台)。
HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle);
该函数需要一个参数,参数如下:
1.5 SetConsoleCursorPosition(设置光标位置)
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput, /要设置哪个控制台COORD pos /将光标设置的位置
);
通过上述三个功能,实现设置光标位置
1.6 GetConsoleCursorInfo(获得光标信息)
GetConsoleCursorInfo:检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。然后将获得的信息放在一个结构体中
BOOL WINAPI GetConsoleCursorInfo(_In_ HANDLE hConsoleOutput,_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
_ Out_ PCONSOLE_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。
1.7 SetConsoleCursorInfo(设置光标信息)
SetConsoleCursorInfo:设置指定控制台屏幕缓冲区的光标的大小和可见性。
其参数和get方法相同
BOOL WINAPI SetConsoleCursorInfo(HANDLE hConsoleOutput,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
设置前:
设置后:
隐藏光标:
1.8 GetAsyncKeyState(获取按键信息)
GetAsyncKeyState:获取按键情况,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 )
#include<Windows.h>
int main()
{while (1){if (KEY_PRESS(0x30))printf("0\n");else if (KEY_PRESS(0x31))printf("1\n");else if (KEY_PRESS(0x32))printf("2\n");else if (KEY_PRESS(0x33))printf("3\n");else if (KEY_PRESS(0x34))printf("4\n");else if (KEY_PRESS(0x35))printf("5\n");else if (KEY_PRESS(0x36))printf("6\n");else if (KEY_PRESS(0x37))printf("7\n");}return 0;
}
2.游戏设计与分析
2.1地图设计
由于我打印墙体和蛇想使用 □,但是它是宽字符,该如何打印呢?
C语言规定:宽字符的类型wchar_t 和宽字符的输⼊和输出函数。
头文件为:<locale.h>
宽字符与普通字符对比:
由于墙体使用的是宽字符,所以在打印时需要注意!
以下函数实现打印墙体的功能:
#define WALL L'□'
void DrawMap()
{SetPos(0, 0);//上for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL);//左、右for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);SetPos(56, i);wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL);
}
再打印地图之前,我们需要设置窗口的大小,并且给玩家一些引导(Win32API的使用已在上面讲过)
//窗口相关设置
void ScreenPrepare()
{//设置窗体大小system("mode con cols=100 lines=40");system("title 贪吃蛇小游戏");//隐藏光标HideCurSor();//打印欢迎语Welcome();//打印地图DrawMap();//显示提示信息HelpInfo();
}
具体函数的实现我们会在后面给出,先看一下效果吧~
2.2数据结构的设计
2.2.1蛇身的设计
在游戏运行的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变长⼀节,如果我们使用链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:
//蛇节点的定义
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;//指向下一个节点
}SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针
2.2.2蛇的方向
蛇的方向可以一一列举,使用枚举
//蛇的方向
//上、下、做、左、右
enum Direction
{UP = 1,DOWN,LEFT,RIGHT
};
2.2.3游戏的状态
游戏状态,可以⼀⼀列举,使用枚举
//游戏的状态
enum GameState
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END//结束
};
2.2.4管理整条蛇
我们再封装⼀个Snake的结构体来维护整条贪吃蛇。
//整条蛇
typedef struct Snake
{pSnakeNode pSnake; //指向整条蛇的指针pSnakeNode PFood; //指向食物的指针enum Direction Dire; //蛇的方向enum GameState State; //蛇的状态int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快int Score;//游戏得分int FoodScore;//一个食物的分数
}Snake,* pSnake;
2.3.游戏流程
3. 功能的具体实现
游戏开始前
3.1 初始化蛇
游戏开始,蛇的长度默认是3。
- 创建节点,头插法依次连接
- 节点的纵坐标相同,横坐标依次增加2,产生串的效果
- 蛇的方向默认为右
- 打印蛇,并且为蛇头设置颜色
此处颜色设置我们使用Win32API提供的函数:
BOOL WINAPI SetConsoleTextAttribute(_In_ HANDLE hConsoleOutput,//当前设备句柄_In_ WORD wAttributes//颜色
);
//设置颜色
void SetColor(int HeadColor)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HeadColor);
}//打印蛇
void PrintSnake(pSnake ps)
{pSnakeNode cur = ps->pSnake;int flag = 1;while (cur){if (flag == 1){//为蛇头设置颜色SetColor(12);}SetPos(cur->x, cur->y);//设置每个节点的位置wprintf(L"%lc", BODY);cur = cur->next;flag = 0;SetColor(10);}
}//初始化蛇
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;//蛇的长度开始为3for (i = 0; i < 3; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc");return;}cur->next = NULL;cur->x = POS_X + i * 2; //节点横坐标依次增加2cur->y = POS_Y;//头插法将节点相连if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇PrintSnake(ps);//初始化蛇的其它信息ps->Dire = RIGHT;ps->FoodScore = 10;ps->SleepTime = 200;ps->State = OK;ps->Score = 0;
}
3.2 创建食物
食物要随机生成,有以下几个注意事项:
- 食物应该在墙体内
- 食物不可与蛇身重叠
- 由于我们蛇的打印使用的是宽字符,所以食物的坐标应该在2的倍数处
//创建食物
void CreatFood(pSnake ps)
{int x = 0;int y = 0;//食物坐标应该在墙体内
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//食物坐标不与蛇重叠pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//根据坐标创建食物pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreatFood():malloc()");return;}//打印食物pFood->x = x;pFood->y = y;SetColor(14);SetPos(pFood->x, pFood->y);wprintf(L"%lc", FOOD);SetColor(10);ps->pFood = pFood;
}
游戏运行
3.3 检测按键
玩家按上下左右键控制蛇的移动,在检测时需要注意:
- 玩家按的键若与蛇的方向相反,则不做响应;玩家按键符合,则修改蛇的方向
- 玩家按F1或F2修改蛇的移动速度;蛇的速度应大于0,食物的分数最低为1分
- 按键的检测使用Win32API提供的功能:GetAsyncKeyState
//玩游戏
void GameRun(pSnake ps)
{do{//显示分数SetColor(12);SetPos(65, 12);printf("目前得分:%-5d",ps->Score);SetColor(10);SetPos(65, 13);printf("每个食物:%2d分",ps->FoodScore);//检测按键if (KEY_PRESS(VK_UP) && ps->Dire != DOWN){//按上键,且蛇的方向不能向下ps->Dire = UP;}else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP){//按下键,且蛇的方向不能向上ps->Dire = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT){//按左键,且蛇的方向不能向右ps->Dire = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT){//按右键,且蛇的方向不能向左ps->Dire = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){//按ESC,退出游戏ps->State = END;}else if (KEY_PRESS(VK_SPACE)){//按空格,暂停pause();}else if (KEY_PRESS(VK_F1)){//按F1加速,即睡眠时间变短//休眠时间不能是负数,最快就是休眠30msif (ps->SleepTime >= 50){ps->SleepTime -= 20;//速度变快,食物的分数变高ps->FoodScore += 2;}}else if (KEY_PRESS(VK_F2)){//F2减速,睡眠时间变长//食物的分数不能减到负数,最多减为1分if (ps->FoodScore >= 3){ps->SleepTime += 20;ps->FoodScore -= 2;}}//按照蛇的睡眠时间,真正实现休眠Sleep(ps->SleepTime);//休眠后,蛇要移动SnakeMove(ps);} while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键
}
3.4 蛇移动
蛇的移动就是根据蛇的方向,产生节点,判断节点是不是食物。
- 所产生的节点的位置根据蛇的方向而定
- 节点是食物,吃掉食物,蛇身变长
- 节点不是食物,也吃掉食物,但长度不变
//移动蛇
void SnakeMove(pSnake ps)
{//先产生节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove():malloc()");return;}//根据蛇的方向,设定节点的位置switch (ps->Dire){case UP://蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN://蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT://蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT://蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//判断下一个节点是否是食物if (JudgeNext(ps,pNext)){//是食物,吃掉,长度增加EatFood(ps, pNext);}else{//不是食物,吃掉,长度不增加NoFood(ps, pNext);}//未完//撞墙//撞自己
}
3.5 判断节点是否是食物
将所产生的节点与食物的节点对比即可
//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext)
{return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y));
}
3.6 吃食物
吃掉食物很简单,将所产生的节点与蛇想连;连接后得分增加,并释放所产生的节点,再次产生食物。
//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext)
{//吃掉食物,头插法将节点插入pNext->next = ps;ps = pNext;//打印蛇PrintSnake(ps_->snake);//加分PrintSnake(ps->pSnake);//释放食物节点free(ps->pFood);//再次创建食物CreatFood(ps);
}
3.7 不吃食物
不吃食物需要将产生的节点与蛇相连,然后删除蛇尾(将蛇尾打印尾空格,并释放蛇尾节点)
//不吃食物
void NoFood(pSnake ps, pSnakeNode pNext)
{//头插法连接pNext->next = ps->pSnake;ps->pSnake = pNext;//删除蛇尾pSnakeNode cur = ps->pSnake;//找到蛇尾的前一个节点while (cur->next->next){cur = cur->next;}pSnakeNode del = cur->next;//将蛇尾打印尾空格SetPos(del->x, del->y);printf(" ");//释放蛇尾节点free(del);cur->next = NULL;//打印蛇PrintSnake(ps);
}
3.8 撞墙
若蛇头坐标与墙体坐标重合,则说明撞墙了。
//撞墙
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 26){ps->State = KillByWall;}
}
3.9 撞到自己
若蛇头坐标与蛇身节点的坐标重合,则说明撞到自己了。
//撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->State = KILL_BY_SELF;}cur = cur->next;}
}
3.10 游戏结束
给出游戏结束的原因,释放蛇身节点。
//游戏结束
void GameEnd(pSnake ps)
{SetPos(20, 13);SetColor(12);switch (ps->State){case END:printf("您结束了游戏");break;case KILL_BY_SELF:printf("很遗憾!您撞到了自己");break;case KILL_BY_WALL:printf("很遗憾!您撞墙了");break;}pSnakeNode cur = ps->pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps = NULL;SetColor(10);
}
3.11 main函数
让玩家选择
int main()
{srand((unsigned int)time(NULL));setlocale(LC_ALL, "");//适应本地中文环境int ch = 0;do{Snake snake = { 0 };//创建贪吃蛇//游戏开始前的初始化GameStart(&snake);玩游戏GameRun(&snake);游戏结束,善后工作GameEnd(&snake);SetPos(20, 15);printf("还要再来一局吗?(Y/N)");ch = getchar();getchar();//吸收换行} while (ch == 'Y' || ch == 'y');SetPos(0, 30);return 0;
}
4.参考代码
4.1 main
#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"int main()
{srand((unsigned int)time(NULL));setlocale(LC_ALL, "");//适应本地中文环境int ch = 0;do{Snake snake = { 0 };//创建贪吃蛇//游戏开始前的初始化GameStart(&snake);玩游戏GameRun(&snake);游戏结束,善后工作GameEnd(&snake);SetPos(20, 15);printf("还要再来一局吗?(Y/N)");ch = getchar();getchar();//吸收换行} while (ch == 'Y' || ch == 'y');SetPos(0, 30);return 0;
}
4.2 Snake.h
#define _CRT_SECURE_NO_WARNINGS 1#include<locale.h>
#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)#define WALL L'□'//墙体
#define BODY L'●'//蛇身
#define FOOD L'★'//食物//蛇初始位置
#define POS_X 24
#define POS_Y 12//蛇的方向
//上、下、左、右
enum Direction
{UP = 1,DOWN,LEFT,RIGHT
};//游戏的状态
enum GameState
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END//结束
};//蛇节点的定义
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;//指向下一个节点
}SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针//整条蛇
typedef struct Snake
{pSnakeNode pSnake; //指向整条蛇的指针pSnakeNode pFood; //指向食物的指针enum Direction Dire; //蛇的方向enum GameState State; //蛇的状态int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快int Score;//游戏得分int FoodScore;//一个食物的分数
}Snake,* pSnake;//设置光标位置
void SetPos(int x, int y);//设置颜色
void SetColor(int HeadColor);//游戏准备
void GameStart(pSnake psnake);//初始化蛇
void InitSnake(pSnake psnake);//创建食物
void CreatFood(pSnake ps);//打印蛇
void PrintSnake(pSnake ps);//玩游戏
void GameRun(pSnake ps);//移动蛇
void SnakeMove(pSnake ps);//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext);//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext);//不是食物
void NoFood(pSnake ps, pSnakeNode pNext);//撞墙
void KillByWall(pSnake ps);//撞到自己
void KillBySelf(pSnake ps);//游戏结束
void GameEnd(pSnake ps);
4.3 Snake.c
#define _CRT_SECURE_NO_WARNINGS 1#include"Snake.h"//隐藏光标
void HideCurSor()
{CONSOLE_CURSOR_INFO cursor = { 0 };HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);GetConsoleCursorInfo(handle, &cursor);cursor.bVisible = false;SetConsoleCursorInfo(handle, &cursor);
}//设置光标位置
void SetPos(int x, int y)
{COORD pos = { x,y }; //要设置的位置HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//哪个设备SetConsoleCursorPosition(handle, pos);//设置
}void Welcome()
{//显示欢迎语一SetPos(38, 15);printf("欢迎来到贪吃蛇小游戏!");SetPos(40, 24);system("pause");system("cls");//清屏//提示语SetPos(25, 15);printf("按 ↑、↓、←、→ 控制蛇蛇的移动,F1为加速,F2为减速,");SetPos(25, 16);printf("加速将能得到更高的分数");SetPos(40, 24);system("pause");
}//打印地图
void DrawMap()
{system("cls");SetColor(6);SetPos(0, 0);//上for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL);//左、右for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);SetPos(56, i);wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2)wprintf(L"%lc", WALL); SetColor(10);
}//显示提示信息
void HelpInfo()
{SetPos(65, 18);printf("不能穿墙,不能碰到自己,");SetPos(65, 19);printf("按 ↑、↓、←、→ 控制蛇蛇的移动,");SetPos(65, 21);printf("F1为加速,F2为减速,");SetPos(65, 22);printf("ESC:退出游戏 SPACE:暂停");
}//窗口相关设置
void ScreenPrepare()
{//设置窗体大小system("mode con cols=100 lines=40");system("title 贪吃蛇小游戏");//隐藏光标HideCurSor();//打印欢迎语Welcome();//打印地图DrawMap();//显示提示信息HelpInfo();
}//设置颜色
void SetColor(int HeadColor)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HeadColor);
}//打印蛇
void PrintSnake(pSnake ps)
{pSnakeNode cur = ps->pSnake;int flag = 1;while (cur){if (flag == 1){//为蛇头设置颜色SetColor(12);}SetPos(cur->x, cur->y);//设置每个节点的位置wprintf(L"%lc", BODY);cur = cur->next;flag = 0;SetColor(10);}
}//初始化蛇
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;//蛇的长度开始为3for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc");return;}cur->next = NULL;cur->x = POS_X + i * 2; //节点横坐标依次增加2cur->y = POS_Y;//头插法将节点相连if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇PrintSnake(ps);//初始化蛇的其它信息ps->Dire = RIGHT;ps->FoodScore = 10;ps->SleepTime = 200;ps->State = OK;ps->Score = 0;
}//创建食物
void CreatFood(pSnake ps)
{int x = 0;int y = 0;//食物坐标应该在墙体内
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//食物坐标不与蛇重叠pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//根据坐标创建食物pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreatFood():malloc()");return;}//打印食物pFood->x = x;pFood->y = y;SetColor(14);SetPos(pFood->x, pFood->y);wprintf(L"%lc", FOOD);SetColor(10);ps->pFood = pFood;
}//游戏准备
void GameStart(pSnake psnake)
{//设置好窗口、地图ScreenPrepare();//初始化蛇InitSnake(psnake);//创建食物CreatFood(psnake);
}//暂停
void pause()
{while (1){//一直休眠,直到再次按空格键Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}//玩游戏
void GameRun(pSnake ps)
{do{//显示分数SetColor(12);SetPos(65, 12);printf("目前得分:%-5d",ps->Score);SetColor(10);SetPos(65, 13);printf("每个食物:%2d分",ps->FoodScore);//检测按键if (KEY_PRESS(VK_UP) && ps->Dire != DOWN){//按上键,且蛇的方向不能向下ps->Dire = UP;}else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP){//按下键,且蛇的方向不能向上ps->Dire = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT){//按左键,且蛇的方向不能向右ps->Dire = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT){//按右键,且蛇的方向不能向左ps->Dire = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){//按ESC,退出游戏ps->State = END;}else if (KEY_PRESS(VK_SPACE)){//按空格,暂停pause();}else if (KEY_PRESS(VK_F1)){//按F1加速,即睡眠时间变短//休眠时间不能是负数,最快就是休眠30msif (ps->SleepTime >= 50){ps->SleepTime -= 20;//速度变快,食物的分数变高ps->FoodScore += 2;}}else if (KEY_PRESS(VK_F2)){//F2减速,睡眠时间变长//食物的分数不能减到负数,最多减为1分if (ps->FoodScore >= 3){ps->SleepTime += 20;ps->FoodScore -= 2;}}//按照蛇的睡眠时间,真正实现休眠Sleep(ps->SleepTime);//休眠后,蛇要移动SnakeMove(ps);} while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键
}//移动蛇
void SnakeMove(pSnake ps)
{//先产生节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove():malloc()");return;}//根据蛇的方向,设定节点的位置switch (ps->Dire){case UP://蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN://蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT://蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT://蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//判断下一个节点是否是食物if (JudgeNext(ps, pNext)){//是食物,吃掉,长度增加EatFood(ps, pNext);}else{//不是食物,吃掉,长度不增加NoFood(ps, pNext);}//撞墙KillByWall(ps);//撞自己KillBySelf(ps);
}//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext)
{return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y));
}//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext)
{//吃掉食物,头插法将节点插入pNext->next = ps->pSnake;ps->pSnake = pNext;//打印蛇PrintSnake(ps);//加分ps->Score += ps->FoodScore;//释放食物节点free(ps->pFood);//再次创建食物CreatFood(ps);
}//不是食物
void NoFood(pSnake ps, pSnakeNode pNext)
{//头插法连接pNext->next = ps->pSnake;ps->pSnake = pNext;//删除蛇尾pSnakeNode cur = ps->pSnake;//找到蛇尾的前一个节点while (cur->next->next){cur = cur->next;}pSnakeNode del = cur->next;//将蛇尾打印为空格SetPos(del->x, del->y);printf(" ");//释放蛇尾节点free(del);cur->next = NULL;//打印蛇PrintSnake(ps);
}//撞墙
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 26){ps->State = 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->State = KILL_BY_SELF;}cur = cur->next;}
}//游戏结束
void GameEnd(pSnake ps)
{SetPos(20, 13);SetColor(12);switch (ps->State){case END:printf("您结束了游戏");break;case KILL_BY_SELF:printf("很遗憾!您撞到了自己");break;case KILL_BY_WALL:printf("很遗憾!您撞墙了");break;}pSnakeNode cur = ps->pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps = NULL;SetColor(10);}
相关文章:

【贪吃蛇:C语言实现】
文章目录 前言1.了解Win32API相关知识1.1什么是Win32API1.2设置控制台的大小、名称1.3控制台上的光标1.4 GetStdHandle(获得控制台信息)1.5 SetConsoleCursorPosition(设置光标位置)1.6 GetConsoleCursorInfo(获得光标…...

01.领域驱动设计:微服务设计为什么要选择DDD学习总结
目录 1、前言 2、软件架构模式的演进 3、微服务设计和拆分的困境 4、为什么 DDD适合微服务 5、DDD与微服务的关系 6、总结 1、前言 我们知道,微服务设计过程中往往会面临边界如何划定的问题,不同的人会根据自己对微服务的理 解而拆分出不同的微服…...

写静态页面——魅族导航_前端页面练习
0、效果: 1、html代码:: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…...

Go 命令行解析 flag 包之快速上手
本篇文章是 Go 标准库 flag 包的快速上手篇。 概述 开发一个命令行工具,视复杂程度,一般要选择一个合适的命令行解析库,简单的需求用 Go 标准库 flag 就够了,flag 的使用非常简单。 当然,除了标准库 flag 外&#x…...

React16源码: React中commitAllHostEffects内部的commitDeletion的源码实现
commitDeletion 1 )概述 在 react commit 阶段的 commitRoot 第二个while循环中调用了 commitAllHostEffects,这个函数不仅仅处理了新增节点,更新节点最后一个操作,就是删除节点,就需要调用 commitDeletion࿰…...

[机器学习]简单线性回归——梯度下降法
一.梯度下降法概念 2.代码实现 # 0. 引入依赖 import numpy as np import matplotlib.pyplot as plt# 1. 导入数据(data.csv) points np.genfromtxt(data.csv, delimiter,) points[0,0]# 提取points中的两列数据,分别作为x,y …...

2024年搭建幻兽帕鲁服务器价格多少?如何自建Palworld?
自建幻兽帕鲁服务器租用价格表,2024阿里云推出专属幻兽帕鲁Palworld游戏优惠服务器,配置分为4核16G和4核32G服务器,4核16G配置32.25元/1个月、3M带宽96.75元/1个月、8核32G配置10M带宽90.60元/1个月,8核32G配置3个月271.80元。ECS…...

『OpenCV-Python|鼠标作画笔』
Opencv-Python教程链接:https://opencv-python-tutorials.readthedocs.io/ 本文主要介绍OpenCV-Python如何将鼠标作画笔绘制圆或者矩形。 示例一:图片上双击的位置绘制一个圆圈 首先创建一个鼠标事件回调函数,鼠标事件发生时就会被执行。鼠标…...

关于如何利用ChatGPT提高编程效率的
自从去年ChatGPT3.5推出以后,这一年时间在编程过程中我也在慢慢熟悉人工智能的使用,目前来看即使是免费的ChatGPT3.5对于编程效率的提升也是有很大帮助的,虽然在使用过程中确实出现了一些问题,本文记录下我的一些心得体会和用法。…...

Excel VBA ——从MySQL数据库中导出一个报表-笔记
本文主要涉及: VBA中数据库连接参数改成从配置文件获取 VBA连接MySQL数据库 VBA读MySQL数据库 演示两种写入工作簿的代码实现系统环境: Windows 10 64bit Excel 365 64bit WAMP(3.2.2.2 64bit)集成的MariaDB版本为10.4.10&#…...

金融OCR领域实习日志(一)——OCR技术从0到1全面调研
一、OCR基础 任务要求: 工作原理 OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相)检查纸上打印的字符,经过检测暗、亮的模式肯定其形状,而后用…...

ELK日志解决方案
ELK日志解决方案 ELK套件日志系统应该是Elasticsearch使用最广泛的场景之一了,Elasticsearch支持海量数据的存储和查询,特别适合日志搜索场景。广泛使用的ELK套件(Elasticsearch、Logstash、Kibana)是日志系统最经典的案例,使用Logstash和Be…...

嵌入式学习-驱动
嵌入式的一些基本概念 CPU与MCU的区别 CPU(中央处理器,central processing unit) 指集成了运算器、控制器、寄存器、高速缓存等功能模块的芯片,负责执行计算机程序指令的处理器。MCU(单片微型计算机或单片机,microco…...

系统架构17 - 软件工程(5)
软件工程 软件测试测试原则测试方法静态测试动态测试黑盒测试白盒测试灰盒测试自动化测试 测试阶段单元测试集成测试系统测试性能测试验收测试其它测试AB测试Web测试链接测试表单测试 测试用例设计黑盒测试用例白盒测试用例 调试 系统维护遗留系统系统转换转换方式数据转换与迁…...

空气质量预测 | Python实现基于线性回归、Lasso回归、岭回归、决策树回归的空气质量预测模型
文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 政府机构使用空气质量指数 (AQI) 向公众传达当前空气污染程度或预测空气污染程度。 随着 AQI 的上升,公共卫生风险也会增加。 不同国家有自己的空气质量指数,对应不同国家的空气质量标准。 对于空气质量预测,…...

MYSQL数据库基本操作-DQL-基本查询
一.概念 数据库管理系统一个重要功能就是数据查询。数据查询不应是简单返回数据库中存储的数据,还应该根据需要对数据进行筛选以及确定数据以什么样的格式显示。 MySQL提供了功能强大,灵活的语句来实现这些操作。 MySQL数据库使用select语句来查询数据…...

gdb 调试 - 在vscode图形化展示在远程的gdb debug过程
前言 本地机器的操作系统是windows,远程机器的操作系统是linux,开发在远程机器完成,本地只能通过ssh登录到远程。现在目的是要在本地进行图形化展示在远程的gdb debug过程。(注意这并不是gdb remote !!&am…...

Android 13.0 SystemUI下拉状态栏定制二 锁屏页面横竖屏时钟都居中功能实现二
1.前言 在13.0的系统rom定制化开发中,在关于systemui的锁屏页面功能定制中,由于在平板横屏锁屏功能中,时钟显示的很大,并且是在左旁边居中显示的, 由于需要和竖屏显示一样,所以就需要用到小时钟显示,然后同样需要居中,所以就来分析下相关的源码,来实现具体的功能 如图…...

docker 部署xxl-job
docker 部署xxl-job XXL-JOB github地址 https://github.com/xuxueli/xxl-job XXL-JOB 文档地址 https://www.xuxueli.com/xxl-job/ XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品…...

Kafka(九)跨集群数据镜像
目录 1 跨集群镜像的应用场景1.1 区域集群和中心集群1.2 高可用(HA)和灾备(DR)1.3 监管与合规1.4 云迁移1.5 聚合边缘集群的数据 2 多集群架构2.1 星型架构2.2 双活架构2.2 主备架构2.2.1 如何实现Kafka集群的故障转移2.2.1.1 故障转移包括的内容1. 灾难恢复计划2. 非计划内的故…...

第3讲 谈谈final、finally、 finalize有什么不同?
参考 三者区别 final final 是 java 关键字可修饰变量(类成员变量、类静态变量、局部变量和形参):表示不可修改当前变量的值(这里的值可以是地址,也可以是基本类型的值)#(注意:fi…...

MC3172 串口模块
MC3172 支持12个串口对应关系如下 串口模块初始化 第一个是uart0~11 inpin RX 脚 管脚号 outpin TX脚 管脚号 baud 波特率 read_ptr ,数据读取指针 void uart_init(u32 uart_num,u8 in_pin,u8 out_pin,u32 baud,u8* read_ptr) {INTDEV_SET_CLK_RST(uart_num,(INTDEV_RUN|…...

VUE3 加载自定义SVG文件
网上代码通篇一律,需要修改多处地方,特别是component下还要创建一个index.vue的组件,奇奇怪怪。 要在 Vue 项目中使用 svg-sprite-loader 来管理 SVG 图标,你需要执行以下几个步骤: npm install svg-sprite-loader -…...

【数据分析】numpy基础第五天
文章目录 前言Z-Score标准化Z-Score应用示例 Min-Max归一化Min-Max应用示例 总结 前言 第五天是我们的numpy学习计划中的最后一天。 在数据处理和数据分析中,数据预处理是非常重要的一步。我们不可能完全靠肉眼来分析数据,总会有用到各种算法模型的时候…...

CSS 双色拼接按钮效果
<template><view class="sss"><button> <!-- 按钮 --><view class="span"> 按钮 </view> <!-- 按钮文本 --></button></view></template><script></script><style>body {b…...

T05垃圾收集算法与垃圾收集器ParNew CMS
垃圾收集算法与垃圾收集器ParNew & CMS 垃圾收集算法 #### f 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法。根据对象存活周期不同将内存分为几块,一般将java堆分为新生代和老年代,然后根据各个年代的特点选择不同的垃圾收集算法。 在新…...

每日一道面试题:Java中序列化与反序列化
写在开头 哈喽大家好,在高铁上码字的感觉是真不爽啊,小桌板又拥挤,旁边的小朋友也比较的吵闹,影响思绪,但这丝毫不影响咱学习的劲头!哈哈哈,在这喧哗的车厢中,思考着这样的一个问题…...

论文阅读:Vary-toy论文阅读笔记
目录 引言整体结构图方法介绍训练vision vocabulary阶段PDF数据目标检测数据 训练Vary-toy阶段Vary-toy结构数据集情况 引言 论文:Small Language Model Meets with Reinforced Vision Vocabulary Paper | Github | Demo 说来也巧,之前在写论文阅读&…...

【Linux】开始使用 vim 吧!!!
Linux 1 what is vim ?2 vim基本概念3 vim的基本操作 !3.1 vim的快捷方式3.1.1 复制与粘贴3.1.2 撤销与剪切3.1.3 字符操作 3.2 vim的光标操作3.3 vim的文件操作 总结Thanks♪(・ω・)ノ感谢阅读下一篇文章见!…...

多线程面试合集
前言 前文介绍了JVM相关知识,本文将重点介绍多线程相关知识以及工作中的一些经验。 多线程面试合集 什么是多线程?为什么我们需要多线程? 多线程是指在一个进程中同时执行多个线程,每个线程可以执行不同的任务。多线程可以提高…...