Noughts and Crosses

by alexb

HTML

<div class="t3">
    <table>
        <tr>
            <td></td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td></td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td></td>
            <td></td>
            <td></td>
        </tr>
    </table>
    <canvas id="overlay" width="304" height="304"></canvas>
    <p class="winner-message">
        Congratulations <span class="winner"></span>, you won!
    </p>
    <p class="draw-message">
        There are no winners, only losers.
    </p>
    <button class="reset">
        Start a new game ▶
    </button>
    <audio id="tuturu" src="//www.myinstants.com/media/sounds/tuturu_1.mp3"/>
    <audio id="sadtrombone" src="//www.myinstants.com/media/sounds/sadtrombone.swf.mp3"/>
</div>

CSS

body {
    font-family: Arial;
    text-align: center;
}

table {
    border-spacing: 0;
    margin: 30px auto 0;
}

td {
    border: 0 solid #000;
    border-width: 0 2px 2px 0;
    height: 100px;
    font-size: 100px;
    line-height: 100px;
    padding: 0;
    text-align: center;
    width: 100px;
}

td:last-child {
    border-right-width: 0;
}

tr:last-child td {
    border-bottom-width: 0;
}

td[data-player="x"]::before {
    color: #FFC900;
    content: "×";
}

td[data-player="o"]::before {
    color: #40B8E8;
    content: "○";
}

#overlay {
    display: none;
    margin-top: -304px;
    vertical-align: top;
}

.t3[data-winner] #overlay {
    display: initial;
}

.t3[data-winner="x"] .winner::before {
    content: "crosses";
    font-weight: bold;
}

.t3[data-winner="o"] .winner::before {
    content: "noughts";
    font-weight: bold;
}

.winner-message,
.draw-message {
    display: none;
    font-size: 24px;
}

.t3[data-winner] .winner-message {
    display: block;
}

.t3[data-winner="false"] .winner-message {
    display: none;
}

.t3[data-winner="false"] .draw-message {
    display: block;
}

.reset {
    cursor: pointer;
    background: white;
    border: 2px solid black;
    border-radius: 10px;
    font-size: 18px;
    font-weight: bold;
    padding: 0.5em 1em;
    display: none;
}

.t3[data-winner] .reset {
    display: initial;
}

.t3[data-winner="x"] .winner-message {
    color: #FFC900;
}

.t3[data-winner="o"] .winner-message {
    color: #40B8E8;
}

JavaScript

var player; // player whose turn it is, 'x' or 'o'
var winner; // undefined (game in-progress), false (draw) or 'x' or 'o'
var winFrom, winTo; // start/end coordinates for winning sequence
var state; // state of board (matrix of undefined, 'x' and 'o')

$('td').click(function(e) {
    var x = $(e.target).index();
    var y = $(e.target).parent().index();
    
    makeMove(x, y);
    checkWinner();
    updateBoard();
    
    if (winner) {
        document.getElementById('tuturu').play();
    }
    else if (winner === false) {
        document.getElementById('sadtrombone').play();
    }
});

$('.reset').click(function() {
    reset();
    updateBoard();
});

reset(); // need to do an initial reset on load to set everything up

function reset() {
    player = 'x'; // x goes first
    winner = undefined;
    winFrom = undefined;
    winTo = undefined;
    state = [
        new Array(3),
        new Array(3),
        new Array(3)
    ];
}

function makeMove(x, y) {
    if (winner === undefined && !state[y][x]) {
        state[y][x] = player;
        
        if (player === 'x') {
            player = 'o'
        }
        else {
            player = 'x';
        }
    }
}

function updateBoard() {
    for (var i = 0; i < state.length; i++) {
        for (var j = 0; j < state[i].length; j++) {
            var cell = state[i][j];
            var td = $('tr').eq(i).find('td').eq(j);
            
            if (cell) {
                td.attr('data-player', cell);
            }
            else {
                td.removeAttr('data-player');
            }
        }
    }
    
    var overlay = document.getElementById('overlay');
    var ctx = overlay.getContext('2d');
    ctx.clearRect(0, 0, overlay.width, overlay.height);
    if (winner !== undefined) {
        $('.t3').attr('data-winner', winner);
        
        if (winner) {
            ctx.beginPath();
            ctx.moveTo(
                50 + winFrom[1] * 102,
                50 + winFrom[0] * 102);
          ...