手撕——贪吃蛇小游戏(下)
引言
上一章介绍了实现贪吃蛇小游戏必备的知识点。
这章,让我们一起开启手搓核弹之旅吧。
先附上贪吃蛇代码的git:贪吃蛇小游戏_4_23 · Shown_shuai/learn_c - 码云 - 开源中国 (gitee.com)
上一章的窗口:
手撕——贪吃蛇小游戏(上)(前置)-CSDN博客
一、整体思维导图
二、创建贪吃蛇
在游戏开始前,先本地化,为了后面打印的需要
int main()
{//设置本地化setlocale(LC_ALL, "");return 0;
}
用链表来维护贪吃蛇
在snack.h中进行类型的声明
1. 蛇身的节点
//蛇身体的节点
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* nest;
}SnakeNode,* pSnakeNode;
//pSnakeNode是指向节点的指针类型
//等价于:typedef struct SnakeNode* pSnakeNode;
2. 创建贪吃蛇
贪吃蛇有许多需要维护的信息,将贪吃蛇创建为一个结构体。
//蛇的方向
enum DIRECTION
{UP = 1, //上DOWN, //下LEFT, //左RIGHT //右
};
//蛇的状态
//游戏正常,撞墙,撞到自己,正常退出
enum GAME_STATUS
{OK, //正常KILL_BY_WALL, //撞墙KILL_BY_SELF, //撞到自己END_NORMAL //正常退出
};//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake; //指向蛇头的指针pSnakeNode _pFood; //指向食物的指针enum DIRECTION _dir; //蛇的方向enum GAME_STATUES _status; //游戏的状态int _food_weight; //一个食物的分数int _score; //总分数int _sleep_time; //休息时间,时间越长,速度越慢,越短,速度越快
}Snake, *pSnake;
3.用枚举结构体,来枚举蛇属性的状态:
//蛇的方向
enum DIRECTION
{UP = 1, //上DOWN, //下LEFT, //左RIGHT //右
};//蛇的状态
//游戏正常,撞墙,撞到自己,正常退出
enum GAME_STATUS
{OK, //正常KILL_BY_WALL, //撞墙KILL_BY_SELF, //撞到自己END_NORMAL //正常退出
};
4. 定义特殊的符号,和特殊的数据
#define POS_X 24 //游戏刚开始的时候,蛇头的位置
#define POS_Y 5 //游戏刚开始的时候,蛇头的位置
#define WALL L'□'
#define BOOY L'●'
#define FOOD L'★'
三、开始游戏
1.设置游戏窗口大小和名字,隐藏光标
//1.设置窗口的大小,光标隐藏
system("mode con cols=110 lines=35");
system("title 贪吃蛇");HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄(通过这个句柄来对控制台进行操作)CONSOLE_CURSOR_INFO curInfo; //存有光标信息的结构体
//获得和hOutput句柄相关的控制台上的光标信息,存放在curInfo中
GetConsoleCursorInfo(houtput, &curInfo); curInfo.bVisible = false; //修改光标信息//设置获得和hOutput句柄相关的控制台上的光标信息
SetConsoleCursorInfo(houtput, &curInfo);
2. 打印欢迎界面和功能介绍(上一章的知识点):
//光标定位
void SetPos(int x, int y)
{//获取标准输出的句柄(通过这个句柄来对控制台进行操作)HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x, y };SetConsoleCursorPosition(hOutput, pos);
}//打印欢迎界面和功能介绍
void WelcomeToGame()
{SetPos(45, 16);printf("欢迎来的贪吃蛇小游戏\n");SetPos(48, 20);system("pause");system("cls"); //清空屏幕SetPos(35, 16);wprintf(L"用↑,↓,←,→来控制小蛇的移动,按F3加速,F4减速\n");SetPos(43, 18);wprintf(L"加速可以获得更高的分数");SetPos(45, 23);system("pause");system("cls");
}
3.绘制地图:
//3.绘制地图
void CreateMap()
{int i = 0;//上for (int i = 0; i < 35; i++){wprintf(L"%lc", WALL);}//下SetPos(0, 30);for (i = 0; i < 35; i++){wprintf(L"%lc", WALL);}//左for (i = 1; i < 30; i++){SetPos(0, i);wprintf(L"%lc\n", WALL);}//右for (i = 1; i < 30; i++){SetPos(68, i);wprintf(L"%lc\n", WALL);}SetPos(80, 0);
}
4.创建蛇
申请5个节点,使用头插,将蛇链接起来
//初始化蛇身
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSanke(): malloc fail!");return;}cur->nest = NULL;cur->x = POS_X + 2 * i;cur->y = POS_Y;if (ps->_pSnake == NULL){ps->_pSnake = cur;}else{cur->nest = ps->_pSnake;ps->_pSnake = cur;}}//打印:cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->nest;}//设置贪吃蛇的属性ps->_dir = RIGHT;ps->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;ps->_status = OK;
}
5.创建食物
//创建食物
void CreateFood(pSnake ps)
{int x = 0; //取值范围:2 ~ 66 0~64int y = 0; // 1 ~ 30 0~29
again:do{x = rand() % 65 + 2;y = rand() % 29 + 1;} while (x % 2 != 0); //x 必须是 2 的倍数//x 和 y 的坐标不能和蛇的坐标相同pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->nest;}//创建食物的节点:pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreaterFood():malloc fail");return;}pFood->x = x;pFood->y = y;pFood->nest = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pFood = pFood;}
6.初始化游戏的所以代码:
//游戏的初始化
void GameStart(pSnake ps)
{//0.设置窗口的大小,光标隐藏system("mode con cols=110 lines=35");system("title 贪吃蛇");HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄(通过这个句柄来对控制台进行操作)CONSOLE_CURSOR_INFO curInfo; //存有光标信息的结构体//获得和hOutput句柄相关的控制台上的光标信息,存放在curInfo中GetConsoleCursorInfo(houtput, &curInfo); curInfo.bVisible = false; //修改光标信息//设置获得和hOutput句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, &curInfo); //1.打印欢迎界面//2.功能介绍WelcomeToGame();//3.绘制地图CreateMap();//4.创建蛇InitSnake(ps);//5.创建食物CreateFood(ps);
}
四、运行游戏
1. 打印帮助信息
void PrintHelpInfo()
{SetPos(74, 15);wprintf(L"%s", L"不能穿墙,不能咬到自己");SetPos(71, 16);wprintf(L"%s", L"用↑,↓,←,→来控制小蛇的移动");SetPos(74, 17);wprintf(L"%s", L"按F3加速,F4减速\n");SetPos(71, 18);wprintf(L"%s", L"ESC:退出游戏,space:暂停游戏");SetPos(74, 20);wprintf(L"%s", L"制作者:@Run_Teenage");
}
2.定义一个宏,检测按键是否被按过
上一章有讲,可以去看一下
#define KEY_PRESS(kv) ((GetAsyncKeyState(kv)&1) ? 1:0)
3.游戏运行时,整体结构:
//游戏的运行:
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//打印总分数和食物的分数SetPos(74,10);printf("总分数:%d\n", ps->_score);SetPos(74, 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_DOWN) && ps->_dir != UP)//按下键时,并且向上走,不会有反应{ps->_dir = DOWN;}else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){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_ESCAPE)){//正常退出游戏ps->_status = END_NORMAL;}//贪吃蛇走SnakeMove(ps); //蛇走一步的过程Sleep(ps->_sleep_time);} while (ps->_status == OK);
}
4.实现暂停模块
//暂停
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}
5.实现贪吃蛇走一步的过程
//贪吃蛇走
void SnakeMove(pSnake ps) //蛇走一步的过程
{//创建一个节点,表示蛇,即将到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove malloc");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;break;case DOWN:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;break;case LEFT:pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;break;case RIGHT:pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;break;}//检测下一个坐标是不是食物if (NextIsFood(pNextNode, ps)){//是食物EatFood(pNextNode, ps);}else{//不是食物NoFood(pNextNode, ps);}//检测蛇是否撞墙KillByWall(ps);//检测蛇是否撞到自己KillBySelf(ps);
}
5.1检查下一个位置是不是食物
//检测下一个坐标是不是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{return (pn->x == ps->_pFood->x && pn->y == ps->_pFood->y);
}
5.2下一个是食物
//下一个位置是食物,吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{//头插ps->_pFood->nest = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放next节点free(pn);pn = NULL;pSnakeNode cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BOOY);cur = cur->nest;}ps->_score += ps->_food_weight;//重新创建食物CreateFood(ps);
}
5.3下一个位置不是食物
//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{//头插pn->nest = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;while (cur->nest->nest != NULL){SetPos(cur->x, cur->y);wprintf(L"%c", BOOY);cur = cur->nest;}//把最后一个位置的节点打印成空格SetPos(cur->nest->x , cur->nest->y);printf(" ");//释放最后一个位置的节点free(cur->nest);//把倒数第二的节点的地址置为NULLcur->nest = NULL;
}
5.4检查蛇是否撞到墙
//检测蛇是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 68 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 29){ps->_status = KILL_BY_WALL;}
}
5.5检查蛇是否撞到自己
遍历蛇头后面的节点的坐标是否和蛇的头节点重合,重合,则说明自己撞到了自己
//检测蛇是否撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->nest;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;break;}cur = cur->nest;}
}
五、游戏结束 (善后工作)
打印游戏是为什么结束的,释放贪吃蛇的节点
//结束游戏 —— 善后工作(释放内存之类的)
void GameEnd(pSnake ps)
{SetPos(25, 15);switch (ps->_status){case END_NORMAL:printf("正常结束游戏");break;case KILL_BY_WALL:printf("撞上墙了,游戏结束");break;case KILL_BY_SELF:printf("咬到自己了,游戏结束");break;}//释放蛇身节点pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->nest;free(del);}
}
六、合并游戏的全部流程,处理细节问题
#include"snack.h"void test()
{int ch = 0;do{system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1.打印欢迎界面//2.功能介绍//3.绘制地图//4.创建蛇//5.创建食物//6.设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏 —— 善后工作(释放内存之类的)GameEnd(&snake);while (_kbhit()){_getch(); //吸收蛇动时,按键盘的键值带来的影响,不然游戏会有小bug}SetPos(24, 16);printf("再来一局吗?(Y/N)");ch = getchar();while (getchar() != '\n');} while (ch == 'Y' || ch == 'y');SetPos(0, 32);
}
int main()
{//设置本地化setlocale(LC_ALL, "");srand((unsigned int )time(NULL));test();return 0;
}