【问题标题】:MinMax - generating game treeMinMax - 生成游戏树
【发布时间】:2017-06-24 18:00:05
【问题描述】:

我尝试用 Java 编写一个用于连接四游戏的 MinMax 程序,但该程序也应该适用于其他游戏。但是,我遇到了一个问题,我几天都无法通过。节点的值设置不正确。我正在分享我的一段代码,它负责生成一棵树。

也许你会注意到我在哪里犯了错误。

如果有人能帮我解决这个问题,我会很高兴。

public Node generateTree(Board board, int depth) {
    Node rootNode = new Node(board);
    generateSubtree(rootNode, depth);
    minMax(rootNode, depth);
    return rootNode;
}

private void generateSubtree(Node subRootNode, int depth) {
    Board board = subRootNode.getBoard();

    if (depth == 0) {
        subRootNode.setValue(board.evaluateBoard());
        return;
    }

    for (Move move : board.generateMoves()) {
        Board tempBoard = board.makeMove(move);
        Node tempNode = new Node(tempBoard);
        subRootNode.addChild(tempNode);
        generateSubtree(tempNode, depth - 1);
    }
}

public void minMax(Node rootNode, int depth) {
    maxMove(rootNode, depth);
}

public int maxMove(Node node, int depth) {
    if (depth == 0) {
        return node.getValue();
    }
    int bestValue = Integer.MIN_VALUE;
    for (Node childNode : node.getChildren()) {
        int tempValue = minMove(childNode, depth - 1);
        childNode.setValue(tempValue);
        if (tempValue > bestValue) {
            bestValue = tempValue;
        }
    }
    return bestValue;
}

public int minMove(Node node, int depth) {
    if (depth == 0) {
        return node.getValue();
    }
    int bestValue = Integer.MAX_VALUE;
    for (Node childNode : node.getChildren()) {
        int tempValue = maxMove(childNode, depth - 1);
        childNode.setValue(tempValue);
        if (tempValue < bestValue) {
            bestValue = tempValue;
        }
    }
    return bestValue;
}

Board类是棋盘状态的表示。

Move 类保存要执行的移动(整数 [0-8] 用于井字游戏,[0-6] 用于连接四)。

Node 类保存 Move 并评估给定 move 的好坏。此外,持有它的所有孩子。

在代码中我这样使用这个方法:

Node newNode = minmax.generateTree(board, depth, board.getPlayer());
Move newMove = new TicTacToeMove(board.getPlayer(), newNode.getBestMove().getMove(), depth);
board = board.makeMove(newMove);

当给定的动作很明显是一个失败的动作(或获胜)时,我没有收到这个动作。

【问题讨论】:

  • 如果您能提供一个示例,说明您输入的内容、您认为结果应该是什么以及它是什么,那就太好了。
  • 嗨,Board 类是棋盘状态的表示。 Move 类保存要执行的移动(整数 [0-8] 表示 tictactoe,[0-6] 表示连接四)。 Node 类保存 Move 并评估给定 move 的好坏程度。还拥有它的所有孩子。在代码中,我使用这样的方法:Node newNode = minmax.generateTree(board, depth, board.getPlayer()); Move newMove = new TicTacToeMove(board.getPlayer(), newNode.getBestMove().getMove(), depth); board = board.makeMove(newMove); 当很明显给定的举动是失败的举动(或获胜)时,我没有收到这个举动。
  • 什么是“我没有收到这个动作。”意思是?它是一个网络游戏,并且没有向您发送输赢的举动吗?你的意思是它没有放在板上?您的树没有获得新条目吗?
  • 嗨。方法 generateTree(...) 的输出应该是板子给定状态的根。然后,当我执行 node.getBestMove() - 它应该返回具有最佳值的 Move(在 minmax 算法之后)。然后 move.getMove() 返回给定移动的整数 - 应该放置 pawn 的位置。我猜 minmax() 方法中的某些东西是错误的,因为 generateTree() 方法在游戏中到达底部并找到输/赢动作。
  • 看,我想帮助你,但是......你还没有告诉我到底出了什么问题。 它应该以最佳价值返回 Move generateTree() 方法到达游戏的底部 move.getMove() 方法是否返回了 5?

标签: java tic-tac-toe minmax


【解决方案1】:

好吧,你确实犯了几个错误。大约 3-4,取决于你如何计算 ;) 花了我一些调试来弄清楚这一切,但我终于得到了你的答案:D

错误 #1:你所有的父母总是生双胞胎(那个可怜的母亲)

这只是您上传的代码的情况,而不是您问题中的代码,所以也许我们将其视为一半错误? 由于您的树还没有那么大,并且不会破坏您的算法,因此无论如何这都是最不重要的。不过,这还是需要注意的。 在您上传的代码中,您可以在 generateSubtree 方法中执行此操作:

Node tempNode = new Node(tempBoard, move, subRootNode);
subRootNode.addChild(tempNode);

由于该构造函数已将子节点添加到 subRootNode,因此第二行总是将其添加第二次。

错误 #2:该死的深度

如果你还没有达到你想要的深度,但游戏已经决定了,你完全忽略它。因此,在您提供的示例中不起作用,例如,如果您考虑进行第 7 步而不是第 3 步(这将是“正确的”举动),然后对手确实进行了第 3 步,那么您就不算作-10 分,因为你还没有达到你的深度。它仍然不会得到任何孩子,所以即使在你的 minmax 中,它也永远不会意识到这是一条搞砸的路。

这就是为什么在这种情况下每一步都是“可能的”,而你只会得到第一个返回。

在之前的动作中,幸运的是,总有一种方法可以在对手的第三步(也称为第 5 步)中达到失败的一步,这就是正确调用这些步的原因。

好的,那我们该如何解决呢?

private void generateSubtree(Node subRootNode, int depth, int player) {
    Board board = subRootNode.getBoard();
    List<Move> moveList = board.generateMoves();

    if (depth == 0 || moveList.isEmpty()) {
        subRootNode.setValue(board.evaluateBoard(player));
        return;
    }

    for (Move move : moveList) {
        Board tempBoard = board.makeMove(move);
        Node tempNode = new Node(tempBoard, move, subRootNode);
        generateSubtree(tempNode, depth - 1, player);
    }
}

只需事先获取移动列表,然后查看它是否为空(您的 Board 类的 generateMoves() 方法(感谢上帝顺便提供了该方法;))已经检查游戏是否结束,所以如果它也就是说,不会产生任何动作。检查分数的最佳时机。

错误 #3:又是那个该死的深度

我们不是刚刚讨论过吗?

遗憾的是,您的 Min Max 算法本身也存在同样的问题。如果您已达到所需的深度,它甚至只会查看您的值。你需要改变它。

但是,这有点复杂,因为您没有一个很好的小方法来检查游戏是否为您完成。

您可以检查您的值是否已设置,但问题是:它可能设置为0,您也需要考虑到这一点(所以您不能只做if (node.getValue() != 0))。

我只是将每个节点的初始值设置为-1,然后对-1 进行了检查。这不是……你知道……漂亮。但它有效。

public class Node {
    private Board board;
    private Move move;
    private Node parent;
    private List<Node> children = new ArrayList<Node>();;
    private boolean isRootNode = false;

    private int value = -1;
   ...

这在maxMove:

public int maxMove(Node node, int depth) {
    if (depth == 0 || node.getValue() != -1) {
        return node.getValue();
    }
    int bestValue = Integer.MIN_VALUE;
    for (Node childNode : node.getChildren()) {
        int tempValue = minMove(childNode, depth - 1);
        childNode.setValue(tempValue);
        if (tempValue > bestValue) {
            bestValue = tempValue;
        }
    }               
    return bestValue;
}

当然,minMove 也一样。

错误 #4:玩家在和你开玩笑

一旦我改变了这一切,我花了一点时间调试器才意识到为什么它仍然无法工作。

最后一个错误不在您在问题中提供的代码中。你太无耻了! ;)

原来这是您TicTacToeBoard 课程中的一段精彩代码:

@Override
public int getPlayer() {
    // TODO Auto-generated method stub
    return 0;
}

既然你打来电话

        MinMax minmax = new MinMax();
        Node newNode = minmax.generateTree(board, (Integer) spinner.getValue(), board.getPlayer());

TicTacToeMainWindowmakeMove 方法中,您总是会从错误的玩家开始。

您可能自己猜到了,您只需将其更改为:

public int getPlayer() {
    return this.player;
}

它应该可以解决问题。

还有:

此时我想说明几点:

  • 清理您的导入!您的井字游戏实际上仍然导入您的 ConnectFour 类!而且无缘无故。

  • 您的电路板在您的电路板阵列中旋转和镜像。为什么?你知道调试有多烦人吗?我的意思是,我想您可能会这样做:D 另外,如果您的代码有问题并且需要调试,那么覆盖您的电路板toString() 方法非常有帮助,因为这将为您提供一个非常好的和简单的方法在调试器中查看您的电路板。你甚至可以用它来再次旋转它,这样你就不用看它了;)

  • 当我们讨论板的主题时......这只是我,但是......我总是先尝试点击涂漆的表面,然后必须记住:哦,是的,有按钮 :D I意思是...为什么不直接将图像放在按钮上或实现MouseListener,这样您实际上就可以点击绘制的表面?

  • 在提供代码和/或示例图像时,请取出您的测试输出。我说的当然是Player 1 won!s ;)

  • 下次您在 StackOverflow 上提问时,请了解什么是完整、可验证和最小的示例。您问题中的那个不完整或无法验证,而您在 github 上提供的那个是......嗯......不完整(图像丢失),但足够完整。它也是可验证的,但不是最小的。如果您遵循指南,您将很快得到答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-12
    • 2016-08-16
    • 2012-05-02
    相关资源
    最近更新 更多