【问题标题】:Minmax for ConnectFour连接四的 Minimax
【发布时间】:2015-03-29 07:05:47
【问题描述】:

我正在尝试实现一个 minmax 算法来创建一个用于连接四的 AI。我在这方面遇到了很多麻烦,因为我觉得我的事情过于复杂(而且它不能正常工作),也许这里有人可以提供帮助。我将首先发布我的代码,然后在下面发布我遇到的问题。

这是对 minmax 算法的初始调用

public int getColumnForMove(ConnectFour game) 
{
    game.minimax(2, game.getCurrentPlayer(), game);
    int column = game.getBestMove();
    return column;
}

这是被调用的初始极小极大方法(它在 ConnectFour 类中,而不是在单独的 AI 类中调用初始方法的地方)和一个包含用户移动到的每一列的子类如果它移入该列,则作为最小/最大分数。

class ColumnsAndScores
{
    int column;
    int score;

    ColumnsAndScores(int column, int score)
    {
        this.column = column;
        this.score = score;
    }

}

List<ColumnsAndScores> cas = new ArrayList<>();

public void minimax(int depth, int turn, ConnectFour game)
{
    cas = new ArrayList<>();
    minimaxHelper(depth, depth, turn, game);
}

以下是从每组可能的移动中获取最小或最大分数的方法:

public int getMax(List<Integer> list)
{
    int max = Integer.MIN_VALUE;
    int index = -1;

    for (int i = 0; i < list.size(); i++)
    {
        if (list.get(i) > max)
        {
            max = list.get(i);
            index = i;
        }
    }

    return list.get(index);
}

public int getMin(List<Integer> list)
{
    int min = Integer.MAX_VALUE;
    int index = -1;

    for (int i = 0; i < list.size(); i++)
    {
        if (list.get(i) < min)
        {
            min = list.get(i);
            index = i;
        }
    }

    return list.get(index);
}

这是实际的 minimax 方法(它有一堆注释掉的代码,显示它应该返回一个值范围,这取决于板子的好坏,如果它不是一个明确的赢或输,但现在我只是试图让它根据输赢做出决定(如果在所要求的深度内都没有发生,它会随机移动)。

public int minimaxHelper(int originalDepth, int depth, int turn, ConnectFour game) 
{   
    //holds future game states
    ConnectFour futureGameState;

    //holds the current scores
    List<Integer> scores = new ArrayList<>(); 

    //if (not at the lowest depth)
    if (depth !=0)
    {
        if (checkForWin(turn))
        {
            //return Integer.MAX_VALUE or Integer.MIN_VALUE respectively based on who's turn it is
            return (turn % 2 == 0) ? Integer.MAX_VALUE : Integer.MIN_VALUE;

        }

        //recursively call getColumnForMove(depth--, otherTurn) for each column if the column isnt full
        for (int i = 1; i <= ConnectFour.NUM_OF_COLUMNS; i++)
        {
            futureGameState = new ConnectFour();
            futureGameState.setCurrentGameState(game.getCurrentGameState());
            futureGameState.setCurrentPlayer(game.getCurrentPlayer());
            if (futureGameState.isValidMove(i))
            {
                futureGameState.makeMove(i);
                futureGameState.switchPlayer();
                scores.add(minimaxHelper(originalDepth, depth - 1, futureGameState.getCurrentPlayer(), futureGameState));
            }
            else //if move isnt valid return the worst possible value so this column doesnt get chosen
            {
                return (turn % 2 == 0) ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            }

            if (depth == originalDepth)
            {
                ColumnsAndScores newScore;
                if (turn % 2 == 0)
                    newScore = new ColumnsAndScores(i, getMax(scores));
                else
                    newScore = new ColumnsAndScores(i, getMin(scores));

                cas.add(newScore);
            }

        }

        if (turn % 2 == 0)
            return getMax(scores);
        else
            return getMin(scores);

    }
    else
    {
        if (checkForWin(turn))
        {
            //return Integer.MAX_VALUE or Integer.MIN_VALUE respectively based on who's turn it is
            return (turn % 2 == 0) ? Integer.MAX_VALUE : Integer.MIN_VALUE;

        }
        else
        {
            return 0;
        }
        //else
            //if 3 in a row with 2 open spaces that have pieces under those spaces
                //return 100
            //else if 3 in a row with 1 open space that has a piece under that space
                //return 80;
            //else if 3 in a row
                //return 60;
            //else if 2 in a row 
                //return 40
            //else
                //return 0
    }

}

最后,这是一个由 AI 调用的方法,用于从 minimax 也添加了 ColumnAndScores 的列表中获得最佳移动。

public int getBestMove()
{
    int highestScore = Integer.MIN_VALUE;
  int best = -1;

  for (int i = 0; i < cas.size(); ++i) { 
      if (highestScore < cas.get(i).score) {
         highestScore = cas.get(i).score;
          best = i;
      }
  }

  if (highestScore == 0)
    return 1 + ((int) (Math.random() * 7));
  else
    return best;
}

虽然我认为有几个逻辑错误,但我目前最困难的是当我这样做时futureGameState = new ConnectFour(); futureGameState.setCurrentGameState(game.getCurrentGameState());

这应该把它放到一个单独的实例中,这样当我移动时,它应该只持续到树的那个分支,并且不会破坏正在玩的实际游戏,但事实并非如此。它正在改变正在传入的游戏的实际状态。

【问题讨论】:

    标签: java algorithm minimax


    【解决方案1】:

    这个问题很可能是由ConnectFour 的实现引起的,类似于

    private int[][] state;
    public void setCurrentGameState(int[][] newState) {
        this.state = newState;
    }
    

    没关系,但是会导致您的游戏状态“副本”实际上引用 相同 int[][] state,因此对其进行的任何修改都将适用于这两种状态。你想要的是

    public class ConnectFour implements Cloneable<ConnectFour> {
        private static final int NUM_ROWS = 6;
        private static final int NUM_COLS = 7;
        private int[][] state = new int[NUM_ROWS][NUM_COLS];
        // ...
        public ConnectFour clone() {
            int[][] stateCopy = new int[NUM_ROWS][NUM_COLS];
            for (int i = 0; i < NUM_ROWS; i++)
                for (int j = 0; j < NUM_COLS; j++)
                    stateCopy[i][j] = this.state[i][j];
            ConnectFour cloned = new ConnectFour();
            cloned.setCurrentGameState(stateCopy);
            // copy other fields over to cloned
            return cloned;
        }
    }
    

    【讨论】:

      【解决方案2】:

      我只想解决一个问题。您应该尽量不要每个问题有太多,并包含与您的问题相关的代码,例如您的 ConnectFour 类。

      如果您想制作板的副本,您可以在不更改原件的情况下进行修改,您需要制作deep copy,而不是参考的副本。要制作房屋的浅拷贝,请制作房屋钥匙的副本。如果你把它给别人,当你回到家时,你不应该惊讶地看到变化。要对你的房子进行深度复制,你会得到第二批,并根据你房子的蓝图和照片建造一座新房子。如果您将新房子的钥匙交给某人,他/她可能不会立即注意到差异,但任何更改都不会直接影响您,您所做的更改也不会影响收件人。

      “深拷贝”实际上是模棱两可的,因为您的对象可能包含具有对象成员的对象成员。当您制作深拷贝时,您必须决定是制作任何成员对象的深拷贝还是浅拷贝。如果您的 ConnectFour 类包含 Move 对象的 ArrayList,每个对象都是表示列的 int 的包装器,那么您有 3 个选择:

      • 您可以复制对 ArrayList 的引用。
      • 您可以创建一个新的 ArrayList,并引用同一组动作。
      • 您可以创建一个新的 ArrayList 引用移动的副本。

      无论如何,我的猜测是你还没有嵌套的成员对象,所以你的深拷贝方法可能看起来像下面这样:

      public class ConnectFour{
          private int[][] board = new int[6][7];
      
          public setCurrentGameState(int[][] state){ 
              for(int i = 0; i<6; i++)
                  for(int j=0; j<7; j++)
                      board[i][j] = state[i][j];
          }
          ...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多