MinMax - 生成游戏树

时间:2017-06-20 10:24:07

标签: java tic-tac-toe minmax

我尝试用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 类是董事会成员国的代表。

移动类保持移动执行(tic-tac-toe的整数[0-8],连接四的[0-6])。

节点类保存Move并指定给定移动的好坏程度。还有,所有的孩子。

在代码中我使用这种方法:

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

当很明显,给定的举动是一个失败的举动(或获胜),我不会接受这一举动。

1 个答案:

答案 0 :(得分: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);
    }
}

事先获取移动列表,然后查看它是否为空(generateMoves()类的Board方法(顺便说一句,感谢您提供的那个;))已经检查游戏是否已结束,如果是,则不会产生任何移动。完美的时间来检查分数。)

错误#3:再次深度

我们不是刚刚过去了吗?

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

然而,这有点复杂,因为你没有一个很好的小方法,已经检查游戏是否为你完成。

您可以查看您的值是否已设置,但问题是:它可能设置为0,您也需要将其考虑在内(因此您不能只考虑{{1} }})。

我只是将每个节点的初始值设置为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; } 的工作方式相同。

错误#4:玩家正在与你联系

一旦我改变了所有这一切,我花了一点时间与调试器一起意识到为什么它仍然不起作用。

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

原来这是minMove课程中这段精彩的代码:

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()); 的{​​{1}}方法中,您总是会从错误的播放器开始。

正如您可能猜到的那样,您只需将其更改为:

makeMove

它应该可以解决问题。

同时

此时我想说几点:

  • 清理进口!您的TicTacToe实际上仍然导入您的ConnectFour类!无缘无故。

  • 您的电路板在您的电路板阵列中旋转并镜像。为什么?你知道调试有多烦人吗?我的意思是,我猜你可能会这样做:D另外,如果你的代码出现问题并且你需要调试它,那么覆盖你的电路板TicTacToeMainWindow方法会非常有帮助,因为这会给你一个非常好的和容易的在调试器中查看您的电路板的方法。你甚至可以用它来再次旋转它,所以你看不必看它躺在一边;)

  • 虽然我们是董事会的主题......但这只是我...但我总是先尝试点击涂漆表面然后不得不记住:哦是的,有按钮:DI意思是......为什么不把图像放在按钮上或实现public int getPlayer() { return this.player; } ,这样你实际上只需点击绘制的表面?

  • 提供代码和/或示例图像时,请取出测试输出。我当然在谈论toString();)

  • 下次在StackOverflow上提问时,请了解一个完整,可验证且最小的示例。你问题中的一个不完整或可验证,你在github上提供的那个......好......不完整(图像丢失),但足够完整。它也是可验证的,但它并不是最小的。如果您遵循指导原则,您将很快得到答案。