欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[Unity-3D] 牧师与魔鬼游戏之智能帮助

程序员文章站 2024-03-18 19:37:40
...

[Unity-3D] 牧师与魔鬼游戏之智能帮助
在之前的作业中,曾经实现过牧师与魔鬼这个益智小游戏,但是一些小朋友玩这个游戏的时候可能有些困难(比如十年前在qq空间玩这个游戏的我),因此,我们可以开发一个AutoNext的功能,给小朋友提示一下下一步该怎么操作。
[Unity-3D] 牧师与魔鬼游戏之智能帮助

由于这次的智能设计比较简单,只有三个牧师和三个魔鬼,因此我们可以使用状态图来帮助分析游戏当前的状态,然后根据当前状态执行下一步正确操作即可。以下为状态图的表示,每一个状态记录在起始岸(右岸)上的牧师与魔鬼数。
[Unity-3D] 牧师与魔鬼游戏之智能帮助
需要注意的是,每一次箭头指出之后,船的位置会发生变化,也就是说同样的右岸上3人1鬼,船在左侧和船在右侧的操作是不一样的。即使是3人3鬼这样比较小的数据,其状态还是有很多种的(以至于我画流程图到2人2鬼的时候,实在受不了了,于是就把后面的其他过程省略了…)。然而这么多的状态,能成功的解法却很少,把游戏失败的状态以及多余的步骤去掉之后,我们就能得出这个游戏的正确解法流程。
[Unity-3D] 牧师与魔鬼游戏之智能帮助
如此一来,流程就变得简单多了,接下来就是用代码实现了。首先,当用户选择执行AutoNext操作时,需要判断当前游戏状态,然后执行最优解。举个栗子,如当前船在右岸,岸上3人2鬼,接下来的步骤可以是:

  • 1人1鬼上船,岸上剩2人1鬼;
  • 2人上船,岸上剩1人2鬼;
  • 2鬼上船,岸上剩3人;
  • 1人上船,岸上剩2人2鬼;
  • 1鬼上船,岸上剩3人1鬼;

从状态图中可以看出,无论岸上剩下1人2鬼还是2人1鬼(意味对岸是1人2鬼),游戏都会以失败结束,因此方法1和2是不可行的。其次,方法4和5虽然不会导致游戏失败,但从状态图中可看出方法4和5其实是一种回溯,也就是步骤重复,因此也不是最优解。那么只剩下方法3是可行的方案了。
具体实现如下:

Step 1. 在GenGameObject类(创建游戏对象和处理对象运动)中,定义枚举类型act,添加两个枚举型矩阵,分别记录船在左右两岸时不同的游戏状态下的下一步执行操作标记。使用矩阵(状态表)的目的是表达清晰,方便更改维护,且能减少逻辑判断复杂度。
act枚举说明:L(R)表示左岸(右岸)的人物进行上船操作,E表示魔鬼上船,H表示人类(牧师)上船,HE表示1人1鬼上船,HH表示2人上船,EE表示2鬼上船,x代表对应状态不存在或是该状态下游戏结束(失败或胜利)。
[贴一张状态表图片]

    // AI次状态表,以枚举型矩阵形式存储
    private enum act { LH, LE, LHE, RHE, RHH, REE , x};
    private act[,] matLeft = new act[4, 4] {{ act.x, act.LE, act.LE, act.x },
                                            { act.x, act.LHE, act.x, act.x },
                                            { act.x, act.x, act.LH, act.x },
                                            { act.LE, act.LE, act.LE, act.x }};
    private act[,] matRight = new act[4, 4] {{ act.x, act.x, act.REE, act.REE },
                                            { act.x, act.x, act.x, act.x },
                                            { act.x, act.x, act.RHH, act.x },
                                            { act.x, act.RHH, act.REE, act.RHE }};

Step 2. 同样在GenGameObject类中,设计自动执行最优解步骤的函数(仅上船操作)

    // 自动执行最优解步骤
    public void AutoAct()
    {
        int h = HumansOnRight.Count;
        int e = EvilsOnRight.Count;
        if (side == 0)
        {
            // 船在左侧,右岸增员
            act nextMove = matLeft[h, e];
            switch (nextMove)
            {
                case act.LE:
                    GetOn(EvilsOnLeft.Pop());
                    break;
                case act.LH:
                    GetOn(HumansOnLeft.Pop());
                    break;
                case act.LHE:
                    GetOn(HumansOnLeft.Pop());
                    GetOn(EvilsOnLeft.Pop());
                    break;
                default:
                    break;
            }
        }
        else if (side == 1)
        {
            // 船在右侧,右岸减员
            act nextMove = matRight[h, e];
            switch (nextMove)
            {
                case act.REE:
                    GetOn(EvilsOnRight.Pop());
                    GetOn(EvilsOnRight.Pop());
                    break;
                case act.RHH:
                    GetOn(HumansOnRight.Pop());
                    GetOn(HumansOnRight.Pop());
                    break;
                case act.RHE:
                    GetOn(EvilsOnRight.Pop());
                    GetOn(HumansOnRight.Pop());
                    break;
                default:
                    break;
            }
        }
    }

Step 3. 更改UI界面以及AutoNext的相应操作

if (GUI.Button(new Rect(x + width + 5f, (height + 5f) * 2 + y, width, height), "AutoNext"))
                {
                    // 先使船上的任务上岸,便于判断当前游戏状态
                    action.LeftOff();
                    action.RightOff();
                    // 选择最优解,相应人物上船
                    action.Auto();
                    action.BoatMove();
                    action.LeftOff();
                    action.RightOff();
                }

Step 4. 更新BaseCode类中的函数接口

以上,便是牧师与魔鬼小游戏的智能帮助实现,附上视频连接:牧师与魔鬼游戏之智能帮助