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);