I created an HTML5/JS tic-tac-toe game as an exercise and wanted some advice on my js code. A couple of questions: How can I improve the reset method? (currently reloads the page/re-initializes the game)Anything advice on other improvements?Live demo: http://jsfiddle.net/mbtt53tt/tictactoe.html<!DOCTYPE html><html><head>    <title>Triple T's</title>    <meta name=viewport content=width=device-width, initial-scale=1>    <style>        * {box-sizing:border-box;}        body {width:100%;max-width:500px;padding: 0 20px;margin: 0 auto;font: 20px arial;text-align:center;}        h1 {font-size:32px;width:100%;margin:20px auto;}        #game,#reset {margin: 20px auto 0;display: block;width:100%;}        #scoreboard {border:2px solid #ccc;width:100%;margin:10px auto;padding:10px;}        ul {display:flex;list-style:none;margin:0;padding:0;width:100%;}        li {margin:0;padding:0;flex-grow:1;}        button {border:1px solid #ccc;background:#fefefe;padding:7px 14px;margin:10px 20px;}    </style></head><body>    <h1>Triple T's</h1>    <div id=scoreboard>        <ul>            <li>X: <span id=score_x></span></li>            <li>Ties: <span id=score_tie></span></li>            <li>O: <span id=score_o></span></li>        </ul>    </div>    <div id=reset>        <button id=resetScore>Reset score</button>        <button id=resetGame>New Game</button>    </div>    <canvas id=game width=500 height=500>        This text is displayed if your browser does not support HTML5 Canvas.    </canvas>    <script async src=./triplet.js></script></body></html>triplet.js(function () {    function Board(id, c, r) {        if (this instanceof Board) {            this.CANVAS = document.getElementById(id);            this.CTX = this.CANVAS.getContext(2d);            this.WIDTH = this.CANVAS.width || 0;            this.HEIGHT = this.CANVAS.height || 0;            this.COLS = c || 3;            this.ROWS = r || 3;            this.TILEWIDTH = (this.WIDTH / this.COLS);            this.moveCount = 0;            this.board = this.gameBoard(this.TILEWIDTH, this.COLS, this.ROWS);            this.CANVAS.addEventListener('selectstart', function (e) {                e.preventDefault();                return false;            }, false);            this.winner = [false, ];            this.boardDisabled = false;        } else {            return new Board(id, c, r);        }    }    Board.prototype.draw = function () {        var ctx = this.CTX;        ctx.beginPath();        ctx.lineWidth = 5;        ctx.strokeStyle = #168dd9;        // Draw column dividers        for (var i = 1; i <= this.COLS - 1; i++) {            ctx.moveTo(this.TILEWIDTH * i, 0);            ctx.lineTo(this.TILEWIDTH * i, this.HEIGHT);        }        //Draw horizontal dividers        for (var i = 1; i <= this.ROWS - 1; i++) {            ctx.moveTo(0, this.TILEWIDTH * i);            ctx.lineTo(this.WIDTH, this.TILEWIDTH * i);        }        ctx.stroke();    };    Board.prototype.gameBoard = function (t, c, r) {        var b = [],            count = 0;        // Create gameboard array with the following data:        // [x pos, y pos, tile count, empty string for move symbol (x or o)]        for (var y = 0; y < r; y++) {            for (var x = 0; x < c; x++) {                b.push([x * t, y * t, count++, ]);            }        }        return b;    };    Board.prototype.updateScore = function () {        if (supports_html5_storage()) {            var p = sessionStorage.score || {                    score_x: 0,                    score_o: 0,                    score_tie: 0                },                w = score_ + (this.winner[1][0] || tie);            if (sessionStorage.score) {                p = JSON.parse(p);            }            p[w] ++;            sessionStorage.score = JSON.stringify(p);            this.updateScoreBoard();        }    };    Board.prototype.updateScoreBoard = function () {        if (supports_html5_storage()) {            var p = sessionStorage.score ? JSON.parse(sessionStorage.score) : {                score_x: 0,                score_o: 0,                score_tie: 0            };            for (var s in p) {                if (p.hasOwnProperty(s)) {                    document.getElementById(s).innerHTML = p[s];                }            }        }    };    Board.prototype.reset = function (x) {        var timer = x || 4000;        window.setTimeout(function () {                window.location.reload(false);            }, timer);    };    Board.prototype.resetScore = function () {        if (supports_html5_storage()) {            sessionStorage.removeItem(score);            this.updateScoreBoard();        }    };    Board.prototype.move = function (coor) {        var width = this.TILEWIDTH,            ctx = this.CTX,            board = this.board,            blen = board.length;        //Loop through and find tile that click was detected on        for (var i = 0; i < blen; i++) {            if (coor.x > board[i][0] && coor.y > board[i][1] && coor.x < board[i][0] + width && coor.y < board[i][1] + width) {                var x = board[i][0],                    y = board[i][1],                    validTile = board[i][3] === ;                if (validTile) {                    if (this.moveCount++ % 2 === 1) {                        moveO(x, y, width, ctx);                        board[i][3] = o;                    } else {                        moveX(x, y, width, ctx);                        board[i][3] = x;                    }                }                //Check board for winner if move count is 5 or more                if (this.moveCount > 4) {                    this.winner = checkWinner(board);                    var w = this.winner,                        winner = w[0],                        shape = w[1][0],                        boardDisabled = this.boardDisabled;                    //If there is a winner, redraw winning tiles in red                    if (winner && !boardDisabled) {                        if (shape === o) {                            for (var j = 1; j < 4; j++) {                                moveO(board[w[j][1]][0], board[w[j][1]]                                    [1], width, ctx, red, 5);                            }                        } else {                            for (var j = 1; j < 4; j++) {                                moveX(board[w[j][1]][0], board[w[j][1]]                                    [1], width, ctx, red, 5);                            }                        }                    }                    if ((winner || this.moveCount === board.length) && !boardDisabled) {                        if (!winner) {                            //If tie, redraw all moves in red                            for (var j = 0; j < board.length; j++) {                                if (board[j][3] === o) {                                    moveO(board[j][0], board[j][1], width, ctx, red, 5);                                } else {                                    moveX(board[j][0], board[j][1], width, ctx, red, 5);                                }                            }                        }                        this.boardDisabled = true;                        this.updateScore();                        this.reset();                    }                }                break;            }        }    };    function checkWinner(mArr) {        var winner = [false, ];        for (var i = 0; i < mArr.length; i++) {            var hor = [],                ver = [],                diag = [];            if (mArr[i][3] !== ) {                //horizontal                if (i % 3 === 0) {                    for (var j = 0; j < 3; j++) {                        hor.push([mArr[i + j][3], i + j]);                    }                    if (hor.length === 3) {                        winner = isWinner(hor);                        if (winner[0]) {                            return winner;                        }                    }                }                //vertical && diag/anti diag                if (i < 3) {                    for (var j = 0; j + i < mArr.length; j += 3) {                        ver.push([mArr[i + j][3], i + j]);                    }                    if (ver.length === 3) {                        winner = isWinner(ver);                        if (winner[0]) {                            return winner;                        }                    }                    if (i !== 1) {                        for (var z = 0; z + i < mArr.length - i; z += (4 - i)) {                            diag.push([mArr[i + z][3], i + z]);                        }                        if (diag.length === 3) {                            winner = isWinner(diag);                            if (winner[0]) {                                return winner;                            }                        }                    }                }            }        }        return winner;    }    function isWinner(arr) {        arr.sort();        var w = arr[0][0] && arr[0][0] === arr[arr.length - 1][0] ? [true].concat(arr) : [false, ];        return w;    }    function moveO(x, y, r, ctx, fill, lineW) {        var x = x + r / 2,            y = y + r / 2,            r = r / 2 - (r * 0.15);        ctx.beginPath();        ctx.lineWidth = lineW || 3;        ctx.strokeStyle = fill || #333;        ctx.arc(x, y, r, 0, 2 * Math.PI);        ctx.stroke();    }    function moveX(x, y, w, ctx, fill, lineW) {        var pad = w * 0.15,            lineCoor = [                [                    [x + pad, y + pad], //line 1 start                    [x + w - pad, y + w - pad] //line 1 end                ],                [                    [x + pad, y + w - pad], //line 2 start                    [x + w - pad, y + pad] //line 2 end                ]            ];        ctx.beginPath();        ctx.lineWidth = lineW || 3;        ctx.strokeStyle = fill || #333;        for (var i = 0; i < 2; i++) {            ctx.moveTo(lineCoor[i][0][0], lineCoor[i][0][1]);            ctx.lineTo(lineCoor[i][1][0], lineCoor[i][1][1]);        }        ctx.stroke();    }    function clickTouch(e) {        var coor = b.CANVAS.relMouseCoords(e);        if (!b.winner[0]) {            b.move(coor);        }    }    function clickTouchReset(e) {            var target = e.target.id;            if (target === resetScore && confirm(Are you sure you want to reset the score?)) {                b.resetScore();            } else if (target === resetGame) {                b.reset(1);            }        }        // Initialize Game    var b = new Board(game, 3, 3),        resetcon = document.getElementById(reset);    b.draw();    b.updateScoreBoard();    //Add event listeners for click or touch    window.addEventListener(click, clickTouch, false);    window.addEventListener(touchstart, clickTouch, false);    resetcon.addEventListener(click, clickTouchReset, false);    resetcon.addEventListener(touchstart, clickTouchReset, false);})();/*****Get Mouse click coordinates within canvasModified to include touch eventsSource: http://stackoverflow.com/a/9961416******/HTMLCanvasElement.prototype.relMouseCoords = function (event) {    var totalOffsetX = 0,        totalOffsetY = 0,        canvasX = 0,        canvasY = 0,        touch = event.touches,        currentElement = this;    do {        totalOffsetX += currentElement.offsetLeft;        totalOffsetY += currentElement.offsetTop;    }    while (currentElement = currentElement.offsetParent)    canvasX = (touch ? touch[0].pageX : event.pageX) - totalOffsetX;    canvasY = (touch ? touch[0].pageY : event.pageY) - totalOffsetY;    canvasX = Math.round(canvasX * (this.width / this.offsetWidth));    canvasY = Math.round(canvasY * (this.height / this.offsetHeight));    return {        x: canvasX,        y: canvasY    }}function supports_html5_storage() {    try {        return 'sessionStorage' in window && window.sessionStorage !== null;    } catch (e) {        return false;    }}
HTML5 Tic-Tac-Toe game
javascript;game;html5;canvas;tic tac toe
This is not a complete review, just a couple of points that jumped out at me:JavaScriptNamingShort variable names are hard to understand. While ctx, e, etc are somewhat standard, r, lineW, w, coor, b, t, c, b, s, p, mArr, ver, etc are not and it is very hard to understand what they represent. Will you in six month still understand what gameBoard = function (t, c, r) is without searching in the code? I would guess not. But if you use gameBoard = function (tileWidth, columnCount, rowCount), it is immediately clear what it does.So my recommendation would be: find good variable names for the one character names, and write out the abbreviated names (ver, hor, etc).Some function names also do not express well what they do: draw might be better named drawGameField and moveO better drawO (same for moveX).BugsIf I set row and column count to a number different from 3, it seems that nobody can win. Long functionsThe move function is definitely too long. I would at least extract the two red redraws to their own function. Saving fields in local variablesIn your move method, you save fields in local variables:     var width = this.TILEWIDTH,        ctx = this.CTX,        board = this.board,        blen = board.length;Why are you doing this instead of using the fields directly? This seems unnecessary and confusing.Functions of BoardWhy are some functions not part of board? move is, but moveX and moveO are not, for example. This seems odd.CommentsComments on functions are especially important when the variable names are not too good. But even if you change them, I would still like some comments.For example: What is the acceptable range for tile width? If I use for example 50, the board does not look good anymore.Another example: What happens if I set row and column to 4 instead of 3?And one last example: What does draw draw? Everything? Or only the game field, but not the players choices?ResetI extracted your init code in a function:var b;// Initialize Gamefunction init() {    b = new Board(game, 3, 3),            resetcon = document.getElementById(reset);    b.draw();    b.updateScoreBoard();    //Add event listeners for click or touch    window.addEventListener(click, clickTouch, false);    window.addEventListener(touchstart, clickTouch, false);    resetcon.addEventListener(click, clickTouchReset, false);    resetcon.addEventListener(touchstart, clickTouchReset, false);}init();And changed your reset function like this:Board.prototype.reset = function(x) {    this.CANVAS.width = this.CANVAS.width; // clear canvas    init();};And I removed this.reset(); from move. Now the reset works without reloading each time. CSSI would put this in an external css file, and format it properly (each attribute on its own line, etc.). If you care about performance, minify with a tool later instead of writing harder to read code.