Math Home
Programming

Overview

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.

Tic Tac Toe

Let's play!



The Code

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.

Comments
JavaScript
>Set up the canvas and context variables.
>Set up the player variables.
>Set up the variables for the game.
>Create a write function that writes text at a given \((x,y)\) coordinate. The color, text content and size are also determined by the function.
>Create a clear function that clears the canvas.
>Create a line function that draws a line from \((x_1, y_1)\) to \((x_2, y_2).\) The color is determined by the last coordinate.
>Create a roundRect function that draws rounded rectangles. The \((x,y)\)-coordinates, width, height, color, radius, fill and stroke colors can be set.
>Create a render function that runs every 10 milliseconds. What the render function draws depends on the game state.
>>Clear the canvas if the game state is not game over.
>>If the game state is 'in game' or 'game over' draw the board, including the symbols that the players have played.
>>If the game state is 'menu' draw two rounded rectangles that give the player the option of choosing 1 player or 2 player.
>>If the game state is 'menu 2' draw two rounded rectangles that give the player the option of choosing 'X' or 'O'. This will be used in 1 player mode.
>>If the game state is 'game over' give the player the option to play again.
>When the window loads, initialize the game as follows:
>>Load the canvas and context.
>>Initialize the player variables.
>>Initialize the game variables including the game state, computer player, winner and board.
>>Add the function that runs when the player clicks on the canvas.
>>Get the \((x,y)\) coordinates of the mouse click and adjust the coordinates relative to the canvas. The top left of the canvas will be \((0,0).\)
>>Take the following action if the game state is "menu":
>>>Check whether the the click is on the "1 Player" rectangle or the "2 Players" rectangle.
>>>>If 1 player is chosen, set the game state to "menu 2" so the player can choose X or O. The set the computer player to true and reset the board.
>>>>If 2 player is chosen, set the game state to "in game" and reset the board.
>>>>Set the first player to X, the second player to O, and the player's turn to true.
>>Take the following action if the game state is "menu 2":
>>>If the player chooses X, change the game state to "in game".
>>>If the player chooses O, swap the playerOne value to O and the playerTwo value to X. Then set playerTurn to false. Change the game state to "in game".
>>Take the following action if the game state is "game over":
>>>If the player clicked the rectangle to continue playing, set the game state to "menu."
>>Take the following action if the game state is "in game":
>>>Check which cell the player clicked. If it is empty, update the board with the player's move.
>>>Check whether the player has won.
>Create a checkForWin function to check whether a player has won the game.
>>Check each possible way that a player could have won. A player will have won if 3 cells in a row have the same value. If a player has won, draw the line through the 3 cells, state the winner, and set the game state to "game over."
>>Check for a cat. If the game ends in a cat, no player wins. Show a message saying that the game is over and change the game state to "game over."
>Create a function that determines where the computer will play.
>>First check if there is a play where the computer can win. Since the computers plays are stored as -1, check each possible set of 3 cells for whether the sum of their values is -2. If so, two cells must be -1 and one cell must be empty and the computer can win.
>>If the computer cannot win, it should attempt to block the player if the player could win. Check each possible set of 3 cells for whether the sum of their values is 2. If so, play in the empty cell to block the player from winning.
>>If there is no play to win or block, the computer should play the middle if possible.
>>If there is no play to win or block and the middle is taken, make the smartest play available.
>>>Use the needToPlay variable to track whether the computer has played.
>>>If no corner has been played, the computer will play in the top left corner.
>>>If at least one corner has been played, attempt to play in the top middle, left middle, right middle, or bottom middle.
>>>If the only spaces available are in the corners, play a corner space.
>>Use an or statement to play in the order the functions were defined.
>>Check whether the computer has won.
>Run the loop function every 10 milliseconds. The loop will render the graphics based on the current state and will run the computers turn when it should play.
>The start function sets the loop in motion.
>Define the getMousePos function that will return the \((x,y)\) coordinates of where the canvas is clicked.
>When the window loads, run the start function.
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);