HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Tic Tac Toe with Minimax (memory management optimization)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
minimaxwithtoetictacoptimizationmemorymanagement

Problem

I've made a Tic Tac Toe game with minimax in Javascript. It works but I have to use location.reload(true); to automatically reload the page every time user starts a new game (by pressing 'Play again!' in the modal), otherwise the game becomes slower with every round until it stops responding. I guess the problem is in the way I've written runGame() function which does not free the memory and keeps all the context through closure throughout all the rounds. My question is therefore how can I rewrite it so I don't have to reload page on each new round and don't have those memory leaks.

The relevant code:

Board class

```
//Game's constants
var DRAW = 0, PLAYERX = 1, PLAYERO = 2,
SCORES = {
1: 1,
0: 0,
2: -1
};

var dims = 3; //dimensions for the game's board

//Class representing the game's board
function Board(dims, board) {
this.dims = dims;

this.grid = [];
for (var square = 0; square < dims * dims; square++) {
if (board) {
this.grid.push(board[square]);
} else {
this.grid[square] = 0;
}
}
}

//Show visual representation of the grid for debugging purposes
Board.prototype.showGrid = function () {
var grid = [];
for (var i = 0; i < dims * dims; i++) {
grid.push(this.grid[i]);
}
return grid;
};

/*Returns one of the three constants for EMPTY, PLAYERX, or PLAYERO
that correspond to the contents of the board at position (square).*/
Board.prototype.square = function (sqr) {

return this.grid[sqr];
};

//Return an array of all empty squares in form [squares]
Board.prototype.getEmptySquares = function () {
var empty = [];
for (var i = 0; i < this.dims * this.dims; i++) {
if (this.square(i) === 0) empty.push(i);
}
return empty;
};

/*Place player on the board at position (square).
player should be either the constant PLAYERX or PLAYERO.
Does nothing if board square is not empty.*/
Board.prototype.move = function (square

Solution

Your code is leaking indeed. jQuery .on is always adding events to the element. Not replacing them. Since you call this each time it's the player's turn. After 5 games, the event is already called 25 times for each click at the same time.

Fixed App.js

$(document).ready(function () {
    var board;
    //helper function
    function switchPlayer(player) {
        return player === PLAYERX ? PLAYERO : PLAYERX;
    }

    function minimax(board, player) {
        var mult = SCORES[String(player)], thisScore,
            empty = board.getEmptySquares(), l = empty.length,
            maxScore = -1, bestMove = null;

        if (board.checkWin() != 'None') {
            return [SCORES[board.checkWin()], 0];
        } else {
            for (var i = 0; i = maxScore) {
                    maxScore = thisScore;
                    bestMove = empty[i];
                }
            }

            return [mult * maxScore, bestMove];
        }
    }

    //Function that runs the game
    function runGame() {
        //Create a new board for the game
        board = new Board(dims);

        //clear previous board
        for (var i = 0; i ' + text + '');
        $('.winner').modal('show');
    }

    runGame();

    $('#replay').on('click', function () {
        $('.winner').modal('hide');
        //location.reload(true);
        runGame();
    });
});


(ps: shouldn't this be moved to stackoverflow)

Code Snippets

$(document).ready(function () {
    var board;
    //helper function
    function switchPlayer(player) {
        return player === PLAYERX ? PLAYERO : PLAYERX;
    }

    function minimax(board, player) {
        var mult = SCORES[String(player)], thisScore,
            empty = board.getEmptySquares(), l = empty.length,
            maxScore = -1, bestMove = null;

        if (board.checkWin() != 'None') {
            return [SCORES[board.checkWin()], 0];
        } else {
            for (var i = 0; i < l; i++) {
                var copy = board.clone();
                copy.move(empty[i], player);
                thisScore = mult * minimax(copy, switchPlayer(player))[0];

                if (thisScore >= maxScore) {
                    maxScore = thisScore;
                    bestMove = empty[i];
                }
            }

            return [mult * maxScore, bestMove];
        }
    }

    //Function that runs the game
    function runGame() {
        //Create a new board for the game
        board = new Board(dims);

        //clear previous board
        for (var i = 0; i < dims * dims; i++) {
            $('#' + i).text('');
        }
    }
    $('.square').on('click', function () {
        var id = $(this).attr('id');
        if (board.square(id) === 0) {
            $(this).text('X');
            board.move(id, PLAYERX);
            if (board.checkWin() === 'None') {
                AImove(board);
            } else {
                declareWinner(board.checkWin());
            }
        }else{
            // still player's move
        }
    });

    function AImove() {
        var move = minimax(board, PLAYERO)[1];
        board.move(move, PLAYERO);
        $('#' + move).text('O');
        if (board.checkWin() === 'None') {
            // player move
        } else {
            declareWinner(board.checkWin());
        }
    }

    function declareWinner(winner) {
        winner = winner === 1 ? 'Player X' : winner === 2 ? 'Player O' : 'Draw';
        var text = winner == 'Draw' ? "It's a draw!" : winner + ' wins!';
        $('.modal-body').html('<h3>' + text + '</h3>');
        $('.winner').modal('show');
    }

    runGame();

    $('#replay').on('click', function () {
        $('.winner').modal('hide');
        //location.reload(true);
        runGame();
    });
});

Context

StackExchange Code Review Q#112246, answer score: 2

Revisions (0)

No revisions yet.