一.基本知识
要实现这样的一个小游戏,需要掌握结构体,指针,链表,函数,分支循环语句,枚举等,绝大部分的C语言知识都运用到了。
还需要了解下Win32API的一些知识,使用这些知识时,需要包含一个Windows.h的头文件
Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外, 它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口。
下面就根据需要去学习一下。
1.窗口
游戏时,需要改变窗口的大小和标题
控制台窗口可以看成一个平面坐标轴,左上角是原点(0,0),上方是x轴,从左往右x值增加,左边是y轴,从上往下y值增加。
system("mode con cols=100 lines=30");//改变窗口大小
system("title 贪吃蛇"); //修改窗口标题
这两句代码可以修改窗口的大小和标题,大小改为30行和100列。
图中还会看到中间有一句:欢迎来到贪吃蛇小游戏!这句话使用printf函数就可以打印,但是打印是从光标的位置开始打印,光标默认在坐标(0,0)处,我们需要修改这个光标的位置。
COORD
这是一个结构体,它的值表示控制台屏幕上的坐标
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
X,Y就表示屏幕上的坐标。
要修改还需要获得一个句柄,来操作我们的设备。
(1)GetStdHandle函数
这个函数可以获得一个句柄(标准输入,输出,错误)
(2)SetConsoleCurorInfo函数
函数的第一个参数是控制台屏幕缓冲区的句柄,第二个参数是一个指针,指针指向一个结构体类型的数据
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
成员变量的含义:
dwSize:由光标填充的字符单元的百分比。 该值介于 1 到 100 之间。 光标外观各不相同,范围从完全填充单元到显示为单元底部的横线。
bVisible:光标的可见性。(将值改为false后,光标可以隐藏)
通过以上两个函数我们可以改变光标的位置。
int main()
{
printf("AB");
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出的句柄
COORD pos = { 15,20 }; //修改坐标
SetConsoleCursorPosition(handle, pos); //通过句柄来设置(改变) 光标的位置
printf("AB");
return 0;
}
结果如下:
游戏过程由于需要多次修改光标,因此将修改光标的代码封装成一个函数
//改变光标位置
void setPos(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(handle, pos);
}
通过传入x和y,来改变光标位置
(3)GetConsoleCurInfo函数
如果程序处于运行状态,光标会一直闪烁,影响玩家的感受,我们可以将光标隐藏起来。
通过上面三个函数,实现以下代码来隐藏光标。
int main()
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出的句柄
CONSOLE_CURSOR_INFO cursorInfo; //创建一个CONSOLE_CURSOR_INFO类型的变量
GetConsoleCursorInfo(handle, &cursorInfo); //获得操作屏幕光标的权限
cursorInfo.bVisible = false; //修改可见性
SetConsoleCursorInfo(handle, &cursorInfo); //设置属性
getchar();
return 0;
}
2.宽字符
我们平时打印的字符如26个英文字母等,这些属于窄字符,宽字符占两个字节,窄字符占一个字节,所以打印时窄字符会占用一个格子,而宽字符会占用两个格子。
打印时需要用wprintf函数。
打印格式为:
wprintf(L"%lc",L’…');
如下图中所示。
其次,在打印宽字符前,需要修改本地环境。
(1)setlocale函数
第一个参数是要修改的类项,第二个参数是修改环境,第二个参数只有两种"C"和" "。
如果第一个参数值为LC_ALL,那么会修改全部的类型。
使用这个函数前需要包含一个locale.h的头文件
setlocale(LC_ALL, “”); //修补适配本地环境。
(2)检测按键
GetAsyncKeyState函数可以实现检测键盘中的按键是否被按下
函数原型如下
SHORT GetAsyncKeyState(
[in] int vKey
);
函数参数是被检测的按键的虚拟键值,返回值类型为short类型。
在上⼀次调用 GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;
如果最低位被置为1则说明,该按键被按过,否则为0
可以根据最低为的值来判断按键有没有被按下
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
GetAsyncKeyState 函数的返回值和1按位与,如果结果为1,说明该键被按过,为0则没有被按过
通过定义这样一个宏函数来检测按键是否被按过。
二.贪吃蛇实现
有了上面的基础外加C语言知识,我们就可以开始实现贪吃蛇游戏了。
贪吃蛇游戏可以分为开始前,游戏中和结束后。
1.游戏前
(1)设置游戏窗⼝的⼤⼩
窗口大小设置为100列,30行
system(“mode con cols=100 lines=30”);
(2) 设置窗⼝的名字
窗口名字设置为贪吃蛇,大家也可以做修改
system(“title 贪吃蛇”);
(3) 隐藏屏幕光标
使用上面介绍的三个函数来隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出的句柄
CONSOLE_CURSOR_INFO cursorInfo; //创建一个CONSOLE_CURSOR_INFO类型的变量
GetConsoleCursorInfo(handle, &cursorInfo); //获得操作屏幕光标的权限
cursorInfo.bVisible = false; //修改可见性
SetConsoleCursorInfo(handle, &cursorInfo); //设置属性
(4)打印欢迎界⾯-WelcomeToGame
使用我们封装的setPos函数来修改光标位置,然后进行信息的打印。
使用system(“pause”);可以出现下方图中的:请按任意键继续…等字样
void WelcomeToGame()
{
setPos(50, 15);
printf("欢迎来到贪吃蛇小游戏!");
setPos(53, 25);
system("pause");//暂停
system("cls"); //清屏
setPos(40, 15);
printf("游戏规则:用← → ↑ ↓ 控制蛇的移动,吃食物(¥)获得分数");
setPos(49, 16);
printf("空格暂停游戏,Esc退出游戏,数字0减速,数字1加速");
setPos(49, 17);
printf("改变速度可以改变食物分数");
setPos(53, 25);
system("pause");
system("cls");
}
按下任意键后跳到如下界面,在按下任意键后会跳到主界面
(5)创建地图-GetMap
创建一个58列,27行的一个地图,地图打印的是汉字’墙’。大家也可以选择自己喜欢的字符。
由于可能需要多次打印字符,宏定义了几个常量
#define WALL L'墙' //表示墙
#define SNAKE L'@' //表示蛇
#define FOOD L'¥' //表示食物
//下面两个表示蛇身第一个结点的坐标
#define SNAKE_X 24
#define SNAKE_Y 5
创建地图代码如下
void GetMap()
{
//地图上方
for (int i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//地图下方
setPos(0, 26);
for (int i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//地图两边
for (int i = 1; i < 27; i++)
{
setPos(0, i);
wprintf(L"%lc", WALL);
setPos(56, i);
wprintf(L"%lc", WALL);
}
}
绘制完地图后,接下来完成蛇的创建;
(6) 初始化蛇⾝-InitSnake
蛇用链表表示,那么需要一个蛇身的结构体类型
具体内容如下代码
//蛇身结点结构体
typedef struct snakeNode
{
int x;
int y;
struct snakeNode* next;
}snakeNode,*psnakeNode;
snakeNode是这个结构体的别名,而psnakeNode是这个结构体指针的别名
除了蛇,还需要一些其他的信息,如食物分数,当前的总分数,休眠的时间(休眠时间越短速度越快),蛇运动的方向,指向蛇头的指针,指向食物的指针以及当前的游戏状态(正常,咬到自己,撞墙,按Esc退出)等
所以另外创建一个结构体类型,包括以上的全部成员。
具体代码如下
typedef struct snake
{
psnakeNode pSnake; //维护蛇的指针
psnakeNode pFood; //维护食物的指针
int sum_sorce; //总的分数
int food_sorce; //每个食物的分数
int sleep; //走一步休息的时间,决定速度
enum DIRECTION direct; //蛇当前的方向
enum GAME_STATUS status; //游戏当前的状态
}snake,*psnake;
蛇的方向只有上下左右,而游戏状态也只有有限个,可以把它们枚举出来。
//枚举出所有方向
enum DIRECTION
{
UP=1,
DOWN,
LEFT,
RIGHT
};
//枚举出游戏的所有状态
enum GAME_STATUS
{
OK=1, //正常
ESC, //按Esc退出
KILLED_BY_WALL, //撞墙退出
KILLED_BY_SEIF //咬到自己
};
完成以上任务后,就可以来初始化并打印蛇了。
接下来画图来分析如何实现代码。
蛇身长默认初始化为5代码如下
//初始化蛇
void InitSnake(psnake ps)
{
psnakeNode cur = NULL;
//默认蛇长为5
for (int i = 0; i < 5; i++)
{
cur = (psnakeNode)malloc(sizeof(snakeNode));
if (cur == NULL)
{
perror("InitSnake(psnake ps) malloc:");
return;
}
//蛇身坐标
cur->x = SNAKE_X + 2 * i;
cur->y = SNAKE_Y;
cur->next = NULL;
//头插法创建蛇
if (ps->pSnake == NULL)
{
ps->pSnake = cur;
}
else
{
cur->next = ps->pSnake;
ps->pSnake = cur;
}
}
cur = ps->pSnake; //cur指向蛇头
//打印蛇身
while (cur)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
//初始化其他信息
ps->direct = RIGHT; //方向默认为右
ps->pFood = NULL;
ps->food_sorce = 15; //食物分数为15
ps->sleep = 100; //休眠时间0.1s
ps->sum_sorce = 0; //初始总分为0分
ps->status = OK; //初始游戏状态正常
}
打印蛇身,实际上就是遍历链表,创建蛇,就是创建链表。
(7)创建⻝物-CreateFood
有了蛇,还需要食物,要将食物显示在屏幕上,需要坐标,因此创建一个snakeNode类型的变量,作为食物。
生成食物需要注意,食物是随机生成的,而且不能在墙外,因此坐标受到限制。其次,食物不能和蛇身重叠。
创建食物的代码如下
//创建食物
void CreateFood(psnake ps)
{
int x = 0;
int y = 0;
psnakeNode cur = NULL;
do
{
psnakeNode cur = ps->pSnake;
//随机生成食物坐标
x = rand() % 54 + 2;
y = rand() % 25 + 1;
if (x % 2 == 1)
x--;
//食物不能和蛇身重叠
while (cur&&(cur->x!=x&&cur->y!=y))
{
cur = cur->next;
}
} while (cur);
//创建食物和打印食物
psnakeNode food=(psnakeNode)malloc(sizeof(snakeNode));
if (food == NULL)
{
perror("CreateFood(psnake ps) malloc:");
return;
}
food->x = x;
food->y = y;
food->next = NULL;
ps->pFood = food;
//打印食物
setPos(food->x, food->y);
wprintf(L"%lc", FOOD);
}
由于墙面和蛇身是宽字符,占两个字节,因此食物的横坐标需要一定的限制,否则可能会出现问题。
程序要从主函数开始运行,主函数的代码很简短
void game()
{
char ch;
//循环可以多次进行游戏
do
{
snake s = { 0 };
GameStart(&s);//开始前
GameRun(&s); //进行中
GameEnd(&s); //结束后
setPos(30, 17);
Sleep(2000); //休眠2s
printf("再玩一次(Y/N)");
ch = getchar();
getchar();
} while (ch=='Y'||ch=='y');
}
int main()
{
setlocale(LC_ALL, ""); //修改适配本地环境
game();
setPos(0, 28);
return 0;
}
GameStart函数的实现如下
//游戏开始前
void GameStart(psnake ps)
{
//设置窗口大小和窗口名
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(handle, &cursorInfo);
cursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &cursorInfo);
WelcomeToGame();//基本游戏信息
GetMap();//绘制地图
InitSnake(ps);//初始化蛇
CreateFood(ps);//创建食物
}
2.游戏中
游戏进行时,需要控制蛇的移动,需要打印一些帮助信息以及分数信息
当我们调整方向时,需要判断下一个位置处是不是食物,如果是就吃掉并生成新的食物,如果不是食物就从该位置开始打印蛇身,并释放掉尾节点。走一步就休眠0.2s,套上循环,就实现了蛇的运动。
在进行游戏时,需要检测按键是否被按下
实现一个宏函数来完整这一操作
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
游戏进行时的完整代码如下
void GameRun(psnake ps)
{
//打印帮助信息
setPos(65, 15);
printf("空格暂停游戏,Esc退出游戏");
setPos(65, 16);
printf("数字1加速,数字0减速");
do
{
//打印分数
setPos(65, 10);
printf("总分:%d",ps->sum_sorce);
setPos(65, 12);
printf("食物分数:%02d", ps->food_sorce);
//帮助信息
//检测上下左右
if (KEY_PRESS(VK_UP) && ps->direct != DOWN)
{
ps->direct = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->direct != UP)
{
ps->direct = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->direct != RIGHT)
{
ps->direct = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->direct != LEFT)
{
ps->direct = RIGHT;
}
//空格暂停游戏
else if (KEY_PRESS(VK_SPACE))
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//Esc退出游戏
else if (KEY_PRESS(VK_ESCAPE))
{
ps->status = ESC;
break;
}
//数字1加速
else if (KEY_PRESS(VK_NUMPAD1))
{
if (ps->sleep > 25)
{
ps->sleep -= 25;
ps->food_sorce += 5;
}
}
//数字0减速
else if(KEY_PRESS(VK_NUMPAD0))
{
if (ps->food_sorce > 5)
{
ps->food_sorce -= 5;
ps->sleep += 25;
}
}
//移动一步
SnakeMove(ps);
Sleep(ps->sleep);
} while (ps->status == OK);
}
(1)根据蛇头的坐标和⽅向,计算下⼀个节点的坐标
//ps:psnake类型的形参
//创建下一个结点
psnakeNode nextNode = (psnakeNode)malloc(sizeof(snakeNode));
if (nextNode == NULL)
{
perror("SnakeMove(psnake ps) malloc");
return;
}
nextNode->next = NULL;
//确定下个结点坐标
switch (ps->direct)
{
case UP:
nextNode->x = ps->pSnake->x;
nextNode->y = ps->pSnake->y - 1;
break;
case DOWN:
nextNode->x = ps->pSnake->x;
nextNode->y = ps->pSnake->y + 1;
break;
case LEFT:
nextNode->x = ps->pSnake->x - 2;
nextNode->y = ps->pSnake->y;
break;
case RIGHT:
nextNode->x = ps->pSnake->x + 2;
nextNode->y = ps->pSnake->y - 1;
break;
}
(2)下⼀个节点是⻝物
//下一个结点位置处是食物
if (nextNode->x == ps->pFood->x && nextNode->y == ps->pFood->y)
{
//头插到蛇身
nextNode->next = ps->pSnake;
ps->pSnake = nextNode;
//修改分数
ps->sum_sorce += ps->food_sorce;
psnakeNode cur = ps->pSnake;
//重新打印蛇身
while (cur)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
//释放旧的食物
free(ps->pFood);
//生成新的食物
CreateFood(ps);
}
(3)不是⻝物,尾巴删除⼀节
//下一个结点处不是食物
else
{
//头插到蛇头
nextNode->next = ps->pSnake;
ps->pSnake = nextNode;
psnakeNode cur = ps->pSnake;
//找尾结点并同时打印蛇身
while (cur->next->next)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
//释放尾结点
setPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
(4) 判断是否撞墙-Kill_By_Wall
//检测是否撞墙
void Kill_By_Wall(psnake ps)
{
//满足其一就撞墙
if (ps->pSnake->x == 0 ||
ps->pSnake->x == 56 ||
ps->pSnake->y == 0 ||
ps->pSnake->y == 26)
{
//修改游戏状态
ps->status = KILLED_BY_WALL;
}
}
(5)判断是否装上⾃⼰-Kill_By_Self
//检测是否咬到自己
void Kill_By_Self(psnake ps)
{
psnakeNode cur = ps->pSnake->next;
//从蛇第二个结点开始遍历
while (cur)
{
//坐标完全相同,即咬到自己了
if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
{
//修改游戏状态
ps->status = KILLED_BY_SEIF;
return;
}
cur = cur->next;
}
}
当完成到这一步时,游戏已经能运行起来了,但是游戏结束后,玩家是如何结束游戏的(主动退出,撞墙,咬到自己)以及资源的释放等,这些任务还需要完成。
蛇的移动的完整代码如下
//蛇的移动
void SnakeMove(psnake ps)
{
//创建下一个结点
psnakeNode nextNode = (psnakeNode)malloc(sizeof(snakeNode));
if (nextNode == NULL)
{
perror("SnakeMove(psnake ps) malloc");
return;
}
nextNode->next = NULL;
//确定下个结点坐标
switch (ps->direct)
{
case UP:
nextNode->x = ps->pSnake->x;
nextNode->y = ps->pSnake->y - 1;
break;
case DOWN:
nextNode->x = ps->pSnake->x;
nextNode->y = ps->pSnake->y + 1;
break;
case LEFT:
nextNode->x = ps->pSnake->x - 2;
nextNode->y = ps->pSnake->y;
break;
case RIGHT:
nextNode->x = ps->pSnake->x + 2;
nextNode->y = ps->pSnake->y - 1;
break;
}
//下一个结点位置处是食物
if (nextNode->x == ps->pFood->x && nextNode->y == ps->pFood->y)
{
nextNode->next = ps->pSnake;
ps->pSnake = nextNode;
ps->sum_sorce += ps->food_sorce;
psnakeNode cur = ps->pSnake;
while (cur)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
free(ps->pFood);
CreateFood(ps);
}
//下一个结点处不是食物
else
{
nextNode->next = ps->pSnake;
ps->pSnake = nextNode;
psnakeNode cur = ps->pSnake;
while (cur->next->next)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
setPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
//检测是否撞墙
Kill_By_Wall(ps);
//检测是否咬到自己
Kill_By_Self(ps);
}
3.游戏后
当游戏结束后,需要进行以下的一些操作。
游戏结束后的完整代码
//游戏结束
void GameEnd(psnake ps)
{
setPos(30, 15);
switch (ps->status)
{
case ESC:
printf("正常退出游戏!");
break;
case KILLED_BY_WALL:
printf("撞墙,游戏结束!");
break;
case KILLED_BY_SEIF:
printf("咬到自己,游戏结束!");
break;
}
psnakeNode del = ps->pSnake;
psnakeNode cur = NULL;
while (del)
{
cur = del->next;
free(del);
del = cur;
}
ps->pSnake = NULL;
free(ps->pFood);
ps->pFood = NULL;
}
(1)告知结束原因
switch-case语句就可以解决这个问题
setPos(30, 15);
switch (ps->status)
{
case ESC:
printf("正常退出游戏!");
break;
case KILLED_BY_WALL:
printf("撞墙,游戏结束!");
break;
case KILLED_BY_SEIF:
printf("咬到自己,游戏结束!");
break;
}
(2)资源释放
由于结点是从堆上开辟的,所以游戏结束后需要主动去free掉这些内存并将指针置NULL。
psnakeNode del = ps->pSnake;
psnakeNode cur = NULL;
while (del)
{
cur = del->next;
free(del);
del = cur;
}
ps->pSnake = NULL;
free(ps->pFood);
ps->pFood = NULL;
4.源代码
分为三个文件
snake.h
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<Windows.h>
#include<locale.h>
//←→↑↓¥@
#define WALL L'墙'
#define SNAKE L'@'
#define FOOD L'¥'
#define SNAKE_X 24
#define SNAKE_Y 5
//宏函数,检测键盘某按键是否被按下
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
//枚举出所有方向
enum DIRECTION
{
UP=1,
DOWN,
LEFT,
RIGHT
};
//枚举出游戏的所有状态
enum GAME_STATUS
{
OK=1, //正常
ESC, //按Esc退出
KILLED_BY_WALL, //撞墙退出
KILLED_BY_SEIF //咬到自己
};
//蛇身结点结构体
typedef struct snakeNode
{
int x;
int y;
struct snakeNode* next;
}snakeNode,*psnakeNode;
//维护蛇
typedef struct snake
{
psnakeNode pSnake; //维护蛇的指针
psnakeNode pFood; //维护食物的指针
int sum_sorce; //总的分数
int food_sorce; //每个食物的分数
int sleep; //走一步休息的时间,决定速度
enum DIRECTION direct; //蛇当前的方向
enum GAME_STATUS status; //游戏当前的状态
}snake,*psnake;
//游戏开始前
void GameStart(psnake ps);
//欢迎界面
void WelcomeToGame();
//绘制地图
void GetMap();
//初始化蛇
void InitSnake(psnake ps);
//创建食物
void CreateFood(psnake ps);
//游戏进行中
void GameRun(psnake ps);
//蛇的移动
void SnakeMove(psnake ps);
//修改光标位置
void setPos(int x, int y);
//检测是否撞墙
void Kill_By_Wall(psnake ps);
//检测是否咬到自己
void Kill_By_Self(psnake ps);
//游戏结束
void GameEnd(psnake ps);
snake.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"
//改变光标位置
void setPos(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(handle, pos);
}
//欢迎界面
void WelcomeToGame()
{
setPos(50, 15);
printf("欢迎来到贪吃蛇小游戏!");
setPos(53, 25);
system("pause");//暂停
system("cls"); //清屏
setPos(40, 15);
printf("游戏规则:用← → ↑ ↓ 控制蛇的移动,吃食物(¥)获得分数");
setPos(49, 16);
printf("空格暂停游戏,Esc退出游戏,数字0减速,数字1加速");
setPos(49, 17);
printf("改变速度可以改变食物分数");
setPos(53, 25);
system("pause");
system("cls");
}
//绘制地图
void GetMap()
{
//地图上方
for (int i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//地图下方
setPos(0, 26);
for (int i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//地图两边
for (int i = 1; i < 27; i++)
{
setPos(0, i);
wprintf(L"%lc", WALL);
setPos(56, i);
wprintf(L"%lc", WALL);
}
}
//初始化蛇
void InitSnake(psnake ps)
{
psnakeNode cur = NULL;
//默认蛇长为5
for (int i = 0; i < 5; i++)
{
cur = (psnakeNode)malloc(sizeof(snakeNode));
if (cur == NULL)
{
perror("InitSnake(psnake ps) malloc:");
return;
}
cur->x = SNAKE_X + 2 * i;
cur->y = SNAKE_Y;
cur->next = NULL;
//头插法创建蛇
if (ps->pSnake == NULL)
{
ps->pSnake = cur;
}
else
{
cur->next = ps->pSnake;
ps->pSnake = cur;
}
}
cur = ps->pSnake; //cur指向蛇头
//打印蛇身
while (cur)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
//初始化其他信息
ps->direct = RIGHT; //方向默认为右
ps->pFood = NULL;
ps->food_sorce = 15; //食物分数为15
ps->sleep = 100; //休眠时间0.1s
ps->sum_sorce = 0; //初始总分为0分
ps->status = OK; //初始游戏状态正常
}
//创建食物
void CreateFood(psnake ps)
{
int x = 0;
int y = 0;
psnakeNode cur = NULL;
do
{
psnakeNode cur = ps->pSnake;
//随机生成食物坐标
x = rand() % 54 + 2;
y = rand() % 25 + 1;
if (x % 2 == 1)
x--;
//食物不能和蛇身重叠
while (cur&&(cur->x!=x&&cur->y!=y))
{
cur = cur->next;
}
} while (cur);
//创建食物和打印食物
psnakeNode food=(psnakeNode)malloc(sizeof(snakeNode));
if (food == NULL)
{
perror("CreateFood(psnake ps) malloc:");
return;
}
food->x = x;
food->y = y;
food->next = NULL;
ps->pFood = food;
setPos(food->x, food->y);
wprintf(L"%lc", FOOD);
}
//游戏开始前
void GameStart(psnake ps)
{
//设置窗口大小和窗口名
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(handle, &cursorInfo);
cursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &cursorInfo);
WelcomeToGame();//基本游戏信息
GetMap();//绘制地图
InitSnake(ps);//初始化蛇
CreateFood(ps);//创建食物
}
//检测是否撞墙
void Kill_By_Wall(psnake ps)
{
if (ps->pSnake->x == 0 ||
ps->pSnake->x == 56 ||
ps->pSnake->y == 0 ||
ps->pSnake->y == 26)
{
ps->status = KILLED_BY_WALL;
}
}
//检测是否咬到自己
void Kill_By_Self(psnake ps)
{
psnakeNode cur = ps->pSnake->next;
while (cur)
{
if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
{
ps->status = KILLED_BY_SEIF;
return;
}
cur = cur->next;
}
}
//蛇的移动
void SnakeMove(psnake ps)
{
//创建下一个结点
psnakeNode nextNode = (psnakeNode)malloc(sizeof(snakeNode));
if (nextNode == NULL)
{
perror("SnakeMove(psnake ps) malloc");
return;
}
nextNode->next = NULL;
//确定下个结点坐标
switch (ps->direct)
{
case UP:
nextNode->x = ps->pSnake->x;
nextNode->y = ps->pSnake->y - 1;
break;
case DOWN:
nextNode->x = ps->pSnake->x;
nextNode->y = ps->pSnake->y + 1;
break;
case LEFT:
nextNode->x = ps->pSnake->x - 2;
nextNode->y = ps->pSnake->y;
break;
case RIGHT:
nextNode->x = ps->pSnake->x + 2;
nextNode->y = ps->pSnake->y - 1;
break;
}
//下一个结点位置处是食物
if (nextNode->x == ps->pFood->x && nextNode->y == ps->pFood->y)
{
nextNode->next = ps->pSnake;
ps->pSnake = nextNode;
ps->sum_sorce += ps->food_sorce;
psnakeNode cur = ps->pSnake;
while (cur)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
free(ps->pFood);
CreateFood(ps);
}
//下一个结点处不是食物
else
{
nextNode->next = ps->pSnake;
ps->pSnake = nextNode;
psnakeNode cur = ps->pSnake;
while (cur->next->next)
{
setPos(cur->x, cur->y);
wprintf(L"%lc", SNAKE);
cur = cur->next;
}
setPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
//检测是否撞墙
Kill_By_Wall(ps);
//检测是否咬到自己
Kill_By_Self(ps);
}
//游戏进行中
void GameRun(psnake ps)
{
//打印帮助信息
setPos(65, 15);
printf("空格暂停游戏,Esc退出游戏");
setPos(65, 16);
printf("数字1加速,数字0减速");
do
{
//打印分数
setPos(65, 10);
printf("总分:%d",ps->sum_sorce);
setPos(65, 12);
printf("食物分数:%02d", ps->food_sorce);
//帮助信息
//检测上下左右
if (KEY_PRESS(VK_UP) && ps->direct != DOWN)
{
ps->direct = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->direct != UP)
{
ps->direct = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->direct != RIGHT)
{
ps->direct = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->direct != LEFT)
{
ps->direct = RIGHT;
}
//空格暂停游戏
else if (KEY_PRESS(VK_SPACE))
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//Esc退出游戏
else if (KEY_PRESS(VK_ESCAPE))
{
ps->status = ESC;
break;
}
//数字1加速
else if (KEY_PRESS(VK_NUMPAD1))
{
if (ps->sleep > 25)
{
ps->sleep -= 25;
ps->food_sorce += 5;
}
}
//数字0减速
else if(KEY_PRESS(VK_NUMPAD0))
{
if (ps->food_sorce > 5)
{
ps->food_sorce -= 5;
ps->sleep += 25;
}
}
//移动一步
SnakeMove(ps);
Sleep(ps->sleep);
} while (ps->status == OK);
}
//游戏结束
void GameEnd(psnake ps)
{
setPos(30, 15);
switch (ps->status)
{
case ESC:
printf("正常退出游戏!");
break;
case KILLED_BY_WALL:
printf("撞墙,游戏结束!");
break;
case KILLED_BY_SEIF:
printf("咬到自己,游戏结束!");
break;
}
psnakeNode del = ps->pSnake;
psnakeNode cur = NULL;
while (del)
{
cur = del->next;
free(del);
del = cur;
}
ps->pSnake = NULL;
free(ps->pFood);
ps->pFood = NULL;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"
void game()
{
char ch;
do
{
snake s = { 0 };
GameStart(&s);
GameRun(&s);
GameEnd(&s);
setPos(30, 17);
Sleep(2000);
printf("再玩一次(Y/N)");
ch = getchar();
getchar();
} while (ch=='Y'||ch=='y');
}
int main()
{
setlocale(LC_ALL, "");
game();
setPos(0, 28);
return 0;
}
以上就是C实现贪吃蛇的全过程,有适合修改或者错误的地方,欢迎大家发现并指出!