C语言手搓游戏之经典《推箱子》

第一篇博文,我以代码小白的身份与大家一起探索C语言的奥秘。

在听完B站翁凯的《C语言程序设计》后,脑洞大开,决定手搓一款小游戏。

在查阅各种游戏资料与对自己水平的评估后,我决定制作《推箱子》这款经典游戏。

一、开发工具与环境配置

我们使用的是VScode这款开发工具。VS Code 支持语法高亮、代码自动补全(又称 IntelliSense)、代码重构、查看定义功能,并且内置了命令行工具和 Git 版本控制系统。是一个轻量级功能强大的源代码编辑器。

这里需要安装VScode并且配置C语言环境,小张并不想一一赘述,所以礼貌地附上链接供大家参考:VS Code C语言开发环境配置附图版保姆教程

二、创建项目并添加头文件、写入main函数

如果你成功配置了环境,说明你已经成功了一半。接下来我们只需要引入头文件<stdio.h>

写入main函数并printf(“你好,小张同学!”);

#include <stdio.h>
int main(){
    printf("你好,小张同学!");
}

 编译运行:

不出意外的话……

当然会报错啦!!!(doge)

这里需要注意,VScode并不支持中文路径

所以只能忍痛将“小张同学”改为“zhangzhang”再次打开操作了

这次大抵是没意外了……

运行成功!

那么恭喜,你的第一个C语言程序已经写好了 。

三、撰写开发日志

一个好的开发者需要养成写日志的习惯。

它不仅可以让你快速回忆起开发时的各种错误与困难,还能合理地为你的开发提供未来规划,时常回看就能完成知识的闭环。

写就完了:

//开发日志
void mylog(){
    printf("2024/3/36 开始研发\n");
}

这里我们新建名为mylog的函数来撰写日志并方便日后引用

四、正式开始

1、创建二维数组并实现可读性界面

我们的基本思路是:创建二维数组来代替地图,在终端中用printf遍历数组实现游戏界面

我们用“1”代表墙,“0”代表空地,

int map[10][10] = {
        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
        {1, 2, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 0, 0, 3, 0, 0, 0, 0, 1},
        {1, 0, 0, 3, 0, 0, 0, 0, 0, 1},
        {1, 0, 3, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 4, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 0, 0, 0, 4, 0, 4, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
    int i, j;
    for (i = 0; i < 10; i++) // 打印游戏地图
    {
        for (j = 0; j < 10; j++)
        {
            printf("%d", map[i][j]);
        }
        printf("\n");
    };

但貌似结果并不理想,它甚至并不具有一定的可读性,只是打印了一个由数字组成的“地图”。

我们来用汉字来替代它:

int i, j;
for (i = 0; i < 10; i++) // 打印游戏地图
        {
            for (j = 0; j < 10; j++)
            {
                if (map[i][j] == 0)
                {
                    printf("\040\040");//\040为空格的转义字符
                }
                if (map[i][j] == 1)
                {

                    printf("墙");
                }
                if (map[i][j] == 2)
                {
                    printf("人");
                }
                if (map[i][j] == 3)
                {
                    printf("箱");
                }
                if (map[i][j] == 4)
                {
                    printf("终");
                }
            }
            printf("\n");
        };

加上颜色:

printf("\033[36m人\033[0m");
printf("\033[33m箱\033[0m");
printf("\033[31m终\033[0m");

看效果: 

如果我们用生成的exe文件打开呢?

乱码了……

经过查阅资料,我发现只要引入系统库#include <windows.h>,并且在开始时输入函数 SetConsoleOutputCP(65001);就能解决这个问题。(改变字符集)

#include <stdio.h>
#include <windows.h>
// 开发日志
void mylog()
{
    printf("2024/3/36 开始研发\n");
}
int main()
{
    SetConsoleOutputCP(65001);
    ……//此处省略上面的代码
}

最终效果:

2、通过键盘操作

这里首先提出一个新函数“_getch();”

我们知道“getchar();”函数是从键盘获取一个字符,但是需要等待用户打回车才能录入,这就会直接导致游戏可玩性大大下降,而“_getch();”函数可以省略回车,直接录入字符。

使用这个函数,我们需要先引入<conio.h>库:

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

 于是我们就能在键盘上识别到‘w’ ‘s’ ‘a’ ‘d’四个基础控制按键。

我们知道。0代表空地,1代表人。那么人往某个方向上移动一步,实际上可以理解为该位置的数组减1,目标位置的数组加1。

于是我们将这一条思路转换成代码写出来:

int x=1,y=1;//x、y为人的坐标位置默认为(1,1)。
int xfx1,yfx1;//X方向第一个数字记作xfx1,以此类推
     switch (_getch()) // 写入操作程序
        {
        case 'w':
        case 'W':
            xfx1 = -1;
            yfx1 = 0;
            break;
        case 's':
        case 'S':
            xfx1 = 1;
            yfx1 = 0;
            break;
        case 'a':
        case 'A':
            xfx1 = 0;
            yfx1 = -1;
            break;
        case 'd':
        case 'D':
            xfx1 = 0;
            yfx1 = 1;
            break;
        default:
            break;
        }
 if (map[g][x + xfx1][y + yfx1] == 0)
        {
            map[g][x + xfx1][y + yfx1] += 2;
            map[g][x][y] -= 2;
        }

于是我们可以实现人与空地之间位置的互换。 

但是如果人前面是箱子呢?

那就要实现三个数字的位置转换。

即:人的位置变成空地,箱子的位置变成人,箱子前的空地变成箱子。

如果人前面是箱子,箱子前面是终点呢?

同样要实现三个数字的位置转换。

即:人的位置变成空地,箱子的位置变成人,箱子前的终点变成箱子。

Ok,那我们加上一些判断语句。

if (map[x + xfx1][y + yfx1] == 3 && map[x + xfx2][y + yfx2] == 0)
{
    map[x][y] -= 2;
    map[x + xfx1][y + yfx1] -= 1;
    map[x + xfx2][y + yfx2] += 3;
}
if (map[x + xfx1][y + yfx1] == 3 && map[x + xfx2][y + yfx2] == 4)
{
    map[x][y] -= 2;
    map[x + xfx1][y + yfx1] -= 1;
    map[x + xfx2][y + yfx2] -= 1;
}

我们每走一步就要执行一个这样的程序。所以我们把它写入循环。

每次打印新的地图,就要把旧的地图消除掉。所以我们加入清屏函数:system("cls");

while (1) // 建立循环
    {
        system("cls");//清屏
        int i, j, X, Y;
        for (i = 0; i < 10; i++) // 打印游戏地图
        {
            for (j = 0; j < 10; j++)
            {
                if (map[i][j] == 0)
                {
                    printf("\040\040"); //\040为空格的转义字符
                }
                if (map[i][j] == 1)
                {
                    printf("墙");
                }
                if (map[i][j] == 2)
                {
                    x=i,y=j;//我们将人物坐标赋值给x,y
                    printf("\033[36m人\033[0m");
                }
                if (map[i][j] == 3)
                {
                    printf("\033[33m箱\033[0m");
                }
                if (map[i][j] == 4)
                {
                    printf("\033[31m终\033[0m");
                }
            }
            printf("\n");
        };
        int xfx1, xfx2, yfx1, yfx2; // X方向第一个数字记作xfx1,以此类推
        switch (_getch())           // 写入操作程序
        {
        case 'w':
        case 'W':
            xfx1 = -1;
            xfx2 = -2;
            yfx1 = 0;
            yfx2 = 0;
            break;
        case 's':
        case 'S':
            xfx1 = 1;
            xfx2 = 2;
            yfx1 = 0;
            yfx2 = 0;
            break;
        case 'a':
        case 'A':
            xfx1 = 0;
            xfx2 = 0;
            yfx1 = -1;
            yfx2 = -2;
            break;
        case 'd':
        case 'D':
            xfx1 = 0;
            xfx2 = 0;
            yfx1 = 1;
            yfx2 = 2;
            break;
        default:
            break;
        }
        if (map[x + xfx1][y + yfx1] == 0)
        {
            map[x + xfx1][y + yfx1] += 2;
            map[x][y] -= 2;
        }
        if (map[x + xfx1][y + yfx1] == 3 && map[x + xfx2][y + yfx2] == 0)
        {
            map[x][y] -= 2;
            map[x + xfx1][y + yfx1] -= 1;
            map[x + xfx2][y + yfx2] += 3;
        }
        if (map[x + xfx1][y + yfx1] == 3 && map[x + xfx2][y + yfx2] == 4)
        {
            map[x][y] -= 2;
            map[x + xfx1][y + yfx1] -= 1;
            map[x + xfx2][y + yfx2] += 3;
        }
    }

 这里值得注意的是:x,y代表人物坐标,所以每次循环都要把新的人物坐标赋值给x,y。

if (map[i][j] == 2)
{
    x = i, y = j;
    printf("\033[36m人\033[0m");
}
!!!!!!!!在遍历地图的这个位置插入!!!!!!!!

写到这里,我们已经能把箱子推到终点了!!!

3、检查bug及其修补

但是,在游玩中,我们发现:

  1. 人物无法经过终点
  2. 箱子经过终点会把终点覆盖掉

OK,有了bug我们就要想办法修补。

在第一个bug中,我们只需要延续以上的思路,

将人的位置变成空地,终点的位置变成人。

if (map[g][x + xfx1][y + yfx1] == 4)
{
    map[g][x + xfx1][y + yfx1] -= 2;
    map[g][x][y] -= 2;
}

但是,这依然会将终点覆盖掉,导致第二个bug的发生。

我们借鉴老一辈程序员的经验:“能跑的代码就是好代码。”

所以,我们并不希望重新架构我们的基础框架,而是添加一个代码块来解决这个问题(堆屎山)

…………………………………………………………………………………………………………………

那么,这个山该怎么堆下去呢?

我们想到,要想解决终点被覆盖的问题,就要先将终点记录下来,等到人或箱子从终点移出时,再将终点显现出来。

所以,我们需要创建一个数组专门储存终点坐标,每次循环判断终点是否有人或箱子覆盖,如果识别到终点的位置变成空地‘0’,那么则调用终点数组,将该位置的终点值重新赋值给地图数组。

int i, j;
int zhongdian[10][10];   // 建立终点坐标系
for (i = 0; i < 10; i++) // 将地图里的终点赋给终点坐标系
    {
        for (j = 0; j < 10; j++)
        {
            zhongdian[i][j] = map[i][j];
            if (map[i][j] == 2 || map[i][j] == 3)
            {
                zhongdian[i][j] = 0;
            }
        }
    }
while(1){
…………(循环中的代码)

for (i = 0; i < 10; i++) // 调用终点坐标系解决终点被覆盖的问题
        {
            for (j = 0; j < 10; j++)
            {
                if (zhongdian[i][j] == 4 && map[i][j] == 0)
                {
                    map[i][j] = zhongdian[i][j];
                }
            }
        }
}

 这样,被覆盖掉的终点就又能显现在控制台上了

推箱子示例1

4、判定游戏结束

在《推箱子》中,当我们将所有箱子推进终点,游戏就会获得胜利。

那么,我们又该如何实现它呢?

小张的思路是:

当箱子推进终点后,箱子颜色变为绿色,表示已经进入终点,用数字7代替。

遍历地图,统计未推进终点的箱子(3)个数,记为xzs。

当xzs=0时,说明所有箱子已经推进终点,则游戏结束。

for (i = 0; i < 10; i++) // 打印游戏地图
        {
            for (j = 0; j < 10; j++)
            {
                …………(此处省略其他打印汉字的操作)
                if (map[i][j] == 7)
                {
                    printf("\033[32m廂\033[0m");
                }
            }
            printf("\n");
        };
…………(此处省略两处改动之间代码)
if (map[x + xfx1][y + yfx1] == 3 && map[x + xfx2][y + yfx2] == 4)
    {
        map[x][y] -= 2;
        map[x + xfx1][y + yfx1] -= 1;
        map[x + xfx2][y + yfx2] += 3;//这里为改动
    }
……………………………………以下为新添加条件判断……………………………………
if (map[x + xfx1][y + yfx1] == 7 && map[x + xfx2][y + yfx2] == 0)
    {
        map[x][y] -= 2;
        map[x + xfx1][y + yfx1] -= 5;
        map[x + xfx2][y + yfx2] += 3;
    }
if (map[x + xfx1][y + yfx1] == 7 && map[x + xfx2][y + yfx2] == 4)
    {
        map[x][y] -= 2;
        map[x + xfx1][y + yfx1] -= 5;
        map[x + xfx2][y + yfx2] += 3;
    }
…………(此处省略两处改动之间代码)
int xzs=0;
for (i = 0; i < 20; i++) // 统计箱子数,为零则游戏结束
    {
        for (j = 0; j < 20; j++)
        {
            if (map[i][j] == 3)
            {
                xzs++;
            }
        }
    }
    if (xzs == 0)
    {
        printf("vectory!!!");
        _getch();
        break;//跳出循环,游戏结束
    }

 至此,我们推箱子小游戏的最基础部分已经完成,代码已经放在资源里了,大家快去尝试一下吧!

然而作为一个游戏,它的趣味性还并不完善,

作者后续会推出更加完善的推箱子教程,大家敬请期待!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值