- 首先,这个推箱子,是在黑窗口实现的,界面不美观,有能力的写好了可以尝试图形化窗口easyx封装好
先来看看完整代码
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <Windows.h>
#include <stdbool.h>//枚举定义变量
enum Element
{SPACE,WALL,PLAYER,BOX,DEST
};//空地 :0 墙:1 玩家:2 箱子:3 目的地:4
char map[9][9] =
{0,0,1,1,1,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,1,0,1,1,1,1,0,1,1,1,3,0,3,4,1,0,1,4,0,3,2,1,1,1,0,1,1,1,1,3,1,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,
};/**********声明函数**************/
void drawMap(); //绘制地图
void KeyEvent(); //响应按键
void Win(); //判断输赢
void hideCursor(); //隐藏控制台光标
/********************************///显示地图
void drawMap()
{for (int i = 0; i < 9; i++){for (int k = 0; k < 9; k++){switch (map[i][k]){case SPACE:printf(" ");break;case WALL:printf("■");break;case PLAYER:printf("♀");break;case BOX:printf("◆");break;case DEST:printf("●");break;case BOX + DEST:printf("★");break;default:break;}}putchar('\n');}printf("墙:■,玩家:♀,箱子:◆,目的地:●,箱子到达目的地:★\n");puts("W:上");puts("S:下");puts("A:左");puts("D:右");
}
//按键操作
void KeyEvent()
{int lines = 0;int cols = 0;for (int i = 0; i < 9; i++){for (int k = 0; k < 9; k++){if (map[i][k]==PLAYER || map[i][k] == PLAYER + DEST)//定位玩家位置{lines = i;cols = k;}}}switch (getch()){case 'w':case 'W':case 72://玩家上面是空地或目的地if (map[lines - 1][cols] == SPACE || map[lines - 1][cols] == DEST){map[lines][cols] -= PLAYER;map[lines - 1][cols] += PLAYER;}//玩家上面是箱子或者玩家上面是箱子在目的地上else if (map[lines - 1][cols] == BOX || map[lines - 1][cols] == DEST + BOX){//如果有移动的地方(箱子上面是空地或目的地)就进行移动if (map[lines - 2][cols] == SPACE || map[lines - 2][cols] == DEST){map[lines][cols] -= PLAYER;map[lines][cols] += PLAYER;map[lines][cols] -= BOX;map[lines - 2][cols] += BOX;}}break;case 's':case 'S':case 80://玩家下面是空地或目的地if (map[lines + 1][cols] == SPACE || map[lines + 1][cols] == DEST){map[lines][cols] -= PLAYER;map[lines + 1][cols] += PLAYER;}//玩家下面是箱子或者玩家下面是箱子在目的地上else if (map[lines + 1][cols] == BOX || map[lines + 1][cols] == DEST + BOX){//如果有移动的地方(箱子下面是空地或目的地)就进行移动if (map[lines + 2][cols] == SPACE || map[lines + 2][cols] == DEST){map[lines][cols] -= PLAYER;map[lines + 1][cols] += PLAYER;map[lines + 1][cols] -= BOX;map[lines + 2][cols] += BOX;}}break;case 'a':case 'A':case 75://玩家左面是空地或目的地if (map[lines][cols - 1] == SPACE || map[lines][cols - 1] == DEST){map[lines][cols] -= PLAYER;map[lines][cols - 1] += PLAYER;}//玩家左面是箱子或者玩家左面是箱子在目的地上else if (map[lines][cols - 1] == BOX || map[lines][cols - 1] == DEST + BOX){//如果有移动的地方(箱子左面是空地或目的地)就进行移动if (map[lines][cols - 2] == SPACE || map[lines][cols - 2] == DEST){map[lines][cols] -= PLAYER;map[lines][cols - 1] += PLAYER;map[lines][cols - 1] -= BOX;map[lines][cols - 2] += BOX;}}break;case 'd':case 'D':case 77://玩家右面是空地或目的地if (map[lines][cols + 1] == SPACE || map[lines][cols + 1] == DEST){map[lines][cols] -= PLAYER;map[lines][cols + 1] += PLAYER;}//玩家右面是箱子或者玩家右面是箱子在目的地上else if (map[lines][cols + 1] == BOX || map[lines][cols + 1] == DEST + BOX){//如果有移动的地方(箱子右面是空地或目的地)就进行移动if (map[lines][cols + 2] == SPACE || map[lines][cols + 2] == DEST){map[lines][cols] -= PLAYER;map[lines][cols + 1] += PLAYER;map[lines][cols + 1] -= BOX;map[lines][cols + 2] += BOX;}}break;default:break;}
}//隐藏光标,对控制台进行美化
void hideCursor()
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //找到控制台的窗口句柄CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo); //获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(handle, &CursorInfo); //设置控制台光标状态
}//判断输赢并且反馈给用户
void Win()
{int result = 0;for (int i = 0; i < 9; i++){for (int k = 0; k < 9; k++){if (map[i][k] == BOX){result++;}}}if (result == 0){Sleep(100);MessageBox(NULL, L"你赢了", L"Win", MB_OK);exit(0);}
}int main()
{system("mode con lines=25 cols=50");//调整控制台大小hideCursor();while (1){system("cls");drawMap();Win();KeyEvent();}
}
下面对代码进行解释:
定义游戏元素和地图
- 下面是对于推箱子游戏的每个元素用数据的形式进行表示,因为一直用数字也不太方便观看,可读性不高
enum Element
{SPACE,WALL,PLAYER,BOX,DEST
};
- 下面就是你自己定义的地图,当然,你也可以用我们上面用枚举定义好的变量来替换,因为数字比较好敲,我就不进行替换了。
//空地 :0 墙:1 玩家:2 箱子:3 目的地:4
char map[9][9] =
{0,0,1,1,1,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,1,0,1,1,1,1,0,1,1,1,3,0,3,4,1,0,1,4,0,3,2,1,1,1,0,1,1,1,1,3,1,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,
};
- 再接下来就是加载游戏地图部分,逻辑不难我就不多赘述了
但是有一点要注意的是win11下的控制台打印出来的特殊字符占的是一个汉字大小的1/2,所以要在每个特殊字符后面加上一个空格来使这些字符组成一个完整的正方形。(其实就是用易读的符号去替换掉数字而已)
- win11运行效果展示:
win10运行效果展示:
void drawMap()
{for (int i = 0; i < 9; i++){for (int k = 0; k < 9; k++){switch (map[i][k]){case SPACE:printf(" ");break;case WALL:printf("■");break;case PLAYER:printf("♀");break;case BOX:printf("◆");break;case DEST:printf("●");break;case BOX + DEST:printf("★");break;default:break;}}putchar('\n');}printf("墙:■,玩家:♀,箱子:◆,目的地:●,箱子到达目的地:★\n");puts("W:上");puts("S:下");puts("A:左");puts("D:右");
}
按键操作
锁定玩家位置
- 接下来就是最最最重要的逻辑部分了
- 下面进行解释:
首先我们要清楚,对于推箱子这个游戏我们是对玩家进行的移动,所以就不难解释下面这一部分的定位玩家位置的代码,就是要先找到玩家在哪个地方,然后对玩家的上下左右进行判断
int lines = 0;int cols = 0;for (int i = 0; i < 9; i++){for (int k = 0; k < 9; k++){if (map[i][k]==PLAYER || map[i][k] == PLAYER + DEST)//定位玩家位置{lines = i;cols = k;}}}
游戏移动逻辑判断
- 然后就到了该游戏的逻辑部分:
首先,什么情况下玩家可以移动呢,那不就是玩家周围是空地和目的地吗,所以我们就进行判断(这里以向上为例,其它类比)
如果玩家上面是空地和目的地的话,我们就让玩家站的地方(map[lines][cols])进行-=玩家的操作(通俗来说就是玩家走了),然后我们对玩家的上一格进行+=玩家的操作(玩家来了),其实到了这里我们就能体会到用枚举来替换数据的好处了(更直观!!!)
那有的小伙伴就要问了,为什么不用=,而要用+=和-=呢,你可以自己实现一遍,用的如果是=的话就会出现下面的情况,玩家怎么分身了,那是因为不减的话,玩家所在的位置就没有进行清空操作
- 接下来我们解释如果玩家周围有阻挡物(箱子 或者是 箱子在目的地上这个整体)该怎么办,首先肯定要判断这个箱子的周围有没有可以移动的位置,有的话才可以移动,然后玩家所在的地方要进行-=操作(玩家走了),玩家的上一格进行+=玩家的操作和-=箱子的操作(玩家来了,箱子走了),玩家的上上格进行+=箱子的操作(箱子来了)代码如下:
其它三种情况也是同理(就不一一赘述了)
else if (map[lines - 1][cols] == BOX || map[lines - 1][cols] == DEST + BOX){//如果有移动的地方(箱子上面是空地或目的地)就进行移动if (map[lines - 2][cols] == SPACE || map[lines - 2][cols] == DEST){map[lines][cols] -= PLAYER;map[lines - 1][cols] += PLAYER;map[lines - 1][cols] -= BOX;map[lines - 2][cols] += BOX;}}
//按键操作
void KeyEvent()
{switch (getch()){case 'w':case 'W':case 72://玩家上面是空地或目的地if (map[lines - 1][cols] == SPACE || map[lines - 1][cols] == DEST){map[lines][cols] -= PLAYER;map[lines - 1][cols] += PLAYER;}//玩家上面是箱子或者玩家上面是箱子在目的地上else if (map[lines - 1][cols] == BOX || map[lines - 1][cols] == DEST + BOX){//如果有移动的地方(箱子上面是空地或目的地)就进行移动if (map[lines - 2][cols] == SPACE || map[lines - 2][cols] == DEST){map[lines][cols] -= PLAYER;map[lines][cols] += PLAYER;map[lines][cols] -= BOX;map[lines - 2][cols] += BOX;}}break;case 's':case 'S':case 80://玩家下面是空地或目的地if (map[lines + 1][cols] == SPACE || map[lines + 1][cols] == DEST){map[lines][cols] -= PLAYER;map[lines + 1][cols] += PLAYER;}//玩家下面是箱子或者玩家下面是箱子在目的地上else if (map[lines + 1][cols] == BOX || map[lines + 1][cols] == DEST + BOX){//如果有移动的地方(箱子下面是空地或目的地)就进行移动if (map[lines + 2][cols] == SPACE || map[lines + 2][cols] == DEST){map[lines][cols] -= PLAYER;map[lines + 1][cols] += PLAYER;map[lines + 1][cols] -= BOX;map[lines + 2][cols] += BOX;}}break;case 'a':case 'A':case 75://玩家左面是空地或目的地if (map[lines][cols - 1] == SPACE || map[lines][cols - 1] == DEST){map[lines][cols] -= PLAYER;map[lines][cols - 1] += PLAYER;}//玩家左面是箱子或者玩家左面是箱子在目的地上else if (map[lines][cols - 1] == BOX || map[lines][cols - 1] == DEST + BOX){//如果有移动的地方(箱子左面是空地或目的地)就进行移动if (map[lines][cols - 2] == SPACE || map[lines][cols - 2] == DEST){map[lines][cols] -= PLAYER;map[lines][cols - 1] += PLAYER;map[lines][cols - 1] -= BOX;map[lines][cols - 2] += BOX;}}break;case 'd':case 'D':case 77://玩家右面是空地或目的地if (map[lines][cols + 1] == SPACE || map[lines][cols + 1] == DEST){map[lines][cols] -= PLAYER;map[lines][cols + 1] += PLAYER;}//玩家右面是箱子或者玩家右面是箱子在目的地上else if (map[lines][cols + 1] == BOX || map[lines][cols + 1] == DEST + BOX){//如果有移动的地方(箱子右面是空地或目的地)就进行移动if (map[lines][cols + 2] == SPACE || map[lines][cols + 2] == DEST){map[lines][cols] -= PLAYER;map[lines][cols + 1] += PLAYER;map[lines][cols + 1] -= BOX;map[lines][cols + 2] += BOX;}}break;default:break;}
}
判断输赢
- 什么情况下算赢呢?
当你这个游戏通关时,场上就没有箱子了,那么我们就可以用场上是否有箱子来判断输赢(有箱子,没赢;没箱子,赢)
我们可以用弹窗显示的方法进行输赢的反馈,也就是Messagebox()函数
//判断输赢并且反馈给用户
void Win()
{int result = 0;for (int i = 0; i < 9; i++){for (int k = 0; k < 9; k++){if (map[i][k] == BOX){result++;}}}if (result == 0){Sleep(100);MessageBox(NULL, L"你赢了", L"Win", MB_OK);exit(0);}
}
调用所写的功能函数
- 窗口太大,游戏界面太小看起来不太美观
我们可以用调用cmd命令(system()函数)来实现控制台的大小设置 system("mode con lines=25 cols=50");//调整控制台大小
- 然后死循环不断调用函数来实现游戏
int main()
{system("mode con lines=25 cols=50");//调整控制台大小while (1){system("cls");drawMap();Win();KeyEvent();}
}
界面美化
其实到了这里,所需的功能都已经完成了,但是当我们在进行运行的时候会有一个光标一直存在,不太美观
那么我们就可以对光标进行伪装
这段代码来自于https://blog.csdn.net/nocomment_84/article/details/53992730
void hideCursor()
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //找到控制台的窗口句柄CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo); //获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(handle, &CursorInfo); //设置控制台光标状态
}