贪吃蛇游戏分析
贪吃蛇游戏简介
贪吃蛇与扫雷游戏、俄罗斯方块一样,都是久负盛名的小游戏,学会用C语言实现贪吃蛇游戏,有助于我们更好的理解C语言的语法。
贪吃蛇所涉及到的技术包含函数、结构体、枚举、指针、Win32API等。
其中除了Win32API之外,其他都是C语言的语法,当然除了这些之外,还包含C语言的其他一些基础语法,这里我们不过多赘述。
Win32API
Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application), 所以便 称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应⽤程序编程接⼝。
由于Win32API包含很多的函数,而我们在贪吃蛇游戏实现的过程中,只需要用到Win32API中的部分函数,所以我们只对游戏实现过程中需要用到的Win32API函数进行讲解,其余函数不过多赘述。
GetStdHandle
GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
实例:
GetConsoleCursonInfo
检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息。
实例:
CONSOLE_CURSOR_INFO
这个结构体,包含有关控制台光标的信息。
- dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。
- bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE。
SetConsoleCursonInfo
设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。
实例:
控制台屏幕上的坐标COORD
COORD 是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系 (0,0) 的原点位于缓冲区的顶部左侧单元格。
COORD类型的声明:
给坐标赋值:
SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。
实例:
SetPos:封装⼀个设置光标位置的函数
GetAsyncKeyState
获取按键情况,GetAsyncKeyState的函数原型如下:
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
游戏界面
初始页面:
功能介绍:
游戏运行界面:
贪吃蛇游戏实现
根据前面对贪吃蛇游戏的分析,我们知道了完成该游戏所需要的C语言技能及相关的Win32API函数,最后也展示了游戏运行过程中的的各种界面,接下来我们将游戏的整体运行分为三个部分(三个函数),分别为游戏初始化、游戏运行、游戏结束。
游戏初始化
游戏初始化的主要功能是设置游戏的欢迎界面、对游戏功能进行介绍,创建墙、创建蛇、创建食物。
当然在设置前面介绍的功能之前,我们还需要对蛇身结点的结构、蛇的结构、控制台的大小,控制台的标题以及控制台的光标进行设置,如下图:
Welcome()函数
welcome函数主要是对游戏的初始界面进行设置,包含欢迎界面和功能介绍,如下图:
运行效果如下:
CreateMap()函数
CreateMap()函数主要是对游戏中的墙体进行绘制,在绘制墙体之前我们还需要移动控制台的光标,由于墙体使用宽字符进行绘制,宽字符占用两个字节的空间,且使用宽字符需要进行本地化,具体实现如下:
效果如下:
CreateSnake()函数
在游戏初始化过程中,我们还需要创建蛇,由于蛇的身体使用宽字符进行绘制,宽字符占用两个字节的空间,所以我们蛇身的x坐标只能为偶数,除此之外还需要对蛇的其他信息进行设置,如图:
//创建蛇
void CreateSnake(pSnake ps)
{
int i = 0;
for (i = 0; i < 5; i++)
{
pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnextnode == NULL)
{
perror("CreateSnake>malloc");
return;
}
pnextnode->x = POS_X + 2 * i;
pnextnode->y = POS_Y;
pnextnode->next = NULL;
//头插
if (ps->_pSnake == NULL)
{
ps->_pSnake = pnextnode;
}
else
{
pnextnode->next = ps->_pSnake;
ps->_pSnake = pnextnode;
}
}
//打印蛇
pSnakeNode cur = ps->_pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//设置其他信息
ps->_dir = RIGHT;
ps->_Food_Weight = 10;
ps->_score = 0;
ps->_Sleep_Time = 200;
ps->_status = OK;
}
实现效果如下:
CreateFood()函数
除了对蛇的身体进行初始化外,还需要创建食物,由于食物是在一个范围内随机生成的,因此还需要使用随机数,同样食物也是使用宽字符,占用两个字节的空间,食物的x坐标也只能为偶数,且食物不能与蛇的身体重合,如图:
//创建食物
void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
//生成随机坐标
do
{
x = 2 + rand() % 53;
y = 1 + rand() % 25;
} while (x % 2 != 0);
//判断是否与蛇重合
pSnakeNode cur = ps->_pSnake;
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
//创建食物结点
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreateFood>malloc");
return;
}
pFood->x = x;
pFood->y = y;
SetPos(x, y);
wprintf(L"%lc", FOOD);
ps->_pFood = pFood;
}
实现效果如下:
游戏运行
游戏的运行包含:计算总得分和当前食物的分数、打印提示信息、检测按键情况、蛇走一步的过程、蛇走一步后的休眠时间等,还需要对蛇当前的状态进行判断,如图:
//游戏运行
void GameRun(pSnake ps)
{
//打印提示信息
PrintTip();
do
{
SetPos(62, 10);
printf("总得分:%d", ps->_score);
SetPos(62, 11);
printf("当前食物分数:%2d", ps->_Food_Weight);
//判断按键
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_ESCAPE))
{
//退出游戏
ps->_status = END_NORMAL;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停
Pause();
}
else if (KEY_PRESS(VK_F3))
{
//加速
if (ps->_Sleep_Time >= 80)
{
ps->_Sleep_Time -= 30;
ps->_Food_Weight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//减速
if (ps->_Food_Weight > 2)
{
ps->_Sleep_Time += 30;
ps->_Food_Weight -= 2;
}
}
//走一步
SnakeMove(ps);
//休息一下
Sleep(ps->_Sleep_Time);
} while (ps->_status == OK);
}
检测按键:
暂停:
PrintTip()函数
该函数用来打印提示信息,如图:
效果如下:
SnakeMove()函数
该函数是蛇走一步的过程,需要创建下一个结点,判断当前按键的方向,设置下一个结点的x坐标和y坐标,判断下一个结点是不是食物,若是食物就吃掉食物,若不是食物就头插下一个结点,然后将最后一个结点打印成空格并释放掉,最后还要判断蛇是否撞墙或者撞到自己,如图:
//走一步
void SnakeMove(pSnake ps)
{
//为下一个结点分配内存
pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnextnode == NULL)
{
perror("SnakeMove>malloc");
return;
}
switch (ps->_dir)
{
case UP:
pnextnode->x = ps->_pSnake->x;
pnextnode->y = ps->_pSnake->y - 1;
break;
case DOWN:
pnextnode->x = ps->_pSnake->x;
pnextnode->y = ps->_pSnake->y + 1;
break;
case LEFT:
pnextnode->x = ps->_pSnake->x - 2;
pnextnode->y = ps->_pSnake->y;
break;
case RIGHT:
pnextnode->x = ps->_pSnake->x + 2;
pnextnode->y = ps->_pSnake->y;
break;
}
//判断下一个结点是否是食物
if (NextIsFood(pnextnode,ps))
{
//下一步是食物
EatFood(pnextnode, ps);
}
else
{
//下一步不是食物
NoFood(pnextnode, ps);
}
//判断是否撞墙
KILLBYWALL(ps);
//判断是否撞到自己
KILLBYSELF(ps);
}
NextIsFood()函数
该函数用来判断下一节点是否为食物,是食物返回1,不是食物返回0,如图:
EatFood()函数
该函数用来吃掉食物,即将下一个结点头插到蛇身中并打印蛇身,如图:
NoFood()函数
如果下一个结点不是食物,就使用该函数,即头插下一个结点,然后将最后一个结点打印成空格并释放掉,如图:
KILLBYWALL()函数
该函数用来判断蛇是否撞到墙,如果撞到墙,就修改蛇的状态,如图:
KILLBYSELF()函数
该函数用来判断蛇是否撞到自己,如果撞到自己,就修改蛇的状态,如图:
游戏结束
当游戏运行结束后,我们就需要进行一些善后工作,我们可以根据蛇的状态判断游戏是怎样退出的,比如撞到墙、撞到自己或者主动退出,但是无论是以何种方式退出游戏的,最后都要把动态分配的空间释放掉,如图:
贪吃蛇游戏最终代码
头文件Snake.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <stdbool.h>
#include <locale.h>
#include <time.h>
#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//蛇的方向
enum DIRECTION
{
UP,
DOWN,
LEFT,
RIGHT
};
//蛇的状态
enum STATUS
{
OK,
KILL_BY_WALL,
KILL_BY_SELF,
END_NORMAL
};
//创建蛇身结点的结构
//坐标+指向下一个结点的指针
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
//蛇的结构
typedef struct Snake
{
pSnakeNode _pSnake;
pSnakeNode _pFood;
int _score;
int _Food_Weight;
int _Sleep_Time;
enum DIREWCTION _dir;
enum STATUS _status;
}Snake, * pSnake;
//游戏初始化
void GameInit(pSnake ps);
//欢迎界面
void Welcome();
//移动光标
void SetPos(short x, short y);
//打印地图
void CreateMap();
//创建蛇
void CreateSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//打印提示信息
void PrintTip();
//暂停
void Pause();
//走一步
void SnakeMove(pSnake ps);
//判断下一个结点是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);
//下一步是食物
void EatFood(pSnakeNode pn, pSnake ps);
//下一步不是食物
void NoFood(pSnakeNode pn, pSnake ps);
//判断是否撞墙
void KILLBYWALL(pSnake ps);
//判断是否撞到自己
void KILLBYSELF(pSnake ps);
//游戏结束-善后工作
void GameEnd(pSnake ps);
源文件Snake.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Snake.h"
//移动光标
void SetPos(short x, short y)
{
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(houtput, pos);
}
//欢迎界面
void Welcome()
{
SetPos(35, 14);
printf("欢迎来到贪吃蛇小游戏");
SetPos(38, 24);
system("pause");
system("cls");
SetPos(28, 14);
printf("用↑.↓.←.→分别控制蛇的移动,F3为加速,F4为减速");
SetPos(35, 15);
printf("加速将能得到更高的分数");
SetPos(38, 24);
system("pause");
system("cls");
}
//打印地图
void CreateMap()
{
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 <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
//创建蛇
void CreateSnake(pSnake ps)
{
int i = 0;
for (i = 0; i < 5; i++)
{
pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnextnode == NULL)
{
perror("CreateSnake>malloc");
return;
}
pnextnode->x = POS_X + 2 * i;
pnextnode->y = POS_Y;
pnextnode->next = NULL;
//头插
if (ps->_pSnake == NULL)
{
ps->_pSnake = pnextnode;
}
else
{
pnextnode->next = ps->_pSnake;
ps->_pSnake = pnextnode;
}
}
//打印蛇
pSnakeNode cur = ps->_pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//设置其他信息
ps->_dir = RIGHT;
ps->_Food_Weight = 10;
ps->_score = 0;
ps->_Sleep_Time = 200;
ps->_status = OK;
}
//创建食物
void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
//生成随机坐标
do
{
x = 2 + rand() % 53;
y = 1 + rand() % 25;
} while (x % 2 != 0);
//判断是否与蛇重合
pSnakeNode cur = ps->_pSnake;
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
//创建食物结点
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreateFood>malloc");
return;
}
pFood->x = x;
pFood->y = y;
SetPos(x, y);
wprintf(L"%lc", FOOD);
ps->_pFood = pFood;
}
//游戏初始化
void GameInit(pSnake ps)
{
//设置窗口
system("mode con cols=100 lines=32");
system("title 贪吃蛇");
//隐藏光标
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(houtput, &cursor_info);
cursor_info.bVisible = false;
SetConsoleCursorInfo(houtput, &cursor_info);
//欢迎界面
Welcome();
//打印地图
CreateMap();
//创建蛇
CreateSnake(ps);
//创建食物
CreateFood(ps);
}
//打印提示信息
void PrintTip()
{
SetPos(62, 15);
printf("不能穿墙,不能咬到自己");
SetPos(62, 16);
printf("用↑.↓.←.→分别控制蛇的移动");
SetPos(62, 17);
printf("F3为加速,F4为减速");
SetPos(62, 18);
printf("按ESC退出游戏,按space暂停游戏");
}
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&1)?1:0)
//暂停
void Pause()
{
while (1)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//判断下一个结点是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{
if (pn->x == ps->_pFood->x && pn->y == ps->_pFood->y)
{
return 1;
}
else
{
return 0;
}
}
//下一步是食物
void EatFood(pSnakeNode pn, pSnake ps)
{
//头插
ps->_pFood->next = ps->_pSnake;
ps->_pSnake = ps->_pFood;
//释放下一个结点
free(pn);
pn = NULL;
//打印蛇
pSnakeNode cur = ps->_pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//加分数
ps->_score += ps->_Food_Weight;
//重新创建食物
CreateFood(ps);
}
//下一步不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{
//头插
pn->next = ps->_pSnake;
ps->_pSnake = pn;
//打印前面的结点,将最后一个结点释放掉
pSnakeNode cur = ps->_pSnake;
while (cur->next->next != NULL)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
//将最后一个结点打印成空格
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
//判断是否撞墙
void KILLBYWALL(pSnake ps)
{
if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
{
ps->_status = 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->_status = KILL_BY_SELF;
}
cur = cur->next;
}
}
//走一步
void SnakeMove(pSnake ps)
{
//为下一个结点分配内存
pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnextnode == NULL)
{
perror("SnakeMove>malloc");
return;
}
switch (ps->_dir)
{
case UP:
pnextnode->x = ps->_pSnake->x;
pnextnode->y = ps->_pSnake->y - 1;
break;
case DOWN:
pnextnode->x = ps->_pSnake->x;
pnextnode->y = ps->_pSnake->y + 1;
break;
case LEFT:
pnextnode->x = ps->_pSnake->x - 2;
pnextnode->y = ps->_pSnake->y;
break;
case RIGHT:
pnextnode->x = ps->_pSnake->x + 2;
pnextnode->y = ps->_pSnake->y;
break;
}
//判断下一个结点是否是食物
if (NextIsFood(pnextnode,ps))
{
//下一步是食物
EatFood(pnextnode, ps);
}
else
{
//下一步不是食物
NoFood(pnextnode, ps);
}
//判断是否撞墙
KILLBYWALL(ps);
//判断是否撞到自己
KILLBYSELF(ps);
}
//游戏运行
void GameRun(pSnake ps)
{
//打印提示信息
PrintTip();
do
{
SetPos(62, 10);
printf("总得分:%d", ps->_score);
SetPos(62, 11);
printf("当前食物分数:%2d", ps->_Food_Weight);
//判断按键
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_ESCAPE))
{
//退出游戏
ps->_status = END_NORMAL;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停
Pause();
}
else if (KEY_PRESS(VK_F3))
{
//加速
if (ps->_Sleep_Time >= 80)
{
ps->_Sleep_Time -= 30;
ps->_Food_Weight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//减速
if (ps->_Food_Weight > 2)
{
ps->_Sleep_Time += 30;
ps->_Food_Weight -= 2;
}
}
//走一步
SnakeMove(ps);
//休息一下
Sleep(ps->_Sleep_Time);
} while (ps->_status == OK);
}
//游戏结束-善后工作
void GameEnd(pSnake ps)
{
SetPos(22, 12);
switch (ps->_status)
{
case END_NORMAL:
printf("您主动退出,游戏结束!");
break;
case KILL_BY_WALL:
printf("您撞到墙,游戏结束!");
break;
case KILL_BY_SELF:
printf("您撞到自己,游戏结束!");
break;
}
//释放蛇身
pSnakeNode cur = ps->_pSnake;
while (cur)
{
pSnakeNode next = cur->next;
free(cur);
cur = next;
}
}
测试文件test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Snake.h"
void test()
{
int ch = 0;
do
{
system("cls");
//创建蛇
Snake snake = { 0 };
//游戏初始化
GameInit(&snake);
//游戏运行
GameRun(&snake);
//游戏结束-善后工作
GameEnd(&snake);
SetPos(22, 13);
printf("您是否还要继续(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();
return 0;
}