个人主页~
源码在Gitee仓库~
上一篇贪吃蛇(上)~
贪吃蛇
- 四、核心的实现
- 游戏测试
- 1、GameStart
- (1)控制台窗口大小和名字设置
- (2)光标隐藏
- (3)打印欢迎界面
- (4)创建地图
- (5)初始化蛇
- (6)创建第一个食物
- 最终的GameStart
- 2、GameRun
- (1)定义一个宏来检测按键状态
- (2)PrintHelpInfo
- (3)SnakeMove
- (4)NextIsFood
- (5)EatFood
- (6)NoFood
- (7)KillBySelf
- (8)KillByWall
- 最终的GameRun
- 3、GameEnd
- 五、源代码拷贝
- Snake.h
- Snake.c
- game.h
- 实际运行
四、核心的实现
游戏测试
#include
void test() { int ch = 0; srand((unsigned int)time(NULL));//时间戳,用来实现随机数 do { Snake snake = { 0 }; GameStart(&snake); GameRun(&snake); GameEnd(&snake); SetPos(20, 15); printf("再来⼀局吗?(Y/N):"); ch = getchar(); getchar(); } while (ch == 'Y'); SetPos(0, 27); } int main() { //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印 setlocale(LC_ALL, ""); //测试逻辑 test(); return 0; } 1、GameStart
(1)控制台窗口大小和名字设置
system("mode con cols=100 lines=30"); system("title 贪吃蛇");
(2)光标隐藏
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(houtput, &CursorInfo); CursorInfo.bVisible = false; SetConsoleCursorInfo(houtput, &CursorInfo);
(3)打印欢迎界面
void WelcomeToGame() { SetPos(40, 15); printf("欢迎来到贪吃蛇⼩游戏"); SetPos(40, 25);// “按任意键继续”的出现的位置 system("pause");//可以让页面暂停在这个位置,直到用户按下一个键 system("cls");//清除屏幕 SetPos(25, 12); printf("⽤ ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n"); SetPos(25, 13); printf("加速将能得到更⾼的分数。\n"); SetPos(40, 25); // “按任意键继续”的出现的位置,这里可以让文字出现的位置看起来比较美观 system("pause"); system("cls"); }
(4)创建地图
组成地图的小格子需要用宽字符打印
58行就打印29次
#define WALL L'□' //在头文件中定义 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 <= 25; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } //右 for (i = 1; i <= 25; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } }
(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("malloc fail"); exit(1); } cur->next = NULL; cur->x = POS_X + 2 * i;//将蛇的节点由蛇尾到蛇头创建好 cur->y = POS_Y; if (ps->_pSnake == NULL) { ps->_pSnake = cur;//若没有蛇身节点则建立的节点为蛇身节点 } else { cur->next = ps->_pSnake; ps->_pSnake = cur;//若有蛇身节点则新创建的节点成为头节点 } } cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY);//将蛇的身体依据链表打印出来 cur = cur->next; } ps->_dir = RIGHT;//初始蛇的方向 ps->_food_weight = 10;//每个食物的分数 ps->_sleep_time = 200;//每两次打印蛇身的间隔,也就是蛇身的速度 ps->_sorce = 0;//初始总分数 ps->_status = OK;//蛇的初始状态 }
(6)创建第一个食物
void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 53 + 2;//我们要的x坐标值介于2~54间,任意数%53得到的值介于0~52,加上2就在2~56范围 y = rand() % 25 + 1;//我们要的y坐标值介于1~25间,任意数%25得到的值介于0~24,加上1就在1~25范围 //随机数时间戳,根据时间计算的数据,由于时间是不可修改切没有相同时候的,所以它产生的数字被认为是随机数 } while (x % 2 != 0); pSnakeNode cur = ps->_pSnake;//记录蛇头结点 while (cur) { if (x == cur->x && y == cur->y) { goto again; } cur = cur->next; }//如果食物与蛇身上某一节点重合了,则回到again处重新生成 pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//不重合就生成食物 if (pFood == NULL) { perror("malloc fail"); exit(2); } pFood->x = x; pFood->y = y; pFood->next = NULL; SetPos(x, y); wprintf(L"%lc", FOOD);//在对应位置处打印食物 ps->_pFood = pFood; }
最终的GameStart
void GameStart(pSnake ps) { //设置控制台窗⼝的⼤⼩,30⾏,100列 //mode 为DOS命令 system("mode con cols=100 lines=30"); //设置cmd窗⼝名称 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); //创造第⼀个⻝物 CreateFood(ps); }
2、GameRun
(1)定义一个宏来检测按键状态
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
在上一篇博文中我们介绍了GetAsyncKeyState函数,我们封装一个宏可以判断某个键是否被按下
(2)PrintHelpInfo
void PrintHelpInfo() { SetPos(64, 14); wprintf(L"%ls", L"不能穿墙,不能咬到自己"); SetPos(64, 16); wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动"); SetPos(64, 18); wprintf(L"%ls", L"按F3加速,F4减速"); SetPos(64, 20); wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏"); SetPos(64, 24); wprintf(L"%ls", L"s_little_monster_倾情制作"); }
(3)SnakeMove
void SnakeMove(pSnake ps) { pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));//开辟预测的下一个节点 if (pNextNode == NULL) { perror("malloc fail"); exit(3); } switch (ps->_dir)//用switch语句判断此时蛇的走向 { 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;//注意左右移动是加减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); } //判断下一个位置是否为食物,是则进入EatFood,不是则进入NoFood KillBySelf(ps); KillByWall(ps);//检查是否撞到自己或墙而死亡 }
(4)NextIsFood
int NextIsFood(pSnakeNode pn,pSnake ps) { return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y); //如果当前位置是食物则返回非0值,不是返回0 }
(5)EatFood
void EatFood(pSnakeNode pn, pSnake ps) { ps->_pFood->next = ps->_pSnake; ps->_pSnake = ps->_pFood;//吃掉食物,让食物成为蛇的头节点 free(pn);//因为节点pn与节点_pFood是一个节点,所以free掉其中的一个 pn = NULL; pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; }//打印蛇身 ps->_sorce += ps->_food_weight;//加分 CreateFood(ps);//重新制造食物 }
(6)NoFood
void NoFood(pSnakeNode pn, pSnake ps) { pn->next = ps->_pSnake; ps->_pSnake = pn;//同EatFood,将下一节点成为头结点 pSnakeNode cur = ps->_pSnake; while (cur->next->next != NULL) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; }//将除了尾节点以外的节点打印 SetPos(cur->next->x, cur->next->y); printf(" ");//将尾节点打印为空格 free(cur->next);//free掉尾节点 cur->next = NULL; }
(7)KillBySelf
void KillBySelf(pSnake ps) { pSnakeNode pur = ps->_pSnake->next; while (pur) { //当此时的蛇头位置与蛇身某一节点重合时 if (pur->x == ps->_pSnake->x && pur->y == ps->_pSnake->y) { ps->_status = KILL_BY_SELF;//修改状态为 KILL_BY_SELF break; } pur = pur->next; } }
(8)KillByWall
void KillByWall(pSnake ps) { //当此时的蛇头位置与墙体重合时 if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26) { ps->_status = KILL_BY_WALL;//修改状态为 KILL_BY_WALL } }
最终的GameRun
void GameRun(pSnake ps) { PrintHelpInfo();//游戏玩法帮助打印 do { SetPos(64, 10); printf("总分数:%d\n", ps->_sorce); 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)) { Pause();//暂停,等待再按下空格继续 } else if (KEY_PRESS(VK_ESCAPE)) { ps->_status = END_NORMAL; } else if (KEY_PRESS(VK_F3))//F3为加速,休眠时间变少也就是蛇的速度变快,每个食物分数增加2 { if(ps->_sleep_time > 80)//速度不能太快 { ps->_sleep_time -= 30; ps->_food_weight += 2; } } else if (KEY_PRESS(VK_F4))//F4为减速,休眠时间变多也就是蛇的速度变慢,每个食物分数减少2 { if(ps->_food_weight > 2)//食物分数要在2分以上 { ps->_sleep_time += 30; ps->_food_weight -= 2; } } SnakeMove(ps);//蛇每走一步要进行的活动 Sleep(ps->_sleep_time);//走一步休眠的时间,也就是蛇的速度 } while (ps->_status == OK);//当游戏状态为OK时循环继续 }
3、GameEnd
当游戏状态不为OK时,告知游戏结束的原因并释放蛇身
void GameEnd(pSnake ps) { SetPos(24, 12); switch (ps->_status) { case END_NORMAL: wprintf(L"主动结束游戏"); break; case KILL_BY_SELF: wprintf(L"撞到自己了,游戏结束"); break; case KILL_BY_WALL: wprintf(L"撞到墙了,游戏结束"); break; } pSnakeNode pur = ps->_pSnake; while (pur) { pSnakeNode del = pur; pur = pur->next; free(del); } }
五、源代码拷贝
Snake.h
#pragma once #include
#include #include #include #include #define POS_X 24 #define POS_Y 5 #define WALL L'□' #define BODY L'◆' #define FOOD L'★' enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT }; enum GAME_STATUS { OK, KILL_BY_WALL, KILL_BY_SELF, END_NORMAL }; typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode, *pSnakeNode; typedef struct Snake { pSnakeNode _pSnake; pSnakeNode _pFood; enum DRECTION _dir; enum GAME_STATUS _status; int _food_weight; int _sorce; int _sleep_time; }Snake,*pSnake; void SetPos(short x, short y); void GameStart(pSnake ps); void CreateMap(); void WelcomeToGame(); void InitSnake(pSnake ps); void CreateFood(pSnake ps); void GameRun(pSnake ps); void SnakeMove(pSnake ps); int NextIsFood(pSnakeNode pn, pSnake ps); void EatFood(pSnakeNode pn, pSnake ps); void NoFood(pSnakeNode pn, pSnake ps); void KillByWall(pSnake ps); void KillBySelf(pSnake ps); void GameEnd(pSnake ps); Snake.c
#include "snake.h" void SetPos(short x, short y) { HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { x,y }; 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() { //上 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 <= 25; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } //右 for (i = 1; i <= 25; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } } 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("malloc fail"); exit(1); } cur->next = NULL; cur->x = POS_X + 2 * i;//将蛇的节点由蛇尾到蛇头创建好 cur->y = POS_Y; if (ps->_pSnake == NULL) { ps->_pSnake = cur;//若没有蛇身节点则建立的节点为蛇身节点 } else { cur->next = ps->_pSnake; ps->_pSnake = cur;//若有蛇身节点则新创建的节点成为头节点 } } cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY);//将蛇的身体依据链表打印出来 cur = cur->next; } ps->_dir = RIGHT;//初始蛇的方向 ps->_food_weight = 10;//每个食物的分数 ps->_sleep_time = 200;//每两次打印蛇身的间隔,也就是蛇身的速度 ps->_sorce = 0;//初始总分数 ps->_status = OK;//蛇的初始状态 } void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 53 + 2;//我们要的x坐标值介于2~54间,任意数%53得到的值介于0~52,加上2就在2~56范围 y = rand() % 25 + 1;//我们要的y坐标值介于1~25间,任意数%25得到的值介于0~24,加上1就在1~25范围 //随机数时间戳,根据时间计算的数据,由于时间是不可修改切没有相同时候的,所以它产生的数字被认为是随机数 } while (x % 2 != 0); pSnakeNode cur = ps->_pSnake;//记录蛇头结点 while (cur) { if (x == cur->x && y == cur->y) { goto again; } cur = cur->next; }//如果食物与蛇身上某一节点重合了,则回到again处重新生成 pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//不重合就生成食物 if (pFood == NULL) { perror("malloc fail"); exit(2); } pFood->x = x; pFood->y = y; pFood->next = NULL; SetPos(x, y); wprintf(L"%lc", FOOD);//在对应位置处打印食物 ps->_pFood = pFood; } 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); CreateFood(ps); } void PrintHelpInfo() { SetPos(64, 14); wprintf(L"%ls", L"不能穿墙,不能咬到自己"); SetPos(64, 16); wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动"); SetPos(64, 18); wprintf(L"%ls", L"按F3加速,F4减速"); SetPos(64, 20); wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏"); SetPos(64, 24); wprintf(L"%ls", L"s_little_monster_倾情制作"); } #define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0) void Pause() { while (1) { Sleep(200); if (KEY_PRESS(VK_SPACE)) { break; } } } int NextIsFood(pSnakeNode pn,pSnake ps) { return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y); //如果当前位置是食物则返回非0值,不是返回0 } void EatFood(pSnakeNode pn, pSnake ps) { ps->_pFood->next = ps->_pSnake; ps->_pSnake = ps->_pFood;//吃掉食物,让食物成为蛇的头节点 free(pn);//因为节点pn与节点_pFood是一个节点,所以free掉其中的一个 pn = NULL; pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; }//打印蛇身 ps->_sorce += ps->_food_weight;//加分 CreateFood(ps);//重新制造食物 } void NoFood(pSnakeNode pn, pSnake ps) { pn->next = ps->_pSnake; ps->_pSnake = pn;//同EatFood,将下一节点成为头结点 pSnakeNode cur = ps->_pSnake; while (cur->next->next != NULL) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; }//将除了尾节点以外的节点打印 SetPos(cur->next->x, cur->next->y); printf(" ");//将尾节点打印为空格 free(cur->next);//free掉尾节点 cur->next = NULL; } void KillBySelf(pSnake ps) { pSnakeNode pur = ps->_pSnake->next; while (pur) { //当此时的蛇头位置与蛇身某一节点重合时 if (pur->x == ps->_pSnake->x && pur->y == ps->_pSnake->y) { ps->_status = KILL_BY_SELF;//修改状态为 KILL_BY_SELF break; } pur = pur->next; } } void KillByWall(pSnake ps) { //当此时的蛇头位置与墙体重合时 if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26) { ps->_status = KILL_BY_WALL;//修改状态为 KILL_BY_WALL } } void SnakeMove(pSnake ps) { pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));//开辟预测的下一个节点 if (pNextNode == NULL) { perror("malloc fail"); exit(3); } switch (ps->_dir)//用switch语句判断此时蛇的走向 { 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;//注意左右移动是加减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); } //判断下一个位置是否为食物,是则进入EatFood,不是则进入NoFood KillBySelf(ps); KillByWall(ps);//检查是否撞到自己或墙而死亡 } void GameRun(pSnake ps) { PrintHelpInfo();//游戏玩法帮助打印 do { SetPos(64, 10); printf("总分数:%d\n", ps->_sorce); 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)) { Pause();//暂停,等待再按下空格继续 } else if (KEY_PRESS(VK_ESCAPE)) { ps->_status = END_NORMAL; } else if (KEY_PRESS(VK_F3))//F3为加速,休眠时间变少也就是蛇的速度变快,每个食物分数增加2 { if(ps->_sleep_time > 80)//速度不能太快 { ps->_sleep_time -= 30; ps->_food_weight += 2; } } else if (KEY_PRESS(VK_F4))//F4为减速,休眠时间变多也就是蛇的速度变慢,每个食物分数减少2 { if(ps->_food_weight > 2)//食物分数要在2分以上 { ps->_sleep_time += 30; ps->_food_weight -= 2; } } SnakeMove(ps);//蛇每走一步要进行的活动 Sleep(ps->_sleep_time);//走一步休眠的时间,也就是蛇的速度 } while (ps->_status == OK);//当游戏状态为OK时循环继续 } void GameEnd(pSnake ps) { SetPos(24, 12); switch (ps->_status) { case END_NORMAL: wprintf(L"主动结束游戏"); break; case KILL_BY_SELF: wprintf(L"撞到自己了,游戏结束"); break; case KILL_BY_WALL: wprintf(L"撞到墙了,游戏结束"); break; } pSnakeNode pur = ps->_pSnake; while (pur) { pSnakeNode del = pur; pur = pur->next; free(del); } }
game.h
#include "snake.h" #include
void test() { int c = 0; do { system("cls"); Snake snake = { 0 }; GameStart(&snake); GameRun(&snake); GameEnd(&snake); SetPos(20, 15); printf("再来一局吗?(Y/N):"); c = getchar(); while (getchar() != '\n'); } while (c == 'Y'); SetPos(0, 27); } int main() { setlocale(LC_ALL, "");//本地化 srand((unsigned int)time(NULL));//时间戳随机数 test(); return 0; } 实际运行
贪吃蛇的实际运行
今日分享就到这里了~
还没有评论,来说两句吧...