啊哈c的推箱子游戏(更新附带失败判断!)
以下是原文,2020.6.28更新了推箱子失败判断,以及优化方向代码,还新加了博主的无情嘲讽 。
因为啊哈c后面推箱子游戏未附代码,而且说实话考虑很多种状况和之前走迷宫的思路还是有区别的。并且我在网上搜的时候没有相似的参考信息,大一这学期快结束了才准备搞这个。一早上的bug ,调试了很多次才好。
说实话,网上信息对新人极不友好,我查推箱子代码,很多csdn博客连界面都写好了,对刚刚学c的我们打击好大啊。总之,我觉得有必要像啊哈c的作者啊哈磊那样写点对新生友好的内容,不然书上一堆枯燥无味的语法真的难读下去。
进入正题。二维数组画出地图,其中s表示人,o表示箱子,*表示目的地,@把箱子推入目的地。如果想换地图,只需改写二维数组和初始位置(x,y)。由于精力有限单纯好玩,所以没写出箱子推入角落等失败条件判断。源代码如下:
经提醒源代码在啊哈c 3.0版本以上运行有错,因为getch()函数头文件conio.h未添加,另外getch()函数可移植性较差,所以还是报错的话就用getchar()替代,每次输入w,a,s,d后再输入回车即可。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
int main()
{
system("color f9");//我喜欢的言和蓝(ff9999)
//#是墙;s是人;o是箱子;*是目的地;
char a[12][12]={
"##########",
"## ###",
"##o### #",
"# s o o #",
"# **# o ##",
"##**# ##",
"##########",
};
int x=3,y=2,i;//x,y是初始坐标;
char direction;
for(i=0;i<7;i++)
puts(a[i]);
while(a[4][2]!='@'||a[4][3]!='@'||a[5][2]!='@'||a[5][3]!='@')
//所有箱子推入结束
{
direction=getch();
switch(direction)
{
//w s a d, 方向键
case'w':if(a[x-1][y]!='#')/*给w附注释,下面类似
检测前面(就是所对应方向,下同)是不是墙,可以不要但运算会变大*/
{
if(a[x-1][y]==' ')//空格直接走
{
a[x][y]=' ';
x--;
a[x][y]='s';/*我一开始写成a[x--][y]='s'
结果不对,其实应该写成--x才能正常运行*/
}
else if(a[x-1][y]=='o'&&a[x-2][y]!='#')//如果是箱子,并且前面不是墙
{
if(a[x-2][y]==' ')//箱子前是空格推得动
{
a[x][y]=' ';
x--;
a[x][y]='s';
a[x-1][y]='o';
}
else if(a[x-2][y]=='*')//必须有else,否则可能多运行一步。
//箱子前是目的地就换成@。
{
a[x][y]=' ';
x--;
a[x][y]='s';
a[x-1][y]='@';
}
}
else if(a[x-1][y]=='*')//s人想进入目的地
{
a[x][y]=' ';//空格不要紧,不会洗掉原有目的地。
//后面有代码防止目的缺失
x--;
a[x][y]='s';
}
else if(a[x-1][y]=='@')//想在目的地范围内移箱子
{
if(a[x-2][y]=='*')
{
a[x][y]=' ';
x--;
a[x][y]='*';
a[x-1][y]='@';
}
else if(a[x-2][y]==' ')
{
a[x][y]=' ';
x--;
a[x][y]='*';
a[x-1][y]='o';
}
}
}break;//下面方向键类似
case's':if(a[x+1][y]!='#')
{
if(a[x+1][y]==' ')
{
a[x][y]=' ';
x++;
a[x][y]='s';
}
else if(a[x+1][y]=='o'&&a[x+2][y]!='#')
{
if(a[x+2][y]==' ')
{
a[x][y]=' ';
x++;
a[x][y]='s';
a[x+1][y]='o';
}
else if(a[x+2][y]=='*')
{
a[x][y]=' ';
x++;
a[x][y]='s';
a[x+1][y]='@';
}
}
else if(a[x+1][y]=='*')
{
a[x][y]=' ';
x++;
a[x][y]='s';
}
else if(a[x+1][y]=='@')
{
if(a[x+2][y]=='*')
{
a[x][y]=' ';
x++;
a[x][y]='*';
a[x+1][y]='@';
}
else if(a[x+2][y]==' ')
{
a[x][y]=' ';
x++;
a[x][y]='*';
a[x+1][y]='o';
}
}
}break;
case'a':if(a[x][y-1]!='#')
{
if(a[x][y-1]==' ')
{
a[x][y]=' ';
y--;
a[x][y]='s';
}
else if(a[x][y-1]=='o'&&a[x][y-2]!='#')
{
if(a[x][y-2]==' ')
{
a[x][y]=' ';
y--;
a[x][y]='s';
a[x][y-1]='o';
}
else if(a[x][y-2]=='*')
{
a[x][y]=' ';
y--;
a[x][y]='s';
a[x][y-1]='@';
}
}
else if(a[x][y-1]=='*')
{
a[x][y]=' ';
y--;
a[x][y]='s';
}
else if(a[x][y-1]=='@')
{
if(a[x][y-2]=='*')
{
a[x][y]=' ';
y--;
a[x][y]='*';
a[x][y-1]='@';
}
else if(a[x][y-2]==' ')
{
a[x][y]=' ';
y--;
a[x][y]='*';
a[x][y-1]='o';
}
}
}break;
case'd':if(a[x][y+1]!='#')
{
if(a[x][y+1]==' ')
{
a[x][y]=' ';
y++;
a[x][y]='s';
}
else if(a[x][y+1]=='o'&&a[x][y+2]!='#')
{
if(a[x][y+2]==' ')
{
a[x][y]=' ';
y++;
a[x][y]='s';
a[x][y+1]='o';
}
else if(a[x][y+2]=='*')
{
a[x][y]=' ';
y++;
a[x][y]='s';
a[x][y+1]='@';
}
}
else if(a[x][y+1]=='*')
{
a[x][y]=' ';
y++;
a[x][y]='s';
}
else if(a[x][y+1]=='@')
{
if(a[x][y+2]=='*')
{
a[x][y]=' ';
y++;
a[x][y]='*';
a[x][y+1]='@';
}
else if(a[x][y+2]==' ')
{
a[x][y]=' ';
y++;
a[x][y]='*';
a[x][y+1]='o';
}
}
}break;
}
if(a[4][2]!='s'&&a[4][2]!='@')a[4][2]='*';
if(a[4][3]!='s'&&a[4][3]!='@')a[4][3]='*';
if(a[5][2]!='s'&&a[5][2]!='@')a[5][2]='*';
if(a[5][3]!='s'&&a[5][3]!='@')a[5][3]='*';
/*我觉得我做的最好的一步,检测目的地是不是人或者箱子,
再加载目的地,克服换位置时目的地*号丢失*/
system("cls");//清屏
for(i=0;i<7;i++)
puts(a[i]);//重新打印
}
system("cls");
printf("you win!\n");
Sleep(5000);
return 0;
}
——————————————————假装更新线————————————————
2020.6.28 更新
如果搜到这片文章,说明你还是一个在用啊哈c学c语言的小白,而博主据写这篇文章已经有两年多更新了,快大三了,从小白变成了蒟蒻都不如的菜鸡(连萌新都不敢当,群里巨佬都在装萌新怎么办 )。
不过都是这么一步一步走过来的,我走了很多弯路,我想对曾经的那个小白说很多,这样我可能现在不会这么急。实在建议计算机小白们一开始就去打acm比赛,那怕最后放弃了也无所谓,只要入门了都能随便切那些一般工作的面试题(比如力扣,笑)。不知道怎么入门的可以看看我的这个回答:acm入门
直接正文吧,两年了,啊哈c2013年出版,到现在2020年了,仍然只有我在第一手更新,说多了都是泪。
上面代码200多行,而且wsad方向键其实功能差不多我硬是整了4的case然后if else套if else,哈哈哈,不愧是小白的我。
如果和我一样从啊哈c只是初学c语言,请听我唠叨这一段。对于相同的功能,比如方向键这种,只有很小的代码差别,如果只是四个方向还能多敲一下搞出来,如果要再来几个不是得重复劳动,这就叫造轮子,就是已有的东西不去用反而还要自己再设计,众所周知轮子已经是圆的了,不可能还有其他形状了(说莱洛三角形的,亲亲这边建议你去清华读数学系呢 ),你不去用还要造个方的吗?所以,把这个东西封装起来。简单在此处来说就是把这个功能变成一个函数去调用,多次调用即可,不用我们一个一个方向去再打一遍相同的内容。
下面就封装了移动函数,xx,yy表示移动距离,比如向右时xx=-1,yy=0;表示x+xx,y+yy,这样就成了原来点的(x-1,y)。有了这个函数你甚至可以定义自己的移动方向,比如用q键表示放大招把箱子斜着推,只需要让xx=1,yy=1即可,就朝右下方向推箱子了,甚至可以用e键调用dir(2,2),你可以斜着把箱子推两格!
#是墙;s是人;o是箱子;米字号是目的地,对了,居然有人怀疑我这个推箱子没法过???你自己试试,我用了239步就过了(如果有更短的步数过图,请务必回复我)
void dir(int xx, int yy) {
//从主函数开始读是好习惯(*^▽^*)
if (a[x +xx][y+yy] != '#')/*给w附注释,下面类似
检测前面(就是所对应方向,下同)是不是墙,可以不要但运算会变大*/
{
if (a[x +xx][y+yy] == ' ')//空格直接走
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = 's';
}
else if (a[x +xx][y+yy] == 'o'&&a[x+xx+xx][y+yy+yy] != '#')//如果是箱子,并且箱子前面不是墙
{
if (a[x+xx+xx][y+yy+yy] == ' ')//箱子前是空格推得动
{
a[x][y] = ' ';//清除原位置的人
x+=xx;
y += yy;
a[x][y] = 's';
a[x +xx][y+yy] = 'o';
}
else if (a[x +xx+xx][y+yy+yy] == '*')//必须有else if,否则可能多运行一步。
//箱子前是目的地就换成@。
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = 's';
a[x +xx][y+yy] = '@';
}
}
else if (a[x+xx][y+yy] == '*')//s人想进入目的地
{
a[x][y] = ' ';//空格不要紧,不会洗掉原有目的地。
//后面有代码防止目的缺失
x+=xx;
y += yy;
a[x][y] = 's';
}
else if (a[x +xx][y+yy] == '@')//想在目的地范围内移箱子
{
if (a[x +xx+xx][y+yy+yy] == '*')
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = '*';
a[x +xx][y+yy] = '@';
}
else if (a[x+xx+xx][y+yy+yy] == ' ')
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = '*';
a[x +xx][y+yy] = 'o';
}
}
}
}
然后就是判断失败函数,说实话,写的很简单,判断箱子有没有进死角,进死角就说明gme over。
有一个箱子两面相邻贴墙的话肯定没法推了,推箱子不是拉箱子,你难道把他挤出来到目的地吗?(笑)
//类似这种
#########
# o#
# * #
#########
代码如下:
bool fail() {
int i, j;
for (i = 0; i < 7; i++) {
for (j= 0; j < 10; j++) {
if (a[i][j] == 'o') {
//遇到箱子判断一下
if (a[i + 1][j] == '#' && (a[i][j + 1] == '#' || a[i][j-1] == '#')) {
//下面是墙,如果左右有墙就卡死
return false;
}
if (a[i -1][j] == '#' && (a[i][j + 1] == '#' || a[i][j-1] == '#')) {
//上面是墙,如果左右有墙就卡死
return false;
}
}
}
}
return true;
}
可以看出来非常粗糙,两个for循环判断,时间复杂度O(n2),还好地图小,实际上应该用四个点来记录箱子位置,这样就不用每次都去两个for循环找这个箱子在哪了。不过这样就是代码将变得完全不一样了。而实际上这种判断失败条件太简单了,漏了很多条件,比如下面这种,只有一面贴墙却失败了:
#######
# #####
## o #
# * ####
############
可以看出了虽然只有上面贴墙,人在外面的话但是没法从o的右边推箱子,因为右边那三个空格人是进不去的。我百度了下没有代码来专门判断推箱子失败条件的,我一向讨厌造轮子,不过这次搜不出来,就和我写这个推箱子一样,之前从没有人发过思路或者代码。
实际上我的思路很简单,就是先用二维数组记录箱子坐标,通过深度搜索或者广度搜索来判断那些空格可以到达,能到达标1,不能标为0,然后枚举所有箱子是否进“死角”。
下面是精简后代码,当然没有实现上面说的判断方法,因为答主太懒了 。这个版本方向可以随便修改,步数也能修改,你想让这个人走“日”字步(比如一次操作向左走三步,向下走两步)也是可以的,只需要调用函数dir(2,3)即可。
另外添加了失败判断条件,game over.,最后,如果你的步数超过239步以上,还会遭到博主的无情嘲笑,如果你过这个图少于239步请务必私聊或者评论我,答主应该是最少步数了。
代码如下:
#include<conio.h>//getch()函数头文件
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int x = 3, y = 2;//x,y是初始坐标;
char a[12][12] = {
"##########",//存图,注意人不能推动一个以上的箱子
"## ###",
"##o### #",
"# s o o #",
"# **# o ##",
"##**# ##",
"##########",
};
void dir(int xx, int yy) {
//从主函数开始读是好习惯(*^▽^*)
if (a[x +xx][y+yy] != '#')/*给w附注释,下面类似
检测前面(就是所对应方向,下同)是不是墙,可以不要但运算会变大*/
{
if (a[x +xx][y+yy] == ' ')//空格直接走
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = 's';
}
else if (a[x +xx][y+yy] == 'o'&&a[x+xx+xx][y+yy+yy] != '#')//如果是箱子,并且箱子前面不是墙
{
if (a[x+xx+xx][y+yy+yy] == ' ')//箱子前是空格推得动
{
a[x][y] = ' ';//清除原位置的人
x+=xx;
y += yy;
a[x][y] = 's';
a[x +xx][y+yy] = 'o';
}
else if (a[x +xx+xx][y+yy+yy] == '*')//必须有else if,否则可能多运行一步。
//箱子前是目的地就换成@。
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = 's';
a[x +xx][y+yy] = '@';
}
}
else if (a[x+xx][y+yy] == '*')//s人想进入目的地
{
a[x][y] = ' ';//空格不要紧,不会洗掉原有目的地。
//后面有代码防止目的缺失
x+=xx;
y += yy;
a[x][y] = 's';
}
else if (a[x +xx][y+yy] == '@')//想在目的地范围内移箱子
{
if (a[x +xx+xx][y+yy+yy] == '*')
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = '*';
a[x +xx][y+yy] = '@';
}
else if (a[x+xx+xx][y+yy+yy] == ' ')
{
a[x][y] = ' ';
x+=xx;
y += yy;
a[x][y] = '*';
a[x +xx][y+yy] = 'o';
}
}
}
}
bool fail() {
int i, j;
for (i = 0; i < 7; i++) {
for (j= 0; j < 10; j++) {
if (a[i][j] == 'o') {
//遇到箱子判断一下
if (a[i + 1][j] == '#' && (a[i][j + 1] == '#' || a[i][j-1] == '#')) {
//下面是墙,如果左右有墙就卡死
return false;
}
if (a[i -1][j] == '#' && (a[i][j + 1] == '#' || a[i][j-1] == '#')) {
//上面是墙,如果左右有墙就卡死
return false;
}
}
}
}
return true;
}
int main()
{
system("color f9");//我喜欢的言和蓝(ff9999)
//#是墙;s是人;o是箱子;*是目的地;
int i,cnt=0;//cnt统计步数
char direction;
for (i = 0; i < 7; i++)
puts(a[i]);
while (a[4][2] != '@' || a[4][3] != '@' || a[5][2] != '@' || a[5][3] != '@')
//所有箱子推入结束
{
cnt++;//操作一次步数加一次撞墙了也算哦!
direction = getch();//w s a d, 方向键
//注意啊哈c下载的编译器支持getch(),如果vs想运行尽量用getchar()函数
if (direction == 'w'||direction == 'W')//大小写都支持
dir(-1, 0);//相比之前是不是好了很多,只需要行函数调用即可
else if (direction == 's' || direction == 'S')dir(1, 0);
else if (direction == 'a' || direction == 'A')dir(0, -1);
else if (direction == 'd' || direction == 'D')dir(0, 1);
if (a[4][2] != 's'&&a[4][2] != '@')a[4][2] = '*';
if (a[4][3] != 's'&&a[4][3] != '@')a[4][3] = '*';
if (a[5][2] != 's'&&a[5][2] != '@')a[5][2] = '*';
if (a[5][3] != 's'&&a[5][3] != '@')a[5][3] = '*';
/*我觉得我做的最好的一步,检测目的地是不是人或者箱子,
再加载目的地,克服换位置时目的地*号丢失*/
system("cls");//清屏
for (i = 0; i < 7; i++)
puts(a[i]);//重新打印
if (!fail()) {
//判断这个状态是否失败
printf("you lose\n");
for (i = 0; i <= 10; i++)printf("Game over!!!!\n");
Sleep(5000);
return 0;
}
}
system("cls");
if(cnt>239)printf("虽然你赢了但你用了%d步\n博主只用了239步",cnt);
else printf("Congratulation!you win!\n");
Sleep(5000);
return 0;
}