MiniMax算法在实践中 - tic tac toe。在什么基础上我们选择正确的举措?

时间:2014-05-31 20:03:34

标签: algorithm tic-tac-toe minimax

我现在处理了一个星期。

我构建了一个游戏板树,我对它们进行了评估 - >每个节点都有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 个答案:

答案 0 :(得分:1)

正如here所描述的那样,最佳移动是通过最大化或最小化转弯的递归计算值来选择的,这取决于它是玩家1还是玩家2的转弯。这意味着在每次评估中,算法交替地采用两个玩家的视角并选择最佳移动。这个细节可以通过始终最大化该值来实现,并根据玩家乘以1-1因子,仅作为10和{{的值1}}可以发生移动。

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