一.游戏介绍
二.游戏实现
1.test.c:游戏测试
1>设置适配本地的环境
2>在test()中完成游戏的测试逻辑
1.创建贪吃蛇
2.初始化游戏
3.运行游戏
4.结束游戏(善后工作)
3>代码实现
#include "snake.h"
#include <locale.h>
void test()
{
system("cls");
int op = 0;
do
{
system("cls");
Snake snake = { 0 };
//初始化游戏
GameStart(&snake);
//运行游戏
GameRun(&snake);
//结束游戏
GameEnd(&snake);
SetPos(20, 14);
printf("亲,还想再来一次吗(Y or N)?:");
op = getchar();
while(getchar()!='\n');//清理\n
} while (op == 'Y' || op == 'y');
SetPos(0, 27);
}
int main()
{
//创建适配本地的环境
setlocale(LC_ALL, "");
srand((unsigned int)time(NULL));
test();
return 0;
}
2.snake.h:类型声明,函数声明
snake.c:函数实现
1>创建贪吃蛇
1.贪吃蛇节点的定义:包含坐标,指向下一个节点的坐标
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
2.贪吃蛇的定义:蛇头位置,食物位置,
蛇的运动方向,蛇的运动状态,(枚举声明)
蛇的速度,(通过sleep来实现速度的改变)
一个食物的分数,总分数
注:该步是为了便于维护贪吃蛇的信息
typedef struct Snake
{
pSnakeNode pSnake;//指向蛇头的位置的指针
pSnakeNode pfood;//指向食物的位置的指针
enum DIRCTION dir;//蛇的运动方向
enum GAME_STATE state;//蛇的运动状态
int sleep_time;//睡眠时间,睡眠时间越短,速度越快
int food_weight;//一个食物的分数
int score;//总分数
}Snake,*pSnake;
//蛇的运动方向:上,下,左,右
enum DIRCTION
{
UP=1,
DOWN,
LEFT,
RIGHT
};
//蛇的运动状态:正常,撞到墙,撞到自己,正常结束
enum GAME_STATE
{
OK,
KILL_BY_WALL,
KILL_BY_SELF,
END_NORMAL
};
2>初始化游戏
1.设置窗口大小,隐藏光标
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);//设置控制台光标状态
2.打印环境界面,进行功能介绍
1>封装一个函数实现光标的定位
void SetPos(short x, short y)
{
//获得标准输出设备的句柄
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定位光标位置
COORD pos = { x,y };
SetConsoleCursorPosition(houtput, pos);
}
2>打印环境界面函数的实现
void WelcomeToGame()
{
SetPos(40, 15);
wprintf(L"欢迎来到贪吃蛇小游戏\n");
SetPos(40, 20);
system("pause");
system("cls");//清理屏幕
SetPos(25, 14);
wprintf(L"使用↑,↓,←,→控制蛇的移动,按F3加速,F4减速\n");
SetPos(25, 15);
wprintf(L"加速可以获得更高分数\n");
SetPos(42, 20);
system("pause");
system("cls");
}
3.绘制地图
1>在地图中2x=y,所以在打印横行时,应该注意加个空行
2>按上下左右的顺序实现墙体的打印,在左(右)打印时需要将光标定位在开头(结尾)处
3>代码实现
void CreateMap()
{
int i = 0;
//上
for (i = 0;i < 29; i++)
{
wprintf(L"%lc ", L'□');
}
//下
SetPos(0, 26);
for (i = 0; i < 29; i++)
{
wprintf(L"%lc ",L'□');
}
//左
for (i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", L'□');
}
//右
for (i = 1; i <= 25; i++)
{
SetPos(56,i);
wprintf(L"%lc", L'□');
}
}
4.创建蛇
1>初始化蛇身:为贪吃蛇开辟5个节点作为初始长度,位置定位在(24,5)
2>设置贪吃蛇最初的一些属性:
默认运动方向:RIGHT
默认速度大小:200毫秒
游戏状态:OK
最初成绩:0
一个食物的分数:10
3>代码实现
void InitSnake(pSnake ps)
{
//初始化蛇身
for (int i = 0; i < 5; i++)
{
//创建节点
pSnakeNode psn = (pSnakeNode)malloc(sizeof(SnakeNode));
if (psn == NULL)
{
perror("malloc");
exit(1);
}
//位置初始化
psn->x = 24+2*i;
psn->y = 5;
psn->next = NULL;
//头插串联5个节点
if (ps->pSnake == NULL)
{
ps->pSnake = psn;
}
else
{
psn->next = ps->pSnake;
ps->pSnake = psn;
}
}
//打印蛇身
pSnakeNode cur = ps->pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//设置参数
ps->sleep_time = 200;
ps->score = 0;
ps->food_weight = 10;
ps->state = OK;
ps->dir = RIGHT;
}
5.创建食物
1>随机生成食物位置
2>食物位置应该满足:
不在墙体内,不在蛇体内
食物位置x应该是2的倍数
3>打印食物
4>代码实现
void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
//判断不是2的倍数,不在墙体内
do
{
//x:2-55
//y: 1-26
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} 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("malloc");
exit(1);
}
else
{
pFood->x = x;
pFood->y = y;
pFood->next = NULL;
SetPos(x, y);
wprintf(L"%lc", FOOD);
ps->pfood = pFood;
}
}
3>游戏运行
1.打印帮助信息
void PrintHelpInfo()
{
SetPos(65, 14);
wprintf(L"%ls", L"不能撞墙,不能咬到自己");
SetPos(65, 15);
wprintf(L"%ls", L"按↑,↓,←,→控制蛇的移动");
SetPos(65, 16);
wprintf(L"%ls",L"按F3加速,按F4减速");
SetPos(65, 17);
wprintf(L"%ls", L"按ESC退出游戏,按空格键暂停游戏");
}
2.检测按键状态,以判断运动方向
1>当按下的状态与当前运动状态相反时,其运动方向不变
2>按键状态检测的宏
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1)?1:0)
3>代码实现
do
{
SetPos(65, 10);
printf("总分:%d\n", ps->score);
SetPos(65, 11);
printf("一个食物的分数:%d\n", 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_F3))
{
//加速
if (ps->food_weight < 18)
{
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;
}
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
//退出游戏
ps->state = END_NORMAL;
}
} while (ps->state == OK);
3.蛇移动一步
1>创建一个下一个节点
pSnakeNode NextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (NextNode == NULL)
{
perror("malloc");
exit(1);
}
//确定下一个节点的坐标
switch (ps->dir)
{
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;
break;
}
2>下一个节点是否是食物
int NextIsFood(pSnakeNode psn, pSnake ps)
{
return ((psn->x == ps->pfood->x && psn->y == ps->pfood->y) ? 1 : 0);
}
1.是食物,则吃食物
void EatFood(pSnakeNode psn, pSnake ps)
{
//以头插法将食物节点连接在原链表上
psn->next = ps->pSnake;
ps->pSnake = psn;
//打印蛇
pSnakeNode cur = ps->pSnake;
while (cur != NULL)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//显示总分
ps->score += ps->food_weight;
//释放食物位置,因为此时有两个节点指向食物所处位置
free(ps->pfood);
ps->pfood = NULL;
//创建新的食物
CreateFood(ps);
}
2.不是食物
void NoFood(pSnakeNode psn, pSnake ps)
{
//以头插法将食物节点连接在原链表上
psn->next = ps->pSnake;
ps->pSnake = psn;
//打印蛇
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);
printf(" ");
free(cur->next);
cur->next = NULL;
}
3>此时撞墙
1.当x=0or56,ory=0or26时,表明蛇已经撞到墙了,此时将蛇的状态改为Kill_By_Wall
2.代码实现
int KillByWall(pSnake ps)
{
if (ps->pSnake->x == 0 || ps->pSnake->x == 56 || ps->pSnake->y == 0 || ps->pSnake->y == 26)
{
ps->state = KILL_BY_WALL;
return 1;
}
return 0;
}
4>此时撞到自己
1.判断每个节点对应的坐标是否与蛇头坐标相等,若相等则撞到自己,此时更改蛇的状态为 Kill_By_Self
2.代码实现
int KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->pSnake->next;
while (cur)
{
if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
{
ps->state = KILL_BY_SELF;
return 1;
}
cur = cur->next;
}
return 0;
}
4.蛇休息
1>蛇每一移动一步让它睡眠一段时间
4>结束游戏
1.根据蛇的状态给出提示信息
//1.判断游戏状态,给出提示信息
SetPos(24, 12);
switch (ps->state)
{
case END_NORMAL:
printf("您主动退出游戏!\n");
break;
case KILL_BY_SELF:
printf("您撞到自己了哟!游戏结束!\n");
break;
case KILL_BY_WALL:
printf("您撞到墙了哟!游戏结束!\n");
break;
}
2.释放链表
//2.释放链表
pSnakeNode cur = ps->pSnake;
pSnakeNode del = NULL;
while (cur)
{
del = cur;
cur = cur->next;
free(del);
}
3.