使用minimax /计算机移动选择(TicTacToe / Javascript)的错误?

时间:2013-05-23 13:52:32

标签: javascript html minimax game-theory

我正在尝试实施一个单人游戏,在这个游戏中,计算机玩家永远不会失败(每次强制平局或获胜)。在搜索之后似乎使用minimax策略是相当标准的,但我似乎无法使我的算法正常工作,这导致计算机选择了一个非常糟糕的举动。任何人都可以帮我找到我的代码出错的地方吗?

'O'是用户(最大),'X'是计算机(最小)

编辑:我重新发布了我的代码,我修复了getComputerNextMove中的一些内容以及一些小错误...现在我的代码找到了更好的分数,但如果有人获胜,它并不总是有用(功能似乎没问题,我想我只是没有检查正确的位置)。它也没有采取最佳举措。底部有一些测试人员功能可以查看minimax如何工作/测试游戏状态(获胜者/抽奖)。

tictactoe.js:

// Setup
var user = new Player('user', 'O', 'green');
var computer = new Player('computer', 'X', 'pink');
window.players = [user, computer];

var gameBoard = new GameBoard();
gameBoard.initializeBoard();

// Trackers for the board and moves
var wins = ['012', '345', '678', '036', '147', '258', '048', '246'];
var moves = 1;
var currentPlayer = players[0];
var game = true;

function Player(name, selector, color) {
    this.name = name;
    this.moves = [];
    this.selector = selector;
    this.color = color;

    this.makeMove = function(boxId){
        if(!gameBoard.isGameOver() && gameBoard.isValidMove(boxId)){
            markBox(this, boxId);
            this.updateMoves(boxId);
            gameBoard.updateBoard(this.selector, boxId);
            setTimeout(function () { nextTurn() }, 75);
        }
        else{
            if(gameBoard.isXWinner())
                endGame("Computer");
            else if(gameBoard.isOWinner())
                endGame("You");
            else
                endGame();
        }
    }

    this.updateMoves = function(boxId){
        moves = moves + 1;
        this.moves.push(boxId);
    }
}

function Square(mark){
    this.mark = mark;
    this.position;
    this.id;
    this.score = 0; //for minimax algorithm
}

function GameBoard(){
    this.state = [9];

    this.initializeBoard = function(){
        for(var space = 0; space < 9; space++){
            var square = new Square("-");
            this.state[space] = square;
            this.state[space].position = space;
            this.state[space].id = space;
            this.state[space].score = 0;
        }
    }

    this.getAvailableSpaces = function(){
        var availableSpaces = [];
        for(var space = 0; space < this.state.length; space++){
            if(this.state[space].mark === "-")
                availableSpaces.push(this.state[space]);
        }
        return availableSpaces;
    }

    this.updateBoard = function(selector, position){
        this.state[position].mark = selector;
    }

    this.getPositionOfMarks = function(){
        var positionOfMarks = [];
        for(var sqr=0; sqr<this.state.length; sqr++){
            var positionOfMark = {
                'position' : this.state[sqr].position,
                'mark' : this.state[sqr].mark
            }
            positionOfMarks.push(positionOfMark);
        }
        return positionOfMarks;
    }

    this.getXMovesAsString = function(){
        var positionOfMarks = this.getPositionOfMarks();
        var xMoves = "";
        for(var pm=0; pm < positionOfMarks.length; pm++){
            if(positionOfMarks[pm].mark === "X"){
                var m = parseInt(positionOfMarks[pm].position);
                xMoves += m;
            }
        }
        return xMoves;
    }

    this.getOMovesAsString = function(){
        var positionOfMarks = this.getPositionOfMarks();
        var oMoves = "";
        for(var pm=0; pm < positionOfMarks.length; pm++){
            if(positionOfMarks[pm].mark === "O"){
                var m = parseInt(positionOfMarks[pm].position);
                oMoves += m;
            }
        }
        return oMoves;
    }

    this.isValidMove = function(boxId){
        return this.state[boxId].mark === "-"
    }

    this.isOWinner = function(){
        var winner = false;
        var oMoves = this.getOMovesAsString();
        for(var win=0; win<wins.length; win++){
            if(oMoves.search(wins[win]) >= 0)
                winner = true
        }
        return winner;
    }

    this.isXWinner = function(){
        var winner = false;
        var xMoves = this.getXMovesAsString();
        for(var win=0; win<wins.length; win++){
            if(xMoves.search(wins[win]) >= 0)
                winner = true;
        }
        return winner;
    }

    this.isDraw = function(){
        var draw = true;
        if(!this.isOWinner() && !this.isXWinner()){
            for(var space=0; space < this.state.length; space++){
                if(this.state[space].mark === "-"){
                    draw = false;
                }
            }
        }
        return draw;
    }

    this.isGameOver = function(){
        var gameOver = false;
        if(gameBoard.isDraw() ||
            gameBoard.isOWinner() ||
            gameBoard.isXWinner())
                gameOver = true;
        return gameOver;
    }

    this.printBoardToConsole = function(){
        var row1 = [];
        var row2 = [];
        var row3 = [];
        for(var space=0; space<this.state.length; space++){
            if(space < 3)
                row1.push((this.state[space].mark));
            else if(space >= 3 && space < 6)
                row2.push((this.state[space].mark));
            else
                row3.push((this.state[space].mark));
        }

        console.log(row1);
        console.log(row2);
        console.log(row3);
    }

    GameBoard.prototype.clone = function(){
        var clone = new GameBoard();
        clone.initializeBoard();
        for(var square=0; square < this.state.length; square++){
            clone.state[square].mark = this.state[square].mark;
            clone.state[square].position = this.state[square].position;
            clone.state[square].id = this.state[square].id;
            clone.state[square].score = this.state[square].score;
        }
        return clone;
    }
}


// Handle clicks
$(".box").click(function (event) {
    if (getCurrentPlayer() === user && game == true){
        user.makeMove(event.target.id);
    }
    else overlay("Wait your turn!");
});

// *** MOVES ***
// With every move, these functions update the state of the board
function getComputerNextMove(board){
    var availableSquares = board.getAvailableSpaces();
    var prevScore = -100000;
    var bestMove;
    for(var square = 0; square < availableSquares.length; square++){
        var gameBoardChild = board.clone();
        gameBoardChild.updateBoard("X", availableSquares[square].position);
        console.log("gameBoardChild: " + gameBoardChild.printBoardToConsole());
        console.log("===================");
        var currentScore = min(gameBoardChild);
        console.log("Child Score: " + currentScore);
        if(currentScore > prevScore){
            prevScore = currentScore;
            bestMove = availableSquares[square].position;
            console.log("Best Move: " + bestMove);
        }
    }
    console.log("====================================");
    return bestMove;
}

function max(board){
    if(board.isOWinner()) return 1;
    else if(board.isXWinner()) return -1;
    else if(board.isDraw()) return 0;

    var availableSquares = board.getAvailableSpaces();
    var bestScore = -100000;
    for(var square=0; square<availableSquares.length; square++){
        var gameBoardChild = board.clone();
        gameBoardChild.updateBoard("O", availableSquares[square].position);
        var move = min(gameBoardChild);
        if(move > bestScore)
            bestScore = move;
    }
    return bestScore;
}

function min(board){
    if(board.isOWinner()) return 1;
    else if(board.isXWinner()) return -1;
    else if(board.isDraw()) return 0;

    var availableSquares = board.getAvailableSpaces();
    var bestScore = 100000;
    for(var square=0; square<availableSquares.length; square++){
        var gameBoardChild = board.clone();
        gameBoardChild.updateBoard("X", availableSquares[square].position);
        var move = max(gameBoardChild);
        if(move < bestScore)
            bestScore = move;
    }
    return bestScore;
}

function markBox(player, boxId) {
    $("#" + boxId).text(player.selector).addClass(player.color);
}

// *** SETTERS & GETTERS
function getCurrentPlayer() {
    return currentPlayer;
}

function setCurrentPlayer(player) {
    currentPlayer = player;
    return currentPlayer;
}

function nextTurn() {
    if(!gameBoard.isGameOver()){
        if (getCurrentPlayer() === user) {
            setCurrentPlayer(computer);
            computer.makeMove(getComputerNextMove(gameBoard));
        }
        else setCurrentPlayer(user);
    }
    else{
        if(gameBoard.isOWinner())
            endGame("You");
        else if(gameBoard.isXWinner())
            endGame("Computer");
        else
            endGame();
    }
}

function endGame(winner) {
    game = false;
    if (winner) {
        overlay(winner.name + " wins!");
    } else overlay("It's a draw!");
}

//Toggles info box (to avoid using alerts)
function overlay(message) {
    $("#info").text(message);
    $el = $("#overlay");
    $el.toggle();
}

function buildTestBoard(test){
    var board = new GameBoard();
    board.initializeBoard();

    if(test === "minimax"){
        board.updateBoard("O", 0);
        board.updateBoard("O", 2);
        board.updateBoard("O", 4);
        board.updateBoard("O", 8);
        board.updateBoard("X", 1);
        board.updateBoard("X", 6);
        board.updateBoard("X", 7);
    }

    else if(test === "xwin"){
        board.updateBoard("X", 6);
        board.updateBoard("X", 4);
        board.updateBoard("X", 2);
    }

    board.printBoardToConsole();
    return board;
}

function testMinimax(){
    var board = buildTestBoard("minimax");
    var move = getComputerNextMove(board);
    console.log("Best move: " + move);
}

function testWinner(){
    var board = buildTestBoard("xwin");
    return board.isXWinner();
}

的index.html:

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width">

        <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->

        <link rel="stylesheet" href="css/normalize.css">
        <link rel="stylesheet" href="css/main.css">
        <script src="js/vendor/modernizr-2.6.2.min.js"></script>

    </head>
    <body>
        <!--[if lt IE 7]>
            <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please
                <a href="http://browsehappy.com/">upgrade your browser</a> or
                <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a>
                to improve your experience.
            </p>
        <![endif]-->
        <h2 id="title">Single Player Tic Tac Toe</h2>

        <div id='0' class='box'></div>
        <div id='1' class='box'></div>
        <div id='2' class='box'></div>
        <br />
        <div id='3' class='box'></div>
        <div id='4' class='box'></div>
        <div id='5' class='box'></div>
        <br />
        <div id='6' class='box'></div>
        <div id='7' class='box'></div>
        <div id='8' class='box'></div>

        <div id="overlay">
            <div>
                <p id="info"></p>
            </div>
        </div>



        <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
        <script>
            var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];
            (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
            g.src='//www.google-analytics.com/ga.js';
            s.parentNode.insertBefore(g,s)}(document,'script'));
        </script>
    </body>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.1.min.js"><\/script>')</script>
    <script src="js/plugins.js"></script>
    <script src="js/main.js"></script>
    <script src="js/game/tictactoe.js"></script>
</html>

的CSS:

.box {
    width:100px;
    height:100px;
    display:inline-block;
    background:#eee;
    margin:3px 1px;
    text-align:center;
    font-size:24px;
    font-family:arial;
    vertical-align:bottom;
    line-height:450%
}
.box:hover {
    cursor: pointer;
}
a {
    text-decoration:none;
}
.label {
    position:relative;
    bottom:0;
    right:0
}
.green {
    background:#90EE90;
}
.pink {
    background:#FDCBBB;
}
.credit {
    color:#333;
    font-family:arial;
    font-size:13px;
    margin-top:40px;
}

#overlay {
    position: absolute;
    left: 300px;
    top: 0px;
    width:20%;
    height:20%;
    text-align:center;
    z-index: 1000;
}

#overlay div {
    width:100px;
    margin: 100px auto;
    background-color: #fff;
    border:1px solid #000;
    padding:15px;
    text-align:center;
    align: right;
}

0 个答案:

没有答案