【问题标题】:Tic Tac Toe 3D - Minimax optimization井字游戏 3D - Minimax 优化
【发布时间】:2018-01-08 22:21:06
【问题描述】:

我有这个用于井字游戏的 Minimax 的 JavaScript 实现。它最适合 3x3 板尺寸,但当涉及 3D 板时,页面崩溃。认为 PHP 会更快,并尝试通过对 PHP 使用 AJAX 请求来调用 Minimax,但它不起作用 - 超过了 900 秒的最大执行时间。

代码如下:

var winningCombos   = new Array();

winningCombos[0]    = [0, 1, 2];
winningCombos[1]    = [3, 4, 5];
winningCombos[2]    = [6, 7, 8];

// Per columns
winningCombos[3]    = [0, 3, 6];
winningCombos[4]    = [1, 4, 7];
winningCombos[5]    = [2, 5, 8];

// Diagonals
winningCombos[6]    = [0, 4, 8];
winningCombos[7]    = [2, 4, 6];

// Second board
//Per lines
winningCombos[8]    = [9, 10, 11];
winningCombos[9]    = [12, 13, 14];
winningCombos[10]   = [15, 16, 17];

// Per columns
winningCombos[11]   = [9, 12, 15];
winningCombos[12]   = [10, 13, 16];
winningCombos[13]   = [11, 14, 17];

// Diagonals
winningCombos[14]   = [9, 13, 17];
winningCombos[15]   = [11, 13, 15];

// Third board
// Per lines
winningCombos[16]   = [18, 19, 20];
winningCombos[17]   = [21, 22, 23];
winningCombos[18]   = [24, 25, 26];

// Per columns
winningCombos[19]   = [18, 21, 24];
winningCombos[20]   = [19, 22, 25];
winningCombos[21]   = [20, 23, 26];

// Diagonals
winningCombos[22]   = [18, 22, 26];
winningCombos[23]   = [20, 22, 24];

// 3D Winning
winningCombos[24]   = [0, 13, 26];
winningCombos[25]   = [20, 13, 6];

// Per lines
winningCombos[26]   = [0, 10, 20];
winningCombos[27]   = [3, 13, 23];
winningCombos[28]   = [6, 16, 26];

let free            = ' ';
let boardSize       = 27;
let board           = new Array();
let activePlayer    = 'Human';
let i;
let choice;
let human;
let humanTurn;
let computer;
let computerTurn;
let humanWin = 0;
let computerWin = 0;

$(document).ready(function() {
    $("button").click(function(){
        $("#intro").addClass("hidden");
        $("#board").removeClass("hidden");
    });
});

function startGame(player) {
    for (i = 0; i < boardSize; i += 1) {
        board[i] = free;
    }

    activePlayer = 'Human';

    if (player == "X") {
        human = "X";
        humanTurn = "<p>X</p>";
        computer = "O";
        computerTurn = "<p>O</p>";
    } else {
        human = "O";
        humanTurn = "<p>O</p>";
        computer = "X";
        computerTurn = "<p>X</p>";
    }
}

function makeMove(pos) {
    if (board[pos] === free && !gameOver(board)) {
        board[pos] = human;
        document.getElementById(pos).innerHTML = humanTurn;

        if (!gameOver(board)) {
            activePlayer = 'Computer';
            makeComputerMove();
        }
    }
}

function makeComputerMove() {
    miniMax(board, 0);
    var move = choice;
    board[move] = computer;
    document.getElementById(move).innerHTML = computerTurn;
    choice = [];
    activePlayer = 'Human';
}

function score(possibleGame) {
    var score = getWinner(possibleGame);

    if (score === 3) {
        return 0;
    } else if (score === 1) {
        return -1;
    } else if (score === 2) {
        return 1;
    }
}

function miniMax(tempBoard, depth) {
    if (getWinner(tempBoard) !== 0) {
        return score(tempBoard);
    }

    depth += 1;
    var scores  = new Array();
    var moves   = new Array();
    var availableMoves = getAvailableMoves(tempBoard);
    var move, possibleGame;
    for (var i = 0; i < availableMoves.length; i += 1) {
        move = availableMoves[i];
        possibleGame = generateNewGame(move, tempBoard);
        scores.push(miniMax(possibleGame, depth));
        moves.push(move);
        tempBoard = undoMove(tempBoard, move);
    }

    var maxScore, maxScoreIndex, minScore, minScoreIndex;

    if (activePlayer === 'Computer') {
        maxScore = Math.max.apply(Math, scores);
        maxScoreIndex = scores.indexOf(maxScore);
        choice = moves[maxScoreIndex];

        return scores[maxScoreIndex];

    } else {
        minScore = Math.min.apply(Math, scores);
        minScoreIndex = scores.indexOf(minScore);
        choice = moves[minScoreIndex];

        return scores[minScoreIndex];
    }
}

function undoMove(possibleGame, move) {
    possibleGame[move] = free;
    changePlayerTurn();
    return possibleGame;
}

function getAvailableMoves(tempBoard) {
    var availableMoves = new Array();
    for (var i = 0; i < boardSize; i += 1) {
        if (board[i] === free) {
            availableMoves.push(i);
        }
    }
    return availableMoves;
}

function generateNewGame(move, possibleGame) {
    var piece = changePlayerTurn();
    possibleGame[move] = piece;
    return possibleGame;
}

function changePlayerTurn() {
    var turn;
    if (activePlayer === 'Computer') {
        turn = computer;
        activePlayer = 'Human';
    } else {
        turn = human;
        activePlayer = 'Computer';
    }

    return turn;
}

function gameOver(tempBoard) {
    if (getWinner(tempBoard) === 0) {
        return 0;
    } else if (getWinner(tempBoard) === 1) {
        alert("You won!");
        humanWin += 1;
        if (human === "X") {
            document.getElementById("xPlayerScore").value = humanWin;
        } else {
            document.getElementById("oPlayerScore").value = humanWin;
        }
    } else if (getWinner(tempBoard) === 2) {
        alert("Computer won!");
        computerWin += 1;
        if (computer === "X") {
            document.getElementById("xPlayerScore").value = computerWin;
        } else {
            document.getElementById("oPlayerScore").value = computerWin;
        }
    } else if (getWinner(tempBoard) === 3) {
        alert("The game was a draw!");
    }
    return 1;
}

function getWinner(tempBoard) {
    for (i = 0; i < winningCombos.length; i += 1) {
        if (tempBoard[winningCombos[i][0]] === human &&
            tempBoard[winningCombos[i][1]] === human &&
            tempBoard[winningCombos[i][2]] === human) {
            return 1; // human won
        } else if (tempBoard[winningCombos[i][0]] === computer &&
            tempBoard[winningCombos[i][1]] === computer &&
            tempBoard[winningCombos[i][2]] === computer) {
            return 2; // computer won
        }
    }

    // if (tempBoard[0] === human && tempBoard[1] === human && tempBoard[2] === human) {
        //  return 1;
        // } else if (tempBoard[0] === computer && tempBoard[1] === computer && tempBoard[2] === computer) {
        //  return 2;
        // }

        // if (tempBoard[3] === human && tempBoard[4] === human && tempBoard[5] === human) {
        //  return 1;
        // } else if (tempBoard[3] === computer && tempBoard[4] === computer && tempBoard[5] === computer) {
        //  return 2;
        // }

        // if (tempBoard[6] === human && tempBoard[7] === human && tempBoard[8] === human) {
        //  return 1;
        // } else if (tempBoard[6] === computer && tempBoard[7] === computer && tempBoard[8] === computer) {
        //  return 2;
        // }

        // if (tempBoard[0] === human && tempBoard[3] === human && tempBoard[6] === human) {
        //  return 1;
        // } else if (tempBoard[0] === computer && tempBoard[3] === computer && tempBoard[6] === computer) {
        //  return 2;
        // }

        // if (tempBoard[1] === human && tempBoard[4] === human && tempBoard[7] === human) {
        //  return 1;
        // } else if (tempBoard[1] === computer && tempBoard[4] === computer && tempBoard[7] === computer) {
        //  return 2;
        // }

        // if (tempBoard[2] === human && tempBoard[5] === human && tempBoard[8] === human) {
        //  return 1;
        // } else if (tempBoard[2] === computer && tempBoard[5] === computer && tempBoard[8] === computer) {
        //  return 2;
        // }

        // if (tempBoard[0] === human && tempBoard[4] === human && tempBoard[8] === human) {
        //  return 1;
        // } else if (tempBoard[0] === computer && tempBoard[4] === computer && tempBoard[8] === computer) {
        //  return 2;
        // }

        // if (tempBoard[2] === human && tempBoard[4] === human && tempBoard[6] === human) {
        //  return 1;
        // } else if (tempBoard[2] === computer && tempBoard[4] === computer && tempBoard[6] === computer) {
        //  return 2;
        // }

    if (tempBoard.indexOf(free) >= 0) {
        return 0; // not finished yet
    }

    return 3; // the game was a draw
}

function playAgain() {
    if (getWinner(board) != 0) {
        for (var j = 0; j < boardSize; j += 1) {
            document.getElementById(j).innerHTML = "";
        }
        resetBoard();
    }
}

function resetBoard() {
    for (i = 0; i < boardSize; i += 1) {
        board[i] = free;
    }
}

在 HTML 中,单击时会调用 makeMove(position) 函数。

我确实了解它必须生成多少个位置和可能的游戏,并且经过的时间在某种程度上是正常的,但我想知道是否有一段代码可以优化以能够构建一个有效的井字游戏脚趾 3D。

【问题讨论】:

    标签: javascript jquery minimax


    【解决方案1】:

    您可能想尝试设置深度限制,因为递归调用会很快在此处累加。比如:

    if(depth < MAX_DEPTH) {
       for (var i = 0; i < availableMoves.length; i += 1) {
            move = availableMoves[i];
            possibleGame = generateNewGame(move, tempBoard);
            scores.push(miniMax(possibleGame, depth));
            moves.push(move);
            tempBoard = undoMove(tempBoard, move);
       }
    }
    

    这可能会给你一个不错的对手,同时减少递归调用。

    如果这不是一个选项(假设您仍在浏览器的调用堆栈限制内)。您可以将繁重的工作转移给网络工作者,因为这将允许它在后台运行。这将需要一些重写。请参阅:https://www.w3schools.com/html/html5_webworkers.asp 和 https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

    【讨论】:

    • 您好,谢谢您的回答。所以网络工作者会更快地生成选择,或者它只会消除页面崩溃问题?
    • webworker 不需要让它更快,而是在后台运行。这有望使您的浏览器不会挂起,并且您可以在完成时显示“对手正在思考...”之类的消息。但是,如果超过最大调用堆栈大小,您仍然可能会收到错误或崩溃。有关尺寸信息,请参阅:stackoverflow.com/questions/7826992/…
    • 好吧,当我第一次从你那里听说网络工作者时,我不得不做一些研究。我有一个可能的解决方案,那就是创建一个名为 init 的新文件,其中包含所有声明(板、免费、计算机、播放器等),以及网络工作者的新文件(我将把所有的 miniMax 功能)。我在makeComputerMove 函数(从上面)中调用网络工作者,并从网络工作者文件中发送名为choice 的变量作为响应。这是一个好概念吗?
    • 很高兴听到您取得了进展。你的概念听起来不错。想不出有什么要改变的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-21
    • 1970-01-01
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多