目录
一、游戏流程设计
- GameStart
- WelcomeToGame:打印欢迎界面
- CreateMap:打印地图
- InitSnake:初始化蛇身
- InitFood:初始化食物
- GameRun
- SnakeMove:实现蛇身移动
- EatFood:判断是否吃到食物并对吃到食物进行处理
- NoFood:判断是否吃到食物并对没有吃到食物进行处理
- DropByWall:判断是否撞墙
- DropBySelf:判断是否撞到自己
- GameEnd
判断游戏终止的条件,并销毁中途创建的数据
二、游戏实现原理
2.1如何创建并管理数据
- 贪吃蛇在吃到食物后节点会加长,为了便于管理数据,简化操作方式,采用链表的形式进行操作。
- 将游戏运行时的状态位全部保存在结构体中管理。
typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode,*pSnakeNode; enum DIRECTION { UP=1, DOWN, RIGHT, LEFT, }; enum STATUS { OK, NORMAL, DROP_BY_WALL, DROP_BY_SELF, ESC, }; typedef struct Snake { pSnakeNode _pSnake; //指向蛇头的指针 pSnakeNode _pFood; //指向食物的指针 enum DIRECTION _dir; enum STATUS _status; int _food_weight; //食物权重由速度决定 int _score; //总分数 int _sleeptime; //休眠时间 }snakestruct,*psnake;
2.2如何实现蛇身移动
先移动头节点,打印后续所有节点。因为之前打印了尾节点,所以二次打印时要在原先尾节点的位置打印空格
2.3如何实现食物随机放置
采用rand函数,详见博主另一篇博客:http://t.csdnimg.cn/YJLFO
2.4如何检测按键与调整光标位置
涉及到控制台相关知识,详见博主另一篇博客:http://t.csdnimg.cn/Vyyo1
三、源代码
3.1 test.c
#include"snake.h"
#include<locale.h>
void test()
{
system("cls");
//创建贪吃蛇
snakestruct snake = { 0 };
//初始化游戏
GameStart(&snake);
//运行游戏
GameRun(&snake);
//结束游戏 - 善后工作
GameEnd(&snake);
SetPos(0, 27);
}
int main()
{
setlocale(LC_ALL, "");
srand((unsigned int)time(NULL));
test();
return 0;
}
3.2 snake.h
#pragma once
#include<windows.h>
#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<string.h>
#define POS_X 24
#define POS_Y 5
//蛇身/食物 链表
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
//方向枚举
enum DIRECTION
{
UP=1,
DOWN,
RIGHT,
LEFT,
};
//状态位枚举
enum STATUS
{
OK,
NORMAL,
DROP_BY_WALL,
DROP_BY_SELF,
ESC,
};
//总数据管理
typedef struct Snake
{
pSnakeNode _pSnake; //指向蛇头的指针
pSnakeNode _pFood; //指向食物的指针
enum DIRECTION _dir;
enum STATUS _status;
int _food_weight; //食物权重由速度决定
int _score; //总分数
int _sleeptime; //休眠时间
}snakestruct,*psnake;
//游戏开始
void GameStart(psnake ps);
void WelcomeToGame();
void CreateMap();
void InitSnake(psnake ps);
void InitFood(psnake ps);
//游戏运行
void GameRun(psnake ps);
void SnakeMove(psnake ps);
void EatFood(psnake ps, pSnakeNode pn);
void NoFood(psnake ps, pSnakeNode pn);
void DropByWall(psnake ps);
void DropBySelf(psnake ps);
//游戏结束
void GameEnd(psnake ps);
3.3 snake.c
#include"snake.h"
//设置光标函数
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
// 获取标准输出的句柄
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
// 设置标准输出设备上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
//打印欢迎界面
void WelcomeToGame()
{
SetPos(40, 14);
wprintf(L"欢迎来到贪吃蛇小游戏\n");
SetPos(42, 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");
}
//创建地图
void CreateMap()
{
SetPos(0, 0);
for (int i = 0; i < 29; i++)
{
wprintf(L"□");
}
for (int i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"□");
}
for (int i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"□");
}
SetPos(0, 26);
for (int i = 0; i < 29; i++)
{
wprintf(L"□");
}
SetPos(0, 30);
}
//初始化蛇身
void InitSnake(psnake ps)
{
pSnakeNode cur = NULL;
//创建虚拟节点:避免了空指针的判断
pSnakeNode dummynode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (dummynode == NULL)
{
perror("malloc");
exit(1);
}
dummynode->next = NULL;
for (int i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("malloc");
exit(1);
}
cur->next = NULL;
cur->x = POS_X + 2 * i;
cur->y = POS_Y;
//头插法
cur->next = dummynode->next;
dummynode->next = cur;
}
ps->_pSnake = dummynode->next;
//打印
while (ps->_pSnake)
{
SetPos(ps->_pSnake->x, ps->_pSnake->y);
wprintf(L"●");
ps->_pSnake = ps->_pSnake->next;
}
ps->_pSnake = dummynode->next;
//设置属性
ps->_dir = RIGHT;
ps->_score = 0;
ps->_food_weight = 10;
ps->_sleeptime = 200; //单位为毫秒
ps->_status = OK;
}
//初始化食物
void InitFood(psnake ps)
{
int food_x, food_y = 0;
again:
do
{
food_x = (rand() % 53) + 2; //2~54
food_y = (rand() % 25) + 1; //0~24
} while (food_x % 2 != 0);
pSnakeNode cur = ps->_pSnake;
while (cur)
{
if (food_x == cur->x && food_y == cur->y)
{
goto again;
}
cur = cur->next;
}
//创建节点
pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
if (food == NULL)
{
perror("malloc");
exit(1);
}
food->x = food_x;
food->y = food_y;
food->next = NULL;
SetPos(food_x, food_y);
wprintf(L"★");
ps->_pFood = food;
}
//游戏开始总函数
void GameStart(psnake ps)
{
//准备工作
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);//设置控制台光标状态
//打印环境界面
WelcomeToGame();
//绘制地图
CreateMap();
//创建蛇
InitSnake(ps);
//创建食物
InitFood(ps);
}
//打印帮助界面
void HelpPrint()
{
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退出游戏,按空格暂停游戏");
SetPos(64, 18);
wprintf(L"%ls", L"CSDN@都市隸人");
}
//判断下一节点是不是食物
int NextIsFood(psnake pf, pSnakeNode ps)
{
return (pf->_pFood->x == ps->x && pf->_pFood->y == ps->y);
}
//吃食物操作
void EatFood(psnake ps, pSnakeNode pn)
{
//头插法
ps->_pFood->next = ps->_pSnake;
ps->_pSnake = ps->_pFood;
//释放下一个节点
free(pn);
pn = NULL;
//打印
pSnakeNode cur = ps->_pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"●");
cur = cur->next;
}
ps->_score += ps->_food_weight;
//重新创建食物
InitFood(ps);
}
//不是食物操作
void NoFood(psnake ps, pSnakeNode pn)
{
//头插法
pn->next = ps->_pSnake;
ps->_pSnake = pn;
pSnakeNode cur = ps->_pSnake;
//打印
while (cur->next->next)
{
SetPos(cur->x, cur->y);
wprintf(L"●");
cur = cur->next;
}
SetPos(cur->next->x, cur->next->y);
printf(" ");
//释放节点
free(cur->next);
cur->next = NULL;
}
//撞墙操作
void DropByWall(psnake ps)
{
if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||
ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
{
ps->_status = DROP_BY_WALL;
}
}
//撞到自己操作
void DropBySelf(psnake ps)
{
pSnakeNode cur = ps->_pSnake->next;
while (cur)
{
if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
{
ps->_status = DROP_BY_SELF;
break;
}
cur = cur->next;
}
}
//蛇身移动主函数
void SnakeMove(psnake ps)
{
pSnakeNode pnextpos = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnextpos == NULL)
{
perror("malloc");
exit(1);
}
switch (ps->_dir)
{
case UP:
pnextpos->x = ps->_pSnake->x;
pnextpos->y = ps->_pSnake->y-1;
break;
case DOWN:
pnextpos->x = ps->_pSnake->x;
pnextpos->y = ps->_pSnake->y + 1;
break;
case LEFT:
pnextpos->x = ps->_pSnake->x-2;
pnextpos->y = ps->_pSnake->y;
break;
case RIGHT:
pnextpos->x = ps->_pSnake->x + 2;
pnextpos->y = ps->_pSnake->y;
break;
}
//食物检测
if (NextIsFood(ps, pnextpos))
{
EatFood(ps, pnextpos);
}
else
{
NoFood(ps, pnextpos);
}
//撞墙检测
DropByWall(ps);
//撞自己检测
DropBySelf(ps);
}
//判断按键是否被按下
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//游戏运行主函数
void GameRun(psnake ps)
{
HelpPrint();
do
{
//刷新打印
SetPos(64, 10);
printf("总分数:%d\n", ps->_score);
SetPos(64, 11);
printf("当前食物的分数:%2d\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_SPACE))
{
while (1)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
else if (KEY_PRESS(VK_ESCAPE))
{
//正常退出游戏
ps->_status = NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
//加速
if (ps->_sleeptime > 80)
{
ps->_sleeptime -= 30;
ps->_food_weight += 2; //分数权重增加
}
}
else if (KEY_PRESS(VK_F4))
{
//减速
if (ps->_food_weight > 2)
{
ps->_sleeptime += 30;
ps->_food_weight -= 2; //分数权重减少
}
}
SnakeMove(ps);//蛇走一步的过程
Sleep(ps->_sleeptime);
} while (ps->_status==OK);
}
//游戏结束主函数
void GameEnd(psnake ps)
{
SetPos(24, 12);
switch (ps->_status)
{
case NORMAL:
wprintf(L"您主动结束游戏\n");
break;
case DROP_BY_WALL:
wprintf(L"您撞到墙上,游戏结束\n");
break;
case DROP_BY_SELF:
wprintf(L"您撞到了自己,游戏结束\n");
break;
}
//销毁链表
pSnakeNode cur = ps->_pSnake;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}