【问题标题】:MinMax algorithm in practice - tictactoe. On what basis we choose the right move?实践中的 MiniMax 算法 - 井字游戏。我们在什么基础上选择正确的举动?
【发布时间】:2014-05-31 20:03:33
【问题描述】:

我已经处理了一个星期。

我已经构建了一个游戏板树并对其进行了评估 -> 每个节点都有int evaluation 字段我在什么基础上选择了正确的移动?我无法从任何教程、youtube 视频中获得它。算法好像没什么用。

在我的评估方法中: 1 个十字/圆圈内联 10^0 点,2 个十字/圆圈在线 10^1 点,... n 个十字/圆圈在线 10^(n-1) 个点。是 + 还是 - 10^(n-1) 个点取决于树的级别。

我在左上方的字段上打了叉,我愚蠢的算法认为这是计算机播放器的最佳轨道(CIRCLE):

[O| | |
------
 | | |
------
 | | |

Evaluation -1
TotalEvaluation -1
Dobry  X
, O|X| |
------
 | | |
------
 | | |

Evaluation 0
TotalEvaluation -1
Dobry  X
, O|X| |
------
O| | |
------
 | | |

Evaluation -11
TotalEvaluation -12
Dobry  X
, O|X| |
------
O| | |
------
X| | |

Evaluation 10
TotalEvaluation -2
Dobry  X
, O|X| |
------
O|O| |
------
X| | |

Evaluation -31
TotalEvaluation -33
Dobry  X
, O|X| |
------
O|O| |
------
X| |X|

Evaluation 30
TotalEvaluation -3
Dobry  X
]

如果您理解其中任何一个,请尝试解释。

这是我的节点:

package tictactoe;

import java.util.ArrayList;

import static tictactoe.Field.*;

public class Node {
    static int KEY = 0;
    int level, key, evaluation;
    int[][] board;
    ArrayList<Node> children;
    int stamp;
    int totalEval;
    Node parent;

    Node(int[][] board, int level, int stamp, Node parent) {
        this.board = board;
        this.level = level;
        this.stamp = stamp;
        this.parent = parent;
        totalEval = 0;
        children = new ArrayList<Node>();
        key = KEY++;
        setEvaluation();
        if(parent != null)
            totalEval = parent.totalEval + evaluation;
        else
            totalEval = evaluation;

    }

    public void setEvaluation() {
        int evaluation = 0;
        int howManyInLine = board.length;
        int opponentSign = CIRCLE;
        if(stamp == CIRCLE)
            opponentSign = CROSS;
        for(; howManyInLine > 0; howManyInLine--) {
            if(level % 2 == 0) {
                evaluation += countInlines(stamp, howManyInLine);
                evaluation -= countInlines(opponentSign, howManyInLine);
            } else {
                evaluation -= countInlines(stamp, howManyInLine);
                evaluation += countInlines(opponentSign, howManyInLine);
            }
        }
        this.evaluation = evaluation;
    }

    public int countInlines(int sign, int howManyInLine) {
        int points = (int) Math.pow(10, howManyInLine - 1);
        int postiveCounter = 0;
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[i].length; j++) {
                //czy od tego miejsca jest cos po przekatnej w prawo w dol, w lewo w dol, w dol, w prawo
                if(toRigth(i, j, sign, howManyInLine))
                    postiveCounter++;
                if(howManyInLine > 1) {
                    if(toDown(i, j, sign, howManyInLine))
                        postiveCounter++;
                    if(toRightDiagonal(i, j, sign, howManyInLine))
                        postiveCounter++;
                    if(toLeftDiagonal(i, j, sign, howManyInLine))
                        postiveCounter++;
                }
            }
        }
        return points * postiveCounter;
    }

    public boolean toRigth(int i, int j, int sign, int howManyInLine) {
        for(int start = j; j < start + howManyInLine; j++)
            if(j >= board.length || board[i][j] != sign)
                return false;
        return true;
    }

    public boolean toDown(int i, int j, int sign, int howManyInLine) {
        for(int start = i; i < start + howManyInLine; i++)
            if(i >= board.length || board[i][j] != sign)
                return false;
        return true;
    }

    public boolean toRightDiagonal(int i, int j, int sign, int howManyInLine) {
        int startJ = j;
        for(int start = i; i < start + howManyInLine; i++, j++)
            if(i >= board.length || j >= board.length || board[i][j] != sign)
                return false;
        return true;
    }

    public boolean toLeftDiagonal(int i, int j, int sign, int howManyInLine) {
        int startJ = j;
        for(int start = i; i < start + howManyInLine; i++, j--)
            if(i >= board.length || j < 0 || board[i][j] != sign)
                return false;
        return true;
    }

    public boolean gameOver() {
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board.length; j++) {
                if(toRigth(i, j, CROSS, board.length))
                    return true;
                if(toDown(i, j, CROSS, board.length))
                    return true;
                if(toRightDiagonal(i, j, CROSS, board.length))
                    return true;
                if(toLeftDiagonal(i, j, CROSS, board.length))
                    return true;
                if(toRigth(i, j, CIRCLE, board.length))
                    return true;
                if(toDown(i, j, CIRCLE, board.length))
                    return true;
                if(toRightDiagonal(i, j, CIRCLE, board.length))
                    return true;
                if(toLeftDiagonal(i, j, CIRCLE, board.length))
                    return true;
            }
        }

        return false;
    }

    @Override
    public String toString() {
        String s = "";

        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[i].length; j++) {
                if(board[i][j] == CROSS)
                    s += "X";
                if(board[i][j] == CIRCLE)
                    s += "O";
                if(board[i][j] == EMPTY)
                    s += " ";
                s += "|";
            }
            s += "\n";
            if(i < board.length - 1) {
                for(int k = 0; k < board.length * 2; k++)
                    s += "-";
            }
            s += "\n";
        }
        s += "Evaluation " + evaluation + "\n";
        s += "TotalEvaluation " + totalEval + "\n";
        s += "Dobry ";
        if(stamp == CROSS)
            s += " X\n";
        else
            s += " O\n";

        return s;
    }

}

然后我从这些节点构造一棵树:

package tictactoe;

import static tictactoe.Field.*;

import java.awt.GridLayout;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Tree {
    Node root;
    GameBoard gameBoard;
    Player player;

    public Tree(GameBoard gameBoard, Player player) {
        this.gameBoard = gameBoard;
        this.player = player;
        addNodes();
    }

    public void addNodes() {
        if(root == null)
            root = new Node(toIntArray(gameBoard.getFields()), 0, player.getStamp(), null);
        addChildren(root);
    }

    public void addChildren(Node parent) {
        if(parent.level < 5 && !parent.gameOver()) {
            for(int i = 0; i < parent.board.length; i++) {
                for(int j = 0; j < parent.board.length; j++) {
                    if(parent.board[i][j] == EMPTY) {
                        int[][] copy = hardCopy(parent.board);
                        int stamp = 0;
                        if(parent.level % 2 == 0)
                            stamp = CROSS;
                        else
                            stamp = CIRCLE;

                        copy[i][j] = stamp;
                        Node child = new Node(copy, parent.level + 1, player.getStamp(), parent);
                        System.out.println(stamp);
                        parent.children.add(child);
                        addChildren(child);
                    }
                }
            }
        }
    }

    public int[][] getBestMove() {
        System.out.println("----------");
        ArrayList<Node> childrenList = getBestNode(root, new ArrayList<Node>());
        ArrayList<Node> track = new ArrayList<Node>();
        System.out.println("ROZMIAR: " + childrenList.size());
        Node bestChild = null;
        int max = Integer.MIN_VALUE;
        for(Node node : childrenList)
            if(node.evaluation > max) {
                max = node.evaluation;
                bestChild = node;
            }
        System.out.println("NAJLEPSZY");
        System.out.println(bestChild);

        //znajdowanie przodka
        Node moveToDo = bestChild;
        while (moveToDo.parent.parent != null) {
            track.add(0, moveToDo);
            moveToDo = moveToDo.parent;
        }
        track.add(0, moveToDo);
        track.add(0, moveToDo.parent);
        System.out.println(moveToDo);
        ///
        JFrame jf = new JFrame();
        jf.setLayout(new GridLayout());
        JTextArea jta = new JTextArea(track.toString());
        JScrollPane jsp = new JScrollPane(jta, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        jf.add(jsp);
        jf.setVisible(true);
        jf.setLocation(600, 0);
        jf.pack();

        ////

        return moveToDo.board;
    }

    public ArrayList<Node> getBestNode(Node node, ArrayList<Node> childrenList) {
        for(Node n : node.children) {
            getBestNode(n, childrenList);
            if(n.children.size() == 0)
                childrenList.add(n);
        }
        return childrenList;
    }

    public void print(Node node) {
        System.out.println(node);
        for(Node n : node.children)
            print(n);
    }

    public static int[][] hardCopy(int[][] t) {
        int[][] copy = new int[t.length][t.length];
        for(int i = 0; i < t.length; i++) {
            for(int j = 0; j < t.length; j++) {
                copy[i][j] = t[i][j];
            }
        }
        return copy;
    }
}

【问题讨论】:

  • 搜索空间很小——我相信你可以探索所有的动作直到游戏结束(+1 为胜利,-1 为失败,0 为平局)。
  • @Dukeling 好的,但是如何选择 MOVE?我们得到了叶子 -1,0,1 以及如何回到树中并选择移动?
  • 迭代树上的值。假设玩家最佳移动并使每个节点的值与其子节点中该玩家的最佳选择相同。因此,如果有一个 1 作为孩子并且它是 MAX 的移动,那么将该节点也设为 1(因为 MAX 的最佳移动是 1,它会导致有保证的胜利)。
  • 在实践中,您从不使用 MinMax,而是使用 NegaMax。
  • @Dukeling我是为 3x3 做的,它的工作原理是 4x4 和更高,所以我做了评估方法。你知道更好的评估规则(给船上的情况打分)请看看:stackoverflow.com/questions/23982531/…

标签: algorithm tic-tac-toe minimax


【解决方案1】:

here 所述,通过最大化或最小化轮到的递归计算值来选择最佳移动,具体取决于轮到玩家一还是玩家二。这意味着在每次评估中,算法交替地考虑两个玩家的观点并选择最佳移动。这个细节可以通过总是最大化值来实现,根据玩家的不同乘以1-1的因子,因为只有10-1的值可以发生在移动中。

此外,可以使用修剪;这意味着一旦找到对当前玩家最佳的移动,即分别找到价值1-1,就可以停止评估。

【讨论】:

  • 我是为 3x3 做的,它的工作原理是 4x4 及更高,所以我做了评估方法。你知道更好的评估规则(给船上的情况打分)请看看:stackoverflow.com/questions/23982531/…
  • 如果评估正确但速度慢得无法接受,则可以使用一些启发式方法。一种是通过知道最佳值进行修剪,另一种是只有最后放置的棋子才能完成获胜链,这意味着在叶子中不必评估整个棋盘。是否已经考虑到这一点?
猜你喜欢
  • 2015-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多