1.游戏结构体的定义
1.1蛇身节点的定义
贪吃蛇的身体我们需要用到链表的格式,结构体中存储横纵坐标x,y和指向自身的指针next。
控制台的整体坐标逻辑:
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
1.2游戏管理各项内容的结构体
游戏过程我们需要一个结构体来管理整个游戏的状态,它需要有:
①指向蛇头的指针
②指向食物的指针
③当前累计得分
④当前一个食物多少分
⑤蛇移动的速度
⑥游戏当前的状态
需要枚举常量来表示
enum GAME_STATUS
{
OK = 1,
ESC,
KILL_BY_WALL,
KILL_BY_SELF
};
⑦蛇移动的方向
需要枚举常量来表示
enum DIR
{
UP=1,
DOWN,
LEFT,
RIGHT
};
根据以上七点,我们得出总游戏的结构体:
typedef struct Snake
{
pSnakeNode SHead;
pSnakeNode Food;
int Score;
int FoodWeight;
int SleepTime;
enum GAME_STATUS Stauts;
enum DIR dir;
}Snake,*pSnake;
2.游戏开始前的初始化
2.1本地地区化
因为打印地图,蛇身,食物需要用到宽字符,我们需要先进行本地化
头文件加#include<locale.h>,
setlocale(LC_ALL, "");
2.2设置控制台窗口大小,窗口名字,隐藏闪烁的光标
2.2.1窗口大小
我们设置横坐标0到150,纵坐标0到40
system("mode con cols=150 lines=40");
2.2.2窗口名字
system("title 贪吃蛇");
2.2.3隐藏闪烁的光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = { 0 };
GetConsoleCursorInfo(handle, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &CursorInfo);
2.3打印欢迎信息
2.3.1第一个,欢迎界面
我们要打印内容到控制台我们需要的坐标位置,就需要创建一个函数SetPos来帮助我们实现定位操作
void SetPos(int x, int y)
{
HANDLE hOutput = NULL;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(hOutput, pos);
}
完成以后再进行第一个界面的打印就会方便得多
SetPos(60, 17);
printf("欢迎来到贪吃蛇小游戏");
SetPos(63, 29);
system("pause");
system("cls");
2.3.2第二个,规则界面
SetPos(54, 16);
printf("用↑,↓,←,→控制蛇移动方向");
SetPos(54, 17);
printf("F3为加速,F4为减速,加速可以提高得分");
SetPos(63, 29);
system("pause");
system("cls");
2.4绘制地图
对于地图使用的符号,我们暂定
#define WALL L'□'
我们的目标是打印横坐标0到80,纵坐标0到30的地图
void CreatMap()
{
int i = 0;
//上
SetPos(0, 0);
for (i = 0; i <= 80; i += 2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 30);
for (i = 0; i <= 80; i+=2)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 0; i <= 30; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 0; i <= 30; i++)
{
SetPos(80, i);
wprintf(L"%lc", WALL);
}
}
2.5初始化蛇(同时完成游戏总结构体其他信息的初始化)
对于蛇身使用的符号,我们暂定
#define BODY L'●'
对于蛇头的起始坐标,我们暂定
#define POS_X 34
#define POS_Y 15
初始化:
过程中涉及到的影响速度的初始休眠时间,我们暂定
#define SLEEPTIME 200
void InitSnake(pSnake ps)
{
int i = 0;
for (i = 0; i < 5; i++)
{
pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake():malloc");
return;
}
cur->x = POS_X + 2 * i;
cur->y = POS_Y;
cur->next = NULL;
if (ps->SHead == NULL)
{
ps->SHead = cur;
}
else
{
cur->next = ps->SHead;
ps->SHead = cur;
}
}
//建立完成,开始打印蛇身
pSnakeNode cur = NULL;
for (cur = ps->SHead; cur; cur = cur->next)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
}
//顺便完成其他信息的初始化
ps->dir = UP;
ps->Food = NULL;
ps->FoodWeight = 10;
ps->Score = 0;
ps->SleepTime = SLEEPTIME;
ps->Stauts = OK;
}
2.6初始化食物
对于食物,我们暂定
#define FOOD L'¥'
过程中,我们需要完成两件事
①确定一个可以用的坐标
②向内存申请节点,存储到我们总结构体的FOOD中去
void InitFood(pSnake ps)
{
//先确定一个可用的x,y坐标
int x;
int y;
again:
x = 0;
y = 0;
//横坐标不能为偶数
do
{
x = rand() % 77 + 2;
y = rand() % 29 + 1;
} while (x % 2 != 0);
//不能出现在蛇身上
pSnakeNode cur = ps->SHead;
while (cur)
{
if ((cur->x) == x && (cur->y) == y)
goto again;
cur = cur->next;
}
pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pfood == NULL)
{
perror("InitFood():malloc");
return;
}
pfood->x = x;
pfood->y = y;
pfood->next = NULL;
ps->Food = pfood;
SetPos(pfood->x, pfood->y);
wprintf(L"%lc", FOOD);
}
3.游戏过程中的操作
3.1在游戏过程中,始终显示规则信息
可在此处注明版权信息
void PrintHelpInfo()
{
SetPos(100, 15);
printf("不能穿墙,不能咬到自己");
SetPos(100, 16);
printf("用↑,↓,←,→控制蛇移动方向");
SetPos(100, 17);
printf("F3为加速,F4为减速,加速可以提高得分");
SetPos(100, 18);
printf("ESC为退出,space为暂停");
SetPos(100, 20);
printf("@版权归@九州~空城所有");
}
3.2游戏过程的循环
3.2.1每次更新总分信息和每个食物分值信息
SetPos(100, 10);
printf("当前总分是%5d", ps->Score);
SetPos(100, 11);
printf("当前每个食物的分值%2d", ps->FoodWeight);
3.2.2检测按键是否被按过,并做对应的修改
需要定义一个宏,来检测按键是否被按过
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1?1:0)
然后就可以进行操作了
if (KEY_PRESS(VK_UP) && (ps->dir != DOWN))
{
ps->dir = UP;
}
if (KEY_PRESS(VK_DOWN) && (ps->dir != UP))
{
ps->dir = DOWN;
}
if (KEY_PRESS(VK_LEFT) && (ps->dir != RIGHT))
{
ps->dir = LEFT;
}
if (KEY_PRESS(VK_RIGHT) && (ps->dir != LEFT))
{
ps->dir = RIGHT;
}
if (KEY_PRESS(VK_ESCAPE))
{
ps->Stauts = ESC;
break;
}
if (KEY_PRESS(VK_SPACE))
{
GameSpace();
}
if (KEY_PRESS(VK_F3))
{
if ((ps->SleepTime) >= 80)
{
ps->SleepTime -= 30;
ps->FoodWeight += 2;
}
}
if (KEY_PRESS(VK_F4))
{
if ((ps->FoodWeight) >2)
{
ps->SleepTime += 30;
ps->FoodWeight -= 2;
}
}
3.2.3睡眠一下(实现移动时的停顿)
Sleep(ps->SleepTime);
3.2.4走一步
需要注意走的下一步是不是食物
void SnakeMove(pSnake ps)
{
pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNext == NULL)
{
perror("SnakeMove():malloc");
return;
}
switch (ps->dir)
{
case UP:
pNext->x = ps->SHead->x;
pNext->y = ps->SHead->y - 1;
break;
case DOWN:
pNext->x = ps->SHead->x;
pNext->y = ps->SHead->y + 1;
break;
case LEFT:
pNext->x = ps->SHead->x - 2;
pNext->y = ps->SHead->y;
break;
case RIGHT:
pNext->x = ps->SHead->x + 2;
pNext->y = ps->SHead->y;
}
//下一个坐标是食物
if ((pNext->x) == (ps->Food->x) && (pNext->y) == (ps->Food->y))
{
NextisFood(ps,pNext);
}
else
{
NextnoFood(ps, pNext);
}
}
3.2.4.1走的下一步是食物
头插pNext,打印一次蛇,改变分数,再新建一个食物
void NextisFood(pSnake ps, pSnakeNode pNext)
{
pNext->next = ps->SHead;
ps->SHead = pNext;
//再打印一次蛇
pSnakeNode cur = ps->SHead;
while(cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//分数改变
ps->Score += ps->FoodWeight;
free(ps->Food);
//再新建一个食物
InitFood(ps);
}
3.2.4.2走的下一步不是食物
void NextnoFood(pSnake ps, pSnakeNode pNext)
{
pNext->next = ps->SHead;
ps->SHead = pNext;
pSnakeNode cur = ps->SHead;
while (cur->next->next)
{
//顺带打印蛇身
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
3.2.5检测撞墙
只需要改变状态即可
void KillByWall(pSnake ps)
{
if ((ps->SHead->x) == 0 || (ps->SHead->x == 80) || (ps->SHead->y == 0) || (ps->SHead->y == 30))
{
ps->Stauts = KILL_BY_WALL;
}
}
3.2.6检测撞到自己
在while循环中,我们通过return可以直达函数外,避免直接改变状态可能导致的死循环
void KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->SHead->next;
while (cur)
{
if ((cur->x) == (ps->SHead->x) && (cur->y) == (ps->SHead->y))
{
ps->Stauts = KILL_BY_SELF;
return;
}
cur = cur->next;
}
}
4.游戏结束时内存的释放
释放时我们需要完成这三点
①注明蛇的死亡方式
②释放贪吃蛇节点
③释放食物节点
void GameEnd(pSnake ps)
{
//说明怎么挂的
SetPos(20, 32);
switch (ps->Stauts)
{
case ESC:
printf("游戏正常退出结束");
break;
case KILL_BY_SELF:
printf("很遗憾,你咬到自己了,游戏结束");
break;
case KILL_BY_WALL:
printf("很遗憾,你撞墙了,游戏结束");
}
//释放贪吃蛇节点
pSnakeNode cur = ps->SHead;
while (cur)
{
pSnakeNode tmp = cur;
cur = cur->next;
free(tmp);
}
ps->SHead = NULL;
//释放食物节点
free(ps->Food);
ps = NULL;
}
5.源代码
注:需要将控制台程序-属性-终端中的默认终端应用程序更改为Windows控制台主机
有用的话不妨关注一下gitee