P&D 过河游戏智能帮助实现

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

本次作业之所以选择做牧师与恶魔(P&D)的智能帮助,主要是我觉得用代码能够写出游戏中的状态图挺有意思。
接下来看看实现的效果,这个是3个牧师和3个恶魔的效果。
这里写图片描述
这个是5个牧师和4个恶魔的效果(为什么没有4个牧师和恶魔的效果呢?因为那无解!我也是用了算法算后才发现这种情况是无解的)
这里写图片描述

算法简述

接下来说说算法,算法主要就是先创建一个有向图,图的存储结构可以用邻接矩阵或者邻接表,这里我选择了后者,用两个链表List来实现。创建完图后,每次要提示就通过当前状态来找到在图中的位置,然后宽度优先搜索找到终点。看一下这条最短路径的起始点就可以得出在当前状态下,下一步应该走去哪一个状态。

因为不想自己画一个图而且代码里的图也不好展示出来,所以不要脸的引用了师兄博客(https://blog.csdn.net/kiloveyousmile/article/details/71727667)的图,这个图是3个牧师和3个恶魔时候的状态图。
这里写图片描述

节点类

然后就是实现这个图了,首先我先定义了一个状态节点类,这个节点中的P,D是指这个状态下左边的牧师和恶魔数量,其实右岸也行,不过我选择了左岸,这主要是在建图的时候要注意就好了,同时要注意到,船在不同的岸也是不同的状态。

public class node{
    //node存的是左岸的P,D
    public int P;
    public int D;
    public bool ifBoatSizeRight; //false left , true right
    public List<node> adjacentNodes;
    public node (node cpyNode){
        this.P = cpyNode.P;
        this.D = cpyNode.D;
        this.ifBoatSizeRight = cpyNode.ifBoatSizeRight;
        this.adjacentNodes = new List<node>(cpyNode.adjacentNodes);

    }

    public node(int P , int D , bool boatSize){
        this.P = P;
        this.D = D;
        this.ifBoatSizeRight = boatSize;
        adjacentNodes = new List<node> ();
    }

    public void addAdjacentNode(node newAdjacentNode){
        if (!ifNodeExitInList (newAdjacentNode , adjacentNodes)) {
            adjacentNodes.Add (newAdjacentNode);
        }
    }

    public static bool ifNodeExitInList(node findNode , List<node> searchList){
        foreach (node temp in searchList) {
            if (ifTwoNodeSame(temp , findNode) ) {
                return true;
            }
        }
        return false;
    }
    //两个statu等价,除了P和D要相等外,船也要在同一岸.
    public static bool ifTwoNodeSame(node first , node second){
        if (first.P == second.P && first.D == second.D
            && first.ifBoatSizeRight == second.ifBoatSizeRight) {
            return true;
        } else {
            return false;
        }

    }
    //到相接的点所用的操作
    public operation[] getOperationByAnthoerNode(){
        operation[] result = new operation[adjacentNodes.Count];
        for (int i = 0; i < adjacentNodes.Count; i++) {
            result [i] = new operation (Mathf.Abs(adjacentNodes [i].P - P),Mathf.Abs(adjacentNodes [i].D - D) );
        }
        return result;
    }

    public static node operator - (node first , operation second ){
        return new node (first.P - second.P, first.D - second.D , !first.ifBoatSizeRight);
    }

    public static node operator + (node first , operation second ){
        return new node (first.P + second.P, first.D + second.D ,  !first.ifBoatSizeRight);
    }
}

操作类

注意到刚才node的类里用到一个operation类,这个类是用来描述一个”操作”,例如P=1,D=1,的时候,意味着把一个牧师和一个恶魔放上船并且开船的操作。那么可以看到我重载了节点node和”操作”operation的+和-的操作符,这里是什么意义呢?其实一个节点加上一个”操作”,就意味着船把另一岸的P和D运到了这一岸,该节点的P和D增加了。那么同理,意味着的话就是把当前岸的P和D运到另一岸。在这个游戏中,”操作”主要有5种,就是1P,1D,1P1D,2P和2D。

public class operation {
    public int P;
    public int D;
    public operation(int P , int D){
        this.P = P;
        this.D = D;
    }

}

带权节点类

然后我还定义了一个带权节点类,在搜索的时候用,继承node,其中的length代表走到该节点的长度(事实上我后来并没有用到),然后startNode就代表着走到该节点时最开始的节点是哪个。

public class withLengthNode:node{
    public int length;
    public node startNode;
    public withLengthNode(node thisNode , int length , node startNode = null) :base(thisNode){
        this.length = length;
        this.startNode = startNode;
    }

    public static bool ifNodeExitInList(node findNode , List<withLengthNode> searchList){
        foreach (node temp in searchList) {
            if (node.ifTwoNodeSame(temp , findNode) ) {
                return true;
            }
        }
        return false;
    }
}

状态图类

接来下就是状态图这个类了,主要两个功能,建图和宽度优先找下一步,
建图就是先把初始点放进一个队列,然后进行五个操作,将合法且不在队列中的点再放入队列中,并互相相连,然后直到队列中的每一个点都已经进行过五次操作后,图就建好了。
而宽度优先找下一步也是很类似,也是用一个队列先把起始点放入,然后将邻接点都放入队列,直到找到终点。注意到只要状态合法,就一定能找到终点,返回值是一个“操作”operation,给场记来让人上船并开船。

public class statusGraph{
    private List<node> allGraphNodes;
    private operation[] nodeOperations = {new operation (0, 1) , new operation (1, 0) , 
        new operation (1, 1) , new operation (2, 0) , new operation (0, 2) };
    private int maxP;
    private int maxD;
    private bool boatStartSize ;
    private node endStatusNode ;

    public statusGraph(int maxP ,int maxD , bool boatStartSize){
        this.maxP = maxP;
        this.maxD = maxD;
        this.boatStartSize = boatStartSize;
        if (boatStartSize == true) {
            endStatusNode = new node (maxP, maxD, !boatStartSize);
        } else {
            endStatusNode =  new node (0, 0, !boatStartSize);
        }

        createGraph ();
    }


    private void createGraph(){
        allGraphNodes = new List<node> ();
        if (boatStartSize == true) {
            //开始船在右岸
            allGraphNodes.Add(new node(0, 0, boatStartSize));
        } else {
            //开始船在左岸
            allGraphNodes.Add(new node(maxP, maxD, boatStartSize));
        }

        for (int index = 0 ; index != allGraphNodes.Count ; index++) {
            node thisNode = allGraphNodes[index];
            foreach (operation op in nodeOperations) {

                node adjcentNode;
                if (thisNode.ifBoatSizeRight ) {
                    //当前船在右岸,由于node是左岸,因此船送人过来,node的P和D应该增加。
                    adjcentNode = thisNode + op;
                } else {
                    adjcentNode = thisNode - op;
                }

                node anotherSizeNode = getAnotherSizeNode (adjcentNode);
                //若两岸node都合法
                if (ifNodeValid (adjcentNode) && ifNodeValid(anotherSizeNode)  ) {
                    //若node在Graph中,node不加入graph
                    adjcentNode = getNodeFromList (adjcentNode);
                    //两个node互相接通(函数内会判断是否已有那个新的node,有就不加了)
                    adjcentNode.addAdjacentNode (thisNode);
                    thisNode.addAdjacentNode (adjcentNode);

                    /*Debug.Log (thisNode.P + " " + thisNode.D + " " + thisNode.ifBoatSizeRight +
                        "->" + adjcentNode.P + " " +  adjcentNode.D + " " + adjcentNode.ifBoatSizeRight );*/
                }

            }
        }
    }

    private bool ifNodeValid(node test){
        return (test.P >= test.D || test.P == 0) && test.D <= maxD && test.P <= maxP;
    }

    private node getNodeFromList(node findNode){
        foreach (node temp in allGraphNodes) {
            //两个statu等价,除了P和D要相等外,船也要在同一岸,list有node就返回node
            if (node.ifTwoNodeSame(temp , findNode) ) {
                return temp;
            }
        }
        //linkedList没有node,就把node加入里面 
        allGraphNodes.Add(findNode);
        return findNode;
    }
    //得到另一岸的node
    private node getAnotherSizeNode(node thisSizeNode){
        return new node(maxP - thisSizeNode.P 
            , maxD - thisSizeNode.D , !thisSizeNode.ifBoatSizeRight );
    }

    public operation getNextStep(node nowNode){
        node anotherSizeNode = getAnotherSizeNode (nowNode);//用当前node得到graph中的node
        if(ifNodeValid (nowNode) && ifNodeValid(anotherSizeNode) ){
            nowNode = getNodeFromList (nowNode);
            node nextNode = getStartNodeByWidthSearch (nowNode);
            if (nextNode == null) {
                return null;
            } else {
                return new operation (Mathf.Abs (nowNode.P - nextNode.P), Mathf.Abs (nowNode.D - nextNode.D));
            }
        }
        else{
            return null;
        }

    }

    private node getStartNodeByWidthSearch(node startNode){
        List<withLengthNode> alreadySearchNode = new List<withLengthNode>();
        //开始点已搜索
        alreadySearchNode.Add (new withLengthNode(startNode , 0) );
        //与开始点相接的点全部放入list中,起始点就是相接的node自身,也即当前下一步要走的地方
        foreach (node adjacentNode in startNode.adjacentNodes) {
            if (node.ifTwoNodeSame (adjacentNode, endStatusNode)) {
                return adjacentNode;
            } else {
                alreadySearchNode.Add (new withLengthNode (adjacentNode, 1, adjacentNode));
            }

        }

        for (int i = 1; i < alreadySearchNode.Count; i++) {
            //alreadySearchNode [i]是当前要搜索的点,将所有与它相接且不在list中的node都放到待搜索list中
            foreach (node adjacentNode in alreadySearchNode[i].adjacentNodes ) {
                //如果当前node的下一个node是目的点,返回node的开始点,也就是下一步要走的地方
                if (node.ifTwoNodeSame (adjacentNode, endStatusNode)) {
                    return alreadySearchNode[i].startNode;
                }
                //如果当前node的下一个node不是目的点,看它是否已被搜索,没被搜索就加入list
                else if (!withLengthNode.ifNodeExitInList (adjacentNode, alreadySearchNode)) {
                    alreadySearchNode.Add (new withLengthNode(adjacentNode , alreadySearchNode[i].length + 1 , alreadySearchNode[i].startNode ) );
                }

            }
        }
        return null;
    }
}

状态图的代码已经给完了,这个类最重要的作用就是提供一个得到下一步应该做什么的接口,然后写完这些,在原来的P&D的基础上稍做修改(之前写的不是很好,改起来也挺麻烦),点击提示按钮完成相应的动作即可,P&D的代码就不再详细讲述了。

一些想法

状态图(有限状态机)对于这些当前状态已知,操作较少,状态较少而且后果已知的游戏感觉还是挺好的,但假若当前状态未知,比如打牌是不知道对方有什么牌的,又或者执行一个操作可后果未知,比如放一个技能伤害不固定,还有生成出来的状态太多,这些情况下状态图可能就不太有效了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值