Tic-tac-toe A.I.在Java中使用minimax不起作用

时间:2015-06-28 12:15:27

标签: java tic-tac-toe minimax

我正在努力建立A.I.对于使用minimax算法的Tic-tac-toe但它返回奇怪的动作 - 有时它只返回我认为的第一个可用的移动(左上,上中,右上......),但有时它只返回看起来像“随机“移动。但据我所知,这些动作并非随机而且A.I.在相同条件下播放总是一样的。

当有2个人类玩家时,游戏会起作用。整个游戏都很大,所以我创建了AITest类,它应该显示行为的重要部分。它让两个A.I.玩家玩和打印板。请注意,这是单独的课程,不是根据最佳实践编写的,而是大大简化(不检查胜利...)。但是A.I.显然应该是显而易见的。是愚蠢的。

感谢您的任何建议。

package tic_tac_toe;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AITest {

    public enum Tile {
        EMPTY, O, X;
    }

    // [row][column]
    private static Tile[][] board;
    private static int moveCount = 0;

    private Tile mySeed;
    private Tile opponentSeed;

    public static void main(String[] args) {
        board = new Tile[3][3];
        for (int y = 0; y < board.length; y++) {
            for (int x = 0; x < board.length; x++) {
                board[y][x] = Tile.EMPTY;
            }
        }

        AITest ai = new AITest(Tile.X);
        AITest opponent = new AITest(Tile.O);
        // simulate some moves
        for (int i = 0; i < 15; i++) {
            checkReset();
            int[] move = ai.getNextMove(board);
            board[move[0]][move[1]] = Tile.X;
            printBoard();

            checkReset();
            int[] opponentMove = opponent.getNextMove(board);
            board[opponentMove[0]][opponentMove[1]] = Tile.O;
            printBoard();
        }
    }

    private static void checkReset() {
        moveCount++;
        if (moveCount == 9) {
            for (int y = 0; y < board.length; y++) {
                for (int x = 0; x < board.length; x++) {
                    board[y][x] = Tile.EMPTY;
                }
            }
            moveCount = 0;
        }

    }

    private static void printBoard() {
        StringBuilder sb = new StringBuilder();
        sb.append("-----------------------\n");
        Map<Tile, String> map = new HashMap<>();
        map.put(Tile.EMPTY, "e");
        map.put(Tile.O, "O");
        map.put(Tile.X, "X");
        for (Tile[] row : board) {
            for (Tile t : row) {

                sb.append(map.get(t) + "|");
            }
            sb.append("\n");
        }
        System.out.println(sb.toString());
    }

    public AITest(Tile seed) {
        mySeed = seed;
        opponentSeed = (mySeed == Tile.X) ? Tile.O : Tile.X;
    }

    public int[] getNextMove(Tile[][] board) {
        int[] result = minimax(board, mySeed);
        // row, column
        return new int[] { result[1], result[2] };
    }

    private int[] minimax(Tile[][] board, Tile player) {
        // mySeed is maximizing, opponentSeed is minimizing
        int bestScore = (player == mySeed) ? Integer.MIN_VALUE
                : Integer.MAX_VALUE;
        int currentScore;
        int bestRow = -1;
        int bestCol = -1;

        List<Point> possibleMoves = getPossibleMoves(Arrays.copyOf(board,
                board.length));
        if (possibleMoves.isEmpty()) {
            bestScore = getScore(board, player);
        } else {
            for (Point move : possibleMoves) {
                // try the move
                board[move.y][move.x] = player;
                if (player == mySeed) {
                    currentScore = minimax(board, opponentSeed)[0];
                    if (currentScore > bestScore) {
                        bestScore = currentScore;
                        bestRow = move.y;
                        bestCol = move.x;
                    }
                } else {
                    currentScore = minimax(board, mySeed)[0];
                    if (currentScore < bestScore) {
                        bestScore = currentScore;
                        bestRow = move.y;
                        bestCol = move.x;
                    }
                }
                // undo the move
                board[move.y][move.x] = Tile.EMPTY;
            }
        }
        return new int[] { bestScore, bestRow, bestCol };
    }

    private List<Point> getPossibleMoves(Tile[][] board) {
        List<Point> possibleMoves = new ArrayList<>();
        for (int y = 0; y < board.length; y++) {
            for (int x = 0; x < board.length; x++) {
                if (board[y][x] == Tile.EMPTY) {
                    possibleMoves.add(new Point(x, y));
                }
            }
        }
        return possibleMoves;
    }

    private int getScore(Tile[][] board, Tile player) {
        if (isWinner(board, mySeed)) {
            return 1;
        }
        if (isWinner(board, opponentSeed)) {
            return -1;
        }
        return 0;
    }

    private boolean isWinner(Tile[][] board, Tile player) {
        // rows
        for (int i = 0; i < board.length; i++) {
            if (checkLine(player, board[i])) {
                return true;
            }
        }
        // columns
        for (int i = 0; i < board.length; i++) {
            if (checkLine(player, board[0][i], board[1][i], board[2][i])) {
                return true;
            }
        }
        // diagonals
        return checkLine(player, board[0][0], board[1][1], board[2][2])
                || checkLine(player, board[0][2], board[1][1], board[2][0]);
    }

    private boolean checkLine(Tile player, Tile... line) {
        for (Tile tile : line) {
            if (tile != player) {
                return false;
            }
        }
        return true;
    }
}

1 个答案:

答案 0 :(得分:3)

MinMax非常依赖于具有良好的评估功能(“评分”电路板的功能,告诉您它有多好)。您的评估功能应如下:

  1. 如果赢了比赛,获胜者将获得很多积分。赢得比赛后没什么关系。
  2. 否则,为占用可用于更多获胜动作的牌块提供部分功劳。例如,中心牌可用于4个胜利位置,角落用于3个,侧面用于2个。因此,如果您有选择且对手不能赢得一对一,则中心牌更好。
  3. 您当前的评估功能已被破坏,因为它仅实现#1,而仅在电路板已满时实现。要查看一个很好的改进,请在检查minimax()中的可用移动之前检查双方的胜利,如果任何一方获胜,请检查,返回值为getScore()