游戏智能


作业要求

P&D 过河游戏智能帮助实现,程序具体要求:

  • 实现状态图的自动生成
  • 讲解图数据在程序中的表示方法
  • 利用算法实现下一步的计算
  • 参考:P&D 过河游戏智能帮助实现

实现过程

首先我们对这个过河问题进行一下简单的分析。游戏中的每一个状态可以由当前左右两边各有多少个恶魔和牧师,以及现在船在哪一边来唯一的标识。而且在保证每一边恶魔数量不多于牧师,船上人数大于0小于等于2的情况下,不同状态之间存在相互转移关系。所以整个游戏的运行过程可以表示为一个图的形式,而且这个图是无向有环的,因为两个相连的状态必然能够相互转换,而且经过一段时间的变换可能会回到之前的状态。那么整个问题就变为在状态转移图上寻找一条从起始状态(3个恶魔和3个牧师都在右边)到结束状态(3个恶魔和3个牧师都在左边)的路径。所以整个算法实际上就是一个图搜索算法,这里我们可以使用深度优先搜索。
为了方便调试,我首先写了一个C++的模拟程序。这里使用一个三元组(ld, lp, side)ld:左边恶魔数、lp:左边牧师数、side:船在哪一边,这三个信息唯一的标识一个状态。使用一个visited数组记录当前是否展开过某个状态,这样可以防止搜索过程中重复展开已经展开过的状态。使用一个search函数递归的进行搜索,然后返回一个记录了可行路径的栈。详细程序如下:

#include <bits/stdc++.h>
using namespace std;bool visited[4][4][2]; //标志一个状态是否被展开过 bool is_valid(int ld, int lp){ //判断一个状态是否违反规则(某一边恶魔大于牧师) if((lp!=0 && ld > lp) || ((3-lp!=0) && (3-ld)> (3-lp)))return false;return true;
}stack<pair<int, int> > search(int ld, int lp, int side){ //输入一个状态,进行寻路 visited[ld][lp][side] = true;stack<pair<int, int> > path;if(ld==3 && lp==3){  //结束状态,用全0标记 path.push(pair<int, int>(0,0));return path;}int D = (side==0)?ld:(3-ld);int P = (side==0)?lp:(3-lp);int next_side = (side==0)?1:0;for(int d = 0; d <= D; d++){for(int p = 0; p <= P; p++){if(d+p>2 || d+p==0)continue;int temp_ld = (side==0)?ld-d:ld+d;int temp_lp = (side==0)?lp-p:lp+p;if(!is_valid(temp_ld, temp_lp))continue; //检查变换之后是否违反规则 if(visited[temp_ld][temp_lp][next_side])continue; //检测变换之后的状态是否已经展开过 printf("(%d, %d, %d)->(%d, %d, %d)\n",ld,lp,side,temp_ld,temp_lp,next_side);path = search(temp_ld, temp_lp, next_side); //递归查找一条路径 if(path.size()!=0){ //如果返回结果为空,说明是死路 path.push(pair<int,int>(d,p));return path;}}}return path;
}int main(){memset(visited, false, sizeof(visited));stack<pair<int, int> > path;path = search(0,0,1); //左边没有Devil和Priest,船在右边作为起始状态 int curr_side = 1;while(path.size()!=0){pair<int, int> move = path.top();path.pop();curr_side = (curr_side==1)?0:1;if(move.first==0 && move.second==0){cout<<"success!"<<endl; break;}printf("move %d Devils and %d Priests ", move.first,move.second);string move_str = (curr_side==0)?"from right to left":"from left to right";cout<<move_str<<endl;}
}

程序运行结果

程序运行之后显示了从(0,0,1)到(3,3,0)状态,也就是从起始状态到结束状态的搜索路径。最后输出了一系列的移动策略。

状态转化图

将上面程序输出的状态转移信息放到一个状态转换图中,就得到了以下结果:

上图并不是完整的状态转移关系,准确来说只是深度优先搜索过程中的状态转移关系。这个状态图显示了一条从起始状态到结束状态的路径,以及搜索过程中展开的一些死路。在展开过程中我没有展开那些违反规则(GameOver)的状态,也不会展开那些曾经展开过的状态,所以整个状态转移图就非常简单。
当然,开始状态不一定是(0,0,1),我们可以从任何合法的状态起步。由于整个状态转移图是全联通的,所以我们必然能够找到一个到达结束状态的路径。(这里可以简单证明一下,我们选择的开始状态必然是从(0,0,1)转换来的,所以存在到达(0,0,1)的路径,而(0,0,1)存在到达(3,3,0)的路径,所以这个问题一定有解)

实现代码

接下来的任务就是将这一段代码结合到之前的代码中,从而实现自动运行的功能。C#中的库和C++有些区别,我使用C#的Stack代替C++的stack,C#的KeyValuePair代替C++的pair。为了使整个移动效果依次进行,而不是计算出了路径一下就运行完了,我在update函数中每隔一段时间调用一下nextstep,然后nextstep会从栈顶得到当前路径并进行移动,其他部分的实现大体没变。

public class Controller : MonoBehaviour, ISceneController, IUserAction
{private GameObject left_land, right_land, river;private CharacterModel[] MCharacter;  private BoatModel MBoat;private bool[,,] visited;public CCActionManager actionManager;public CCJudgement judgement;Stack<KeyValuePair<int, int>> path;bool auto_mode;float spendTime;// Start is called before the first frame updatevoid Awake(){SSDirector director = SSDirector.getInstance();director.currentSceneController = this;MCharacter = new CharacterModel[6];director.currentSceneController.LoadResources();actionManager = gameObject.AddComponent<CCActionManager>() as CCActionManager;visited = new bool[4,4,2];auto_mode = false;}public void LoadResources(){Vector3 left_land_pos = new Vector3(-8F, 0, 0);Vector3 right_land_pos = new Vector3(8F, 0, 0);Vector3 river_pos = new Vector3(0, -0.5F, 0);Vector3 boat_pos = new Vector3(4F, 0.25F, 0);Vector3[] charactor_pos = { new Vector3(5.25F, 1.25F, 0), new Vector3(6.25F, 1.25F, 0), new Vector3(7.25F, 1.25F, 0),new Vector3(8.25F, 1.25F, 0), new Vector3(9.25F, 1.25F, 0), new Vector3(10.25F, 1.25F, 0) };left_land = Object.Instantiate(Resources.Load("Land", typeof(GameObject)), left_land_pos, Quaternion.identity, null) as GameObject;left_land.name = "left_land";right_land = Object.Instantiate(Resources.Load("Land", typeof(GameObject)), right_land_pos, Quaternion.identity, null) as GameObject;right_land.name = "right_land";river = Object.Instantiate(Resources.Load("River", typeof(GameObject)), river_pos, Quaternion.identity, null) as GameObject;river.name = "river";MBoat = new BoatModel(boat_pos);for (int i = 0; i < 6; i++){if (i < 3)MCharacter[i] = new CharacterModel(0, charactor_pos[i], i);elseMCharacter[i] = new CharacterModel(1, charactor_pos[i], i);}}public int get_character_side(int num){return MCharacter[num].get_side();}public void change_game_situation(){UserGUI.situation = judgement.GetSituation();if (UserGUI.situation != 0) stop_all();}public void stop_all(){for (int i = 0; i < 6; i++)MCharacter[i].stop_character();MBoat.stop_boat();}public void enable_all(){for (int i = 0; i < 6; i++)MCharacter[i].enable_character();MBoat.enable_boat();}bool is_valid(int ld, int lp){ //判断一个状态是否违反规则(某一边恶魔大于牧师) if ((lp != 0 && ld > lp) || ((3 - lp != 0) && (3 - ld) > (3 - lp))) return false;return true;}Stack<KeyValuePair<int, int> > search(int ld, int lp, int side){ //输入一个状态,进行寻路 visited[ld,lp,side] = true;Stack<KeyValuePair<int, int> > path = new Stack<KeyValuePair<int, int>>();if (ld == 3 && lp == 3){  //结束状态,用全0标记 path.Push(new KeyValuePair<int, int>(0, 0));return path;}int D = (side == 0) ? ld : (3 - ld);int P = (side == 0) ? lp : (3 - lp);int next_side = (side == 0) ? 1 : 0;for (int d = 0; d <= D; d++){for (int p = 0; p <= P; p++){if (d + p > 2 || d + p == 0) continue;int temp_ld = (side == 0) ? ld - d : ld + d;int temp_lp = (side == 0) ? lp - p : lp + p;if (!is_valid(temp_ld, temp_lp)) continue; //检查变换之后是否违反规则 if (visited[temp_ld,temp_lp,next_side]) continue; //检测变换之后的状态是否已经展开过 //Debug.Log("(%d, %d, %d)->(%d, %d, %d)\n", ld, lp, side, temp_ld, temp_lp, next_side);path = search(temp_ld, temp_lp, next_side); //递归查找一条路径 if (path.Count != 0){ //如果返回结果为空,说明是死路 path.Push(new KeyValuePair<int, int>(d, p));return path;}}}return path;}public void auto_move(){for (int i = 0; i < 4; i++)for (int j = 0; j < 4; j++)for (int k = 0; k < 2; k++)visited[i, j, k] = false;int left_d = 0, left_p = 0, right_d = 0, right_p = 0;for (int i = 0; i < 6; i++){if (i < 3){if (get_character_side(i) == -1)right_d += 1;elseleft_d += 1;}else{if (get_character_side(i) == -1)right_p += 1;elseleft_p += 1;}}int side = (MBoat.get_side()==-1)?1:0;path = search(left_d, left_p, side);auto_mode = true;this.stop_all();} void next_step(){if (path.Count == 0) return;int side = MBoat.get_side();KeyValuePair<int, int> move = path.Pop();if (move.Key == 0 && move.Value == 0){Debug.Log("success");return;}string output = "move " + (move.Key).ToString() + " Devils and " + (move.Value).ToString() + " Priests ";string move_str = (side == 0) ? "from right to left" : "from left to right";output += move_str;Debug.Log(output);for(int i = 0; i < 6; i++){if (MCharacter[i].get_whether_on_boat()){to_land(i);}}int curr_d = 0, curr_p = 0;for(int i = 0; i < 6; i++){if(MCharacter[i].get_side() == side){if(i < 3 && curr_d < move.Key){take_boat(i);curr_d+=1;}if(i >= 3 && curr_p < move.Value){take_boat(i);curr_p += 1;}}}move_boat();}public void move_boat(){Debug.Log("Move boat");if (MBoat.is_empty())return;MBoat.turn_side();int[] custom_num = MBoat.get_customs();if (custom_num[0] != -1){MCharacter[custom_num[0]].turn_side();actionManager.Move(MCharacter[custom_num[0]].character, MCharacter[custom_num[0]].get_dst(), 20);}if (custom_num[1] != -1){MCharacter[custom_num[1]].turn_side();actionManager.Move(MCharacter[custom_num[1]].character, MCharacter[custom_num[1]].get_dst(), 20);}actionManager.Move(MBoat.boat, MBoat.get_dst(), 20);this.stop_all();Debug.Log(UserGUI.situation);}public void click_character(int character_num){if (MCharacter[character_num].get_whether_on_boat())to_land(character_num);elsetake_boat(character_num);}public void take_boat(int character_num){if (!MBoat.has_empty() || MCharacter[character_num].get_side() != MBoat.get_side()||MCharacter[character_num].get_whether_on_boat())return;Vector3 boat_seat = MBoat.get_seat(character_num);MCharacter[character_num].take_boat(boat_seat);}public void to_land(int character_num){if (!MCharacter[character_num].get_whether_on_boat())return;MBoat.clear_seat(character_num);MCharacter[character_num].to_land();}// Update is called once per framevoid Update(){if (!auto_mode) return;spendTime += Time.deltaTime;if(spendTime > 1.5){spendTime = 0;next_step();}}public void restart(){MBoat.restart();auto_mode = false;for (int i = 0; i < 6; i++)MCharacter[i].restart();enable_all();}
}

运行效果


点击Auto Move按钮之后,程序就会根据当前状态计算出一条可以达到最终状态的运动路径,并自动控制角色的上下船和船的左右移动。
完整工程文件请查看我的Github,如果有什么问题请及时指出,谢谢。

unity3D学习9 游戏智能相关推荐

  1. Unity3D学习——射箭游戏(工厂模式)

    成品展示 每两秒会获得一个稳定风速,玩家可以把握住这个瞬间射箭,击中不同环得分不同,不击中不扣分,60s内够100分则获胜 游戏制作 脚本挂载与预制 箭靶子预制 在一个空对象上挂载五个扁平同心圆柱,设 ...

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

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

  3. Unity3D学习:结合Kinect进行游戏开发 | 孤舟博客

    最近需要学习Unity3D和Kinect交互进行开发.查阅网上的资料,一直没有找到详尽而又简单的方案.今天终于摸索出来在Unity3D中使用Kinect的方法,特此做个笔记. 一.Unity和Kine ...

  4. Unity3D 学习笔记4 —— UGUI+uLua游戏框架

    Unity3D 学习笔记4 -- UGUI+uLua游戏框架 使用到的资料下载地址以及基础知识 框架讲解 拓展热更过程 在这里我们使用的是uLua/cstolua技术空间所以提供的UGUI+uLua的 ...

  5. 【Unity3d学习】使用物理引擎——打飞碟游戏的物理引擎改进与射箭游戏设计

    文章目录 写在前面 HitUFO的物理引擎改进版本 物理引擎的改进版本思路与实现 PhysicsAction PhysicsManager 新接口类IActionManager 动作管理器基类的变化 ...

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

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

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

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

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

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

  9. 深度揭秘强化学习技术与落地!智源大会「强化学习与决策智能」专题论坛

    决策智能是国家新一代人工智能的重要发展方向,强化学习是实现决策智能的核心技术之一.在强化学习中,智能体与环境进行不断的交互,基于环境的反馈学习如何选择一系列动作,以使长期累积的奖励和最大.近年来,该方 ...

最新文章

  1. mysql 用户 多主机_MySQL单主机多实例部署
  2. mysql 授权管理
  3. properties文件如何注解多行加#
  4. SAP GUI 遇到 Error in Parser-Thread 错误的解决方法
  5. ubuntu上的wordpress安装
  6. java高可用grpc_GRPC java 分布式调用链跟踪实践
  7. python 文件按行读写
  8. 转载浅谈MFC内存泄露检测及内存越界访问保护机制
  9. AJAX实例二:实现类似Google的搜索提示【原著】
  10. 【UNITY3D 游戏开发之三】NGUI HUDTEXT 的练习源码及资源
  11. ghost还原固态硬盘_解决方法:SSD可以使用Ghost软件吗?最后说清楚了
  12. TeamTalk源码分析(一)—— TeamTalk介绍
  13. COOC6.2增加同义词合并无意义词删除等功能
  14. pacman下载时经常出现Operation too slow. Less than 1 bytes/sec transferred the last 10 seconds
  15. shell 亚瑟王环
  16. linu系统中dhcp的搭建
  17. Apple Music预告登场 暗示AirPod 3新款耳机发售?
  18. 搞懂这 9 个步骤,DNS 访问原理就明明白白了
  19. 对于“条件竞争”的利用
  20. linux升级内核后vnc显示没有桌面,Intel NUC(NUC6i3SYH)在不接显示器的情况下VNC不显示桌面(Ubuntu 18.04)...

热门文章

  1. 用java实现combin函数_【算法-Java实现】组合总和
  2. API接口安全—webservice、Swagger、WEBpack
  3. 徐良汪苏泷解约恐赔千万 网友称:冰三尺非一日寒
  4. Python 循环语句和字符串内置函数
  5. 奶牛专题1:圆圈舞蹈
  6. jsp页面添加视频播放
  7. 测试做得好,犯错少不了【30个最容易犯的错误】谨记
  8. Openstack kvm win7镜像制作(转)
  9. php sprintf 小数,php sprintf函数
  10. 深度学习与计算机视觉