Tic Tac Toe 3D - Minimax优化

时间:2018-01-08 22:21:06

标签: javascript jquery minimax

我有一个Minimax的JavaScript实现用于Tic Tac Toe游戏。它适用于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中,我点击时会调用div makeMove(position)函数。

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

1 个答案:

答案 0 :(得分: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);
   }
}

这可能会在减少递归呼叫的同时为您提供一个体面的对手。

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