小白的编程经验(二维数组推箱子游戏)

推箱子游戏和迷宫游戏有异曲同工之妙,不过在游戏玩法实现上略有不同,需要考虑的情况更多更复杂。
项目代码放在了码云(gitee)上,有兴趣的可以下载看看。
https://gitee.com/YHF_200623/C_boxGame

推箱子游戏

先简单介绍一下游戏,游戏玩法和传统的推箱子一样,控制角色推动箱子。当所有箱子都在目标点上时,游戏结束。
玩家操作的角色用 @ 表示,墙壁用 # 表示,道路用空格表示,目标点用 0 表示,箱子用 $ 表示。

在这里插入图片描述

定义并初始化数据

需要的定义的数据有:

  • 地图数组
  • 角色当前坐标

角色当前坐标没啥好说的直接定义就可以了

    int x = 6,y = 3;	// 角色当前坐标(根据地图初始化)	

主要是地图数组的定义,可以有两种表现方式:
    第一种是直接在地图数组中写字符的ASCII码,使用 %c 的格式符输出对应字符。
    第二种是在地图数组中用0、1、2等指代各种符号,在打印时判断输出对应的字符。
这里为了便于在代码中观察地图,我使用第二种方法。

   //迷宫数组 路=0 墙壁=1 角色=2 箱子=3 目标点=4  
    char map[8][8] = {
        {0,0,1,1,1,1,0,0},
        {0,0,1,4,4,1,0,0},
        {0,1,1,0,4,1,1,0},
        {0,1,0,0,3,4,1,0},
        {1,1,0,3,0,0,1,1},
        {1,0,0,1,3,3,0,1},
        {1,0,0,2,0,0,0,1},
        {1,1,1,1,1,1,1,1}
    };   

其实在考虑角色与箱子移动时,也有两种思路:

  • 直接赋值,将一个位置的值赋值成为另一个位置的值,这么做的好处是比较直观,但是较麻烦。
  • 计算变化的值,当角色和箱子到到达一个位置,当前位置的值加上他们自身的值,离开时减去自身的值。举个例子,路的值为0,角色的值为2,箱子的值为3,目标点的值为4。当角色在路上时的值(0+2)为 2,当角色在目标点上时的值(2+4)为6,当箱子在路上时的值(0+3)为3,当箱子在目标点上(3+4)为7。因此,2和6的值显示的都是角色,同理,3和7的值显示的都是箱子。这种写法还有一种好处是当角色和箱子离开当前位置后,不需要知道当前位置原来是上面,如果是赋值,还需要区分原来位置是路还是目标点。

这里我采用的是第二种方法,下面写一下显示地图的语句。

显示地图

显示地图,其实就是遍历迷宫数组,在打印时判断输出对应的字符,用两层for循环加switch语句即可

        //显示地图
        for(int i=0;i<8;i++)
        {
            for(int j=0;j<8;j++)
            {
                switch(map[i][j])
                {
                    case 0:printf("  ");break;
                    case 1:printf("# ");break;
                    case 2:printf("@ ");break;
                    case 3:printf("$ ");break;
                    case 4:printf("O ");break;
 					case 6:printf("@ ");break;
                    case 7:printf("$ ");break;
                }
            }
            printf("\n");
        }

将上述代码组合在一起,我们就有了如下代码,并可以通过运行,显示出迷宫地图。

#include<stdio.h>

int main(int argc,const char* argv[])
{
    //迷宫数组 路=0 墙壁=1 角色=2 箱子=3 目标点=4  
    char map[8][8] = {
        {0,0,1,1,1,1,0,0},
        {0,0,1,4,4,1,0,0},
        {0,1,1,0,4,1,1,0},
        {0,1,0,0,3,4,1,0},
        {1,1,0,3,0,0,1,1},
        {1,0,0,1,3,3,0,1},
        {1,0,0,2,0,0,0,1},
        {1,1,1,1,1,1,1,1}
    };
    int x = 6, y = 3; // 角色当前坐标(根据地图初始>化)
  
  		//显示地图
        for(int i=0;i<8;i++)
        {
            for(int j=0;j<8;j++)
            {
                switch(map[i][j])
                {
                    case 0:printf("  ");break;
                    case 1:printf("# ");break;
                    case 2:printf("@ ");break;
                    case 3:printf("$ ");break;
                    case 4:printf("O ");break;
                    case 6:printf("@ ");break;
                    case 7:printf("$ ");break;
                }
            }
            printf("\n");
        }
}

运行结果:
在这里插入图片描述

清屏

每次显示地图时,上一次的地图会残留在终端上,看着很难受。linux系统可以使用函数system调用系统命令clear,实现清屏效果,需要调用头文件stdlib.h。windows系统可以使用windows.h头文件也可以使用stdlib.h,但里面的清屏命令需要改成cls。

system("clear");

获取键盘输入

可以使用getchar等函数获取键盘的输入,但是为了界面美观。使用了一个getch.h的头文件,用来获取键盘输入,并隐藏窗口的输入信息。

百度网盘链接(getch.h)
提取码:1111

如果是windows系统,可以使用自带的 conio.h 中的,getch()方法,但是因为方向键会返回两个值,所以需要调用两次。

#include<stdio.h>
#include<conio.h>

int main(int argc,const char* argv[])
{ 
    while(1)
	{
    	printf("%d\n",getch()+getch());	
    } 
}

使用 linux 系统的朋友,只需要把getch.h头文件与程序代码放在同一个目录下即可,可以通过#include"getch.h"方式调用,也可将getch.h的内容复制到程序中。

#include<stdio.h>
#include"getch.h"

int main(int argc,const char* argv[])
{ 
    while(1)
	{
    	printf("%d\n",getch());	
    } 
}

windows系统中返回值与linux系统中的略有不同。建议使用前自己测试一下方向键的返回值。

方向键windowlinux
向上296183
向下304184
向左299186
向右301185

顺便提一下#include""与#include<>的区别:

  • #include<> 是从系统指定的路径下加载头文件(操作系统是通过设置环境变量来指定加载头文件的路径)
  • #include “” 先从当前路径(文件路径)下加载头文件,如果找不到,再去系统指定的路径下加载

使用虚拟机的朋友可以放在共享文件夹中(程序+头文件)

玩法实现

地图有了,输入也有了,如何通过输入控制角色(@)移动、推箱子呢?角色移动其实就是通过对二维数组中的内容进行操作,推箱子子就是判断角色相邻的位置是否是箱子,并且箱子前方是否能移动。

打个比方,当我按向上键时,getch函数返回给我一个值,我通过对这个返回值进行判断,执行角色向上走的操作。这个时候需要思考如何什么情况下角色可以往上走?当遇到上方是墙的时候,角色是不能移动的,只有当上方是路的时候,角色才能向上移动,这时把上方的值设为角色,把下方的值设为路,并且更新角色坐标,就实现了角色的移动,其他的移动操作也是如此。

如果需要推动箱子,则需要先判断在箱子的前方是否能移动(路或者目标点)。如果是,更新这三个位置的值并修改当前角色位置坐标。如果不是,则不进行移动。

代码如下

		//玩法实现
		switch(getch())
        {
            case 183:
                if(map[x-1][y] == 3 && map[x-2][y] != 1 && map[x-2][y] != 3)    
                {
                    map[x-2][y] += 3 ; 
                    map[x-1][y] -= 1;  // -3+2
                    map[x][y] -= 2;
                    x--;
                }
                else if(map[x-1][y] == 0 || map[x-1][y] == 4)   
                {
                    map[x-1][y] += 2;
                    map[x][y] -= 2;
                    x--;
                }
                break;
            case 184:
                if(map[x+1][y] == 3 && map[x+2][y] != 1 && map[x+2][y] != 3)    
                {
                    map[x+2][y] += 3 ; 
                    map[x+1][y] -= 1;  // -3+2
                    map[x][y] -= 2;
                    x++;
                }
                else if(map[x+1][y] == 0 || map[x+1][y] == 4)   
                {
                    map[x+1][y] += 2;
                    map[x][y] -= 2;
                    x++;
                }
                break;
            case 185:
                if(map[x][y+1] == 3 && map[x][y+2] != 1 && map[x][y+2] != 3)    
                {
                    map[x][y+2] += 3 ; 
                    map[x][y+1] -= 1;  // -3+2
                    map[x][y] -= 2;
                    y++;
}
                else if(map[x][y+1] == 0 || map[x+1][y+1] == 4)
                {
                    map[x][y+1] += 2;
                    map[x][y] -= 2;
                    y++;
                }
                break;
            case 186:
                if(map[x][y-1] == 3 && map[x][y-2] != 1 && map[x][y-2] != 3)
                {
                    map[x][y-2] += 3 ;
                    map[x][y-1] -= 1;  // -3+2
                    map[x][y] -= 2;
                    y--;
                }
                else if(map[x][y-1] == 0 || map[x][y-1] == 4)
                {
                    map[x][y-1] += 2;
                    map[x][y] -= 2;
                    y--;
                }
                break;
        }

这样游戏玩法算是实现了,但是会感觉代码太长了。仔细研究一下代码可以发现大部分的内容都是相同的,我们找找不同的地方。只有坐标的位置发生改变,有时候x+1,y不变,有时候x不变,y-1。不变的可以看出+0,+1 就是+1,-1可以看作+(-1),这样我们就可以找到大概的规律了。定义两个新的变量p _ x,p _ y 来存放偏移量。

		int p_x = 0,p_y = 0;
        switch(getch())
        {
            case 183:p_x = -1;break;
            case 184:p_x = 1;break;
            case 185:p_y = 1;break;
            case 186:p_y = -1;break;
        }
        if(p_x != p_y)
        {
            if(0 == map[x+p_x][y+p_y] || 4 == map[x+p_x][y+p_y])    
            {
                map[x+p_x][y+p_y] += 2;
                map[x][y] -= 2;
                x += p_x;
                y += p_y;
            }
            else if((3 == map[x+p_x][y+p_y] || 7 == map[x+p_x][y+p_y]) && (0 == map[x+2*p_x][y+2*p_y] || 4 == map[x+2*p_x][y+2*p_y]))
            {
                map[x+2*p_x][y+2*p_y] += 3;
                map[x+p_x][y+p_y] -= 1;
                map[x][y] -= 2;
                x += p_x;
                y += p_y;
            }
        }

确定获胜条件

什么时候游戏算结束了呢,当在目标点处箱子数量等于4个,游戏就结束了。统计箱子数量可以放在显示地图的循环里,当箱子在目标点处(值为7),cnt++。因此可以写出游戏结束的条件:

	//结束判断
 	if(4 == cnt)
    {
        printf("游戏结束\n");
        return 0;
    }

组合代码

我们现在已经实现的功能有:

  1. 显示地图
  2. 清屏
  3. 获取键盘输入
  4. 玩法实现
  5. 确定获胜条件

一个游戏大部分的代码都在这里了,我们需要对它们进行组装使用即可。先捋一捋游戏流程:

  1. 清屏
  2. 显示地图
  3. 判断获胜条件
  4. 获取键盘输入
  5. 控制角色移动
  6. 回到第一步

根据游戏流程,可以看出需要使用死循环。在每次循环清屏并显示地图。然后判断获胜条件,如果获胜就退出,否则游戏继续。通过获取键盘输入的返回值,执行对应的角色移动操作。然后进入下一次循环。

组合代码(linux)如下:

#include<stdio.h>
#include"getch.h"
#include<stdlib.h>

int main(int argc,const char* argv[])
{
    //迷宫数组 路=0 墙壁=1 角色=2 箱子=3 目标点=4  
    char map[8][8] = { 
        {0,0,1,1,1,1,0,0},
        {0,0,1,4,4,1,0,0},
        {0,1,1,0,4,1,1,0},
        {0,1,0,0,3,4,1,0},
        {1,1,0,3,0,0,1,1},
        {1,0,0,1,3,3,0,1},
        {1,0,0,2,0,0,0,1},
        {1,1,1,1,1,1,1,1}
    };  
    int x = 6, y = 3; // 角色当前坐标(根据地图初始化)
    
    //游戏开始  
    while(1)
    {   
        int cnt = 0;  // 统计完成的箱子数
    
        // 清屏
        system("clear");
    
        //显示地图
        for(int i=0;i<8;i++)
        {
            for(int j=0;j<8;j++)
            {
                switch(map[i][j])
                {
                    case 0:printf("  ");break;
                    case 1:printf("# ");break;
                    case 2:printf("@ ");break;
                    case 3:printf("$ ");break;
                    case 4:printf("O ");break;
                    case 6:printf("@ ");break;
                    case 7:printf("$ ");cnt++;break;
                }
            }
            printf("\n");
        }

        //判断游戏结束
        if(cnt == 4)
        {
            printf("游戏结束\n");
            return 0;
        }

        //键盘输入
        int p_x = 0,p_y = 0;
        switch(getch())
        {
            case 183:p_x = -1;break;
            case 184:p_x = 1;break;
            case 185:p_y = 1;break;
            case 186:p_y = -1;break;
        }
        if(p_x != p_y)
        {
            if(0 == map[x+p_x][y+p_y] || 4 == map[x+p_x][y+p_y])
            {
                map[x+p_x][y+p_y] += 2;
                map[x][y] -= 2;
                x += p_x;
                y += p_y;
            }
            else if((3 == map[x+p_x][y+p_y] || 7 == map[x+p_x][y+p_y]) && (0 == map[x+2*p_x][y+2*p_y] || 4 == map[x+2*p_x][y+2*p_y]))
            {
                map[x+2*p_x][y+2*p_y] += 3;
                map[x+p_x][y+p_y] -= 1;
                map[x][y] -= 2;
                x += p_x;
   				y += p_y;
            }
        }
    }
}

windows 需要调用conio.h头文件,并且调用两次getch()函数。同时,不同系统的键值不同,需要修改键值。最后,系统命令清屏,里面的清屏命令改成cls。

组合代码(window)如下:

#include<stdio.h>
#include<conio.h>
#include<windows.h>

int main(int argc,const char* argv[])
{
    //迷宫数组 路=0 墙壁=1 角色=2 箱子=3 目标点=4  
    char map[8][8] = { 
        {0,0,1,1,1,1,0,0},
        {0,0,1,4,4,1,0,0},
        {0,1,1,0,4,1,1,0},
        {0,1,0,0,3,4,1,0},
        {1,1,0,3,0,0,1,1},
        {1,0,0,1,3,3,0,1},
        {1,0,0,2,0,0,0,1},
        {1,1,1,1,1,1,1,1}
    };  
    int x = 6, y = 3; // 角色当前坐标(根据地图初始化)
    
    //游戏开始  
    while(1)
    {   
        int cnt = 0;  // 统计完成的箱子数
    
        // 清屏
        system("cls");
    
        //显示地图
        for(int i=0;i<8;i++)
        {
            for(int j=0;j<8;j++)
            {
                switch(map[i][j])
                {
                    case 0:printf("  ");break;
                    case 1:printf("# ");break;
                    case 2:printf("@ ");break;
                    case 3:printf("$ ");break;
                    case 4:printf("O ");break;
                    case 6:printf("@ ");break;
                    case 7:printf("$ ");cnt++;break;
                }
            }
            printf("\n");
        }

        //判断游戏结束
        if(cnt == 4)
        {
            printf("游戏结束\n");
            return 0;
        }

        //键盘输入
        int p_x = 0,p_y = 0;
        switch(getch()+getch())
        {
            case 296:p_x = -1;break;
            case 304:p_x = 1;break;
            case 301:p_y = 1;break;
            case 299:p_y = -1;break;
        }
        if(p_x != p_y)
        {
            if(0 == map[x+p_x][y+p_y] || 4 == map[x+p_x][y+p_y])
            {
                map[x+p_x][y+p_y] += 2;
                map[x][y] -= 2;
                x += p_x;
                y += p_y;
            }
            else if((3 == map[x+p_x][y+p_y] || 7 == map[x+p_x][y+p_y]) && (0 == map[x+2*p_x][y+2*p_y] || 4 == map[x+2*p_x][y+2*p_y]))
            {
                map[x+2*p_x][y+2*p_y] += 3;
                map[x+p_x][y+p_y] -= 1;
                map[x][y] -= 2;
                x += p_x;
   				y += p_y;
            }
        }
    }
}

如果要显示移动步数,只需要定义一个变量记录步数,在移动时时记录变量自增即可,这里就不做演示了。

结语

希望这篇文章对你有所帮助

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值