贪吃蛇会是C/C++选手学完做的一个小DIY,考察了游戏的设计逻辑和C/C++基础编程能力。由于贪吃蛇的设计过程相对简单,采用面向过程(函数式)编程来实现。
一、游戏逻辑设计
贪吃蛇的游戏逻辑可以归纳如下:
-
蛇在一个区域中向上下左右四个方向移动
-
吃到食物之后会变长(增长 1 格),在蛇尾巴添加一格
-
蛇头的运动决定蛇的运动方向,只要蛇头不碰墙或者不撞到自己,游戏就能继续
二、蛇活动区域设计:
蛇的活动区域就是一个方格,本质就是一个字符二维矩阵。在这个矩阵中,有四种方格:墙(’#’)、蛇(’*’)、食物(‘O’)和空白(’ ‘)。蛇的运动会动态改变这些格子的状态
// 一开始蛇在中间,头朝右运动 // H = 20, W = 26 char mp[H][W] = { "#########################", "# #", "# #", "# #", "# #", "# #", "# #", "# #", "# #", "# ** #", "# #", "# #", "# #", "# #", "# #", "# #", "# #", "# #", "# #", "#########################" };
三、蛇的表示设计:
一条蛇实际上就是“一条链”,在每个“节骨眼”(格子)都会有自己的状态,这些状态分别是上(UP),下(DOWN),左(LEFT),右(RIGHT),而这些状态就决定了蛇的运动方向,间接影响了区域中方格的状态。这四个状态可以使用枚举类型表示:
enum direction { UP, DOWN, LEFT, RIGHT, null } ; //紧接着定义 enum direction status[H][W],表示方格的状态(上下左右,仅对蛇身生效,若非蛇格,则为null),记得main刚开始要初始化: void init() { for (int i = 1; i <= 18; ++i) { for (int j = 1; j <= 23; ++j) { status[i][j] = null; } } status[9][12] = RIGHT; status[9][11] = RIGHT; while (true) { // 同时随机生成食物,注意食物不能在蛇身上和墙(外) int rx = 1 + rand()%23; int ry = 1 + rand()%18; if (mp[rx][ry] == ' ') { mp[rx][ry] = 'O'; return ; } } }
四、蛇的运动设计
蛇的运动,根源来自于蛇头。所以可以从蛇头开始,循着status[][]的信息,进行DFS,一直更新到蛇尾状态结束。而如果蛇头刚好吃到食物,则在DFS开始之前,在蛇尾巴再加上一格。
// (hx,hy)是蛇头,从此DFS,线性更新蛇,模拟蛇的运动 void dfs(int hx,int hy) { if (status[hx][hy] == UP) { mp[hx][hy] = mp[hx+1][hy]; if (mp[hx][hy] == ' ') { tail = {hx-1,hy}; // tail是尾巴标记,方便吃到食物增长 status[hx][hy] = null; return ; } dfs(hx+1,hy); } else if (status[hx][hy] == DOWN) { mp[hx][hy] = mp[hx-1][hy]; if (mp[hx][hy] == ' ') { tail = {hx+1,hy}; status[hx][hy] = null; return ; } dfs(hx-1,hy); } else if (status[hx][hy] == LEFT) { mp[hx][hy] = mp[hx][hy+1]; if (mp[hx][hy] == ' ') { tail = {hx,hy-1}; status[hx][hy] = null; return ; } dfs(hx,hy+1); } else if (status[hx][hy] == RIGHT) { mp[hx][hy] = mp[hx][hy-1]; if (mp[hx][hy] == ' ') { tail = {hx,hy+1}; status[hx][hy] = null; return ; } dfs(hx,hy-1); } }
贪吃蛇还要相应键盘输入,应该添加键盘相应事件,并更新蛇头状态,并调用DFS模块:
void operation() { while (true) { int hx = head.first; // 蛇头x坐标 int hy = head.second; // 蛇头y坐标 // 键盘相应事件 if (kbhit()) { char ch = getch(); if (ch == 'w' && status[hx][hy] != DOWN) { // 往上 --hx; status[hx][hy] = UP; } else if (ch == 's' && status[hx][hy] != UP) { // 往下 ++hx; status[hx][hy] = DOWN; } else if (ch == 'a' && status[hx][hy] != RIGHT) { // 往左 --hy; status[hx][hy] = LEFT; } else if (ch == 'd' && status[hx][hy] != LEFT) { // 往右 ++hy; status[hx][hy] = RIGHT; } else { goto T1; } } else { T1: if (status[hx][hy] == UP) { --hx; status[hx][hy] = UP; } if (status[hx][hy] == DOWN) { ++hx; status[hx][hy] = DOWN; } if (status[hx][hy] == LEFT) { --hy; status[hx][hy] = LEFT; } if (status[hx][hy] == RIGHT) { ++hy; status[hx][hy] = RIGHT; } } if (mp[hx][hy] == '#' || mp[hx][hy] == '*') { printf("a"); printf("You lose! Get %d points",score); exit(0); } // ---------------- if (mp[hx][hy] == 'O') { int tx = tail.first; int ty = tail.second; if (status[tx][ty] == UP) { ++tx; status[tx][ty] = UP; mp[tx][ty] = '*'; } if (status[tx][ty] == DOWN) { --tx; status[tx][ty] = DOWN; mp[tx][ty] = '*'; } if (status[tx][ty] == LEFT) { ++ty; status[tx][ty] = LEFT; mp[tx][ty] = '*'; } if (status[tx][ty] == RIGHT) { --ty; status[tx][ty] = RIGHT; mp[tx][ty] = '*'; } tail = {tx,ty}; while (true) { int rx = 1 + rand()%23; int ry = 1 + rand()%18; if (mp[rx][ry] == ' ') { mp[rx][ry] = 'O'; break; } } delay -= 10; // 运动加速,但不得少于100 delay = delay > 100 ? delay : 100; score += 10; // 得分增加 } dfs(hx,hy); // 新蛇头,进行蛇蠕动 head = {hx,hy}; display(); } }
五、刷新控制台:
贪吃蛇游戏需要不断地刷新控制台,用System(“cls”)当然可以解决这个问题,但当蛇地活动区域较大时,由于输出缓冲的问题,看到的屏幕是闪烁的。良好的设计应该解决这个问题,这里我们可以使用双缓冲区解决这个问题。
HANDLE hOutput, hOutBuf;//控制台屏幕缓冲区句柄 HANDLE *houtpoint;//显示指针 COORD coord = { 0,0 }; //双缓冲处理显示 DWORD bytes = 0; bool showcircle = false;
在main模块中引入下列定义:
//创建新的控制台缓冲区 hOutBuf = CreateConsoleScreenBuffer( GENERIC_WRITE,//定义进程可以往缓冲区写数据 FILE_SHARE_WRITE,//定义缓冲区可共享写权限 NULL, CONSOLE_TEXTMODE_BUFFER, NULL ); hOutput = CreateConsoleScreenBuffer( GENERIC_WRITE,//定义进程可以往缓冲区写数据 FILE_SHARE_WRITE,//定义缓冲区可共享写权限 NULL, CONSOLE_TEXTMODE_BUFFER, NULL ); //隐藏两个缓冲区的光标 CONSOLE_CURSOR_INFO cci; cci.bVisible = 0; cci.dwSize = 1; SetConsoleCursorInfo(hOutput, &cci); SetConsoleCursorInfo(hOutBuf, &cci);
展示活动区域的时候使用双缓冲区输出:
void display() { showcircle = !showcircle; if (showcircle) { houtpoint = &hOutput; } else { houtpoint = &hOutBuf; } for (int i = 0; i < H; ++i) { coord.X = 0; coord.Y = i; WriteConsoleOutputCharacterA(*houtpoint, (char*)mp[i], W, coord, &bytes); } //设置新的缓冲区为活动显示缓冲 SetConsoleActiveScreenBuffer(*houtpoint); Sleep(delay); }
欢迎同步关注:ACMFans_Club微信公众号
c/c++开发分享贪吃蛇C/C++面向过程源码分析(双缓冲区 | 248行代码)地址:https://blog.csdn.net/weixin_44026604/article/details/107623402
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/598448.html