前言:
贪吃蛇是一个久负盛名的游戏,与俄罗斯方块和扫雷游戏相同都是非常经典的游戏
一.游戏相关知识
1.COORD
(1)说明:
控制台屏幕上的坐标,有两个参数x,y,分别对应着横纵坐标,(0,0)对应控制台屏幕左上角顶格位置
(2)声明:
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
2.GetStdHandle
(1)说明:
它用于从一个特定的标准设备中获得一个句柄,用来操作该设备(具体咋理解呢?就相当于你炒菜的肯定是要用锅的嘛,那既然要用锅,那就一定要用把手呗,通过该把手来操控这口锅,然后进行炒菜。这里的把手就相当与这里的句柄,必须你获得了该句柄才能对操作台屏幕上的某些信息进行操作)
(2)声明:
HANDLE WINAPI GetStdHandle(
_In_ DWORD nStdHandle
);
(3)参数:
STD_INPUT_HANDLE((DWORD)-10) | 标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台。 |
STD_OUTPUT_HANDLE((DWORD)-11) | 标准输出设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$ 。 |
STD_ERROR_HANDLE((DWORD)-12) | 标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$ 。 |
由于我们是要向屏幕输出信息,所以这里我们对该函数的传参选择第二个参数 !!
3.CONSOLE_CURSOR_INFO
(1)说明:
是一个结构体,用来存储控制台屏幕上的光标的占比大小和可见性
(2)声明:
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
4. GetConsoleCursorInfo
(1)说明:
用来获取有关控制台屏幕上的光标的占比和可见性的相关信息
(2)声明:
BOOL WINAPI GetConsoleCursorInfo(
_In_ HANDLE hConsoleOutput,
_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
(3)参数:
第一个就是刚才的句柄 ,第二个是光标
5.SetConsoleCursorInfo
(1)说明:
对修改过后的光标信息进行设置
(2)声明:
BOOL WINAPI SetConsoleCursorInfo(
_In_ HANDLE hConsoleOutput,
_In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
6.SetConsoleCursorPosition
(1)说明:
设置光标的位置(初始打印位置)
(2)声明:
BOOL WINAPI SetConsoleCursorPosition(
_In_ HANDLE hConsoleOutput,
_In_ COORD dwCursorPosition
);
7. GetAsyncKeyState
(1)说明:
获取键盘上按键的状态,调用函数时键是向上还是向下,以及上次调用 GetAsyncKeyState 后是否按下了该键。
(2)声明:
SHORT GetAsyncKeyState(
[in] int vKey
);
(3)参数:
这里的参数是虚拟键代码,传参可以传常数(VK_ESCAPE
),也可以传value(0x1B)。在该游戏会用到的虚拟键代码有:
VK_ESCAPE | 0x1B | ESC 键 |
VK_SPACE | 0x20 | 空格键 |
VK_LEFT | 0x25 | LEFT ARROW 键 |
VK_UP | 0x26 | UP ARROW 键 |
VK_RIGHT | 0x27 | RIGHT ARROW 键 |
VK_DOWN | 0x28 | DOWN ARROW 键 |
VK_F1 | 0x70 | F1 键 |
VK_F2 | 0x71 | F2 键 |
(4)使用方式:
检测按键状态,是看它返回值的最低位,最低位为1表示按下,为0表示没按下
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 1) ? 1 : 0
8.setlocale
(1)说明:头文件为:<locale.h>
用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项
(2)声明:
char* setlocale (int category, const char* locale);
(3)参数:
第一个参数:该参数为类项,如果为LC_ALL,就会影响所有类项,如果只想影响某一个的话就需要单独设置(所有类项说明)
第二个参数:
只有两种取值:""(本地模式),"C"(正常模式),在正常模式下,一个小数点就是一个点,但我们的Chinese的模式下一个点是占两个字符位置,从而在本地模式下进行宽字符打印。
(4)宽字符打印:
用wprintf进行打印,在打印是必须加上L,用%lc进行宽字符打印。
wprintf(L"%lc", WALL);
二.游戏设计与分析:
2.1 游戏主逻辑
在程序开始时就需要设置为本地模式,然后进入游戏
该游戏分为三个阶段:游戏开始(GameStart),游戏运行(GameRun),游戏结束(GameEnd)
2.2 游戏开始
主要进行一些页面的打印以及蛇身和食物的设计
void GameStart(pSnake ps)
{
//1.先设置窗口的 标题 大小
system("title 贪吃蛇");
system("mode con cols=100 lines=30");
//2.设置光标的隐藏
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(houtput, &cursor_info);
cursor_info.bVisible = false;
SetConsoleCursorInfo(houtput, &cursor_info);
//3.打印欢印界面和游戏规则
welcomGame();
//4.绘制地图(障碍物)
createMap();
//5.创建蛇 ==> 初始化蛇身
SnakeInit(ps);
//6.创建食物
createFood(ps);
}
2.2.1 欢迎界面打印
(1)展示
(2)实现:
首先我们在启动游戏的时候会打印一个游戏初始界面。上面有着欢迎来到贪吃蛇游戏 。
关于其如何实现:
1.这个启动界面的名称我们需要进行修改成你想要的名字--贪吃蛇。这时需要用到cmd命令--title来实现,在c语言中用system来实现。
system("title 贪吃蛇");
2.启动界面的大小也需要调整,用到的也是cmd命令--mode
system(mode con cols=100 lines=30);
3.在控制台屏幕上的默认打印位置在最开始,然而这里我们想将打印初始位置设置在屏幕中央,此时就需要对光标位置(打印初始位置)为进行设置,这里用到win32API函数中的SetConsoleCursorPosition ,这里我们可以将设置坐标给封装成一个函数,在我们下次使用时就可以直接传参就好了!!
void SetPos(short x, short y)
{
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x ,y };
SetConsoleCursorPosition(houtput, pos);
}
当标题、屏幕大小和光标位置都设置完成我们就可以进行欢迎页面的打印了!!
4.清除当面页面的信息以便打印游戏规则的界面 ==> 用到cmd命令 cls
system("cls");
2.2.2 游戏规则界面:
实现原理跟上面欢迎界面类似,不再赘述
两个页面打印的代码实现:
//打印欢印界面和游戏规则
void welcomGame()
{
//界面1
//设置光标
SetPos(38, 12);
printf("欢印来到贪吃蛇小游戏");
SetPos(38, 20);
system("pause");
//清空
system("cls");
//界面2
SetPos(32, 12);
printf("用↑.↓.→.← 来表示蛇移动的方向\n");
SetPos(32, 13);
printf("F1为加速,F2为减速,加速吃到食物可以得到更高的分\n");
SetPos(32, 20);
system("pause");
system("cls");
}
2.2.3 地图打印
由如下图我们可以看见一些小方块,这些就是我们自定义的地图,贪吃蛇只能在这里面行动。
(1)说明:
我们所要实现的地图大小为27行(y),58列(x),需要进行上下左右的小方块的打印:
上:0~58打印小方块,由于是宽字符打印,故只需要打印29个小方块
下:同上一样
左:1~25打印小方块,共打印25个
右:同左一样
(2)代码实现:
//这里使用define定义
#define WALL L'□'
//小方块打印方式
wprintf(L"%lc", WALL);
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 < 26; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 1; i < 26; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
2.2.4 初始化蛇身
我们想要实现一条长度为5的蛇,这里我们可以考虑用链表,想要一个长度为5的蛇,那不就是一个链表中有5个被串起来的节点,我们将每一个节点打印在屏幕上,然后就成了一条蛇。
创建一条蛇:
1.因为是要打印在屏幕上,所有我们需要知道坐标,所有蛇身的每一个节点(结构体)中存储的有x(横坐标),y(纵坐标),next(指向下一个蛇身的节点)
//定义一个节点,将很多个节点给串起来就成了一条蛇
typedef struct SnakeNode
{
//坐标
int x;
int y;
//找到贪吃蛇的下一个节点
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
2.关于蛇,它也是需要进行维护的,所以我们也需创建一个蛇(结构体),里面存储有:指向蛇的头节点的指针,指向食物节点的指针,每一个食物的分数,总分数,蛇的运行速度,蛇运动的方向以及蛇的状态
蛇运动的方向和状态都是一个枚举类型。
关于蛇运行的速度,这里采用休眠时间快满来设定蛇的运行速度,休眠时间越短蛇运动越快,越长运动越慢。
//蛇行动的方向
typedef enum SnakeDir
{
UP = 1,
DOWN,
LEFT,
RIGHT
}SnakeDir;
//蛇运动的状态
typedef enum SnakeState
{
OK, //正常
KILL_BY_WALL, //撞到墙
KILL_BY_SELF, //撞到自己
NORMOL_END //正常结束游戏
}SnakeState;
//定义一条蛇,以便维护蛇的一些相关信息
typedef struct Snake
{
pSnakeNode phSnake; //指向蛇的头部的指针
pSnakeNode pFood; //指向食物节点的指针
SnakeDir Dir; //蛇运动的方向
SnakeState Ste; //蛇运动的状态,有快有慢
int sleepTime; //判断蛇运动快慢的标志,就是用休眠时间来让它进行快或者慢的运动
int foodScore; //食物的分数
int allScore; //总分
}Snake, * pSnake;
初始化蛇:
蛇的初始位置是从(24,3)开始到(32,3)结束,(32,3)是蛇的头节点
蛇的移动速度是200ms
游戏状态是OK
总分为0
每个食物的分数为10
蛇的初始运动方向为RIGHT
打印蛇:
遍历蛇的每一个节点,然后进行宽字符打印
代码实现:
#define BODY L'●'
void SnakeInit(pSnake ps)
{
//创建5个节点,并将上述5个节点组成一条蛇
pSnakeNode cur = NULL;
int i = 0;
for (i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("SnakeInit :: malloc\n");
return;
}
//设置坐标
cur->x = X + i * 2;
cur->y = Y;
cur->next = NULL;
if (ps->phSnake == NULL)
{
ps->phSnake = cur;
}
else
{
//头插法
cur->next = ps->phSnake;
ps->phSnake = cur;
}
}
//遍历这5个节点,打印蛇的身体
cur = ps->phSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//初始化蛇里面的各种数据
ps->allScore = 0;
ps->foodScore = 10;
ps->sleepTime = 200;
ps->Ste = OK;
ps->Dir = RIGHT;
}
效果演示:
2.2.5 创建一个食物
食物的创建:
1. 随机生成坐标(不能超出地图范围)
2.不能生成在墙上
3.不能生成在蛇的身上
4.食物的长度也必须为2个字符大小
代码实现:
void createFood(pSnake ps)
{
//注意食物必须也占两行,并且不能够创建到墙中或者蛇自己身上
pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
if (food == NULL)
{
perror("createFood::malloc");
return;
}
int x = 0;
int y = 0;
a:
do
{
x = rand() % 53 + 2; // x:2~54
y = rand() % 25 + 1; // y:1~25
} while (x % 2 != 0);
pSnakeNode cur = ps->phSnake;
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto a;
}
cur = cur->next;
}
food->x = x;
food->y = y;
food->next = NULL;
ps->pFood = food;
//SetPos是自己封装的一个设置光标的函数
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
2.3 游戏运行(GameRun)
我们在游戏运行界面右边有一个帮助的显示信息,来提示玩家。
根据游戏状态来进行游戏,如果游戏状态为OK就继续游戏,否则游戏结束。
如果游戏继续就检测运动方向和运动速度,就是检测按键状态(当前方向必须与按键方向相同,否则就直接会碰到自己,会退出游戏)
在GameRun中我们运用一个do while循环,游戏至少先执行一次,然后进行循环判断状态是否为OK,如果不为OK就会跳出循环,从而结束游戏,进行游戏的善后工作。
void GameRun(pSnake ps)
{
helpPrint();
do
{
SetPos(68, 8);
printf("得分:%d\n", ps->allScore);
SetPos(68, 9);
printf("每个食物的分数:%2d\n", ps->foodScore);
//现在要让贪吃蛇动起来,此时需要确定它的方向(确定按键状态)
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->Ste = NORMOL_END;
}
else if (KEY_PRESS(VK_F1))
{
//加速,最多加速四次
if (ps->sleepTime > 80)
{
ps->sleepTime -= 30;
ps->foodScore += 2;
}
}
else if (KEY_PRESS(VK_F2))
{
//减速,也最多减速四次
if (ps->foodScore > 2)
{
ps->sleepTime += 30;
ps->foodScore -= 2;
}
}
//休眠,然后下一次准备再次执行该程序,只到不满足的条件出现就跳出该循环
Sleep(ps->sleepTime);
//贪吃蛇走动一步
snakeMove(ps);
} while (ps->Ste == OK);
}
1.helpPrint
//帮助打印显示信息
void helpPrint()
{
SetPos(68, 12);
printf("不能穿墙,不能碰到自己\n");
SetPos(68, 13);
printf("用↑.↓.→.← 来表示蛇移动的方向\n");
SetPos(68, 14);
printf("F1表示加速,F2表示减速\n");
SetPos(68, 15);
printf("ESC:退出游戏 SPACE:暂停\n");
SetPos(68, 20);
printf("比特就业课授权\n");
}
2.KEY_PRESS
我们可以简单的的定义一个宏来检测某个按键是否按下
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 1) ? 1 : 0
3.pause
当我们按下空格的时候需要进行休眠 ==> 暂停,再次按下空格,跳出循环继续行动!!
void pause()
{
while (1)
{
Sleep(500);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
4.snakeMove
每一次在进行循环的时候都要让蛇往指定方向走一步,并且需要判断下一个节点是不是食物,是不是墙壁,是不是蛇自己尾部的节点。如果为食物就应该吃掉食物,如果不是正常往指定方向行走一步;如果是墙壁,那么蛇被杀死,游戏结束;如果是蛇自己的节点,那么蛇也会被杀死,游戏结束;
代码实现:
void snakeMove(pSnake ps)
{
//有四种走动方式:上 下 左 右
//创建下一个节点
pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{
perror("snakeMove :: malloc");
return;
}
if (ps->Dir == UP)
{
pnext->x = ps->phSnake->x;
pnext->y = ps->phSnake->y - 1;
}
else if (ps->Dir == DOWN)
{
pnext->x = ps->phSnake->x;
pnext->y = ps->phSnake->y + 1;
}
else if (ps->Dir == RIGHT)
{
pnext->x = ps->phSnake->x + 2;
pnext->y = ps->phSnake->y;
}
else if (ps->Dir == LEFT)
{
pnext->x = ps->phSnake->x - 2;
pnext->y = ps->phSnake->y;
}
if (isNextFood(pnext, ps))
{
eatFood(pnext, ps);
}
else
{
noFood(pnext, ps);
}
if (KillByWall(ps))
{
ps->Ste = KILL_BY_WALL;
}
if (KillBySelf(ps))
{
ps->Ste = KILL_BY_SELF;
}
}
(1)isNextFood
int isNextFood(pSnakeNode pn,pSnake ps)
{
return (pn->x == ps->pFood->x) && (pn->y == ps->pFood->y);
}
(2)eatFood
void eatFood(pSnakeNode pn, pSnake ps)
{
pn->next = ps->phSnake;
ps->phSnake = pn;
ps->allScore += ps->foodScore;
pSnakeNode cur = ps->phSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
free(ps->pFood);
ps->pFood = NULL;
createFood(ps);
}
(3)noFood
void noFood(pSnakeNode pn, pSnake ps)
{
pn->next = ps->phSnake;
ps->phSnake = pn;
pSnakeNode cur = ps->phSnake;
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;
}
(4)KillByWall
int KillByWall(pSnake ps)
{
if ((ps->phSnake->x == 0) || (ps->phSnake->x == 56) ||
(ps->phSnake->y == 0) || (ps->phSnake->y == 26))
{
return 1;
}
return 0;
}
(5)KillBySelf
int KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->phSnake->next;
while (cur)
{
if ((ps->phSnake->x == cur->x) && (ps->phSnake->y == cur->y))
{
return 1;
}
cur = cur->next;
}
return 0;
}
2.4 游戏结束(GameEnd)
在游戏结束后,我们需要对整个游戏过程中所创建的所有节点进行释放。还可以加上一个功能,在游戏结束以后,输入一个y/Y(yes)来继续进行游戏,如果输入n/N(no),不再进行游戏,此时就退出游戏。
void GameEnd(pSnake ps)
{
pSnakeNode cur = ps->phSnake;
while (cur)
{
pSnakeNode pn = cur->next;
free(cur);
cur = pn;
}
}
2.5 主函数
#include"Snake.h"
void test()
{
srand((unsigned int)time(NULL));
int ch = 0;
do
{
Snake snake = { 0 };
//初始化游戏
GameStart(&snake);
//进行游戏
GameRun(&snake);
结束游戏
GameEnd(&snake);
SetPos(14, 12);
printf("游戏结束!还要再来一把吗(Y/N):");
ch = getchar();
//判断是否存在读YYYYYY/YYYYyyy的情况
while(getchar() != '\n');
} while (ch == 'y' || ch == 'Y');
}
int main()
{
//设置适配环境,只有在本地模式(Chinese)下才支持宽字符的打印
setlocale(LC_ALL, "");
test();
return 0;
}
三. 贪吃蛇实现代码
1.Snake.h:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <stdbool.h>
#include <locale.h>
#include <time.h>
//定义一个节点,将很多个节点给串起来就成了一条蛇
typedef struct SnakeNode
{
//坐标
int x;
int y;
//找到贪吃蛇的下一个节点
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
//typedef struct SnakeNode* pSnakeNode
//蛇行动的方向
typedef enum SnakeDir
{
UP = 1,
DOWN,
LEFT,
RIGHT
}SnakeDir;
//蛇运动的状态
typedef enum SnakeState
{
OK, //正常
KILL_BY_WALL, //撞到墙
KILL_BY_SELF, //撞到自己
NORMOL_END
}SnakeState;
//定义一条蛇,以便维护蛇的一些相关信息
typedef struct Snake
{
pSnakeNode phSnake; //指向蛇的头部的指针
pSnakeNode pFood; //指向食物节点的指针
pSnakeNode phFood; //现有很多食物,指向很多食物的头结点
SnakeDir Dir; //蛇运动的方向
SnakeState Ste; //蛇运动的状态,有快有慢
int sleepTime; //判断蛇运动快慢的标志,就是用休眠时间来让它进行快或者慢的运动
int foodScore; //食物的分数
int allScore; //总分
}Snake, * pSnake;
//初始化游戏
void GameStart(pSnake ps);
//进行游戏
void GameRun(pSnake ps);
//结束游戏
void GameEnd(pSnake ps);
//定位光标设置
void SetPos(short x, short y);
//欢迎界面的打印
void welcomGame();
//地图的创建
void createMap();
//蛇的初始化
void SnakeInit(pSnake ps);
//创建一个食物
void createFood(pSnake ps);
//贪吃蛇走动
void snakeMove(pSnake ps);
//判断下一个节点是否为食物
int isNextFood(pSnakeNode pn, pSnake ps);
//下一个节点为食物,吃掉食物
void eatFood(pSnakeNode pn, pSnake ps);
//下一个节点不为食物
void noFood(pSnakeNode pn, pSnake ps);
//被墙撞shu
int KillByWall(pSnake ps);
//被自己杀死
int KillBySelf(pSnake ps);
//创建多个食物
void createMoreFood(ps);
//是否为多个食物中的一个
int isMoreNextFood(pSnakeNode pn, pSnake ps);
2.Snack.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include "Snake.h"
#define BODY L'●'
#define FOOD L'★'
#define WALL L'□'
#define X 24
#define Y 3
#define FOODSIZE 3
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 1) ? 1 : 0
void SetPos(short x, short y)
{
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x ,y };
SetConsoleCursorPosition(houtput, pos);
}
//打印欢印界面和游戏规则
void welcomGame()
{
//界面1
//设置光标
SetPos(38, 12);
printf("欢印来到贪吃蛇小游戏");
SetPos(38, 20);
system("pause");
//清空
system("cls");
//界面2
SetPos(32, 12);
printf("用↑.↓.→.← 来表示蛇移动的方向\n");
SetPos(32, 13);
printf("F1为加速,F2为减速,加速吃到食物可以得到更高的分\n");
SetPos(32, 20);
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 < 26; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 1; i < 26; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
void SnakeInit(pSnake ps)
{
//创建5个节点,并将上述5个节点组成一条蛇
pSnakeNode cur = NULL;
int i = 0;
for (i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("SnakeInit :: malloc\n");
return;
}
//设置坐标
cur->x = X + i * 2;
cur->y = Y;
cur->next = NULL;
if (ps->phSnake == NULL)
{
ps->phSnake = cur;
}
else
{
//头插法
cur->next = ps->phSnake;
ps->phSnake = cur;
}
}
//遍历这5个节点,打印蛇的身体
cur = ps->phSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//初始化蛇里面的各种数据
ps->allScore = 0;
ps->foodScore = 10;
ps->sleepTime = 200;
ps->Ste = OK;
ps->Dir = RIGHT;
}
void createFood(pSnake ps)
{
//注意食物必须也占两行,并且不能够创建到墙中或者蛇自己身上
pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
if (food == NULL)
{
perror("createFood::malloc");
return;
}
int x = 0;
int y = 0;
a:
do
{
x = rand() % 53 + 2; // x:2~54
y = rand() % 25 + 1; // y:1~25
} while (x % 2 != 0);
pSnakeNode cur = ps->phSnake;
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto a;
}
cur = cur->next;
}
food->x = x;
food->y = y;
food->next = NULL;
ps->pFood = food;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
void GameStart(pSnake ps)
{
//1.先设置窗口的 标题 大小
system("title 贪吃蛇");
system("mode con cols=100 lines=30");
//2.设置光标的隐藏
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(houtput, &cursor_info);
cursor_info.bVisible = false;
SetConsoleCursorInfo(houtput, &cursor_info);
//3.打印欢印界面和游戏规则
welcomGame();
//4.绘制地图(障碍物)
createMap();
//6.创建蛇 ==> 初始化蛇身
SnakeInit(ps);
//5.创建食物
createFood(ps);
//5.创建多个食物
//createMoreFood(ps);
}
//帮组打印额外的信息
void helpPrint()
{
SetPos(68, 12);
printf("不能穿墙,不能碰到自己\n");
SetPos(68, 13);
printf("用↑.↓.→.← 来表示蛇移动的方向\n");
SetPos(68, 14);
printf("F1表示加速,F2表示减速\n");
SetPos(68, 15);
printf("ESC:退出游戏 SPACE:暂停\n");
SetPos(68, 20);
printf("比特就业课授权\n");
}
void pause()
{
while (1)
{
Sleep(500);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
int isNextFood(pSnakeNode pn,pSnake ps)
{
return (pn->x == ps->pFood->x) && (pn->y == ps->pFood->y);
}
void eatFood(pSnakeNode pn, pSnake ps)
{
pn->next = ps->phSnake;
ps->phSnake = pn;
ps->allScore += ps->foodScore;
pSnakeNode cur = ps->phSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
free(ps->pFood);
ps->pFood = NULL;
createFood(ps);
}
void noFood(pSnakeNode pn, pSnake ps)
{
pn->next = ps->phSnake;
ps->phSnake = pn;
pSnakeNode cur = ps->phSnake;
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;
}
int KillByWall(pSnake ps)
{
if ((ps->phSnake->x == 0) || (ps->phSnake->x == 56) ||
(ps->phSnake->y == 0) || (ps->phSnake->y == 26))
{
return 1;
}
return 0;
}
int KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->phSnake->next;
while (cur)
{
if ((ps->phSnake->x == cur->x) && (ps->phSnake->y == cur->y))
{
return 1;
}
cur = cur->next;
}
return 0;
}
void snakeMove(pSnake ps)
{
//有四种走动方式:上 下 左 右
//创建下一个节点
pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{
perror("snakeMove :: malloc");
return;
}
if (ps->Dir == UP)
{
pnext->x = ps->phSnake->x;
pnext->y = ps->phSnake->y - 1;
}
else if (ps->Dir == DOWN)
{
pnext->x = ps->phSnake->x;
pnext->y = ps->phSnake->y + 1;
}
else if (ps->Dir == RIGHT)
{
pnext->x = ps->phSnake->x + 2;
pnext->y = ps->phSnake->y;
}
else if (ps->Dir == LEFT)
{
pnext->x = ps->phSnake->x - 2;
pnext->y = ps->phSnake->y;
}
if (isNextFood(pnext, ps))
{
eatFood(pnext, ps);
}
else
{
noFood(pnext, ps);
}
if (KillByWall(ps))
{
ps->Ste = KILL_BY_WALL;
}
if (KillBySelf(ps))
{
ps->Ste = KILL_BY_SELF;
}
}
void GameRun(pSnake ps)
{
helpPrint();
do
{
SetPos(68, 8);
printf("得分:%d\n", ps->allScore);
SetPos(68, 9);
printf("每个食物的分数:%2d\n", ps->foodScore);
//现在要让贪吃蛇动起来,此时需要确定它的方向(确定按键状态)
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->Ste = NORMOL_END;
}
else if (KEY_PRESS(VK_F1))
{
//加速,最多加速四次
if (ps->sleepTime > 80)
{
ps->sleepTime -= 30;
ps->foodScore += 2;
}
}
else if (KEY_PRESS(VK_F2))
{
//减速,也最多减速四次
if (ps->foodScore > 2)
{
ps->sleepTime += 30;
ps->foodScore -= 2;
}
}
//休眠,然后下一次准备再次执行该程序,只到不满足的条件出现就跳出该循环
Sleep(ps->sleepTime);
//贪吃蛇走动一步
snakeMove(ps);
} while (ps->Ste == OK);
}
void GameEnd(pSnake ps)
{
pSnakeNode cur = ps->phSnake;
while (cur)
{
pSnakeNode pn = cur->next;
free(cur);
cur = pn;
}
}
3.test.c:
#include"Snake.h"
void test()
{
srand((unsigned int)time(NULL));
int ch = 0;
do
{
Snake snake = { 0 };
//初始化游戏
GameStart(&snake);
//进行游戏
GameRun(&snake);
结束游戏
GameEnd(&snake);
SetPos(14, 12);
printf("游戏结束!还要再来一把吗(Y/N):");
ch = getchar();
while(getchar() != '\n');
} while (ch == 'y' || ch == 'Y');
}
int main()
{
//设置适配环境,只有在本地模式(Chinese)下才支持宽字符的打印
setlocale(LC_ALL, "");
test();
return 0;
}