赞
踩
贪吃蛇小游戏想必大家都玩过吧,现在就要C语言代码来实现一下贪吃蛇小游戏
在实现之前,我们要对C语言结构体、指针、链表(单链表)有一定的基础
先来看一下预期运行效果
这里实现贪吃蛇游戏会使用一些Win32 API的知识,这里简单学习一下
Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、绘制图形、使用周边设备等目的,由于这些函数服务的对象是应用程序,所以便称之为Appliccation Programming Interface,简称API。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口。
在我们的电脑中,windows系统使用快捷键win + R可以打开一个窗口,然后输入cmd就可以打开一个控制台程序,这个控制台可以输入一些命令来控制我们的电脑,这里输入cmd即可打开一个控制台程序窗口
本次贪吃蛇小游戏是在VS2022上来实现的,平常我们运行起来的黑框程序就是控制台层序
在VS2022上运行默认是以下情况
这里就需要先修改一个控制台
调出控制台(这里可以使用Win+R,输入cmd调出窗口),点击设置
在默认终端应用程序这里设置成Windows 控制台主机(默认是Windows 终端),点击保存
设置完成后,就是以下这种界面了
这里我们控制台程序是默认大小,这里我们自己设置控制台程序大小,这里使用cmd控制台程序设置窗口的大小(设置大小为行33,列100)
mode con cols=100 lines=33
我们设置控制台名称为 贪吃蛇,使用title 指令
title 贪吃蛇
当然,这些能够在控制台窗口执行的命令,也可以通过调用C语言的system函数在中来完成
这里再补充一个指令,暂停控制台程序
system("pause");
这个指令可以暂停程序运行,并会提示按下任意键继续...
- int main()
- {
- system("mode con cols=100 lines=33");
- system("title 贪吃蛇");
- system("pause");
- return 0;
- }
COORD是Windows API中自定义的一个结构体,表示一个字符在控制台屏幕缓冲区的坐标,坐标(0,0)的原点位于缓冲区的顶部左侧单元格。
COORD类型声明
- typedef struct _COORD {
- SHORT X;
- SHORT Y;
- } COORD, *PCOORD;
给坐标赋值
COORD pos = { 10, 15 };
GetStdHandle是一个Windows API函数。它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值),这个句柄可以操作设备。
HANDLE WINAPI GetStdHandle(_In_ DWORD nStdHandle);
函数参数
函数使用
- HANDLE hOutput = NULL;
- //获取标准输出的句柄(用来标识不同设备的数值)
- hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleCursorInfo函数检索有关指定控制台屏幕缓冲区的光标的大小和可见性的信息
函数语法
- BOOL WINAPI GetConsoleCursorInfo(
- _In_ HANDLE hConsoleOutput,
- _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
- );
CONSOLE_CURSOR_INFO结构体
这个结构体包含了有关控制台光标的信息
- typedef struct _CONSOLE_CURSOR_INFO {
- DWORD dwSize;
- BOOL bVisible;
- } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
dwSize 由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元格底部的水平线条
bVisible 游标的可见性。如果光标可见,则此成员为true;如果不可见,此成员为false
函数参数
这里就用到上面GetStdHandle函数获得的句柄了,还需要用到CONSOLE_CURSOR_INFO结构体(注意,这里第二个参数是指针)
函数使用
- HANDLE hOutput = NULL;
- //获取标准输出的句柄(⽤来标识不同设备的数值)
- hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
- CONSOLE_CURSOR_INFO CursorInfo;
- GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
SetConsoleCursorInfo函数设置指定控制台屏幕缓冲区的光标的大小和可见性
函数参数
- BOOL WINAPI SetConsoleCursorInfo(
- _In_ HANDLE hConsoleOutput,
- _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
- );
与GetConsoleCursorInfo函数参数相同
函数使用
- HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
- //影藏光标操作
- CONSOLE_CURSOR_INFO CursorInfo;
- GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
- CursorInfo.bVisible = false; //隐藏控制台光标
- SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
SetConsoleCursorPosition函数设置指定控制台屏幕缓冲区的位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
- BOOL WINAPI SetConsoleCursorPosition(
- _In_ HANDLE hConsoleOutput,
- _In_ COORD dwCursorPosition
- );
函数参数
函数使用
- HANDLE hOutput = NULL;
- //获取标准输出的句柄(用来标识不同设备的数值)
- hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
- COORD pos = { 10, 5 };
- //设置标准输出上光标的位置为pos
- SetConsoleCursorPosition(hOutput, pos);
这里为了方便后面定位屏幕坐标,单独封装一个函数来实现
- void SetPos(short x, short y)
- {
- COORD pos = { x, y };
- HANDLE hOutput = NULL;
- //获取标准输出的句柄(用来标识不同设备的数值)
- hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
- //设置标准输出上光标的位置为pos
- SetConsoleCursorPosition(hOutput, pos);
- }
GetAsyncKeyState函数获得按键情况
- SHORT GetAsyncKeyState(
- [in] int vKey
- );
函数参数
这里函数参数是虚拟键码。
这里仅列出一些在游戏中可能用到的按键的虚拟键码,可以点击查看详细虚拟键码
VK_UP | 0x26 | ↑ |
VK_DOWN | 0x28 | ↓ |
VK_LEFT | 0x25 | ← |
VK_RIGHT | 0x27 | → |
VK_F3 | 0x72 | F3 |
VK_F4 | 0x73 | F4 |
VK_ESCAPE | 0x1B | Esc |
VK_SPACE | 0x20 | 空格 |
函数返回值
GetAsyncKeyState 函数返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,如果最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
在游戏中我们需要检测一个按键是否被按过,就检测 GetAsyncKeyState 函数返回值的最低值是否是1,可以写一个宏来实现:
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
在贪吃蛇游戏中,我们会涉及到墙体□ 和蛇的身体● 的打印,而在VS中我们输出出来的是?
这就是因为没有本地化设置,无法输出这些特殊字符(宽字符)。
我们需要通过修改地区,让程序来适应不同的区域,我们就需要进行本地化设置
这里就要使用到C语言中的库函数 setlocale 函数
在C标准中,依赖地区的部分有以下几项
数字量的格式
货币量的格式
字符集
日期和时间的表示形式
通过修改地区,程序可以改变它的行为来适应世界的不同地域。但地区的改变可能会影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类型进行修改,下面的一个宏就指定一个类型。
LC_COLLATE :影响字符串表函数 strcoll 和strxfrm。
LC_CTYPE : 影响字符处理函数的行为。
LC_MONETARY : 影响货币格式。
LC_NUMERIC : 影响 printf 的数字格式。
LC_TIME : 影响时间格式 strftime 和 wcsftime 。
LC_ALL : 针对所有类型修改,将以上所有类别设置为给定的语言环境。
setlocale 函数用于修改当前地区,可以针对一个类项进行修改,也可以针对所有类项
函数的第一个参数可以是前面类项中的一个,也可以是LC_ALL(影响所以的类项)
函数的第二个参数
C标准给第二个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地化模式)
这里我们需要进行本地化设置
setlocale(LC_ALL, " ");//切换到本地环境
在屏幕中,我们需要打印宽字符
宽字符的字面量必须加上前缀“L”,否则C语言就会把字符量当成窄字符来处理。前缀“L”子啊单引号(或者双引号)的前面,表示宽字符,对于 wprintf 的占位符是 %lc;双引号的前面,对于wprintf 的占位符是 %ls。
可以看到,这里宽字符占两个窄字符的位置。
在游戏运行的过程中,蛇每吃一次食物,蛇的身体就会变长;这样我们就可以使用链表来存储蛇的信息,蛇的每一个节身体其实就是链表的一个节点。每个节点只需记录蛇身节点在地图上的坐标就可以。
蛇身节点的结构:
- typedef struct Snakenode
- {
- int x;
- int y;
- struct Snakenode* next;
- }Snakenode, * pSnakenode;
- //这里也可以写 typedef Snakenode* pSnakenode
接下来,我们还需要记录游戏过程中的相关信息
贪吃蛇,食物的位置,蛇的方向,游戏状态,当前的分数,每一个食物的分数,蛇的速度等
而这里蛇的方向和游戏状态都可以一一列举出来,这里就使用枚举变量
- //蛇的方向
- enum DIRECT
- {
- UP = 1,
- DOWN,
- LEFT,
- RIGHT
- };
- //蛇的状态——游戏状态
- //正常、撞墙、撞到自己、正常退出
- enum GAME_STATE
- {
- OK,
- KILL_WALL,
- KILL_SELF,
- NORMAL_END
- };
- //贪吃蛇的相关信息
- typedef struct Snake
- {
- pSnakenode psnake; //指向蛇头部的指针
- pSnakenode pfood; //指向食物的指针
- enum DIRECT dir;//蛇的方向
- enum GAME_STATE state;//蛇的状态
- int food_scores;//每个食物的分数
- int all_scores; //总分数
- int sleep_time; //休息的时间 --即蛇的速度
- }Snake;
- typedef Snake* pSnake;
这样,我们就创建了一个Snake结构体来维护游戏相关信息(维护整条贪吃蛇)
游戏大概分析如下
程序开始就设置程序本地化,然后就进入到游戏的主逻辑当中
根据游戏大概分析,游戏可以分为三个阶段
阶段一:游戏开始 --- 完成游戏的初始化
阶段二:游戏运行 --- 完成游戏运行逻辑的实现
阶段三:游戏结束 --- 完成游戏结束的说明,实现资源释放
当然,这里我们玩完一局游戏后,可以选择继续或者结束(这里就以输入Y/N来判断游戏是否继续运行)
这里我们在测试test.c文件开始就让程序本地化
- void test()
- {
- Snake snake = { 0 };
- int ch = 0;
- do
- {
- ch = 0;
- system("cls");
- //游戏初始化
- GameStart(&snake);
- //游戏运行
- GameRun(&snake);
- //游戏结束
- GameOver(&snake);
- KeyFun();
- SetPos(30, 20);
- wprintf(L"再来一局吗? (Y/N)");
- ch = getchar();
- while (getchar() != '\n');
- } while (ch == 'Y' || ch == 'y');
- SetPos(0, 27);
- }
- int main()
- {
- //本地化
- setlocale(LC_ALL, "");
- srand((unsigned int)time(NULL));
- test();
- //KeyFun();
- return 0;
- }
测试大概框架就是这样,接下来就分别来实现这些框架的内容
这里设置控制台大小,100列,33行;设置控制台名称为:贪吃蛇
- //设置窗口名称大小
- system("title 贪吃蛇");
- system("mode con cols=100 lines=33");
隐藏屏幕光标,这里就用到了前面Win32 API的知识
- //隐藏光标
- HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
- CONSOLE_CURSOR_INFO CursorInfo;
- GetConsoleCursorInfo(houtput, &CursorInfo);
- //获得有关指定控制台屏幕缓冲区的光标大小和可见的信息
- CursorInfo.bVisible = false; //隐藏控制台光标
- SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
输出欢迎界面,这里分装成函数WelcomeToGame
我们观察欢迎界面,可以发现这里并不是在坐标为(0,0)处打印的,这里就要用到设置光标位置(这里单独写一个函数,设置光标位置)
设置光标位置
- //设置光标位置
- void SetPos(int x, int y)
- {
- HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
- COORD pos = { x,y };
- SetConsoleCursorPosition(houtput, pos);
- }
接下来就是游戏欢迎界面的打印,这里中间会用到 pause 和 cls(清理屏幕) 指令
- //欢迎界面打印
- void WelcomeToGame()
- {
- //设置光标位置
- SetPos(40, 15);
- printf("欢迎进入贪吃蛇小游戏\n");
- SetPos(42, 20);
- system("pause");
- system("cls");//清理屏幕
- SetPos(20, 11);
- printf("请使用↑ 、 ↓ 、 ← 、 → 来控制贪吃蛇的移动,按F3加速、F4减速 ");
- SetPos(20, 13);
- printf("加速可以获得更多的分数");
- SetPos(20, 15);
- system("pause");
- system("cls"); //清理屏幕
- }
这样就可以实现预期效果图那样了,接下来就是绘制我们贪吃蛇游戏的地图了。
这里我们使用宽字符来打印地图,先来看一下预期效果
我们把地图分为上、下、左、右这四个部分,这样我们只需依次打印这些宽字符就可以了
- //地图绘制
- void CreatMap()
- {
- //上
- int i = 0;
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", WALL);
- }
- //下
- SetPos(0, 26);
- for(i = 0; i < 29; i++)
- {
- wprintf(L"%lc", WALL);
- }
- //左
-
- for (i = 1; i < 26; i++)
- {
- SetPos(0, i);
- wprintf(L"%lc", WALL);
- }
- //右
- for (i = 1; i < 26; i++)
- {
- SetPos(56, i);
- wprintf(L"%lc", WALL);
- }
- }
初始化贪吃蛇,也是创建贪吃蛇,贪吃蛇身体这里其实就是一个链表,里面存放着每个节点的坐标
初始化贪吃蛇也要给上一些初始数据
初始长度为 -- 5
初始方向 -- 向右(RIGHT)
初始状态 -- 正常(OK)
每个食物得分 -- 10
初始总分 -- 0
初始速度 -- 这里设定眠时间为200毫秒
初始蛇的位置 -- 这里就随机生成(也可以指定)
当然初始指向食物的指针置为NULL(因为这里还未创建食物)
- //创建贪吃蛇
- void InitSnake(pSnake ps)
- {
- //创建蛇的身体
- pSnakenode pcur = NULL;
- int i = 0;
- int x, y;//蛇初始位置
- do
- {
- x = rand() % 31 + 4; //x: 4 - 34
- y = rand() % 20 + 2; //y: 1 - 25
- } while (x % 2 != 0);
- for (i = 0; i < 5; i++)
- {
- pcur = (pSnakenode)malloc(sizeof(Snakenode));
- if (pcur == NULL)
- {
- perror("InitSnake()::malloc()");
- return;
- }
- pcur->next = NULL;
- //pcur->x = SNAKE_X + i * 2;
- //pcur->y = SNAKE_Y;
- pcur->x = x + i * 2;
- pcur->y = y;
- //头插到贪吃蛇链表中
- if (ps->psnake == NULL) //链表为空
- {
- ps->psnake = pcur;
- }
- else
- {
- pcur->next = ps->psnake;
- ps->psnake = pcur;
- }
- }
- //输出蛇的初始位置
- pcur = ps->psnake;
- while (pcur)
- {
- SetPos(pcur->x, pcur->y);
- wprintf(L"%lc", SNAKENODE);
- pcur = pcur->next;
- }
- //初始化贪吃蛇的信息
- ps->dir = RIGHT; //蛇的方向
- ps->pfood = NULL; //指向食物 --NULL
- ps->state = OK; //状态
- ps->food_scores = 10; //每个食物的得分
- ps->all_scores = 0; //总分
- ps->sleep_time = 200;//速度,即休息时间 单位是毫秒
- //getchar();
- }
创建完贪吃蛇,接下来就是创建食物了,其实食物和贪吃蛇身体节点一样,都存放着坐标;所以这里就创建一个结构体,再随机生成坐标
这里需要注意:
坐标x必须是偶数
坐标必须在地图内
生成食物的坐标不能与蛇的身体重复
- //创建食物
- void CreatFood(pSnake ps)
- {
- int x, y;//随机生成坐标 x , y
- //x 2-54
- //y 1-25
-
- again:
- do {
- x = rand() % 53 + 2;
- y = rand() % 25 + 1;
- } while (x % 2 != 0);
- //x y 不能与贪吃蛇身体重复
- pSnakenode pcur = ps->psnake;
- while (pcur)
- {
- if (x == pcur->x && y == pcur->y)
- {
- goto again;
- }
- pcur = pcur->next;
- }
- pSnakenode food = (pSnakenode)malloc(sizeof(pSnakenode));
- if (food == NULL)
- {
- perror("CreatFood()::malloc");
- return;
- }
-
- food->x = x;
- food->y = y;
- food->next = NULL;
- ps->pfood = food;
- SetPos(x, y);
- wprintf(L"%lc", FOOD);
- //getchar();
- }
这样我们的初始化就完成了
看预期效果图,我们在地图的右侧输出一些提示信息,并且输出当前得分详情
- void Printgame(pSnake ps) {
- SetPos(60, 15);
- printf("请使用↑ 、 ↓ 、 ← 、 → 来控制贪吃蛇");
- SetPos(60, 16);
- printf("按F3加速、F4减速 ");
- SetPos(60, 18);
- printf("加速可以获得更高的分数 ");
- SetPos(60, 20);
- printf("ESC:退出游戏 space:暂停 ");
- SetPos(60, 10);
- printf("当前总得分:%d", ps->all_scores);
- SetPos(60, 12);
- printf("当前每个食物得分:%d", ps->food_scores);
- SetPos(60, 22);
- printf("努力学习的小廉");
- }
现在就要获取我们键盘按键的信息了,这里写一个宏来判断按键是否被按过
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
这里把获取按键信息直接写到游戏运行这个函数内,顺便看一下游戏运行都需要实现哪些东西?
- //游戏运行
- void GameRun(pSnake ps)
- {
- do
- {
- Printgame(ps);
- //判断按键是否被按过
- if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
- {
- 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();
- }
- else if (KEY_PRESS(VK_ESCAPE)) //游戏正常退出
- {
- ps->state = NORMAL_END;
- break;
- }
- else if (KEY_PRESS(VK_F3))
- {
- if (ps->sleep_time >= 100)
- {
- ps->sleep_time -= 50;
- ps->food_scores += 5;//设定食物分数最高25
- }
- }
- else if (KEY_PRESS(VK_F4))
- {
- if (ps->sleep_time < 300)
- {
- ps->sleep_time += 100;
- ps->food_scores -= 5;//⼀个⻝物分数最低是5分
- }
- }
- Sleep(ps->sleep_time);
- //贪吃蛇的移动
- SnakeMove(ps);
- //判断贪吃蛇是否撞墙
- KillByWall(ps);
- //判断贪吃蛇是否撞到自己
- KillBySelf(ps);
-
- } while (ps->state == OK);
-
- }
这里当游戏状态不是正常运行时,就结束了循环(即游戏结束)
看上述游戏运行代码,可以看到贪吃蛇的移动还有判断蛇是否撞到墙和自己,这些的实现在贪吃蛇移动当中。
蛇身的移动,其实就是根据当前蛇的方向,找到下一个节点,再判断下一个节点是否是食物,和判断是否撞到墙和自己
判断蛇的下一个节点是否是食物,就是判断下一个位置的坐标和实物的坐标是否重复
如果重复,就让蛇身变长一节,如果不是,就让蛇往前走
这里蛇移动还有一些知识,就是直接为蛇下一个位置创建一个新的节点
再判断下一个位置是否是食物,如果是就将节点头插到蛇身链表中,不删除尾节点;如果不是就直接将节点头插到蛇身链表中,删除尾节点(这里还需在蛇的尾部输出两个空格" ")
- //下一个位置是食物
- void IsFood(pSnakenode next, pSnake ps)
- {
- //把下一个位置的节点头插到贪吃蛇中
- next->next = ps->psnake;
- ps->psnake = next;
- //打印贪吃蛇
- pSnakenode cur = ps->psnake;
- while (cur)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", SNAKENODE);
- cur = cur->next;
- }
- ps->all_scores += ps->food_scores;
- CreatFood(ps);
- //SetPos(ps->pfood->x, ps->pfood->y);
- //wprintf(L"%lc", FOOD);
- }
- //下一个位置不是食物
- void NoFood(pSnakenode next, pSnake ps)
- {
- //把下一个位置的节点头插到贪吃蛇中
- next->next = ps->psnake;
- ps->psnake = next;
-
- pSnakenode cur = ps->psnake;
- while (cur->next->next != NULL)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", SNAKENODE);
- cur = cur->next;
- }
- SetPos(cur->next->x, cur->next->y);
- wprintf(L"%ls", L" ");
-
- free(cur->next);
- cur->next = NULL;
- }
- //贪吃蛇的移动
- void SnakeMove(pSnake ps)
- {
- pSnakenode next = (pSnakenode)malloc(sizeof(Snakenode));
- if (next == NULL)
- {
- perror("SnakeMove():malloc()");
- exit(1);
- }
- switch (ps->dir)
- {
- case UP:
- next->x = ps->psnake->x;
- next->y = ps->psnake->y - 1;
- break;
- case DOWN:
- next->x = ps->psnake->x;
- next->y = ps->psnake->y + 1;
- break;
- case LEFT:
- next->x = ps->psnake->x - 2;
- next->y = ps->psnake->y;
- break;
- case RIGHT:
- next->x = ps->psnake->x + 2;
- next->y = ps->psnake->y;
- break;
- }
- //判断下一个位置是不是食物
- if (NextIsFood(next, ps))
- {
- IsFood(next, ps);
- }
- else {
- NoFood(next, 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_WALL;
- }
判断蛇是否撞到自己,就遍历链表,判断蛇身的头结点是否和身体其他节点重复
- //判断贪吃蛇是否撞到自己
- void KillBySelf(pSnake ps)
- {
- pSnakenode pcur = ps->psnake->next;
- while (pcur)
- {
- if (pcur->x == ps->psnake->x && pcur->y == ps->psnake->y)
- {
- ps->state = KILL_SELF;
- break;
- }
- pcur = pcur->next;
- }
- }
到这里,代码就已经实现的差不多了,接下来就是游戏结束后的一些善后工作
游戏结束,打印出来游戏结束的原因,是撞到墙了呢?还是撞到自己了呢?还是按Esc正常退出了呢?
因为我们蛇身的节点是动态申请的内存,我们需要手动释放掉内存(养成好习惯!)
- //游戏结束
- void GameOver(pSnake ps)
- {
- SetPos(8, 12);
- switch(ps->state)
- {
- case KILL_WALL:
- wprintf(L"Sorry,game over because you hit the wall !\n");
- break;
- case KILL_SELF:
- wprintf(L"Sorry,game over because you hit youself !\n");
- break;
- case NORMAL_END:
- wprintf(L"Game exits normally !");
- break;
- }
-
- //释放贪吃蛇的节点内存
- pSnakenode pcur = ps->psnake;
- while (pcur)
- {
- pSnakenode del = pcur;
- pcur = pcur->next;
- free(del);
- }
- ps->psnake = NULL;
- }
到这里我们游戏的代码就已经全部实现了。
Snake.h
- #pragma once
- #define _CRT_SECURE_NO_WARNINGS
-
- #include<stdio.h>
- #include<stdlib.h>
- #include<locale.h>
- #include<windows.h>
- #include<stdbool.h>
- #include<wchar.h>
- #include<time.h>
- #include<conio.h>
-
- //初始数据
- #define SNAKE_X 24
- #define SNAKE_Y 5
- #define WALL L'□'
- #define SNAKENODE L'●'
- #define FOOD L'★'
-
- //蛇的节点
- typedef struct Snakenode
- {
- int x;
- int y;
- struct Snakenode* next;
- }Snakenode;
- typedef Snakenode* pSnakenode;
-
- //蛇的方向
- enum DIRECT
- {
- UP = 1,
- DOWN,
- LEFT,
- RIGHT
- };
- //蛇的状态——游戏状态
- //正常、撞墙、撞到自己、正常退出
- enum GAME_STATE
- {
- OK,
- KILL_WALL,
- KILL_SELF,
- NORMAL_END
- };
-
- //贪吃蛇的相关信息
- typedef struct Snake
- {
- pSnakenode psnake; //指向蛇头部的指针
- pSnakenode pfood; //指向食物的指针
- enum DIRECT dir;//蛇的方向
- enum GAME_STATE state;//蛇的状态
- int food_scores;//每个食物的分数
- int all_scores; //总分数
- int sleep_time; //休息的时间 --即蛇的速度
- }Snake;
- typedef Snake* pSnake;
-
- //设置光标位置
- void SetPos(int x, int y);
- //游戏初始化
- void GameStart(pSnake ps);
- //欢迎界面的输出
- void WelcomeToGame();
- //地图绘制
- void CreatMap();
- //创建贪吃蛇
- void InitSnake(pSnake ps);
- //创建食物
- void CreatFood(pSnake ps);
-
- //游戏运行
- void GameRun(pSnake ps);
- //贪吃蛇的移动
- void SnakeMove(pSnake ps);
- //判断下一个位置是不是食物
- int NextIsFood(pSnakenode next , pSnake ps);
- //下一个位置是食物,吃掉食物
- void IsFood(pSnakenode next, pSnake ps);
- //下一个位置不是食物
- void NoFood(pSnakenode next, pSnake ps);
- //判断贪吃蛇是否撞墙
- void KillByWall(pSnake ps);
- //判断贪吃蛇是否撞到自己
- void KillBySelf(pSnake ps);
- //游戏结束
- void GameOver(pSnake ps);
-
- void KeyFun();
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, 15);
- printf("欢迎进入贪吃蛇小游戏\n");
- SetPos(42, 20);
- system("pause");
- system("cls");//清理屏幕
- SetPos(20, 11);
- printf("请使用↑ 、 ↓ 、 ← 、 → 来控制贪吃蛇的移动,按F3加速、F4减速 ");
- SetPos(20, 13);
- printf("加速可以获得更多的分数");
- SetPos(20, 15);
- system("pause");
- system("cls"); //清理屏幕
- }
- //地图绘制
- void CreatMap()
- {
- //上
-
- int i = 0;
- for (i = 0; i < 29; i++)
- {
- wprintf(L"%lc", WALL);
- }
- //下
- SetPos(0, 26);
- for(i = 0; i < 29; i++)
- {
- wprintf(L"%lc", WALL);
- }
- //左
-
- for (i = 1; i < 26; i++)
- {
- SetPos(0, i);
- wprintf(L"%lc", WALL);
- }
- //右
- for (i = 1; i < 26; i++)
- {
- SetPos(56, i);
- wprintf(L"%lc", WALL);
- }
- //system("pause");
- }
- //创建贪吃蛇
- void InitSnake(pSnake ps)
- {
- //创建蛇的身体
- pSnakenode pcur = NULL;
- int i = 0;
- int x, y;//蛇初始位置
- do
- {
- x = rand() % 31 + 4; //x: 4 - 34
- y = rand() % 20 + 2; //y: 1 - 25
- } while (x % 2 != 0);
- for (i = 0; i < 5; i++)
- {
- pcur = (pSnakenode)malloc(sizeof(Snakenode));
- if (pcur == NULL)
- {
- perror("InitSnake()::malloc()");
- return;
- }
- pcur->next = NULL;
- //pcur->x = SNAKE_X + i * 2;
- //pcur->y = SNAKE_Y;
- pcur->x = x + i * 2;
- pcur->y = y;
- //头插到贪吃蛇链表中
- if (ps->psnake == NULL) //链表为空
- {
- ps->psnake = pcur;
- }
- else
- {
- pcur->next = ps->psnake;
- ps->psnake = pcur;
- }
- }
- //输出蛇的初始位置
- pcur = ps->psnake;
- while (pcur)
- {
- SetPos(pcur->x, pcur->y);
- wprintf(L"%lc", SNAKENODE);
- pcur = pcur->next;
- }
- //初始化贪吃蛇的信息
- ps->dir = RIGHT; //蛇的方向
- ps->pfood = NULL; //指向食物 --NULL
- ps->state = OK; //状态
- ps->food_scores = 10; //每个食物的得分
- ps->all_scores = 0; //总分
- ps->sleep_time = 200;//速度,即休息时间 单位是毫秒
- //getchar();
- }
- //创建食物
- void CreatFood(pSnake ps)
- {
- int x, y;//随机生成坐标 x , y
- //x 2-54
- //y 1-25
-
- again:
- do {
- x = rand() % 53 + 2;
- y = rand() % 25 + 1;
- } while (x % 2 != 0);
- //x y 不能与贪吃蛇身体重复
- pSnakenode pcur = ps->psnake;
- while (pcur)
- {
- if (x == pcur->x && y == pcur->y)
- {
- goto again;
- }
- pcur = pcur->next;
- }
- pSnakenode food = (pSnakenode)malloc(sizeof(pSnakenode));
- if (food == NULL)
- {
- perror("CreatFood()::malloc");
- return;
- }
-
- food->x = x;
- food->y = y;
- food->next = NULL;
- ps->pfood = food;
- SetPos(x, y);
- wprintf(L"%lc", FOOD);
- //getchar();
- }
-
- //游戏初始化
- void GameStart(pSnake ps)
- {
- //设置窗口名称大小,隐藏光标
- system("title 贪吃蛇");
- system("mode con cols=100 lines=33");
-
- //隐藏光标
- 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);
- //system("pause");
- }
- #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
- //暂停
- void Pause()
- {
- while (1)
- {
- Sleep(200);
- if (KEY_PRESS(VK_SPACE))
- break;
- }
- }
- //判断下一个位置是不是食物
- int NextIsFood(pSnakenode next, pSnake ps)
- {
- return (next->x == ps->pfood->x && next->y == ps->pfood->y);
- }
- //下一个位置是食物
- void IsFood(pSnakenode next, pSnake ps)
- {
- //把下一个位置的节点头插到贪吃蛇中
- next->next = ps->psnake;
- ps->psnake = next;
- //打印贪吃蛇
- pSnakenode cur = ps->psnake;
- while (cur)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", SNAKENODE);
- cur = cur->next;
- }
- ps->all_scores += ps->food_scores;
- CreatFood(ps);
- //SetPos(ps->pfood->x, ps->pfood->y);
- //wprintf(L"%lc", FOOD);
- }
- //下一个位置不是食物
- void NoFood(pSnakenode next, pSnake ps)
- {
- //把下一个位置的节点头插到贪吃蛇中
- next->next = ps->psnake;
- ps->psnake = next;
-
- pSnakenode cur = ps->psnake;
- while (cur->next->next != NULL)
- {
- SetPos(cur->x, cur->y);
- wprintf(L"%lc", SNAKENODE);
- cur = cur->next;
- }
- SetPos(cur->next->x, cur->next->y);
- wprintf(L"%ls", L" ");
-
- free(cur->next);
- cur->next = NULL;
- }
-
- //贪吃蛇的移动
- void SnakeMove(pSnake ps)
- {
- pSnakenode next = (pSnakenode)malloc(sizeof(Snakenode));
- if (next == NULL)
- {
- perror("SnakeMove():malloc()");
- exit(1);
- }
- switch (ps->dir)
- {
- case UP:
- next->x = ps->psnake->x;
- next->y = ps->psnake->y - 1;
- break;
- case DOWN:
- next->x = ps->psnake->x;
- next->y = ps->psnake->y + 1;
- break;
- case LEFT:
- next->x = ps->psnake->x - 2;
- next->y = ps->psnake->y;
- break;
- case RIGHT:
- next->x = ps->psnake->x + 2;
- next->y = ps->psnake->y;
- break;
- }
- //判断下一个位置是不是食物
- if (NextIsFood(next, ps))
- {
- IsFood(next, ps);
- }
- else {
- NoFood(next, ps);
- }
- }
-
- //
- void Printgame(pSnake ps) {
- SetPos(60, 15);
- printf("请使用↑ 、 ↓ 、 ← 、 → 来控制贪吃蛇");
- SetPos(60, 16);
- printf("按F3加速、F4减速 ");
- SetPos(60, 18);
- printf("加速可以获得更高的分数 ");
- SetPos(60, 20);
- printf("ESC:退出游戏 space:暂停 ");
- SetPos(60, 10);
- printf("当前总得分:%d", ps->all_scores);
- SetPos(60, 12);
- printf("当前每个食物得分:%d", ps->food_scores);
- SetPos(60, 22);
- printf("努力学习的小廉");
- }
- //判断贪吃蛇是否撞墙
- void KillByWall(pSnake ps)
- {
- if (ps->psnake->x == 0 || ps->psnake->x == 56
- || ps->psnake->y == 0 || ps->psnake->y == 26)
- ps->state = KILL_WALL;
- }
- //判断贪吃蛇是否撞到自己
- void KillBySelf(pSnake ps)
- {
- pSnakenode pcur = ps->psnake->next;
- while (pcur)
- {
- if (pcur->x == ps->psnake->x && pcur->y == ps->psnake->y)
- {
- ps->state = KILL_SELF;
- break;
- }
- pcur = pcur->next;
- }
- }
- //游戏运行
- void GameRun(pSnake ps)
- {
- do
- {
- Printgame(ps);
- //判断按键是否被按过
- if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
- {
- 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();
- }
- else if (KEY_PRESS(VK_ESCAPE)) //游戏正常退出
- {
- ps->state = NORMAL_END;
- break;
- }
- else if (KEY_PRESS(VK_F3))
- {
- if (ps->sleep_time >= 100)
- {
- ps->sleep_time -= 50;
- ps->food_scores += 5;//设定食物分数最高25
- }
- }
- else if (KEY_PRESS(VK_F4))
- {
- if (ps->sleep_time < 300)
- {
- ps->sleep_time += 100;
- ps->food_scores -= 5;//⼀个⻝物分数最低是5分
- }
- }
- Sleep(ps->sleep_time);
- //贪吃蛇的移动
- SnakeMove(ps);
- //判断贪吃蛇是否撞墙
- KillByWall(ps);
- //判断贪吃蛇是否撞到自己
- KillBySelf(ps);
-
- } while (ps->state == OK);
-
- }
- //游戏结束
- void GameOver(pSnake ps)
- {
- SetPos(8, 12);
- switch(ps->state)
- {
- case KILL_WALL:
- wprintf(L"Sorry,game over because you hit the wall !\n");
- break;
- case KILL_SELF:
- wprintf(L"Sorry,game over because you hit youself !\n");
- break;
- case NORMAL_END:
- wprintf(L"Game exits normally !");
- break;
- }
-
- //释放贪吃蛇的节点内存
- pSnakenode pcur = ps->psnake;
- while (pcur)
- {
- pSnakenode del = pcur;
- pcur = pcur->next;
- free(del);
- }
- ps->psnake = NULL;
- }
-
- void KeyFun()
- {
- while (_kbhit())
- {
- int key = _getch();
- }
- }
test.c
- #include"Snake.h"
-
- void test()
- {
- Snake snake = { 0 };
- int ch = 0;
- do
- {
- ch = 0;
- system("cls");
- //游戏初始化
- GameStart(&snake);
- //游戏运行
- GameRun(&snake);
- //游戏结束
- GameOver(&snake);
- KeyFun();
- SetPos(30, 20);
- wprintf(L"再来一局吗? (Y/N)");
- ch = getchar();
- while (getchar() != '\n');
- } while (ch == 'Y' || ch == 'y');
- SetPos(0, 27);
- }
- int main()
- {
- //本地化
- setlocale(LC_ALL, "");
- srand((unsigned int)time(NULL));
- test();
- //KeyFun();
- return 0;
- }
制作不易,如果本篇内容对你有帮助,可以一键三连支持一下!!!
如有错误的地方,也请各位大佬们指出纠正
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。