C语言链表贪吃蛇
大家好,这次的内容是讲解我对 C语言链表贪吃蛇 的理解,以下代码使用的是 VS2022环境下的C语言,若有错误,恳请读者大大们批评指正。
在上一篇博客 C语言数组贪吃蛇(VS2022版) 中,虽然本人初步实现了贪吃蛇的玩法,但是还有许多可改进的地方,如:
1、画面横纵字符坐标不统一,影响游戏观感。
2、getch函数 接收玩家操作会受 Sleep函数暂停 的影响,有明显的操作延迟。
3、代码主要变量分散,不利于后期的维护。
4、数组虽能实现空间移动,但利用率太低,可用链表思想优化。
准备文件 test.c、Snake.h和Snake.c。
视频演示
C语言链表贪吃蛇
一、前置知识
1.Win32 API 的使用
链表贪吃蛇会用到Win32 API的键盘操作,来解决 getch的操作延迟问题,还有光标操作。Win32 API 光标隐藏定位和键盘读取等常用函数 这篇博客已经整理完毕,感兴趣的读者可以去看看。
2.宽字符的使用
为解决横纵字符坐标统一,需要使用宽字符。C语言宽字符 wchar_t 类型 这篇博客已经整理完毕,这里就不赘述了。
二、封装核心数据和函数
数据
在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:
typedef struct SnakeNode // 蛇身节点
{
// 蛇的坐标
int x;
int y;
// 连接下一个蛇身
struct SnakeNode* next;
} SnakeNode, * pSnakeNode;
蛇的方向,可以一一列举,使用枚举:
typedef enum DIRECTION // 蛇移动方向
{
UP = 1, // 上
DOWN, // 下
LEFT, // 左
RIGHT // 右
} DIRECTION;
游戏状态,可以一一列举,使用枚举:
typedef enum GAME_STATUS // 游戏状态
{
NORMAL = 1, // 游戏正常
KILL_BY_WALL, // 蛇撞墙
KILL_BY_SELF, // 蛇吃到自己
END_NORMAL, // 玩家主动退出
} GAME_STATUS;
要方便管理整条贪吃蛇,我们就需要封装一个Snake的结构来维护整条贪吃蛇:
typedef struct Snake
{
pSnakeNode pHead; // 蛇头指针
DIRECTION dir; // 蛇移动方向
GAME_STATUS sta; // 游戏状态
pSnakeNode pfood; // 食物位置
int food_score; // 食物分数
int score; // 游戏得分
int sleepTime; // 暂停时间
} Snake, *pSnake;
函数
因为要频繁调用Win32 API 中的移动光标函数,则将它封装成一个函数:
void SetPos(int x, int y)
{
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获得句柄
COORD pos = { x, y }; // 坐标结构体
SetConsoleCursorPosition(hOutput, pos); // 设置光标位置
}
三、游戏核心逻辑
游戏主逻辑
程序开始就设置程序支持本地模式,然后进入游戏的主逻辑。主逻辑分为3个过程:
1.游戏开始(GameStart)完成游戏的初始化
2.游戏运行(GameRun)完成游戏运行逻辑的实现
3.游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
在 test.c 中:
#include "Snake.h"
void test()
{
srand((unsigned int)time(NULL));
char input = 0;
do
{
system("cls");
// 创建贪吃蛇
Snake snake = { 0 };
// 初始化游戏
GameStart(&snake);
// 游戏运行
GameRun(&snake);
// 游戏结束
GameEnd(&snake);
SetPos(20, 15);
printf("再来一局吗?(Y/N):");
input = getchar();
while (getchar() != '\n'); // 清理多余缓冲区字符
} while (input == 'Y' || input == 'y');
SetPos(0, 27);
}
int main()
{
setlocale(LC_ALL, ""); // 设置到本地模式
test();
return 0;
}
游戏开始
这个模块完成游戏的初始化任务:
1.控制台窗口大小和名字的设置
2.鼠标光标的隐藏
3.打印欢迎界面
4.创建地图
5.初始化蛇
6.创建第一个食物
void GameStart(pSnake snake)
{
//1.控制台窗口大小和名字的设置
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//2.鼠标光标的隐藏
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(hOutput, &CursorInfo);
//3.打印欢迎界面
WelcomeToGame();
//4.创建地图
GreateMap();
//5.初始化蛇
InitSnake();
//6.创建第一个食物
GreateFood();
}
打印欢迎界面
在游戏正式开始之前,做⼀些功能提醒
void WelcomeToGame()
{
// 第一个界面
SetPos(38, 14);
wprintf(L"欢迎来到贪吃蛇小游戏\n");
SetPos(40, 20);
system("pause"); // 暂停
system("cls"); // 清屏
// 第二个界面
SetPos(30, 14);
wprintf(L"用 ↑. ↓. ←. → 来控制蛇的移动,按F3加速,F4减速\n");
SetPos(30, 15);
wprintf(L"加速能得到更高的分数\n");
SetPos(43, 20);
system("pause");
system("cls");
}
创建地图
我们假设实现⼀个棋盘27行,58列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙。
创建地图就是将墙打印出来,因为是宽字符打印,所以使用wprintf函数,打印格式串前使用L打印地图的关键是要算好坐标,才能在想要的位置打印墙体。
上:(0,0)到(56,0)
下:(0,26)到(56,26)
左:(0,1)到(0,25)
右:(56,1)到(56,25)
void GreateMap()
{
// WALL的宏定义:#define WALL L'■'
SetPos(0, 0);
for (int i = 0; i <= 56; i += 2) // 上
{
wprintf(L"%lc", WALL);
}
SetPos(0, 26);
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);
}
for (int i = 1; i <= 25; ++i) // 右
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
初始化蛇
初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。
注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点有⼀半出现在墙体中,另外⼀般在墙外的现象,坐标不好对齐。
蛇最开始长度为5节,每节对应链表的⼀个节点,蛇身的每⼀个节点都有自己的坐标。创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇身后,将蛇的每⼀节打印在屏幕上。
• 蛇的初始位置从 (24,5) 开始。
再设置当前游戏的状态,蛇移动的速度,默认的方向,初始成绩,每个食物的分数。
• 游戏状态是:NORMAL
• 蛇的移动速度:200毫秒
• 蛇的默认方向:RIGHT
• 初始成绩:0
• 每个⻝物的分数:10
void InitSnake(pSnake snake)
{
pSnakeNode cur = NULL;
for (int i = 0; i < 5; ++i)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake()::malloc fail");
exit(1);
}
cur->next = NULL;
cur->x = POS_X + i * 2; // POS_X的宏定义:#define POS_X 24
cur->y = POS_Y; // POS_Y的宏定义:#define POS_Y 4
if (snake->pHead == NULL) // 蛇身节点连接
{
snake->pHead = cur;
}
else
{
cur->next = snake->pHead;
snake->pHead = cur;
}
}
cur = snake->pHead; // 打印节点
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY); // BODY的宏定义:#define BODY L'●'
cur = cur->next;
}
// 属性设置
snake->dir = RIGHT; // 默认向右
snake->score = 0;
snake->food_score = 10;
snake->sleep_time = 200; // 单位为毫秒
snake->sta = NORMAL;
}
创建第一个食物
关于食物,就是在墙体内随机生成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。
● 先随机生成食物的坐标
● x坐标必须是2的倍数
● 食物的坐标不能和蛇身每个节点的坐标重复
● 创建食物节点,打印食物
void GreateFood(pSnake snake)
{
int x = 0;
int y = 0;
again:
x = (rand() % 27 + 1) * 2;
y = rand() % 24 + 1;
// 检查冲突
pSnakeNode cur = snake->pHead;
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
// 创建食物节点
pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pfood == NULL)
{
perror("GreateFood()::malloc faild");
exit(1);
}
pfood->x = x;
pfood->y = y;
pfood->next = NULL;
SetPos(x, y);
wprintf(L"%lc", FOOD); // FOOD的宏定义:#define FOOD L'★'
snake->food = pfood;
}
游戏运行
这个模块完成游戏的运行任务:
1.打印帮助信息PrintHelpInfo
2.蛇身移动
—2.1.NextIsFood
—2.2.EatFood
—2.3.NoFood
—2.4.KillByWall
—2.5.KillBySelf
游戏运行期间,右侧打印帮助信息,提示玩家,坐标开始位置(64, 15),根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
如果游戏继续,就是检测按键情况,确定蛇下⼀步的方向,或者是否加速减速,是否暂停或者退出游戏。
需要的虚拟按键的罗列:
• 上:VK_UP
• 下:VK_DOWN
• 左:VK_LEFT
• 右:VK_RIGHT
• 空格:VK_SPACE
• ESC:VK_ESCAPE
• F3:VK_F3
• F4:VK_F4
确定了蛇的方向和速度,蛇就可以移动了。
void GameRun(pSnake snake)
{
// 打印帮助信息
PrintHelpInfo();
do
{
// 打印总分数和食物
SetPos(64, 10);
printf("总分数:%d", snake->score);
SetPos(64, 11);
printf("当前食物分数:%-2d", snake->food_score);
// KEY_PRESS的宏定义:#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
if (KEY_PRESS(VK_UP) && snake->dir != DOWN)
{
snake->dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && snake->dir != UP)
{
snake->dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && snake->dir != RIGHT)
{
snake->dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && snake->dir != LEFT)
{
snake->dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))
{
// 暂停
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
// 正常退出
snake->sta = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
// 加速
if (snake->sleepTime > 80)
{
snake->sleepTime -= 30;
snake->food_score += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
// 减速
if (snake->food_score > 2)
{
snake->sleepTime += 30;
snake->food_score -= 2;
}
}
SnakeMove(snake); // 蛇移动一步
Sleep(snake->sleepTime);
} while (snake->sta == NORMAL);
}
打印帮助信息PrintHelpInfo和暂停
void PrintHelpInfo()
{
SetPos(64, 14);
wprintf(L"%ls", L"不能穿墙,不能咬到自己");
SetPos(64, 15);
wprintf(L"%ls", L"用 ↑. ↓. ←. → 来控制蛇的移动");
SetPos(64, 16);
wprintf(L"%ls", L"按F3加速,F4减速");
SetPos(64, 17);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
void Pause()
{
while (true)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
蛇身移动
先创建下⼀个节点,根据移动方向和蛇头的坐标,蛇移动到下⼀个位置的坐标。
确定了下⼀个位置后,看下⼀个位置是否是食物(NextIsFood),是食物就做吃食物处理(EatFood),如果不是⻝物则做前进⼀步的处理(NoFood)。
蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。
void SnakeMove(pSnake snake)
{
// 创建一个节点,表示下一个要走的节点
pSnakeNode NextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (NextNode == NULL)
{
perror("SnakeMove()::malloc faild");
exit(1);
}
switch (snake->dir)
{
case UP:
NextNode->x = snake->pHead->x;
NextNode->y = snake->pHead->y - 1;
break;
case DOWN:
NextNode->x = snake->pHead->x;
NextNode->y = snake->pHead->y + 1;
break;
case LEFT:
NextNode->x = snake->pHead->x - 2;
NextNode->y = snake->pHead->y;
break;
case RIGHT:
NextNode->x = snake->pHead->x + 2;
NextNode->y = snake->pHead->y;
break;
}
// 检测下一个是否是食物
if (NextIsFood(snake, NextNode))
{
EatFood(snake, NextNode);
}
else
{
NoFood(snake, NextNode);
}
// 检测蛇是否撞墙
KillByWall(snake);
// 检测蛇是否撞到自己
KillBySelf(snake);
}
NextIsFood
bool NextIsFood(pSnake snake, pSnakeNode move)
{
return (snake->pfood->x == move->x && snake->pfood->y == move->y);
}
EatFood
void EatFood(pSnake snake, pSnakeNode NextNode)
{
// 头插法
snake->pfood->next = snake->pHead;
snake->pHead = snake->pfood;
// 释放下一个位置的节点
free(NextNode);
NextNode = NULL;
pSnakeNode cur = snake->pHead;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
snake->score += snake->food_score;
// 重新创建食物
GreateFood(snake);
}
NoFood
void NoFood(pSnake snake, pSnakeNode NextNode)
{
// 头插法
NextNode->next = snake->pHead;
snake->pHead = NextNode;
pSnakeNode cur = snake->pHead;
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;
}
KillByWall
判断蛇头的坐标是否和墙的坐标冲突
// 检测蛇是否撞墙
void KillByWall(pSnake snake)
{
if (snake->pHead->x == 0 || snake->pHead->x == 56 ||
snake->pHead->y == 0 || snake->pHead->y == 26)
{
snake->sta = KILL_BY_WALL;
}
}
KillBySelf
判断蛇头的坐标是否和蛇身体的坐标冲突
// 检测蛇是否撞到自己
void KillBySelf(pSnake snake)
{
pSnakeNode cur = snake->pHead->next;
while (cur)
{
if (snake->pHead->x == cur->x && snake->pHead->y == cur->y)
{
snake->sta = KILL_BY_SELF;
break;
}
cur = cur->next;
}
}
游戏结束
游戏状态不再是NORMAL(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇身节点。
void GameEnd(pSnake snake)
{
SetPos(24, 12);
switch (snake->sta)
{
case END_NORMAL:
printf("您主动结束游戏\n");
break;
case KILL_BY_WALL:
printf("您撞到墙上,游戏结束\n");
break;
case KILL_BY_SELF:
printf("您撞到了自己,游戏结束\n");
break;
}
// 释放蛇身链表
pSnakeNode cur = snake->pHead;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
四、游戏源代码
test.c
在 test.c 中:
#include "Snake.h"
void test()
{
srand((unsigned int)time(NULL));
char input = 0;
do
{
system("cls");
// 创建贪吃蛇
Snake snake = { 0 };
// 初始化游戏
GameStart(&snake);
// 游戏运行
GameRun(&snake);
// 游戏结束
GameEnd(&snake);
SetPos(20, 15);
printf("再来一局吗?(Y/N):");
input = getchar();
while (getchar() != '\n'); // 清理多余缓冲区字符
} while (input == 'Y' || input == 'y');
SetPos(0, 27);
}
int main()
{
setlocale(LC_ALL, ""); // 设置到本地模式
test();
return 0;
}
Snake.h
在 Snake.h 中:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <locale.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 1) ? 1 : 0)
#define POS_X 24
#define POS_Y 4
#define WALL L'■'
#define BODY L'●'
#define FOOD L'★'
typedef enum DIRECTION // 蛇移动方向
{
UP = 1,
DOWN,
LEFT,
RIGHT
} DIRECTION;
typedef enum GAME_STATUS // 游戏状态
{
NORMAL = 1, // 游戏正常
KILL_BY_WALL, // 撞墙
KILL_BY_SELF, // 吃到蛇身
END_NORMAL, // 正常退出
} GAME_STATUS;
// 蛇身节点
typedef struct SnakeNode
{
// 坐标位置
int x;
int y;
// 下一个节点
struct SnakeNode* next;
} SnakeNode, * pSnakeNode;
// 将蛇各个信息封装成一个对象,方便维护
typedef struct Snake
{
pSnakeNode pHead; // 蛇头位置
pSnakeNode pfood; // 食物位置
DIRECTION dir; // 蛇移动方向
GAME_STATUS sta; // 游戏状态
int food_score; // 食物分数
int score; // 总得分
int sleepTime; // 间隔时间
} Snake, * pSnake;
// 光标定位
void SetPos(int x, int y);
// 游戏开始
void GameStart(pSnake snake);
// 欢迎界面
void WelcomeToGame();
// 创建地图
void GreateMap();
// 初始化贪吃蛇
void InitSnake(pSnake snake);
// 创建食物
void GreateFood(pSnake snake);
// 游戏运行
void GameRun(pSnake snake);
// 蛇移动一步
void SnakeMove(pSnake snake);
// 判断下一个坐标是否是食物
bool NextIsFood(pSnake snake, pSnakeNode move);
void EatFood(pSnake snake, pSnakeNode NextNode);
void NoFood(pSnake snake, pSnakeNode NextNode);
// 检测蛇是否撞墙
void KillByWall(pSnake snake);
// 检测蛇是否撞到自己
void KillBySelf(pSnake snake);
void GameEnd(pSnake snake);
Snake.c
在 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(38, 14);
wprintf(L"欢迎来到贪吃蛇小游戏\n");
SetPos(40, 20);
system("pause");
system("cls");
SetPos(30, 14);
wprintf(L"用 ↑. ↓. ←. → 来控制蛇的移动,按F3加速,F4减速\n");
SetPos(30, 15);
wprintf(L"加速能得到更高的分数\n");
SetPos(43, 20);
system("pause");
system("cls");
}
void GreateMap()
{
SetPos(0, 0);
for (int i = 0; i <= 56; i += 2) // 上
{
wprintf(L"%lc", WALL);
}
SetPos(0, 26);
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);
}
for (int i = 1; i <= 25; ++i) // 右
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
void InitSnake(pSnake snake)
{
pSnakeNode cur = NULL;
for (int i = 0; i < 5; ++i)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake()::malloc fail");
exit(1);
}
cur->next = NULL;
cur->x = POS_X + i * 2;
cur->y = POS_Y;
if (snake->pHead == NULL) // 蛇身节点连接
{
snake->pHead = cur;
}
else
{
cur->next = snake->pHead;
snake->pHead = cur;
}
}
cur = snake->pHead; // 打印节点
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
// 属性设置
snake->dir = RIGHT; // 默认向右
snake->score = 0;
snake->food_score = 10;
snake->sleepTime = 200; // 单位为毫秒
snake->sta = NORMAL;
}
void GreateFood(pSnake snake)
{
int x = 0;
int y = 0;
again:
x = (rand() % 27 + 1) * 2;
y = rand() % 24 + 1;
// 检查冲突
pSnakeNode cur = snake->pHead;
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
// 创建食物节点
pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pfood == NULL)
{
perror("GreateFood()::malloc faild");
exit(1);
}
pfood->x = x;
pfood->y = y;
pfood->next = NULL;
SetPos(x, y);
wprintf(L"%lc", FOOD);
snake->pfood = pfood;
}
void GameStart(pSnake snake)
{
// 0.设置窗口和光标
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(houtput, &CursorInfo);
// 1.打印界面和功能介绍
WelcomeToGame();
// 2.绘制地图
GreateMap();
// 3.创建蛇
InitSnake(snake);
// 4.创建食物
GreateFood(snake);
}
void PrintHelpInfo()
{
SetPos(64, 14);
wprintf(L"%ls", L"不能穿墙,不能咬到自己");
SetPos(64, 15);
wprintf(L"%ls", L"用 ↑. ↓. ←. → 来控制蛇的移动");
SetPos(64, 16);
wprintf(L"%ls", L"按F3加速,F4减速");
SetPos(64, 17);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
void Pause()
{
while (true)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
bool NextIsFood(pSnake snake, pSnakeNode move)
{
return (snake->pfood->x == move->x && snake->pfood->y == move->y);
}
void EatFood(pSnake snake, pSnakeNode NextNode)
{
// 头插法
snake->pfood->next = snake->pHead;
snake->pHead = snake->pfood;
// 释放下一个位置的节点
free(NextNode);
NextNode = NULL;
pSnakeNode cur = snake->pHead;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
snake->score += snake->food_score;
// 重新创建食物
GreateFood(snake);
}
void NoFood(pSnake snake, pSnakeNode NextNode)
{
// 头插法
NextNode->next = snake->pHead;
snake->pHead = NextNode;
pSnakeNode cur = snake->pHead;
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;
}
// 检测蛇是否撞墙
void KillByWall(pSnake snake)
{
if (snake->pHead->x == 0 || snake->pHead->x == 56 ||
snake->pHead->y == 0 || snake->pHead->y == 26)
{
snake->sta = KILL_BY_WALL;
}
}
// 检测蛇是否撞到自己
void KillBySelf(pSnake snake)
{
pSnakeNode cur = snake->pHead->next;
while (cur)
{
if (snake->pHead->x == cur->x && snake->pHead->y == cur->y)
{
snake->sta = KILL_BY_SELF;
break;
}
cur = cur->next;
}
}
void SnakeMove(pSnake snake)
{
// 创建一个节点,表示下一个要走的节点
pSnakeNode NextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (NextNode == NULL)
{
perror("SnakeMove()::malloc faild");
exit(1);
}
switch (snake->dir)
{
case UP:
NextNode->x = snake->pHead->x;
NextNode->y = snake->pHead->y - 1;
break;
case DOWN:
NextNode->x = snake->pHead->x;
NextNode->y = snake->pHead->y + 1;
break;
case LEFT:
NextNode->x = snake->pHead->x - 2;
NextNode->y = snake->pHead->y;
break;
case RIGHT:
NextNode->x = snake->pHead->x + 2;
NextNode->y = snake->pHead->y;
break;
}
// 检测下一个是否是食物
if (NextIsFood(snake, NextNode))
{
EatFood(snake, NextNode);
}
else
{
NoFood(snake, NextNode);
}
// 检测蛇是否撞墙
KillByWall(snake);
// 检测蛇是否撞到自己
KillBySelf(snake);
}
void GameRun(pSnake snake)
{
PrintHelpInfo();
do
{
// 打印总分数和食物
SetPos(64, 10);
printf("总分数:%d", snake->score);
SetPos(64, 11);
printf("当前食物分数:%-2d", snake->food_score);
if (KEY_PRESS(VK_UP) && snake->dir != DOWN)
{
snake->dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && snake->dir != UP)
{
snake->dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && snake->dir != RIGHT)
{
snake->dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && snake->dir != LEFT)
{
snake->dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))
{
// 暂停
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
// 正常退出
snake->sta = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
// 加速
if (snake->sleepTime > 80)
{
snake->sleepTime -= 30;
snake->food_score += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
// 减速
if (snake->food_score > 2)
{
snake->sleepTime += 30;
snake->food_score -= 2;
}
}
SnakeMove(snake); // 蛇移动一步
Sleep(snake->sleepTime);
} while (snake->sta == NORMAL);
}
void GameEnd(pSnake snake)
{
SetPos(24, 12);
switch (snake->sta)
{
case END_NORMAL:
printf("您主动结束游戏\n");
break;
case KILL_BY_WALL:
printf("您撞到墙上,游戏结束\n");
break;
case KILL_BY_SELF:
printf("您撞到了自己,游戏结束\n");
break;
}
// 释放蛇身链表
pSnakeNode cur = snake->pHead;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}