💞💞 前言
hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注🥰🥰收藏🌹🌹🌹
💥个人主页:大耳朵土土垚的博客
💥 所属专栏:C语言学习笔记、C/C++小游戏
💥对于C语言学习疑问的都可以在上面的专栏进行学习哦~ 有问题可以写在评论区或者私信我哦~
一、游戏实现效果展示
二、游戏实现源码
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<locale.h> #include<stdbool.h> #include<Windows.h> #include <time.h> #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) #define WALL L'■' #define SNAKE L'●' #define SNAKE_HEAD L'●' #define FOOD L'★' //蛇最开始的坐标 #define POS_X 50 #define POS_Y 10 #define SCORE 5 //设置一次加几分 #define FOODQUALITY 3//设置一次性开辟食物个数为3 //定义贪吃蛇蛇身节点(使用链表) typedef struct SnakeNode { //定义x y记录蛇身节点坐标 int x; int y; //记录下一个节点 struct SnakeNode* next; }SnakeNode, * pSnakeNode; //定义食物 typedef struct Food { //定义x y 记录食物节点坐标 int x; int y; //记录下一个节点 struct Food* next; }Food, * pFood; //游戏状态 enum GAME_STATUS { OK = 1, ESC, KILL_BY_WALL, KILL_BY_SELF }; //蛇移动方向 enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT }; //定义贪吃蛇 typedef struct Snake { pSnakeNode snakehead; //记录蛇头节点的指针,这样就可以找到整条蛇 int snake_size; //记录蛇的节点个数 int score; //记录当前得分 pFood food; //记录指向食物的指针 int food_size; //记录食物个数 int FoodWeight;//食物权重 enum GAME_STATUS status;//记录游戏当前状态 enum DIRECTION dir; //记录当前蛇移动方向 int SleepTime;//记录蛇休眠时间 }Snake, * pSnake; //函数声明 void GameStart(pSnake psnake);//游戏开始前的初始化 void Setpos(int x, int y); //定位光标 void GameRun(pSnake psnake); //游戏运行 void GameEnd(pSnake psnake);//游戏结束,清理工作 //颜色设置 void color(int c) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置 //注:SetConsoleTextAttribute是一个API(应用程序编程接口) } //定位光标 void Setpos(int x, int y) { HANDLE hanlde = GetStdHandle(STD_OUTPUT_HANDLE);//从标准输出设备中获得句柄 COORD pos = { x, y };//控制台上的坐标 SetConsoleCursorPosition(hanlde, pos);//根据句柄设置控制台光标的位置 } //打印欢迎信息 void Welcom_Print_Info() { //1.先隐藏光标 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄 CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息 CursorInfo.bVisible = false;//隐藏光标 SetConsoleCursorInfo(handle, &CursorInfo); //2.定位光标 Setpos(40,15); //3.打印欢迎信息 color(11);//设置颜色为蓝色 printf("欢迎来到贪吃蛇小游戏~\n"); //定位光标,打印其他信息 Setpos(40, 17); printf("ARE YOU READY?\n"); //4.请按任意键继续...切换页面 Setpos(50, 30); system("pause"); system("cls");//清空屏幕 //5.下一页功能信息介绍 Setpos(40, 15); printf("请用↑ ↓ ← →来控制蛇的移动,F3加速,F4减速"); Setpos(40, 17); printf("加速能获得更高的分数哦~"); //6.请按任意键继续...切换页面 Setpos(50, 30); system("pause"); system("cls"); } //绘制地图 cols=100 lines=35 void Map_Print() { color(11);//设置颜色为蓝 //1.打印第一行 for (int i = 0; i < 100; i += 2) { wprintf(L"%lc", WALL); } //2.打印最后一行 Setpos(0,35); for (int i = 0; i < 100; i += 2) { wprintf(L"%lc", WALL); } //3.打印左边 for (int i = 0; i < 35; i++) { Setpos(0, i); wprintf(L"%lc", WALL); } //3.打印右边 for (int i = 0; i < 35; i++) { Setpos(98, i); wprintf(L"%lc", WALL); } } //打印游戏说明信息 void Info_Print() { color(11);//设置颜色为蓝色 Setpos(110, 10); printf("1.用 ↑.↓.←.→ 来控制蛇的移动"); Setpos(110, 12); printf("2.F3是加速,F4是减速"); Setpos(110, 14); printf("3.不能穿墙,不能咬到自己"); Setpos(115, 20); printf("@胡土土"); } //初始化蛇 void Init_Snake(pSnake psnake) { //创建蛇5个节点 for (int i = 0; i < 5; i++) { //1.创建节点 pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("Init_Snake():malloc fail"); return; } //2.设置节点坐标 cur->x = POS_X+2*i; cur->y = POS_Y; cur->next = NULL; //3.连接节点 if (psnake->snakehead == NULL) { psnake->snakehead = cur; } else { cur->next = psnake->snakehead; psnake->snakehead = cur; } } //4.打印蛇 pSnakeNode cur = psnake->snakehead; while (cur) { if (cur == psnake->snakehead) { //将蛇头颜色设为红色 color(12); Setpos(cur->x, cur->y);//定位 wprintf(L"%lc", SNAKE_HEAD); } else { //将蛇身颜色设为绿色 color(10); Setpos(cur->x, cur->y);//定位 wprintf(L"%lc", SNAKE); } cur = cur->next; } psnake->snake_size = 5; psnake->dir = RIGHT; psnake->FoodWeight = SCORE; psnake->food = NULL; psnake->food_size = 0; psnake->SleepTime = 300; psnake->status = OK; } //初始化食物 void Init_Food(pSnake psnake) { //创建食物j个节点 int j = rand() % 5 + 1; psnake->food_size = j;; for (int i = 0; i < j; i++) { //1.创建节点 pFood cur = (pFood)malloc(sizeof(Food)); if (cur == NULL) { perror("Init_Food():malloc fail"); return; } //2.使用随机数设置节点坐标 //注意x坐标必须为偶数 again: do { cur->x = rand() % 99+1;//不能为0 cur->y = rand() % 34+1;//不能为0 cur->next = NULL; } while (cur->x % 2 != 0); //判断坐标是否与蛇相同 //要用循环遍历蛇节点 pSnakeNode ps = psnake->snakehead; while (ps) { if (ps->x == cur->x && ps->y == cur->y) { goto again; } ps = ps->next; } //3.连接节点 if (psnake->food == NULL) { psnake->food = cur; } else { cur->next = psnake->food; psnake->food = cur; } } //4.打印食物 pFood cur = psnake->food; while (cur) { //将食物颜色设为黄色 color(14); Setpos(cur->x, cur->y);//定位 wprintf(L"%lc", FOOD); cur = cur->next; } } //游戏开始前的初始化 void GameStart(pSnake psnake) { //1.设置控制台信息 system("title 贪吃蛇"); //设置控制台标题为贪吃蛇 system("mode con cols=150 lines=44"); //设置控制台大小 //2.打印欢迎信息 Welcom_Print_Info(); //3.绘制地图 cols=100 lines=35 Map_Print(); //4.打印游戏说明信息 Info_Print(); //5.初始化蛇 Init_Snake(psnake); //6.初始化食物 Init_Food(psnake); } //暂停恢复函数 void pause() { while (1) { Sleep(100); if (KEY_PRESS(VK_SPACE)) //如果再次按下空格就结束暂停状态 { break; } } } // //判断新节点也就是下一个位置是不是食物 bool IsFood(pSnakeNode newnode,pSnake psnake) { //遍历食物节点来判断 pFood cur = psnake->food; while (cur) { if (cur->x == newnode->x && cur->y == newnode->y) { psnake->food_size--; if (psnake->food_size == 0) { //如果没有食物就重新初始化食物 //释放食物节点 if (psnake->food != NULL) { pFood tmp = psnake->food; pFood rem = tmp; while (tmp) { tmp = tmp->next; free(rem); rem = tmp; } psnake->food = NULL; } Init_Food(psnake); } return true; } cur = cur->next; } return false; } //判断新节点也就是下一个位置是不是墙 bool IsWall(pSnakeNode newnode) { //墙的x坐标是0和100,y坐标是0和35 if (newnode->x == 0 || newnode->x == 100 || newnode->y == 0 || newnode->y == 35) { return true; } return false; } //判断新节点也就是下一个位置是不是自己 bool IsSelf(pSnakeNode newnode, pSnake psnake) { //遍历蛇的节点,注意最后一个不需要遍历,因为新增了一个 pSnakeNode cur = psnake->snakehead; while (cur->next) { if (newnode->x == cur->x && newnode->y == cur->y) { return true; } cur = cur->next; } return false; } //蛇移动的函数- 每次走一步 void SnakeMove(pSnake psnake) { //1.先开辟一个新的节点 pSnakeNode newnode = (pSnakeNode)malloc(sizeof(SnakeNode)); if (newnode == NULL) { perror("SnakeMove() malloc fail"); return; } //2.根据蛇当前前进的状态来确定新节点的位置坐标 if (psnake->dir == UP) { newnode->x = psnake->snakehead->x; newnode->y = psnake->snakehead->y - 1;//向上y坐标-1 } if (psnake->dir == DOWN) { newnode->x = psnake->snakehead->x; newnode->y = psnake->snakehead->y + 1;//向下y坐标+1 } if (psnake->dir == LEFT) { newnode->x = psnake->snakehead->x-2;//向上x坐标-2 newnode->y = psnake->snakehead->y ; } if (psnake->dir == RIGHT) { newnode->x = psnake->snakehead->x + 2;//向下x坐标+2 newnode->y = psnake->snakehead->y; } //3.判断新节点位置 if (IsWall(newnode)) { //如果是墙 //撞墙,游戏结束 psnake->status = KILL_BY_WALL; return; } else if (IsSelf(newnode, psnake)) { //如果是自己 //咬到自己,游戏结束 psnake->status = KILL_BY_SELF; return; } else if (IsFood(newnode, psnake)) { //如果是食物 //先+分数 psnake->score +=SCORE; //蛇的节点个数+1 psnake->snake_size++; } //然后连接新节点,头插法 newnode->next = psnake->snakehead; psnake->snakehead = newnode; //打印,注意如果是食物就多打印一个,如果不是食物就打印原来的个数个,并且释放掉尾节点 //可以通过蛇节点个数控制 int size = psnake->snake_size; pSnakeNode cur = psnake->snakehead; pSnakeNode last = psnake->snakehead; while (size) { if (size == psnake->snake_size) { color(12);//蛇头是红色 Setpos(cur->x, cur->y); wprintf(L"%lc", SNAKE_HEAD); } else { color(10);//身体是绿色色 Setpos(cur->x, cur->y); wprintf(L"%lc", SNAKE_HEAD); } size--; last = cur; cur = cur->next; } //如果不是食物,将最后一个节点的位置打印空字符,并且释放该节点 if (cur != NULL) { Setpos(cur->x, cur->y); printf(" "); free(cur); cur = NULL; last->next = NULL;//防止越界访问 } } //运行游戏 void GameRun(pSnake psnake) { do { //打印分数 Setpos(40, 38); color(14);//设置颜色为黄色 printf("总分:%5d", psnake->score); Setpos(40, 40); printf("当前食物分值:%02d", psnake->FoodWeight); if (KEY_PRESS(VK_UP) && psnake->dir != DOWN) { psnake->dir = UP; } else if (KEY_PRESS(VK_DOWN) && psnake->dir != UP) { psnake->dir = DOWN; } else if (KEY_PRESS(VK_LEFT) && psnake->dir != RIGHT) { psnake->dir = LEFT; } else if (KEY_PRESS(VK_RIGHT) && psnake->dir != LEFT) { psnake->dir = RIGHT; } else if (KEY_PRESS(VK_ESCAPE)) { psnake->status = ESC; //退出游戏 break; } else if (KEY_PRESS(VK_SPACE)) { //游戏要暂定 pause();//暂定和回复暂定 } else if (KEY_PRESS(VK_F3)) //加速,休眠时间减少,食物分数增加 { if (psnake->SleepTime >= 80) { psnake->SleepTime -= 30; psnake->FoodWeight += SCORE; } } else if (KEY_PRESS(VK_F4))//减速,休眠时间增加,食物分数减少 { if (psnake->FoodWeight > SCORE) { psnake->SleepTime += 30; psnake->FoodWeight -= SCORE; } } //走一步 SnakeMove(psnake); //睡眠一下 Sleep(psnake->SleepTime); } while (psnake->status == OK); } //游戏结束,清理工作 void GameEnd(pSnake psnake) { system("cls"); color(11);//颜色设置为蓝色 Setpos(40, 15); switch (psnake->status) { case ESC: printf("你已主动退出游戏@-@"); break; case KILL_BY_SELF: printf("很遗憾,你咬到自己了*^*,游戏结束"); break; case KILL_BY_WALL: printf("很遗憾,你撞墙了*^*,游戏结束"); break; } Setpos(50, 18); //释放蛇的节点 pSnakeNode cur = psnake->snakehead; pSnakeNode del = cur; while (cur) { cur = cur->next; free(del); del = cur; } //释放食物节点 if (psnake->food != NULL) { pFood tmp = psnake->food; pFood rem = tmp; while (tmp) { tmp = tmp->next; free(rem); rem = tmp; } } psnake = NULL; } void test() { int start = 'y'; do { //创建贪吃蛇 Snake snake = { 0 }; //游戏开始前的初始化 GameStart(&snake); //运行游戏 GameRun(&snake); //游戏结束 GameEnd(&snake); Setpos(50, 17); system("pause"); system("cls"); Setpos(40, 15); printf("是否要继续游戏?y/n\n"); Setpos(60, 15); start = getchar(); getchar();//清理\n } while (start == 'y' || start == 'Y'); } int main() { //修改适配本地的中文环境 setlocale(LC_ALL, ""); //贪吃蛇游戏测试 test(); Setpos(50, 19); return 0; }
三、游戏实现逻辑
1.游戏框架
void test() { int start = 'y'; do { //创建贪吃蛇 Snake snake = { 0 }; //游戏开始前的初始化 GameStart(&snake); //运行游戏 GameRun(&snake); //游戏结束 GameEnd(&snake); Setpos(50, 17);//设置控制台打印位置 system("pause");//请按任意键继续 system("cls"); Setpos(40, 15); printf("是否要继续游戏?y/n\n"); Setpos(60, 15); start = getchar(); getchar();//清理\n } while (start == 'y' || start == 'Y'); } int main() { //修改适配本地的中文环境 setlocale(LC_ALL, ""); //贪吃蛇游戏测试 test(); Setpos(50, 19); return 0; }
我们实现贪吃蛇需要实现创建贪吃蛇、创建好后进行初始化、运行游戏以及游戏结束这几个模块,并且在最开始以及结束之后通过输入y/n(yes/no)来确认是否开始游戏或者退出游戏,可以通过
do while
循环搭配输入getchar()
从键盘上获取字符来实现
Setpos函数通过接受两个整型参数来定位控制台,控制台可以看成一个坐标系:
//定位光标 void Setpos(int x, int y) { HANDLE hanlde = GetStdHandle(STD_OUTPUT_HANDLE);//从标准输出设备中获得句柄 COORD pos = { x, y };//控制台上的坐标 SetConsoleCursorPosition(hanlde, pos);//根据句柄设置控制台光标的位置 } //这是Windows提供的函数使用时要包含头文件#include<Windows.h>
我们如果要在控制台上打印宽字符’●’或者’★’,汉字也是宽字符,就需要修改本地适配环境,在main函数中://修改适配本地的中文环境 setlocale(LC_ALL, "");
,使用该函数要包含#include<locale.h>
头文件
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<locale.h> #include<stdbool.h> #include<Windows.h> #include <time.h> #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )//控制按键 #define WALL L'■' #define SNAKE L'●' #define SNAKE_HEAD L'●'//这里可以自己定义蛇头 #define FOOD L'★' //蛇最开始的坐标 #define POS_X 50 #define POS_Y 10 #define SCORE 5 //设置一次加几分 #define FOODQUALITY 3//设置一次性开辟食物个数为3 //定义贪吃蛇蛇身节点(使用链表) typedef struct SnakeNode { //定义x y记录蛇身节点坐标 int x; int y; //记录下一个节点 struct SnakeNode* next; }SnakeNode,* pSnakeNode; //定义食物 typedef struct Food { //定义x y 记录食物节点坐标 int x; int y; //记录下一个节点 struct Food* next; }Food, *pFood; //游戏状态 enum GAME_STATUS { OK = 1, ESC, KILL_BY_WALL, KILL_BY_SELF }; //蛇移动方向 enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT }; //定义贪吃蛇 typedef struct Snake { pSnakeNode snakehead; //记录蛇头节点的指针,这样就可以找到整条蛇 int snake_size; //记录蛇的节点个数 int score; //记录当前得分 pFood food; //记录指向食物的指针 int food_size; //记录食物个数 int FoodWeight;//食物权重 enum GAME_STATUS status;//记录游戏当前状态 enum DIRECTION dir; //记录当前蛇移动方向 int SleepTime;//记录蛇休眠时间 }Snake,*pSnake; //函数声明 void GameStart(pSnake psnake);//游戏开始前的初始化 void Setpos(int x, int y); //定位光标 void GameRun(pSnake psnake); //游戏运行 void GameEnd(pSnake psnake);//游戏结束,清理工作
实现贪吃蛇我们首先要定义一个结构体来确定蛇身节点,里面要包括蛇的坐标,x,y还有下一个节点的位置:
//定义贪吃蛇蛇身节点(使用链表) typedef struct SnakeNode { //定义x y记录蛇身节点坐标 int x; int y; //记录下一个节点 struct SnakeNode* next; }SnakeNode,* pSnakeNode;
同理如果你要实现多个食物,我们也需要开辟食物节点,与蛇身节点类似:
//定义食物 typedef struct Food { //定义x y 记录食物节点坐标 int x; int y; //记录下一个节点 struct Food* next; }Food, *pFood;
其实这里也可以不定义,直接使用蛇身节点就行
然后是整条蛇:
//定义贪吃蛇 typedef struct Snake { pSnakeNode snakehead; //记录蛇头节点的指针,这样就可以找到整条蛇 int snake_size; //记录蛇的节点个数 int score; //记录当前得分 pFood food; //记录指向食物的指针 int food_size; //记录食物个数 int FoodWeight;//食物权重 enum GAME_STATUS status;//记录游戏当前状态 enum DIRECTION dir; //记录当前蛇移动方向 int SleepTime;//记录蛇休眠时间 }Snake,*pSnake;
2.游戏开始前初始化
//游戏开始前的初始化 void GameStart(pSnake psnake) { //1.设置控制台信息 system("title 贪吃蛇"); //设置控制台标题为贪吃蛇 system("mode con cols=150 lines=44"); //设置控制台大小 //2.打印欢迎信息 Welcom_Print_Info(); //3.绘制地图 cols=100 lines=35 Map_Print(); //4.打印游戏说明信息 Info_Print(); //5.初始化蛇 Init_Snake(psnake); //6.初始化食物 Init_Food(psnake); }
2.1修改控制台信息
//1.设置控制台信息 system("title 贪吃蛇"); //设置控制台标题为贪吃蛇 system("mode con cols=150 lines=44"); //设置控制台大小
如果发现控制台没改变,我们就需要修改一下默认终端应用程序:
①找到设置
②修改默认终端应用程序
③选择Windows控制台主机
④点击保存
这样既可以按照我们想要的方式打印标题和设置控制台大小了🥳🥳:
2.2打印欢迎信息
//打印欢迎信息 void Welcom_Print_Info() { //1.先隐藏光标 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄 CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息 CursorInfo.bVisible = false;//隐藏光标 SetConsoleCursorInfo(handle, &CursorInfo); //2.定位光标 Setpos(40,15); //3.打印欢迎信息 color(11);//设置颜色为蓝色 printf("欢迎来到贪吃蛇小游戏~\n"); //定位光标,打印其他信息 Setpos(40, 17); printf("ARE YOU READY?\n"); //4.请按任意键继续...切换页面 Setpos(50, 30); system("pause"); system("cls");//清空屏幕 //5.下一页功能信息介绍 Setpos(40, 15); printf("请用↑ ↓ ← →来控制蛇的移动,F3加速,F4减速"); Setpos(40, 17); printf("加速能获得更高的分数哦~"); //6.请按任意键继续...切换页面 Setpos(50, 30); system("pause"); system("cls"); }
效果展示:
打印欢迎信息时我们发现光标一直闪动,所以我们可以通过windows提供的函数来隐藏光标,注意使用这些函数时要包含头文件#include<Windows.h>
✨颜色设置函数
//颜色设置 void color(int c) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置 //注:SetConsoleTextAttribute是一个API(应用程序编程接口) }
提供了一个int类型的参数,通过接收不同颜色的十进制表示来输出不同颜色的字符,上面欢迎信息我们选择的是蓝色
color(11);
十进制颜色参照表:
2.3绘制地图
//绘制地图 cols=100 lines=35 //WALL就是'■',在头文件中被宏定义 void Map_Print() { color(11);//设置颜色为蓝 //1.打印第一行 for (int i = 0; i < 100; i += 2) { wprintf(L"%lc", WALL); } //2.打印最后一行 Setpos(0,35); for (int i = 0; i < 100; i += 2) { wprintf(L"%lc", WALL); } //3.打印左边 for (int i = 0; i < 35; i++) { Setpos(0, i); wprintf(L"%lc", WALL); } //3.打印右边 for (int i = 0; i < 35; i++) { Setpos(98, i); wprintf(L"%lc", WALL); } }
因为我们设置的控制台大小是(150,40),所以根据自己的设计。我们将墙设计为(100,35),如下图所示:
效果如下:
大家可以根据自己的需要选择颜色还有大小,此外最好将x的大小设置为偶数,因为蛇节点以及食物还有中文都是宽字符,占两个位置,但高度和普通字符一样,所以y没有要求;
2.4打印游戏说明信息
//打印游戏说明信息 void Info_Print() { color(11);//设置颜色为蓝色 Setpos(110, 10); printf("1.用 ↑.↓.←.→ 来控制蛇的移动"); Setpos(110, 12); printf("2.F3是加速,F4是减速"); Setpos(110, 14); printf("3.不能穿墙,不能咬到自己"); Setpos(115, 20); printf("@胡土土");//这里可以设置和自己有关的信息作为版权 }
效果如下:
各部分对应坐标如下图:
2.5初始化蛇
//初始化蛇 void Init_Snake(pSnake psnake) { //创建蛇5个节点 for (int i = 0; i < 5; i++) { //1.创建节点 pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("Init_Snake():malloc fail"); return; } //2.设置节点坐标 cur->x = POS_X+2*i; cur->y = POS_Y; cur->next = NULL; //3.连接节点 if (psnake->snakehead == NULL) { psnake->snakehead = cur; } else { cur->next = psnake->snakehead; psnake->snakehead = cur; } } //4.打印蛇 pSnakeNode cur = psnake->snakehead; while (cur) { if (cur == psnake->snakehead) { //将蛇头颜色设为红色 color(12); Setpos(cur->x, cur->y);//定位 wprintf(L"%lc", SNAKE_HEAD); } else { //将蛇身颜色设为绿色 color(10); Setpos(cur->x, cur->y);//定位 wprintf(L"%lc", SNAKE); } cur = cur->next; } //蛇剩下部分初始化 psnake->snake_size = 5; psnake->dir = RIGHT; psnake->FoodWeight = SCORE;//宏定义,是5 psnake->food = NULL; psnake->food_size = 0; psnake->SleepTime = 300;//休眠时间300ms psnake->status = OK; }
这里首先需要创建五个节点(可以根据自己的需求调整),将它们连接在一起,并将它们的坐标初始化好之后就可以在控制台打印啦~ 🥳🎉🎉,然后记得将整条蛇其他部分都初始化一下哦 ~
效果如下:
我们可以通过控制蛇头节点来实现将蛇头打印与其他部分不一样的红色:
if (cur == psnake->snakehead) { //将蛇头颜色设为红色 color(12); Setpos(cur->x, cur->y);//定位 wprintf(L"%lc", SNAKE_HEAD); }
2.6初始化食物
//初始化食物 void Init_Food(pSnake psnake) { //创建食物j个节点 int j = rand() % 5 + 1; psnake->food_size = j;; for (int i = 0; i < j; i++) { //1.创建节点 pFood cur = (pFood)malloc(sizeof(Food)); if (cur == NULL) { perror("Init_Food():malloc fail"); return; } //2.使用随机数设置节点坐标 //注意x坐标必须为偶数 again: do { cur->x = rand() % 99+1;//不能为0 cur->y = rand() % 34+1;//不能为0 cur->next = NULL; } while (cur->x % 2 != 0); //判断坐标是否与蛇相同 //要用循环遍历蛇节点 pSnakeNode ps = psnake->snakehead; while (ps) { if (ps->x == cur->x && ps->y == cur->y) { goto again; } ps = ps->next; } //3.连接节点 if (psnake->food == NULL) { psnake->food = cur; } else { cur->next = psnake->food; psnake->food = cur; } } //4.打印食物 pFood cur = psnake->food; while (cur) { //将食物颜色设为黄色 color(14); Setpos(cur->x, cur->y);//定位 wprintf(L"%lc", FOOD); cur = cur->next; } }
效果如下:
我们使用随机生成函数
rand()%5+1
来随机生成生成1~5个食物,上图随机生成了两个
与初始化蛇类似,我们要创建j个节点,然后赋予坐标,这里注意坐标不能在墙上以及外面(通过取模运算来控制),也不能与贪吃蛇蛇身节点重复(需要遍历蛇节点,每个节点都要判断一下),要判断一下,此外x坐标还不能是奇数,因为宽字符是x是两个一起的不然就可能出现蛇进入一半食物的情况,不好判断,可以使用goto again语句来实现一直生成随机数,直到合适的坐标出现
3.运行游戏
//运行游戏 void GameRun(pSnake psnake) { do { //打印分数 Setpos(40, 38); color(14);//设置颜色为黄色 printf("总分:%5d", psnake->score); Setpos(40, 40); printf("当前食物分值:%02d", psnake->FoodWeight); if (KEY_PRESS(VK_UP) && psnake->dir != DOWN) { psnake->dir = UP; } else if (KEY_PRESS(VK_DOWN) && psnake->dir != UP) { psnake->dir = DOWN; } else if (KEY_PRESS(VK_LEFT) && psnake->dir != RIGHT) { psnake->dir = LEFT; } else if (KEY_PRESS(VK_RIGHT) && psnake->dir != LEFT) { psnake->dir = RIGHT; } else if (KEY_PRESS(VK_ESCAPE)) { psnake->status = ESC; //退出游戏 break; } else if (KEY_PRESS(VK_SPACE)) { //游戏要暂定 pause();//暂定和回复暂定 } else if (KEY_PRESS(VK_F3)) //加速,休眠时间减少,食物分数增加 { if (psnake->SleepTime >= 80) { psnake->SleepTime -= 30; psnake->FoodWeight += SCORE; } } else if (KEY_PRESS(VK_F4))//减速,休眠时间增加,食物分数减少 { if (psnake->FoodWeight > SCORE) { psnake->SleepTime += 30; psnake->FoodWeight -= SCORE; } } //走一步 SnakeMove(psnake); //睡眠一下 Sleep(psnake->SleepTime); } while (psnake->status == OK); }
通过dowhile循环来实现不出意外时,蛇一直移动,平均休眠时间是最开始初始化的300ms,然后通过检测按键来实现不同的逻辑,当按下向上的键时,此时若蛇运动的方向不是向下那么我们就将蛇整个节点中的运动方向改为向上,其他同理;只要蛇运动方向与按键方向不是相反的情况,蛇运动方向就都可以改变
如果按下F3就让蛇休眠时间变短,注意不能为负数,要有一个最低限度,这样蛇运动的速度就变快,此时一个食物对应的分值就可以增加;与之相对,按下F4就让休眠时间边长,对应分值减少,同样要注意不能减为负数,要有一个最低限度
如果按下空格就暂停,再按一下就继续
如果按下Esc就将游戏状态改为ESC,退出游戏
这里的KEY_PRESS()是宏定义
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
如果按了指定按键就返回1
3.1打印分数
//打印分数 Setpos(40, 38); color(14);//设置颜色为黄色 printf("总分:%5d", psnake->score); Setpos(40, 40); printf("当前食物分值:%02d", psnake->FoodWeight);
和之前打印欢迎信息一样,确定位置,然后打印即可,这里要注意打印分数格式
%02d%5d
,这样每次打印都会打印2或5个会覆盖之前的值,这样就不会出现之前的值与现在的值一同打印的情况了,不然每次都要在之前的位置上打印空白字符
效果如下:
3.2游戏暂停
//暂停恢复函数 void pause() { while (1) { Sleep(100); if (KEY_PRESS(VK_SPACE)) //如果再次按下空格就结束暂停状态 { break; } } }
使用死循环让蛇一直休眠,直到再次按下空格键跳出死循环,继续游戏
3.3蛇移动函数
//蛇移动的函数- 每次走一步 void SnakeMove(pSnake psnake) { //1.先开辟一个新的节点 pSnakeNode newnode = (pSnakeNode)malloc(sizeof(SnakeNode)); if (newnode == NULL) { perror("SnakeMove() malloc fail"); return; } //2.根据蛇当前前进的状态来确定新节点的位置坐标 if (psnake->dir == UP) { newnode->x = psnake->snakehead->x; newnode->y = psnake->snakehead->y - 1;//向上y坐标-1 } if (psnake->dir == DOWN) { newnode->x = psnake->snakehead->x; newnode->y = psnake->snakehead->y + 1;//向下y坐标+1 } if (psnake->dir == LEFT) { newnode->x = psnake->snakehead->x-2;//向上x坐标-2 newnode->y = psnake->snakehead->y ; } if (psnake->dir == RIGHT) { newnode->x = psnake->snakehead->x + 2;//向下x坐标+2 newnode->y = psnake->snakehead->y; } //3.判断新节点位置 if (IsWall(newnode)) { //如果是墙 //撞墙,游戏结束 psnake->status = KILL_BY_WALL; return; } else if (IsSelf(newnode, psnake)) { //如果是自己 //咬到自己,游戏结束 psnake->status = KILL_BY_SELF; return; } else if (IsFood(newnode, psnake)) { //如果是食物 //先+分数 psnake->score +=SCORE; //蛇的节点个数+1 psnake->snake_size++; } //然后连接新节点,头插法 newnode->next = psnake->snakehead; psnake->snakehead = newnode; //打印,注意如果是食物就多打印一个,如果不是食物就打印原来的个数个,并且释放掉尾节点 //可以通过蛇节点个数控制 int size = psnake->snake_size; pSnakeNode cur = psnake->snakehead; pSnakeNode last = psnake->snakehead; while (size) { if (size == psnake->snake_size) { color(12);//蛇头是红色 Setpos(cur->x, cur->y); wprintf(L"%lc", SNAKE_HEAD); } else { color(10);//身体是绿色色 Setpos(cur->x, cur->y); wprintf(L"%lc", SNAKE_HEAD); } size--; last = cur; cur = cur->next; } //如果不是食物,将最后一个节点的位置打印空字符,并且释放该节点 if (cur != NULL) { Setpos(cur->x, cur->y); printf(" "); free(cur); cur = NULL; last->next = NULL;//防止越界访问 } }
这里每次移动都开辟一个新的节点,然后根据蛇移动的方向确定新节点的坐标,然后判断新节点的坐标是否是墙或者食物或者自己,如果是墙就撞墙了结束游戏,如果是自己就咬到自己,结束游戏,如果是食物就增加一个节点,并且得分增加,如果什么都不是就休眠一下继续走下一步
✨判断新节点是不是墙
//判断新节点也就是下一个位置是不是墙 bool IsWall(pSnakeNode newnode) { //墙的x坐标是0和100,y坐标是0和35 if (newnode->x == 0|| newnode->x == 100|| newnode->y == 0|| newnode->y == 35) { return true; } return false; }
✨判断新节点是不是自己
//判断新节点也就是下一个位置是不是自己 bool IsSelf(pSnakeNode newnode, pSnake psnake) { //遍历蛇的节点,注意最后一个不需要遍历,因为新增了一个 pSnakeNode cur = psnake->snakehead; while (cur->next) { if (newnode->x == cur->x && newnode->y == cur->y) { return true; } cur = cur->next; } return false; }
这里需要循环遍历自己,要注意,有了新节点,最后一个节点就不需要了,所以遍历到倒数第二个节点即可
✨判断新节点是不是食物
//判断新节点也就是下一个位置是不是食物 bool IsFood(pSnakeNode newnode,pSnake psnake) { //遍历食物节点来判断 pFood cur = psnake->food; while (cur) { if (cur->x == newnode->x && cur->y == newnode->y) { psnake->food_size--; if (psnake->food_size == 0) { //如果没有食物就重新初始化食物 //释放食物节点 if (psnake->food != NULL) { pFood tmp = psnake->food; pFood rem = tmp; while (tmp) { tmp = tmp->next; free(rem); rem = tmp; } psnake->food = NULL; } Init_Food(psnake); } return true; } cur = cur->next; } return false; }
因为食物有多个,所以要遍历食物,如果是食物,那么食物节点就少一个
psnake->food_size--;
,当食物节点个数为0时我们就要重新生成食物节点Init_Food(psnake);
,在重新生成之前要记得将之前的食物节点都遍历释放掉
✨连接新节点
如果游戏没有结束,也就是蛇没有撞墙或者咬到自己,我们就需要连接新节点,和初始化蛇时连接节点一样使用头插法
//然后连接新节点,头插法 newnode->next = psnake->snakehead; psnake->snakehead = newnode;
✨再次打印蛇
连接完节点之后就可以打印蛇了🎉🎉🎉
当然如果不是食物,那么蛇的节点不会增加,连接了新节点,就需要释放尾节点,我们可以通过蛇的size来遍历打印:
while (size) { if (size == psnake->snake_size) { color(12);//蛇头是红色 Setpos(cur->x, cur->y); wprintf(L"%lc", SNAKE_HEAD); } else { color(10);//身体是绿色色 Setpos(cur->x, cur->y); wprintf(L"%lc", SNAKE_HEAD); } size--; last = cur; cur = cur->next; }
如果此时cur不为NULL说明还剩一个节点没打印,也就是说新节点不是食物,所以我们要释放尾节点,注意不要忘了将该位置打印空字符(两个哦~),不然蛇就会一直变大
//如果不是食物,将最后一个节点的位置打印空字符,并且释放该节点 if (cur != NULL) { Setpos(cur->x, cur->y); printf(" "); free(cur); cur = NULL; last->next = NULL;//防止越界访问 }
4.游戏结束
//游戏结束,清理工作 void GameEnd(pSnake psnake) { system("cls");//清屏 color(11);//颜色设置为蓝色 Setpos(40, 15); switch (psnake->status) { case ESC: printf("你已主动退出游戏@-@"); break; case KILL_BY_SELF: printf("很遗憾,你咬到自己了*^*,游戏结束"); break; case KILL_BY_WALL: printf("很遗憾,你撞墙了*^*,游戏结束"); break; } Setpos(50, 18); //释放蛇的节点 pSnakeNode cur = psnake->snakehead; pSnakeNode del = cur; while (cur) { cur = cur->next; free(del); del = cur; } //释放食物节点 if (psnake->food != NULL) { pFood tmp = psnake->food; pFood rem = tmp; while (tmp) { tmp = tmp->next; free(rem); rem = tmp; } } psnake = NULL; }
游戏结束后,清理屏幕
system("cls");//清屏
,并根据不同状态设置不同的结束语,比如撞墙的状态printf("很遗憾,你撞墙了*^*,游戏结束");
,然后释放蛇的节点以及食物的节点,将整条蛇的指针置空
四、结语
以上就是贪吃蛇的模拟实现啦~ 我们使用了Windows提供的一些函数来设置控制台信息以及颜色,并且通过对链表的学习构建了蛇的节点,并使用结构体指针封装了整条蛇,此外还有一些个性化的设计,当然这里还有很多有趣的功能没有实现等着大家去挖掘开发,希望之后能和大家多多交流学习~ 🥳🎉🎉