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

Java NIM游戏和AI玩家使用Minimax算法-AI做出失败的动作,java,algorithm,minimax,Java,Algorithm,Minimax,我有一个任务要写一个NIM游戏,里面有一个人类玩家和一个AI玩家。这个游戏是“吝啬鬼”(最后一个必须拿起棍子的人输了)。人工智能应该使用极大极小算法,但它的动作会让它更快地失去,我不明白为什么。我已经走了好几天的死胡同了。 Minimax算法的要点是不丢失,如果它处于丢失位置,则尽可能延迟丢失的动作,对吗 考虑以下几点: 敏捷板=新敏捷板(34,2) 34=棒的二进制编码位置,2堆2个棒 2=桩的数量 我们从这个场景开始,用*字符表示一根棍子: Row 0: ** Row 1: ** 在这

我有一个任务要写一个NIM游戏,里面有一个人类玩家和一个AI玩家。这个游戏是“吝啬鬼”(最后一个必须拿起棍子的人输了)。人工智能应该使用极大极小算法,但它的动作会让它更快地失去,我不明白为什么。我已经走了好几天的死胡同了。 Minimax算法的要点是不丢失,如果它处于丢失位置,则尽可能延迟丢失的动作,对吗

考虑以下几点:

敏捷板=新敏捷板(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;
    }
}
公共类极小极大
{
下一步公共行动;
公共int evaluateComputerMove(智能板,int深度)
{
int maxValue=-2;
int计算;
if(board.isFinal())
{
返回-1;
}
对于(移动n:此.generateSuccessors(板))
{
NIMBoard newBoard=新的NIMBoard(board.getPos(),board.getNumPiles());
新板移动(n);
计算=此。evaluateHumanMove(新板,深度+1);
如果(计算>最大值)
{
最大值=计算值;
如果(深度==0)
{
System.out.println(“设置下一步”);
this.nextMove=n;
}
}
}
如果(最大值==-2)
{
返回0;
}
返回最大值;
}
公共int evaluateHumanMove(敏捷板,int深度)
{
int minValue=2;
int计算;
if(board.isFinal())
{
返回1;
}
对于(移动n:此.generateSuccessors(板))
{
NIMBoard newBoard=新的NIMBoard(board.getPos(),board.getNumPiles());
新板移动(n);
计算=此。evaluateComputerMove(新板,深度+1);
//minValue=Integer.min(此.evaluateComputerMove(新板,深度+1),minValue);
如果(计算值<最小值)
{
最小值=计算值;
}
}
如果(最小值==2)
{
返回0;
}
返回最小值;
}
公共ArrayList generateSuccessors(NIMBoard start)
{
ArrayList继承者=新的ArrayList();
对于(int i=start.getNumPiles()-1;i>=0;i--)
{
对于(长j=start.getCountForPile(i);j>0;j--)
{
移动新移动=新移动(i,j);
继承人。添加(新移动);
}
}
归还继承人;
}
}

公共类NIMBoard
{
/**
*我们使用4位来存储棍子的数量,从而得到这些数字
*最大值:
*-16桩
*-每桩15根
*/
专用静态整数桩位大小=4;
私人长pos;
私有整数;
私人长桩;
/**
*实例化一个新的NIM板
*@param pos每个桩中的杆数
*@param numPiles桩数
*/
公共灵巧板(长位置,整数)
{
超级();
this.pos=pos;
this.numPiles=numPiles;
this.pileMask=(long)Math.pow(2,NIMBoard.PILE\u BIT\u SIZE)-1;
}
/**
*这是终局板吗?
*@如果只剩下一根棍子,返回true
*/
公共布尔值isFinal()
{
返回此.onePileHasOnlyOneStick();
}
/**
*找出板子上是否有一堆只有一根木棍的木桩
*@如果是,则返回true
*/
公共布尔值onePileHasOnlyOneStick()
{        
整数计数=0;
for(int i=0;i1)
{
返回false;
}
返回true;
}
public int getNumPiles()
{
返回这个.numPiles;
}
公共长getPos()
{
返回此.pos;
}
公共长GetCountInFile(整数堆)
{
返回this.pos&(this.pileMask>(pile*NIMBoard.pile\u BIT\u SIZE);
}
公共移动(移动)
{

this.pos=this.pos-(move.getCount()对于人工智能来说,你认为是更好的一步,实际上并不是更好的一步。在这种情况下,人类玩家会从第一排拿走两个木棍,而计算机仍然会被卡住拿最后一个木棍。这并不保证你的程序能正常工作,但我认为你应该尝试一些不同的测试用例。例如,see如果你认为人类玩家会输,AI会做什么。

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

该算法的思想不是将状态id分配给当前状态,该状态id等于最小状态id,该最小状态id不与可能结束的状态的任何状态id重叠。如果可以使用id 0、1和3移动并到达状态,则当前状态应具有状态id 2。 任何丢失的状态都应该有id
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();
    }

}