当前位置: 首页 > 知识库问答 >
问题:

NIM游戏和AI玩家使用Minimax算法-AI做出失败的动作

宋腾
2023-03-14

我有一个任务,要写一个有人类玩家和人工智能玩家的NIM游戏。游戏是玩“Misere”(最后一个必须拿起棍子的人输了)。人工智能应该使用极小极大算法,但它的动作让它输得更快,我不知道为什么。我已经陷入死胡同好几天了。极小极大算法的目的是不输,如果它处于输的位置,尽可能推迟输的动作,对吗?

考虑以下因素:

NIMBoard board=新NIMBoard(34,2);

  • 34=杆的二进制编码位置,2堆2杆
  • 2=桩的数量

所以我们从这个场景开始,这个*字符代表一根棍子:

Row 0: **
Row 1: **

在这种特殊的电路板情况下,Minimax算法总是会提出“从第1行移除2根棍子”的动作。这显然是一个糟糕的举动,因为它在第0行留下了2根棍子,人类玩家可以从第0行中拾取1根棍子并赢得比赛。

AI玩家应该选择从任何一堆中选择一根棍子。这留给人类玩家:

Row 0: *
Row 1: **

所以无论人类玩家现在做出哪一步棋,当计算机在这之后做出下一步棋时,人类玩家总是会输。明明是更好的策略,但为什么算法没有建议这一步棋?

public class Minimax
{

    public Move nextMove;

    public int evaluateComputerMove(NIMBoard board, int depth)
    {
        int maxValue = -2;
        int calculated;
        if(board.isFinal())
        {
            return -1;
        }
        for(Move n : this.generateSuccessors(board))
        {
            NIMBoard newBoard = new NIMBoard(board.getPos(), board.getNumPiles());
            newBoard.parseMove(n);
            calculated = this.evaluateHumanMove(newBoard, depth + 1);
            if(calculated > maxValue)
            {
                maxValue = calculated;
                if(depth == 0)
                {
                    System.out.println("Setting next move");
                    this.nextMove = n;
                }
            }

        }

        if(maxValue == -2)
        {
            return 0;
        }
        return maxValue;
    }

    public int evaluateHumanMove(NIMBoard board, int depth)
    {
        int minValue = 2;
        int calculated;
        if(board.isFinal())
        {
            return 1;
        }
        for(Move n : this.generateSuccessors(board))
        {
            NIMBoard newBoard = new NIMBoard(board.getPos(), board.getNumPiles());
            newBoard.parseMove(n);
            calculated = this.evaluateComputerMove(newBoard, depth + 1);
            // minValue = Integer.min(this.evaluateComputerMove(newBoard, depth + 1), minValue);
            if(calculated < minValue)
            {
                minValue = calculated;
            }
        }
        if(minValue == 2)
        {
            return 0;
        }

        return minValue;
    }

    public ArrayList<Move> generateSuccessors(NIMBoard start)
    {
        ArrayList<Move> successors = new ArrayList<Move>();
        for(int i = start.getNumPiles() - 1; i >= 0; i--)
        {
            for(long j = start.getCountForPile(i); j > 0; j--)
            {
                Move newMove = new Move(i, j);
                successors.add(newMove);
            }
        }

        return successors;
    }
}
public class NIMBoard
{
    /**
     * We use 4 bits to store the number of sticks which gives us these
     * maximums:
     * - 16 piles
     * - 15 sticks per pile
     */
    private static int PILE_BIT_SIZE = 4;
    private long pos;
    private int numPiles;
    private long pileMask;

    /**
     * Instantiate a new NIM board
     * @param pos Number of sticks in each pile
     * @param numPiles Number of piles
     */
    public NIMBoard(long pos, int numPiles)
    {
        super();
        this.pos        = pos;
        this.numPiles   = numPiles;

        this.pileMask   = (long) Math.pow(2, NIMBoard.PILE_BIT_SIZE) - 1;
    }

    /**
     * Is this an endgame board?
     * @return true if there's only one stick left
     */
    public boolean isFinal()
    {
        return this.onePileHasOnlyOneStick();
    }

    /**
     * Figure out if the board has a pile with only one stick in it
     * @return true if yes
     */
    public boolean onePileHasOnlyOneStick()
    {        
        int count = 0;

        for(int i = 0; i < this.numPiles; i++)
        {
            count += this.getCountForPile(i);
        }

        if(count > 1)
        {
            return false;
        }

        return true;
    }


    public int getNumPiles()
    {
        return this.numPiles;
    }

    public long getPos()
    {
        return this.pos;
    }


    public long getCountInPile(int pile)
    {
        return this.pos & (this.pileMask << (pile * NIMBoard.PILE_BIT_SIZE));
    }

    public long getCountForPile(int pile)
    {
        return this.getCountInPile(pile) >> (pile * NIMBoard.PILE_BIT_SIZE);
    }

    public void parseMove(Move move)
    {
        this.pos = this.pos - (move.getCount() << (move.getPile() * NIMBoard.PILE_BIT_SIZE));
    }

    @Override
    public String toString()
    {
        String tmp = "";
        for(int i = 0; i < this.numPiles; i++)
        {
            tmp += "Row " + i + "\t";
            for(int j = 0; j < this.getCountForPile(i); j++)
            {
                tmp += "*";
            }
            tmp += System.lineSeparator();
        }

        return tmp.trim();
    }

}

共有2个答案

濮君植
2023-03-14

对于人类玩家,你不应该有不同的功能。你应该假设两个玩家都使用最好的策略,因为你在实施它,所以两个玩家的代码应该是相同的。

该算法的思想不是将状态id分配给当前状态,该状态id等于最小状态id,该最小状态id不会与可能结束的状态的任何状态id重叠。如果可以移动并达到id为0、1和3的状态,则当前状态应具有状态id 2。任何丢失的状态都应具有id 0。

如果你的当前状态为id 0,无论你做什么动作,你都不会输。否则,你可以找到一个将棋盘移动到id 0状态的动作,这意味着另一个玩家会输。

陆翔飞
2023-03-14

你认为对人工智能来说是更好的一步,实际上并不是更好的一步。在这种棋盘游戏的情况下,人类玩家会从第一排拿走两根棍子,而计算机仍然会被卡在最后一根棍子上。这并不能保证您的程序正常工作,但我认为您应该尝试一些不同的测试用例。例如,如果你假设人类玩家会输,那么看看AI会做什么。

 类似资料:
  • 此为在原版2048的基础上,添加了电脑AI解题,并稍微修改了UI添加按钮来触发AI。 AI的核心在/js/myAI.js里,相关函数在window.myPlugin里 核心算法是用dfs搜3步后使代价函数window.myPlugin.evalCost期望值最小的走法, 代价函数的设计目的是让块尽量按由大到小顺序堆叠在右上角,并合并。 实验效果是基本能保证到2048,偶尔到4096甚至8192(概率较小)。

  • 我的剧本有问题,我要我的敌人跟着并向玩家旋转。当他四处走动的时候。这似乎很有效,但是当我的玩家在y上旋转180时,我的敌人似乎会回去很多(他的位置),只有当我的玩家回到他的正常旋转时,敌人才会回来。我到底做错了什么?

  • 我正在制作一个本地比赛的MMORPG游戏,我已经开始在服务器上工作,我遇到的问题是,我想要一种方法来检测每个玩家看到的其他玩家,这样我就可以将他们周围玩家的信息发送给特定的玩家。 首先,我想到了将一个2d圆形对象附加到玩家对象上,并对数据结构中的每个玩家进行碰撞检查,但这将非常耗费性能,有合适的算法吗?请帮帮我!

  • 《头号玩家》的启发 看过《头号玩家》的朋友们应该都记得在2045年虚拟现实技术和人工智能技术多么的强大,然而虚拟世界对现实世界的影响和冲击也是不容小觑的,万一哪天人工智能控制了人类就像智子控制了基础科学一样那该怎么办呢?当然这有些开脑洞了,言归正传,个人觉得第一个真正的人工智能应该会出现在虚拟世界里,那么最直接的虚拟世界就是游戏世界,而且是网络游戏。 这个系列的设计 最终目标:做一款简单有趣的小游

  • 我问这个问题的动机是,我发现了一个在图数据集上使用机器学习的有趣问题。有关于这个主题的论文。例如,“从有向图上的标记和未标记数据中学习”(周,黄,斯科普夫)。然而,我没有人工智能或机器学习的背景,所以在从事任何科学工作之前,我想为更普通的观众写一个更小的程序。 几年前,我写了一款名为Solumns的游戏。它是经典世嘉游戏《柱子》的邪恶变体。受巴斯特的启发,它暴力地选择对玩家不利的颜色组合。这很难。

  • 我试图为一个棋盘游戏找到一个更好的启发式函数,我将在代码后指定其规则。我的评估功能是: 初始板持有绿色和红色令牌,如图所示。人工智能先移动,使用与你相反的颜色,攻击你的代币。在黑色单元上,令牌可以正交(左、右、上、下)或对角移动。如果是在白血球上,你只能正交移动。 当您将令牌移动到对手令牌旁边时,您将删除该方向上所有对手的令牌。例如,如果我将绿色令牌从 C4 移动到 C5,我将杀死 C-6 到 C