游戏智能

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

  • 实现状态图的自动生成
  • 讲解图数据在程序中的表示方法
  • 利用算法实现下一步的计算

游戏状态图

参考博客中的游戏状态转化图,用“左岸牧师数、左岸恶魔数、船的位置(左/右)”即可以完整地表示游戏所处的状态。

  • 注意,我们不关心此刻牧师/恶魔在船上还是在岸上,船靠左,则船上的人都记为左岸;船靠右,则船上的人都记为右岸

如下图所示,箭头表示当处于这个状态时,需要转移到的下一个状态。这样的转移需要逐步接近成功状态,而避免进入错误状态。
在这里插入图片描述

代码实现

获取下一个状态

编写一个ai文件,实现一个函数以获取下一状态

  • 参数:输入当前状态“左岸牧师数、左岸恶魔数、船的位置(左/右)”

  • 返回:根据状态转移图,获得下一状态。返回值为,从船所在的岸 转移到 另一岸 的牧师数量和恶魔数量,用next[2]记录。

    • 例如下图的这个状态转移过程,需要转移的牧师数量为1,需要转移的恶魔数量为1
    • 不需要记录船是从左到右还是从右到左,因为每次状态转移都包含了船的一次转移过程:船载着人到达另一岸
      在这里插入图片描述
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace myGame {
    public class AIcontroller : MonoBehaviour {

        public int[] getNextAction(int curNumDevilLeft, int curNumPriestLeft, int curBoatPos)
        {
            //next[0]  从 岸 转移到 另一岸 的牧师数
            //next[1]  从 岸 转移到 另一岸 的恶魔数
            int[] next = new int[2];

            int P = curNumPriestLeft, D = curNumDevilLeft, B = curBoatPos;  //1:左边 2:右边
            if (P == 3 && D == 3 && B == 1)
            {
                next[0] = 1;
                next[1] = 1;
            }
            else if(P == 3 && D == 2 && B == 2)
            {
                next[0] = 0;
                next[1] = 1;
            }
            else if(P == 2 && D == 2 && B == 2)
            {
                next[0] = 1;
                next[1] = 0;
            }
            else if(P == 3 && D == 1 && B == 2)
            {
                next[0] = 0;
                next[1] = 1;
            }
            else if(P == 3 && D == 2 && B == 1)
            {
                next[0] = 0;
                next[1] = 2;
            }
            else if(P == 3 && D == 0 && B == 2)
            {
                next[0] = 0;
                next[1] = 1;
            }
            else if(P == 3 && D == 1 && B == 1)
            {
                next[0] = 2;
                next[1] = 0;
            }
            else if(P == 1 && D == 1 && B == 2)
            {
                next[0] = 1;
                next[1] = 1;
            }
            else if(P == 2 && D == 2 && B == 1)
            {
                next[0] = 2;
                next[1] = 0;
            }
            else if(P == 0 && D == 2 && B == 2)
            {
                next[0] = 0;
                next[1] = 1;
            }
            else if(P == 0 && D == 3 && B == 1)
            {
                next[0] = 0;
                next[1] = 2;
            }
            else if(P == 0 && D == 1 && B == 2)
            {
                next[0] = 0;
                next[1] = 1;
            }
            else if(P == 2 && D == 1 && B == 1)
            {
                next[0] = 2;
                next[1] = 0;
            }
            else if(P == 1 && D == 1 && B == 1)
            {
                next[0] = 1;
                next[1] = 1;
            }
            else if(P == 0 && D == 2 && B == 1)
            {
                next[0] = 0;
                next[1] = 2;
            }
            return next;
        }

	    // Use this for initialization
	    void Start () {
		
	    }
	
	    // Update is called once per frame
	    void Update () {
		
	    }
    }
 }
FirstController

FirstController中需要处理的任务如下:

获取当前状态

统计在左边的牧师/恶魔数量和船的位置。

        //得到当前状态
        //计算所有在左边的牧师/恶魔
        int numDevilLeft = leftBank.getNumDevil(), numPriestLeft = leftBank.getNumPriest();  
        int numDevilBoat = boat.getNumDevil(), numPriestBoat = boat.getNumPriest();
        if (boat.getState() == 1)
        {
            numDevilLeft += numDevilBoat;
            numPriestLeft += numPriestBoat;
        }
        Debug.Log("numDevilLeft " + numDevilLeft);
        Debug.Log("numPriestLeft " + numPriestLeft);
        int boatPos = boat.getState();
获取下一状态

通过调用ai得到需要转移的牧师/恶魔数

//获取下一个状态
        int[] next = ai.getNextAction(numDevilLeft, numPriestLeft, boat.getState());
        Debug.Log("next[0] " + next[0]);
        Debug.Log("next[1] " + next[1]);
移动牧师/恶魔

需要转移的牧师/恶魔,就是要放在船上的人。但是由于当前玩家的动作,使得船上可能已经有了一些牧师/恶魔。

此时想要使“点击”次数最少,遵循以下逻辑:

numDveilToBoat = 0, numPriestToBoat = 0 表示已经完成的在船上的人数,目标是和next一样
先boat -> 岸
         第一个是P
            next[0] > 0 留着 numPriestToBoat++
            next[0] = 0 放回去
         第一个是D 
            next[1] 同上 numDveilToBoat++
         第二个是P
            next[0] = 2 且 numPriestToBoat = 1 留着
            next[0] = 1 放回去
         第二个是D
            同上
再 岸 -> boat
        看一下 num和next没满足的,放岸上的去船上即可

移动的过程只要调用之前实现好的clickACharacter函数即可。

		int numPriestToBoat = 0, numDevilToBoat = 0;
        int[] personOnBoat = boat.getPersonOnBoat();
        if (personOnBoat[0] >= 3 && personOnBoat[0] <= 6)   //第一个是P
        {
            if (next[0] > 0)           //P保持不动
                numPriestToBoat++;
            else if(next[0] == 0)      //把这个P放去岸上
            {
                clickACharacter(characters[personOnBoat[0]]);
            }
        }
        else if(personOnBoat[0] >= 0 && personOnBoat[0] <= 2)  //第一个是D
        {
            if (next[1] > 0)
                numDevilToBoat++;
            else if(next[1] == 0)
            {
                clickACharacter(characters[personOnBoat[0]]);
            }
        }
        
        if (personOnBoat[1] >= 3 && personOnBoat[1] <= 6)        //第二个是P
        {
            if (next[0] == 2 || (next[0] == 1 && numPriestToBoat == 0))    //P不用动
                numPriestToBoat++;
            else if (next[0] == 0 || (next[0] == 1 && numPriestToBoat == 1))   //把这个P放去岸上
            {
                clickACharacter(characters[personOnBoat[1]]);
            }
        }
        else if (personOnBoat[1] >= 0 && personOnBoat[1] <= 2)     //第二个是D
        {
            if (next[1] == 2 || (next[1] == 1 && numDevilToBoat == 0))
                numDevilToBoat++;
            else if(next[1] == 0 || (next[1] == 1 && numDevilToBoat == 1))
            {
                clickACharacter(characters[personOnBoat[1]]);
            } 
        }

        /*
         岸 -> 船
         */
        for (int i = 0; i < next[0] - numPriestToBoat; i++)            // P
        {
            int index = -1;
            if (boatPos == 1)
                index = leftBank.getAPriestIndex();
            else if (boatPos == 2)
                index = rightBank.getAPriestIndex();

            clickACharacter(characters[index]);
        }
        for(int i = 0; i < next[1] - numDevilToBoat; i++)            // P
        {
            int index = -1;
            if (boatPos == 1)
                index = leftBank.getADevilIndex();
            else if (boatPos == 2)
                index = rightBank.getADevilIndex();
              
            clickACharacter(characters[index]);
        }

调用的小函数再在 BoatController 和 CharacterController 中实现。

移动船

由于移动Character的过程是运动的,需要花费一定时间完成。如果马上调用moveBoat函数,则角色还没有到船上就过河了。

使用延迟函数Invoke,在延迟0.6s后再调用moveBoat函数

//移动船
        Invoke("moveBoat", (float)0.6);
增加按钮

增加一个"NextAction"按钮。

在接口中增加函数

//玩家与游戏交互的接口(玩家的所有操作)
    public interface IUserAction
    {
        void moveBoat();
        void clickACharacter(CharactersController character);
        void restart();
        void NextActionAI();
    }

userGUI在页面上显示按钮

if (GUI.Button(new Rect(Screen.width / 2, 240, 170, 30), "Tips:NextAction"))
            {
                action.NextActionAI();
            }

实验结果

演示视频 14s之后使用“NextAction”按钮
github传送门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值