Tic-tac-toe is a solve game, meaning there is a way to play so that you never lose. We program a game in which the computer never loses but also has the option of two human players.
Let's play!
Everything is done within a canvas element.
<canvas width='300px' height='300px' id='myCanvas'></canvas>
Click the code or the description to see the connection.
var canvas, ctx;
var playerTurn, playerOneVal, playerTwoVal;
var gameState, computerPlayer, winner, board;
function write(x, y, color, text, size) { if (size == null) { size = 60; } var colString = color.toString(16); while (colString.length < 6) { colString = "0" + colString; } colString = "#" + colString; ctx.font= size + "px Monospace"; ctx.fillStyle = colString; ctx.fillText(text, x+3, y+15); }
function clear(x,y,w,h) { ctx.beginPath(); ctx.rect(x, y, w, h); ctx.fillStyle = "#aaa"; ctx.fill(); ctx.closePath(); }
function drawLine(x1,y1,x2,y2,color) { var colString = color.toString(16); while (colString.length < 6) { colString = "0" + colString; } colString = "#" + colString; ctx.beginPath(); ctx.globalAlpha = 1; ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.lineWidth = 4; ctx.strokeStyle = colString; ctx.stroke(); ctx.closePath(); }
function roundRect(x, y, width, height, color, radius, fill, stroke) { var colString = color.toString(16); while (colString.length < 6) { colString = "0" + colString; } colString = "#" + colString; if (typeof stroke == 'undefined') { stroke = true; } if (typeof radius === 'undefined') { radius = 5; } if (typeof radius === 'number') { radius = {tl: radius, tr: radius, br: radius, bl: radius}; } else { var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0}; for (var side in defaultRadius) { radius[side] = radius[side] || defaultRadius[side]; } } ctx.beginPath(); ctx.moveTo(x + radius.tl, y); ctx.lineTo(x + width - radius.tr, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); ctx.lineTo(x + width, y + height - radius.br); ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height); ctx.lineTo(x + radius.bl, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); ctx.lineTo(x, y + radius.tl); ctx.quadraticCurveTo(x, y, x + radius.tl, y); ctx.strokeStyle = colString; ctx.fillStyle = colString; ctx.closePath(); ctx.fill(); if (stroke) { ctx.stroke(); } }
function render () {
if (gameState != 'game over') { clear(0, 0, 300, 300); }
if (gameState == 'in game' || gameState == 'game over') { drawLine(100, 0, 100, 300, 0); drawLine(200, 0, 200, 300, 0); drawLine(0, 100, 300, 100, 0); drawLine(0, 200, 300, 200, 0); for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (board[i][j] == 1) { write(30+100*i, 50+100*j, 0, playerOneVal); } if (board[i][j] == -1) { write(30+100*i, 50+100*j, 0, playerTwoVal); } } } }
if (gameState == 'menu') { roundRect(10, 10, 280, 50, 0xffffff, 25); write(30, 30, 0, "1 Player", 40); roundRect(10, 80, 280, 50, 0xffffff, 25); write(30, 100, 0, "2 Player", 40); }
if (gameState == 'menu 2') { roundRect(10, 10, 280, 50, 0xaaaaaa, 25); write(30, 30, 0, "X or O?", 40); roundRect(10, 80, 90, 50, 0xffffff, 25); write(40, 100, 0, "X", 40); roundRect(160, 80, 90, 50, 0xffffff, 25); write(190, 100, 0, "O", 40); }
if (gameState == 'game over') { roundRect(10, 30, 280, 50, 0xaaaaaa, 25); write(30, 50, 0, winner, 40); roundRect(10, 100, 290, 50, 0xffffff, 25); write(30, 120, 0, "Play Again?", 40); }
}
window.onload = function() {
canvas = document.getElementById("myCanvas"); ctx = canvas.getContext("2d");
playerTurn = true; playerOneVal = 'X'; playerTwoVal = 'O';
gameState = 'menu'; computerPlayer = 'false'; winner = ''; board = []; for (var i = 0; i < 3; i++) { board[i] = []; for (var j = 0; j < 3; j++) { board[i][j] = 0; } }
canvas.addEventListener('click', function(evt) {
var mousePos = getMousePos(canvas, evt); clickX = mousePos.x; clickY = mousePos.y;
if (gameState == "menu") {
if (10 < clickX && clickX < 290) { if (10 < clickY && clickY < 60) {
gameState = "menu 2"; computerPlayer = true; for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { board[i][j] = 0; } }
} if (80 < clickY && clickY < 130) {
gameState = "in game"; computerPlayer = false; for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { board[i][j] = 0; } }
}
playerOneVal = 'X'; playerTwoVal = 'O'; playerTurn = true;
}
}
else if (gameState == "menu 2") {
if (10 < clickX && clickX < 100) { if (80 < clickY && clickY < 130) { gameState = 'in game'; } }
if (160 < clickX && clickX < 250) { if (80 < clickY && clickY < 130) { playerOneVal = 'O'; playerTwoVal = 'X'; playerTurn = false; gameState = 'in game'; } }
}
else if (gameState == "game over") {
if (100 < clickY && clickY < 150) { gameState = "menu"; }
}
else if (gameState == "in game") {
for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (100*i < clickX && clickX < 100*i + 100) { if (100*j < clickY && clickY < 100*j + 100) { if (board[i][j] == '') { if (playerTurn) { board[i][j] = 1; } else { board[i][j] = -1; }
checkForWin();
} } } } }
}
}, false);
}
function checkForWin() {
if (board[0][0] != 0 && board[0][0] == board[1][0] && board[0][0] == board[2][0]) { drawLine(0, 50, 300, 50, 0xff0000); if (board[0][0] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; } if (board[0][1] != 0 && board[0][1] == board[1][1] && board[0][1] == board[2][1]) { drawLine(0, 150, 300, 150, 0xff0000); if (board[0][1] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; } if (board[0][2] != 0 && board[0][2] == board[1][2] && board[0][2] == board[2][2]) { drawLine(0, 250, 300, 250, 0xff0000); if (board[0][2] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; } if (board[0][0] != 0 && board[0][0] == board[0][1] && board[0][0] == board[0][2]) { drawLine(50, 0, 50, 300, 0xff0000); if (board[0][0] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; } if (board[1][0] != 0 && board[1][0] == board[1][1] && board[1][0] == board[1][2]) { drawLine(150, 0, 150, 300, 0xff0000); if (board[1][0] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; } if (board[2][0] != 0 && board[2][0] == board[2][1] && board[2][0] == board[2][2]) { drawLine(250, 0, 250, 300, 0xff0000); if (board[2][0] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; } if (board[0][0] != 0 && board[0][0] == board[1][1] && board[0][0] == board[2][2]) { drawLine(0, 0, 300, 300, 0xff0000); if (board[0][0] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; } if (board[2][0] != 0 && board[2][0] == board[1][1] && board[2][0] == board[0][2]) { drawLine(300, 0, 0, 300, 0xff0000); if (board[2][0] == -1) { winner = playerTwoVal + ' wins!'; } else { winner = playerOneVal + ' wins!'; } gameState = 'game over'; }
var cat = true; for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (board[i][j] == 0) { cat = false; } } } if (cat) { winner = 'Draw!'; gameState = 'game over'; }
}
function computerPlay() {
playToWin = function () { if (board[0][0] + board[1][0] + board[2][0] == -2) { for (var i = 0; i < 3; i++) { board[i][0] = -1; } return true; } if (board[0][1] + board[1][1] + board[2][1] == -2) { for (var i = 0; i < 3; i++) { board[i][1] = -1; } return true; } if (board[0][2] + board[1][2] + board[2][2] == -2) { for (var i = 0; i < 3; i++) { board[i][2] = -1; } return true; } if (board[0][0] + board[0][1] + board[0][2] == -2) { for (var i = 0; i < 3; i++) { board[0][i] = -1; } return true; } if (board[1][0] + board[1][1] + board[1][2] == -2) { for (var i = 0; i < 3; i++) { board[1][i] = -1; } return true; } if (board[2][0] + board[2][1] + board[2][2] == -2) { for (var i = 0; i < 3; i++) { board[2][i] = -1; } return true; } if (board[0][0] + board[1][1] + board[2][2] == -2) { for (var i = 0; i < 3; i++) { board[i][i] = -1; } return true; } if (board[0][2] + board[1][1] + board[2][0] == -2) { for (var i = 0; i < 3; i++) { board[i][2-i] = -1; } return true; } return false; }
playToBlock = function () { if (board[0][0] + board[1][0] + board[2][0] == 2) { for (var i = 0; i < 3; i++) { if (board[i][0] == 0) { board[i][0] = -1; } } return true; } if (board[0][1] + board[1][1] + board[2][1] == 2) { for (var i = 0; i < 3; i++) { if (board[i][1] == 0) { board[i][1] = -1; } } return true; } if (board[0][2] + board[1][2] + board[2][2] == 2) { for (var i = 0; i < 3; i++) { if (board[i][2] == 0) { board[i][2] = -1; } } return true; } if (board[0][0] + board[0][1] + board[0][2] == 2) { for (var i = 0; i < 3; i++) { if (board[0][i] == 0) { board[0][i] = -1; } } return true; } if (board[1][0] + board[1][1] + board[1][2] == 2) { for (var i = 0; i < 3; i++) { if (board[1][i] == 0) { board[1][i] = -1; } } return true; } if (board[2][0] + board[2][1] + board[2][2] == 2) { for (var i = 0; i < 3; i++) { if (board[2][i] == 0) { board[2][i] = -1; } } return true; } if (board[0][0] + board[1][1] + board[2][2] == 2) { for (var i = 0; i < 3; i++) { if (board[i][i] == 0) { board[i][i] = -1; } } return true; } if (board[0][2] + board[1][1] + board[2][0] == 2) { for (var i = 0; i < 3; i++) { if (board[i][2-i] == 0) { board[i][2-i] = -1; } } return true; } return false; }
playMiddle = function () { if (board[1][1] == '') { board[1][1] = -1; return true; } return false; }
playSmart = function () {
var needToPlay = true;
if (board[0][0] == 0 && board[0][2] == 0 && board[2][0] == 0 && board[2][2] == 0) { board[0][0] = -1; needToPlay = false; }
else if (needToPlay) { if (board[1][0] == 0) { board[1][0] = -1; needToPlay = false; } else if (board[0][1] == 0) { board[0][1] = -1; needToPlay = false; } else if (board[2][1] == 0) { board[2][1] = -1; needToPlay = false; } else if (board[1][2] == 0) { board[1][2] = -1; needToPlay = false; } }
if (needToPlay) { if (board[0][0] == 0) { board[0][0] = -1; } else if (board[0][2] == 0) { board[0][2] = -1; } else if (board[2][0] == 0) { board[2][0] = -1; } else if (board[2][2] == 0) { board[2][2] = -1; } } return true;
}
playerTurn = playToWin() || playToBlock() || playMiddle() || playSmart();
checkForWin();
}
function loop() { render(); if (!playerTurn && computerPlayer) { computerPlay(); } }
function start() { setInterval(loop,10); }
function getMousePos(canvas, evt) { var rect = canvas.getBoundingClientRect(); return { x: Math.floor(evt.clientX - rect.left), y: Math.floor(evt.clientY - rect.top) }; }
window.addEventListener("load", start, false);