在之前的作业中,曾经实现过牧师与魔鬼这个益智小游戏,但是一些小朋友玩这个游戏的时候可能有些困难(比如十年前在qq空间玩这个游戏的我),因此,我们可以开发一个AutoNext的功能,给小朋友提示一下下一步该怎么操作。
由于这次的智能设计比较简单,只有三个牧师和三个魔鬼,因此我们可以使用状态图来帮助分析游戏当前的状态,然后根据当前状态执行下一步正确操作即可。以下为状态图的表示,每一个状态记录在起始岸(右岸)上的牧师与魔鬼数。
需要注意的是,每一次箭头指出之后,船的位置会发生变化,也就是说同样的右岸上3人1鬼,船在左侧和船在右侧的操作是不一样的。即使是3人3鬼这样比较小的数据,其状态还是有很多种的(以至于我画流程图到2人2鬼的时候,实在受不了了,于是就把后面的其他过程省略了…)。然而这么多的状态,能成功的解法却很少,把游戏失败的状态以及多余的步骤去掉之后,我们就能得出这个游戏的正确解法流程。
如此一来,流程就变得简单多了,接下来就是用代码实现了。首先,当用户选择执行AutoNext操作时,需要判断当前游戏状态,然后执行最优解。举个栗子,如当前船在右岸,岸上3人2鬼,接下来的步骤可以是:
从状态图中可以看出,无论岸上剩下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类中的函数接口
以上,便是牧师与魔鬼小游戏的智能帮助实现,附上视频连接:牧师与魔鬼游戏之智能帮助