前言

这次实现一个含提示功能的牧师与恶魔过河小游戏,主要在上一个版本的牧师与恶魔小游戏上进行更改,通过增加一个状态计算和改版了得寻路算法,实现向玩家提示如何胜利完成游戏。游戏主体实现思路见上一篇博客——牧师与恶魔小游戏 动作分离版。
      游戏效果图如下:

状态设置

这里的状态是指右边岸上的牧师数目和恶魔数目,易知道牧师数目和恶魔数目的取值范围均是[0,3],即每个身份的数目取值可能个数为4,设P为牧师,D为恶魔,p为牧师数目,d为恶魔数目,则(p,d)的可能取值为4 * 4 = 16种,即(p,d)即为一个状态,例如(2,1)表示有两个牧师,一个魔鬼。而这个游戏,实质上是进行状态之间的转换,经过一系列的状态转换,使得状态最终从(3,3)变为(0,0)。
      接下来就是这些状态怎么表示的问题了。考虑到需要进行状态的转换,于是,用二维矩阵来表示,其中,每行的下标表示当前状态,列的取值表示能否转换至下一状态,上面已经说了有16中状态,所以,可用边长为16的二维矩阵表示。如下图:

在开始写代码之前,得明确以下两点:
二维矩阵下标和状态的转换关系: 已经确定状态表示为(p,d),其中,p和d的取值范围均为[0,3],在这里,用index代表数组下标,则,转换关系可为:

p = index / 4;
d = index % 4;

两个状态之间可转换的条件:
1. 由于船每次航行最多只能有两个人,最少必须有一个人,所以,船上人的状态可能为(1,1)、(1,0)、(0,1)、(2,0)、(0,2),而我们现在讨论的是右边岸上的状态,则根据船是离岸或上岸,右岸状态可能是加上或减去上述五个状态,而通过前面的式子,可以计算出,对应的下标变换为{ 5, 4, 1, 8, 2 }。
2. 两岸和船上牧师的总数必须为3;两岸和船上恶魔的总数必须为3。(这一点我一开始没有想到,花费了挺多时间来找bug。。。)
3. 下一状态中,当岸上有牧师时,岸上(含船)牧师的数目必须不少于恶魔的数目。在这里可以作个变换,当两边岸上都有牧师时,为了保持牧师的数目不少于恶魔的数目,则必须两岸上,牧师的数目等于恶魔的数目,而当其中一个岸边没有牧师时,则无需这个限制,所以,当pristNumber != 0 && pristNumber != 3 && pristNumber != devilNumber条件成立时,则不符合要求。
      根据上述的两个条件以及状态的表示方式,则可以将所有状态与其可转换的下一状态关联起来,形成一个二维关系矩阵,如下:

画成状态转换图,如下:

具体实现代码如下:

private bool[][] graph = new bool[16][];
//(P,D):(1,1)(1,0)(0,1)(2,0)(0,2)private int[] moveSteps = { 5, 4, 1, 8, 2 };//状态初始化public void init(){for(int i = 0; i < 16; i++){graph[i] = new bool[16];for (int j = 0; j < 16; j++)graph[i][j] = false;}for(int i = 0; i < 16; i++){for(int j = 0; j < 5; j++){int opp = 15 - i;if (i + moveSteps[j] >= 0 && i + moveSteps[j] < 16){if ((((opp - moveSteps[j]) % 4 + i % 4 + moveSteps[j] % 4) == 3) && (((opp - moveSteps[j]) / 4 + i / 4 + moveSteps[j] / 4) == 3))graph[i][i + moveSteps[j]] = true;}if (i - moveSteps[j] >= 0 && i - moveSteps[j] < 16){if ((opp % 4 + (i - moveSteps[j]) % 4 + moveSteps[j] % 4) == 3 && (opp / 4 + (i - moveSteps[j]) / 4 + moveSteps[j] / 4 == 3))graph[i][i - moveSteps[j]] = true;}}}for(int i = 0; i < 16; i++){//根据下一状态确定连线for(int j = 0; j < 16; j++){int pristNumber = j / 4;int devilNumber = j % 4;if(pristNumber != 0 && pristNumber != 3 && pristNumber != devilNumber){graph[i][j] = false;}}}}

寻找路径

在生成了状态转换图后,接下来的工作就是寻找从任意状态到终点状态的路径了,事实上,很多状态是不会发生的。本来以为寻找状态路径和寻找图的路径一样,后来才发现,由于船的位置,可能在右边,也可能在左边,便不能单纯地当图来处理了。在这个时候,假设船的位置与岸上的状态无关,则有32种状态,除了岸上本身的16中状态,再加上船的位置的2种状态。虽然船的位置在一定程度上和岸上状态有关联,但在这里进行简化处理,以32种状态进行计算。用一个二维数组来记录状态是否已被访问,其中,第一维表示岸上状态,第二维表示船的位置。然后,采用深度优先搜索算法,用一个ArrayList结构来模拟栈结构,记录路径。
主要注意两点:
1. 每次进入下一个状态,船的位置必须改变。而状态出栈的时候,也要记得将船的位置状态改变回来。
2. 船的位置不同,导致当前状态转换至下一状态的条件也发生改变。用current表示当前状态,next表示下一状态,则,当船在右边的时候,下一状态牧师和恶魔的数目肯定减少,current%4 <= next%4 且 current / 4 <= next / 4,即current < next;当船在左边时,下一状态牧师和恶魔的数目增加,current % 4 >= next %4 且 current / 4 >= next / 4, current > next。因此,可通过current值下标为分界线,根据船的不同位置,改变可访问的下一状态。
代码如下:

   public ArrayList findPath(int pristNumber, int devilNumber, int boatPosition){bool[][] visited = new bool[16][];for(int i = 0; i < 16; i++){visited[i] = new bool[]{ false, false};}ArrayList path = new ArrayList();int start = pristNumber * 4 + devilNumber;path.Add(start);while(true){//         Debug.Log("start" + start);bool hasChild = false;if (boatPosition == 1){visited[start][0] = true;//(start/4 >= i/4) && (start%4 >= i%4)for (int i = 0; i < start; i++){if(graph[start][i] && !visited[i][1]){path.Add(i);hasChild = true;//                     Debug.Log("here - right");break;}}}else{visited[start][1] = true;for(int i = start; i < 16; i++){if(graph[start][i] && !visited[i][0]){path.Add(i);hasChild = true;//                     Debug.Log("here - left");break;}}}if ((int)path[path.Count - 1] == 0) return path;boatPosition = -boatPosition;if(!hasChild) path.RemoveAt(path.Count - 1);start = (int)path[path.Count - 1];        }}

显示

在寻找出路径之后,需要的就是告诉玩家这个路径。当玩家点击提示按钮后,将路径用文字形式告诉玩家,至于是否选择该路径,由玩家决定。在controller类中增加tips()方法,用于传达当前右岸的状态和船的位置。然后,在ClickGUI类中,添加提示按钮,同时,负责将状态路径转换为用户看得懂的方式。主要思路是根据当前状态和下一状态,确定船当前的位置,很容易知道,当当前状态值大于下一状态值时,船在右边,否则,船在左边。而通过前面那两条状态值和(p,d)之间转换的式子,可以求出p和d的值。最后将结果显示出来即可,代码如下:

       //处理提示if(GUI.Button(new Rect(10, 10, 120, 50), "Tips", buttonStyle) && !showTips){ArrayList path = (Director.getInstance().currentSceneController as FirstController).tips();int stepCount = 0;if(path.Count == 1){pathStr += "No Tips";} else{for(int i = 0; i < path.Count - 1; i++){bool isBoatOnRight = (int)path[i] > (int)path[i + 1] ? true: false;int temp = isBoatOnRight ? (int)path[i] - (int)path[i + 1] : (int)path[i + 1] - (int)path[i];pathStr += "step_";pathStr += ++stepCount;//(P,D):(1,1)(1,0)(0,1)(2,0)(0,2)switch (temp){case 5:pathStr += ": (prist, devil) ";break;case 4:pathStr += ": (prist,     ) ";break;case 1:pathStr += ": (devil,     ) ";break;case 8:pathStr += ": (prist, prist) ";break;case 2:pathStr += ": (devil, devil) ";break; default:Debug.Log("something wrong!");break;}pathStr += isBoatOnRight ? "from right to left\n" : "from left to right\n"; }}showTips = true;}if (showTips){GUIStyle fontStyle = new GUIStyle("button");fontStyle.alignment = TextAnchor.MiddleCenter;fontStyle.fontSize = 15;fontStyle.normal.textColor = Color.white;if (GUI.Button(new Rect(50, 50, Screen.width - 100, Screen.height - 100), pathStr, fontStyle)){showTips = false;pathStr = "";}//          Debug.Log(pathStr);}

其他

演示视频
改进方法:

  1. 可改变状态的表示方法,例如用pair类型,看起来更舒服
  2. 可使用BFS+或者Dijkstra算法来求最短路径。但是在这个游戏中,用DFS即可,结果一样。

已更改的类的代码:

public class AItips  {private static AItips _instance;private bool[][] graph = new bool[16][];//(P,D):(1,1)(1,0)(0,1)(2,0)(0,2)private int[] moveSteps = { 5, 4, 1, 8, 2 };public static AItips GetInstance(){if(_instance == null){_instance = new AItips();}return _instance;}//状态初始化public void init(){for(int i = 0; i < 16; i++){graph[i] = new bool[16];for (int j = 0; j < 16; j++)graph[i][j] = false;}for(int i = 0; i < 16; i++){for(int j = 0; j < 5; j++){int opp = 15 - i;if (i + moveSteps[j] >= 0 && i + moveSteps[j] < 16){if ((((opp - moveSteps[j]) % 4 + i % 4 + moveSteps[j] % 4) == 3) && (((opp - moveSteps[j]) / 4 + i / 4 + moveSteps[j] / 4) == 3))graph[i][i + moveSteps[j]] = true;}if (i - moveSteps[j] >= 0 && i - moveSteps[j] < 16){if ((opp % 4 + (i - moveSteps[j]) % 4 + moveSteps[j] % 4) == 3 && (opp / 4 + (i - moveSteps[j]) / 4 + moveSteps[j] / 4 == 3))graph[i][i - moveSteps[j]] = true;}}}for(int i = 0; i < 16; i++){//根据下一状态确定连线for(int j = 0; j < 16; j++){int pristNumber = j / 4;int devilNumber = j % 4;if(pristNumber != 0 && pristNumber != 3 && pristNumber != devilNumber){graph[i][j] = false;}}}}public ArrayList findPath(int pristNumber, int devilNumber, int boatPosition){bool[][] visited = new bool[16][];for(int i = 0; i < 16; i++){visited[i] = new bool[]{ false, false};}ArrayList path = new ArrayList();int start = pristNumber * 4 + devilNumber;path.Add(start);while(true){//         Debug.Log("start" + start);bool hasChild = false;if (boatPosition == 1){visited[start][0] = true;//(start/4 >= i/4) && (start%4 >= i%4)for (int i = 0; i < start; i++){if(graph[start][i] && !visited[i][1]){path.Add(i);hasChild = true;//                     Debug.Log("here - right");break;}}}else{visited[start][1] = true;for(int i = start; i < 16; i++){if(graph[start][i] && !visited[i][0]){path.Add(i);hasChild = true;//                     Debug.Log("here - left");break;}}}if ((int)path[path.Count - 1] == 0) return path;boatPosition = -boatPosition;if(!hasChild) path.RemoveAt(path.Count - 1);start = (int)path[path.Count - 1];        }}
}
public class ClickGUI : MonoBehaviour {UserAction action;ChaController chac;bool showTips = false;string pathStr = "";//设置点击的人物public void setController(ChaController chaController){chac = chaController;}private void Start(){action = Director.getInstance().currentSceneController as UserAction;}//鼠标点击事件private void OnMouseUp(){try{action.isClickCha(chac);}catch(Exception e){Debug.Log(e.ToString());}finally{Debug.Log("Clicking:" + gameObject.name);}}private void OnGUI(){GUIStyle buttonStyle = new GUIStyle("button");buttonStyle.fontSize = 30;buttonStyle.normal.textColor = Color.blue;//开船事件。由于用鼠标点击的时候,总是点不中,就直接用button来了if ((Director.getInstance().currentSceneController as FirstController).isOver() == 0){if (GUI.Button(new Rect(Screen.width / 2 - 60, Screen.height / 2 - 25, 120, 50), "set sail", buttonStyle)){action.moveBoat();}}//处理提示if(GUI.Button(new Rect(10, 10, 120, 50), "Tips", buttonStyle) && !showTips){ArrayList path = (Director.getInstance().currentSceneController as FirstController).tips();int stepCount = 0;if(path.Count == 1){pathStr += "No Tips";} else{for(int i = 0; i < path.Count - 1; i++){bool isBoatOnRight = (int)path[i] > (int)path[i + 1] ? true: false;int temp = isBoatOnRight ? (int)path[i] - (int)path[i + 1] : (int)path[i + 1] - (int)path[i];pathStr += "step_";pathStr += ++stepCount;//(P,D):(1,1)(1,0)(0,1)(2,0)(0,2)switch (temp){case 5:pathStr += ": (prist, devil) ";break;case 4:pathStr += ": (prist,     ) ";break;case 1:pathStr += ": (devil,     ) ";break;case 8:pathStr += ": (prist, prist) ";break;case 2:pathStr += ": (devil, devil) ";break; default:Debug.Log("something wrong!");break;}pathStr += isBoatOnRight ? "from right to left\n" : "from left to right\n"; }}showTips = true;}if (showTips){GUIStyle fontStyle = new GUIStyle("button");fontStyle.alignment = TextAnchor.MiddleCenter;fontStyle.fontSize = 15;fontStyle.normal.textColor = Color.white;if (GUI.Button(new Rect(50, 50, Screen.width - 100, Screen.height - 100), pathStr, fontStyle)){showTips = false;pathStr = "";}//          Debug.Log(pathStr);}//加点儿游戏的文字说明GUIStyle gUIStyle = new GUIStyle();gUIStyle.fontSize = 30;gUIStyle.normal.textColor = Color.green;GUI.Label(new Rect(Screen.width / 2 - 150, 10, 300, 40), "Priests And Devils", gUIStyle);GUI.Label(new Rect(5, 80, 250, 300), "Priests and Devils is a " +"game in which you will help the Priests and Devils to cross the river." +" There are 3 priests and 3 devils at one side of the river." +" They all want to get to the other side of this river, " +"but there is only one boat and this boat can only carry two persons each time." +" And there must be one person steering the boat from one side to the other side." +" In this game, you can click on them to move them and click the button to move the boat to the other direction. " +"If the priests are out numbered by the devils on either side of the river, they get killed and the game is over." +" You can try it in many ways. Keep all priests alive! Good luck!");}
}

在FirstController类中,只需加一个tips()函数。

    public ArrayList tips(){int pristNumber = 0;int devilNumber = 0;//获取右边对应的人数int[] fromCount = fromLand.getChaNum();pristNumber += fromCount[0];devilNumber += fromCount[1];//将船上人的数目也加上去int[] boatCount = boat.getChaNum();if (boat.getOnWhere() == 1){pristNumber += boatCount[0];devilNumber += boatCount[1];}return ai.findPath(pristNumber, devilNumber, boat.getOnWhere());}

牧师与恶魔过河游戏——智能提示相关推荐

  1. 3D游戏编程与设计 PD(牧师与恶魔)过河游戏智能帮助实现

    3D游戏编程与设计 P&D(牧师与恶魔) 过河游戏智能帮助实现 文章目录 3D游戏编程与设计 P&D(牧师与恶魔) 过河游戏智能帮助实现 一.作业与练习 二.设计简述 1. 状态图基础 ...

  2. 【Unity3d学习】魔鬼与牧师过河游戏智能帮助

    文章目录 写在前面 实验内容 状态图自动生成(使用DFS) 1. 状态表示 2.DFS算法实现 3.DFS生成结果 更改Controller 效果展示 写在前面 本次项目Github地址:传送门 本次 ...

  3. Unity3d入门之路-PD 过河游戏智能帮助

    文章目录 P&D 过河游戏智能帮助 状态图 实现方法 图的表示方法 广度优先搜索 P&D 过河游戏拓展 结果展示 P&D 过河游戏智能帮助 本次作业基本要求是三选一,我选择了P ...

  4. Unity学习之PD 过河游戏智能帮助实现

    Unity学习之P&D 过河游戏智能帮助实现 根据之前设计好的动作分离版过河游戏,我们进行一个简单的状态图AI实现. 转移状态图 状态图老师已经给出: 该状态图只记录了游戏过程中左岸的情况.P ...

  5. 【Unity 3D学习笔记】PD 过河游戏智能实现

    P&D 过河游戏智能帮助实现 实现状态图的自动生成 讲解图数据在程序中的表示方法 利用算法实现下一步的计算 对于过河游戏,首先需要知道其中各个状态之间的转换关系,绘制状态转移图如下: 其中,P ...

  6. unity:PD 过河游戏智能帮助实现

    P&D 过河游戏智能帮助实现 github传送门 状态图 状态图课件有 状态图(Statechart Diagram)是描述一个实体基于事件反应的动态行为,显示了该实体如何根据当前所处的状态对 ...

  7. 3D游戏编程实践——PD 过河游戏智能帮助实现

    P&D 过河游戏智能帮助实现 需求 实现状态图的自动生成 讲解图数据在程序中的表示方法 利用算法实现下一步的计算 实现过程 实现状态图的自动生成&讲解图数据在程序中的表示方法 牧师与魔 ...

  8. PD 过河游戏智能帮助实现

    github传送门 https://github.com/ddghost/unity3d_n/tree/R%26D%E6%99%BA%E8%83%BD%E5%B8%AE%E5%8A%A9%E6%8F% ...

  9. 3D-太阳系、牧师与恶魔小游戏

    1.简单并用程序验证 游戏对象运动的本质是什么? 本质上是游戏对象坐标的变换 使用三种方法实现物体的抛物线运动 Vector3.MoveTowards private float step = Tim ...

最新文章

  1. Docker for mac安装
  2. python的数字比较好_说说 Python3 中的数字处理
  3. IOS 中runtime 不可变数组__NSArray0 和__NSArrayI
  4. redis live 如何安装
  5. [LeetCode] Longest Consecutive Sequence 求解
  6. Bose 700无线消噪耳机评测:让用户不受打扰是它最大的温柔
  7. python和c先学哪个-python和c先学哪个
  8. python中文字体奇怪_利用python检查 AS400的中文字问题
  9. Nginx源码分析 - 主流程篇 - 解析配置文件(13)
  10. Git之路——配置SSH免密码登录
  11. 1.3 收敛数列的性质
  12. win10 SystemParametersInfo 设置屏保 不好使_抖音网红屏保时钟软件(附使用教程)...
  13. OHS简单安装与系统配置
  14. Eclipse执行junit测试时出现Errors occurred during the build. Errors running builder 'Integrated External Too
  15. 麦咖啡导致电脑不能上网
  16. 全国天气预报api接口
  17. mac打开chm文件
  18. php开发的app商城,如何利用PHP语言开发手机APP
  19. 分布式服务器时间同步
  20. Android 7.0 GMS测试 Camera模块CTS fail项分析

热门文章

  1. linux用ftp上传文件
  2. php制作普通网站流程图,分享在线制作流程图的网页
  3. badatatable转成json_datatable转换为json
  4. (PAT)统计给定区间内的三位数中有两位数字相同的完全平方数(如144、676)的个数
  5. Pytorch环境部署
  6. 国产Linux操作系统(深度系统)增加了微软Microsoft Edge浏览器(Linux版本)
  7. 北大青鸟的班主任好当吗_班主任老师工作真不容易
  8. 电脑如何设置微信里打印准考证
  9. 音符起始点检测(音频节奏检测)(2)
  10. ✠OpenGL-5-纹理贴图