为什么我的Minimax算法没有产生正确的移动?

时间:2018-02-11 17:29:49

标签: javascript algorithm minimax

该算法运行良好,没有任何错误,但AI根本不聪明,似乎正在进行随机移动。我已经在这工作了两天,无法弄清楚我哪里出错了。有人可以帮助找到导致它无法正确移动的错误吗?

当开始游戏时,AI应该始终在索引4(中间方格)移动,除非我拿到那个位置,但它不会那样做,而且它根本不会尝试获胜。

$(document).ready(function() {

let X = {
    isComputer: false,
    symbol: "x",
    marker: "<img src='img/x.png'>",
    winMarker: "<img src='img/xWin.png'>"
}
let O = {
    isComputer: false,
    symbol: "o",
    marker: "<img src='img/o.png'>",
    winMarker: "<img src='img/oWin.png'>"
}
let game = {
    board: [0,1,2,3,4,5,6,7,8],
    firstTurn: X,
    xScore: 0,
    oScore: 0,
    turnNumber: 0,
    started: false
}
let winningCombos = [
    [0,1,2], [3,4,5], [6,7,8],
    [0,3,6], [1,4,7], [2,5,8],
    [0,4,8], [2,4,6]
];
let theWinningCombo;
let player = X;
let clearBoardTimeoutID;
let ai1;
let ai2;

function clearBoardForNextGame() {

    clearBoardTimeoutID = 
    setTimeout(function() {
        $('.square').empty();
        game.firstTurn = game.firstTurn == X ? O : X;
        game.turnNumber = 0;
        game.board = [0,1,2,3,4,5,6,7,8];
        game.started = true;
    }, 1500);
}

function thisPlayerWon(board, symbol) {
    for (let i = 0; i < winningCombos.length; i++) {
        let counter = 0;
        for (let j = 0; j < winningCombos[i].length; j++) {
            if (board[winningCombos[i][j]] == symbol) {
                counter++;
            }
            if (counter == 3) {
                theWinningCombo = winningCombos[i];
                return true;
            }
        }
    }
    return false;
}

function showWinnerAndUpdateScore(combo, player) {
    game.started = false;
    combo.forEach(index => $('#' + index).html(player.winMarker));
    player == X ? (game.xScore++, $('#score1').text(game.xScore)) : (game.oScore++, $('#score2').text(game.oScore))
}

function AImove(AIplayer, board) {
    AIplayer = !(game.turnNumber % 2) ? game.firstTurn : (game.firstTurn == X ? O : X);
    let opponent = AIplayer == X ? O : X;

    let bestMove = minimax(AIplayer, board, 0);

    board[bestMove] = AIplayer.symbol;
    $('#' + bestMove).html(AIplayer.marker);
    game.turnNumber++;

    function minimax(player, board, depth) {

        let spotsNotMarked = emptyBoardSpots(board);

        if (thisPlayerWon(board, AIplayer.symbol)) {
            return 10-depth;
        }
        else if (thisPlayerWon(board, opponent.symbol)) {
            return depth-10;
        }
        else if (spotsNotMarked.length == 0) {
            return 0;
        }

        let moves = [];
        let scores = [];

        for (let i = 0; i < spotsNotMarked.length; i++) {

            let index = spotsNotMarked[i];

            let score;

            board[index] = player.symbol;

            if (player == X) {
                score = minimax(O, board, depth+1);
            }
            else {
                score = minimax(X, board, depth+1);
            }

            scores.push(score);

            board[index] = index;

            moves.push(index);
        }

        if (player == AIplayer) {
            return moves[scores.indexOf(Math.max(...scores))];
        }
        else {
            return moves[scores.indexOf(Math.min(...scores))];
        }
    }
}

function emptyBoardSpots(board) {
    return board.filter(square => !isNaN(square));
}

$('.AI-Switch').on('change', function() {
    if (!game.started) {
        this.id == "one" ? (X.isComputer = !(X.isComputer), ai1 = X) : (O.isComputer = !(O.isComputer), ai2 = O);
    }
});

$('#resetButton').on('click', function() {
    clearTimeout(clearBoardTimeoutID);
    $('.square').empty();
    $('.scores').text("0");
    game.board = [0,1,2,3,4,5,6,7,8];
    game.firstTurn = X;
    game.xScore = 0;
    game.oScore = 0;
    game.turnNumber = 0;
    game.started = false;
});

$('#startButton').on('click', function() {
    game.started = true;
});

$('.square').on('click', function() {
    if (game.started && !isNaN(game.board[this.id])) {

        player = !(game.turnNumber % 2) ? game.firstTurn : (game.firstTurn == X ? O : X);

        this.innerHTML = player.marker;
        game.board[this.id] = player.symbol;

        game.turnNumber++;

        if (game.turnNumber > 3 && thisPlayerWon(game.board, player.symbol)) {

            showWinnerAndUpdateScore(theWinningCombo, player);

            clearBoardForNextGame();
        }
        else if (game.turnNumber == 9) {
            clearBoardForNextGame();
        }

        if (O.isComputer && player == X) {
            AImove(player, game.board);
        }
        else if (X.isComputer && player == O) {
            AImove(player, game.board);
        }
    }
});
});

1 个答案:

答案 0 :(得分:2)

问题是minimax的返回值:它是分数还是移动?

问题

您认为递归调用应该返回一个分数,除了应该返回移动时的第一个调用。这确实很方便,但事情并非如此:

  • 只有在检测到胜利或平局时,该功能才会返回分数
  • 所有(!)其他情况下,将返回移动

这意味着在递归树的一半(不在叶子上),你也会向后移动,但是你将它们视为递归树内一级的分数。显然,任何结果都没有意义。

解决方案

让您的minimax函数返回分数,但同时移动(相关时)。这可以通过返回包含两条信息的对象来完成。

这是您的AImove功能,只需进行一些修改即可实现该功能。修改后的行标有***

function AImove(AIplayer, board) {
    AIplayer = !(game.turnNumber % 2) ? game.firstTurn : (game.firstTurn == X ? O : X);
    let opponent = AIplayer == X ? O : X;
    let bestMove = minimax(AIplayer, board, 0).move; // *** Get move part of return value
    board[bestMove] = AIplayer.symbol;
    $('#' + bestMove).html(AIplayer.marker);
    game.turnNumber++;

    function minimax(player, board, depth) {
        if (depth===2) console.log('step');
        let spotsNotMarked = emptyBoardSpots(board);
        if (thisPlayerWon(board, AIplayer.symbol)) {
            return { score: 10-depth }; // *** Object with score (there's no move)
        }
        else if (thisPlayerWon(board, opponent.symbol)) {
            return { score: depth-10 }; // *** idem
        }
        else if (spotsNotMarked.length == 0) {
            return { score: 0 }; // *** idem
        }
        let moves = [];
        let scores = [];
        for (let i = 0; i < spotsNotMarked.length; i++) {
            let index = spotsNotMarked[i];
            let score;
            board[index] = player.symbol;
            if (player == X) {
                score = minimax(O, board, depth+1).score; // *** Get score part
            }
            else {
                score = minimax(X, board, depth+1).score; // *** idem
            }
            scores.push(score);
            board[index] = index;
            moves.push(index);
        }

        let score = (player == AIplayer ? Math.max : Math.min)(...scores); // *** Get score
        return { score, move: moves[scores.indexOf(score)] }; // *** Return both move & score
    }
}