Tic-tac-toe is a classic game played on a grid of 3x3 squares. Two players take turns marking X or O in empty squares, aiming to get three of their marks in a row, either horizontally, vertically, or diagonally. It's a quick and strategic game often enjoyed by people of all ages.
Is a seemingly straightforward game that offers valuable problem-solving opportunities, particularly for beginners. This article provides a step-by-step explanation of how to create the game using an HTML Canvas element and JavaScript.
The tools
JavaScript: It is versatile, compatible with browsers, and supports both front-end and back-end development.
HTML: It organizes and presents information on the web.
CSS: It separates content and design, facilitating maintenance and updates.
If you want to delve deeper into the subject, I'll leave my recommended readings below:
- "Eloquent JavaScript" by Marijn Haverbeke
- "HTML and CSS: Design and Build Websites" by Jon Duckett
- "CSS Secrets: Better Solutions to Everyday Web Design Problems" by Lea Verou
The Concepts
Class: It's like a template for party invitations, defining the design and basic information. In JavaScript, a class is a blueprint for creating objects with properties and methods.
Object: It's like a personalized party invitation representing a distinct object with unique details. In JavaScript, an object is an instance of a class, representing a specific entity with its own characteristics and actions.
Constructor: It's how you prepare invitations, defining the basic structure and initial information. In JavaScript, the constructor is a special method within a class that initializes the object's properties for use.
Inicial Setup
To start, create an HTML file with the basic structure, including a canvas element and a script tag. While you can separate the JavaScript code into a different file, for this tutorial, we'll keep everything in one file.
The canvas element acts as the background for the game's scene. If desired, you can adapt the game to work with other elements like a div tag, but that would require modifying the code provided in this tutorial.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320">
</canvas>
<script>
// Our JavaScript code will reside here.
</script>
</body>
</html>
Figure 1: This part demonstrates a basic HTML structure that includes a canvas element and a script tag.
The Gameβs Classes
Classes in JavaScript provide a way to organize code by creating objects with related properties and methods. In the Tic-Tac-Toe game, we have the TicTacToe class for managing the game instance and the Square class for representing each square on the game board.
The TicTacToe class takes the ID of a canvas element as a parameter, which is used for rendering graphics. The Square class stores position and click information, and has methods for drawing and displaying symbols on the canvas.
Now, let's see how these concepts are applied in the game code.
Figure 2: This is how our basic structure for the game will look like.
When creating a instance, the canvas element ID is passed as a parameter, allowing multiple instances for different screen elements. The Square class represents a square on the game board, storing position, size, and click information. It provides methods to draw the square and display the player's symbol.
The Square class generates square objects for a 3x3 grid. It has properties such as x
, y
, width
, height
, ctx
, and actor
. x
and y
determine the position, width
and height
define the dimensions, ctx
references the canvas interface, and actor indicates the player's symbol ('x' or 'o').
In JavaScript, a constructor is a special function that creates and initializes objects. It establishes the object's properties and methods. To call a constructor, we use the new keyword followed by the constructor function's name. The constructor function associates the new object with the this variable, allowing us to define the object's properties.
When instantiating a Square object, all properties except actor should be provided to the constructor. The actor property should be set to null within the constructor to ensure it exists on Square objects.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {
constructor(x, y, width, height, ctx) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.ctx = ctx;
this.actor = null;
}
draw() {
// Draw is a method used to draw the square.
}
}
</script>
</body>
</html>
Figure 3: Demonstrates the implementation of the Square class.
The class should include a draw()
method responsible for rendering a square and displaying the symbol of the player who interacted with it. The draw()
method utilizes the strokeRect(x, y, width, height)
method, accessed through the ctx
property, to draw the square on the canvas. Additionally, the ctx
property provides access to the strokeStyle
property, allowing for customization of the border color.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {
//...
draw() {
// This part draws the rectangle
this.ctx.strokeStyle = 'black';
this.ctx.strokeRect(this.x, this.y, this.width, this.height);
// This part draws the actor if it is not null.
if (this.actor) {
this.ctx.fillStyle = 'black';
this.ctx.font = '30px Verdana';
this.ctx.textAlign = 'center';
this.ctx.fillText(this.actor, this.x + this.width / 2, this.y + this.height / 2 + 10);
}
}
}
</script>
</body>
</html>
Figure 4: Demonstrates the implementation of the draw() method.
The draw method utilizes the fillText(text, x, y)
method from the ctx
property to inscribe the symbol of the player who interacted with the square. The text is precisely placed at the square's center by adding half of its width and height to its x and y positions. The ctx
property also possesses a textAlign
property that can be set to center, ensuring the text is perfectly aligned towards the center.
Next, we proceed with creating the TicTacToe class, which entails a constructor and three essential methods: click(event)
, checkForWinner()
, and reset()
.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {
}
// The TicTacToe class represents the concrete implementation of the game.
class TicTacToe {
constructor(id) {
}
// The click method is responsible for checking if the mouse clicked within one of the empty squares when the canvas is clicked.
click(event) {
}
// The checkForWinner method determines the game's outcome.
checkForWinner() {
}
// The reset method initiates a fresh start of the game.
reset() {
}
}
</script>
</body>
</html>
Figure 5: Demonstrates the implementation of a TicTacToe class.
The TicTacToe class constructor requires a canvas element id as input. The id is used to fetch the corresponding HTML canvas element using getElementById(id)
and assigned to the canvas
property. The canvas
property is then used to obtain a CanvasRenderingContext2D
instance assigned to the ctx
property, serving as the drawing interface within the canvas.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {
}
// The TicTacToe class represents the concrete implementation of the game.
class TicTacToe {
constructor(id) {
// Get the canvas element and its 2D rendering context.
this.canvas = document.getElementById(id);
this.ctx = this.canvas.getContext('2d');
// This is the empty array that stores the squares.
this.squares = [];
// The constants w and h represent the width and height.
const w = this.canvas.width / 3;
const h = this.canvas.height / 3;
// These sequence of for loops create 3x3 squares.
for (let x = 0; x < 3; x++) {
for (let y = 0; y < 3; y++) {
this.squares.push(new Square(x * w, y * h, w, h, this.ctx));
}
}
// This is the array that creates the actors.
this.actors = ["x", "o"];
// Here we define the current actor.
this.turn = 0;
// Here we define a checker to see if the game has ended.
this.gameOver = false;
// Render all squares on the screen
this.squares.forEach(squares => squares.draw());
// Adds a click event listener to the canvas to execute the 'click' function.
this.canvas.addEventListener('click', function(event) { this.click(event); }.bind(this));
}
// The click function verifies if the mouse was clicked on an empty square when the canvas is clicked.
click(event) {
}
// The checkForWinner function determines the result of the game.
checkForWinner() {
}
// The reset function initiates a new game session.
reset() {
}
}
</script>
</body>
</html>
Figure 6: Demonstrates the implementation of a TicTacToe class constructor.
To instantiate the Square objects, a loop of 3x3 is performed, creating a Square object for each iteration and adding it to the squares
property. The loop starts at (x=0, y=0) and continues until 9 squares are created.
The class requires three additional properties: actors
(an array of two strings representing the players and their symbols 'x' and 'o'), turn
(a number between 0-1 specifying the next player's turn), and gameOver
(a boolean indicating if the game has ended).
In the constructor, the square objects draw()
method is called, and a click event listener is added to the class click()
method.
The click method is triggered when the user clicks on the canvas, and it retrieves the cursor's position from the event argument. The first section of the click method includes a guard clause that resets the game if the gameOver
property is set to true, allowing the user to start a new game by clicking on the canvas.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {
}
// The TicTacToe class represents the concrete implementation of the game.
class TicTacToe {
constructor(id) {
}
// The click method is called whenever the canvas is clicked.
// The method is used to check if the mouse clicked within one of the empty squares.
click(event) {
// Reset the game if it has ended.
if (this.gameOver) {
this.reset();
return;
}
// Retrieve the mouse position.
const x = event.offsetX;
const y = event.offsetY;
// Verify if one of the squares was clicked.
for (let square of this.squares) {
// Allow only empty squares to be clicked.
if (square.actor != null) continue;
// Validate if the mouse is inside the square.
if (x >= square.x && x <= square.x + square.width && y >= square.y && y <= square.height) {
// Assign the actor.
square.actor = this.actors[this.turn];
square.draw();
// Toggle the turn.
this.turn = (this.turn + 1) % this.actors.length;
// Determine if the game should end.
this.checkForWinner();
}
}
}
// The checkForWinner function determines the result of the game.
checkForWinner() {
}
// The reset function initiates a new game session.
reset() {
}
}
</script>
</body>
</html>
Figure 7: Demonstrates the implementation of a TicTacToe class click method.
The method iterates over Square objects, checking if the mouse position is within a square. If true, the actor is added to the square's actor
property to indicate selection. The turn
property allows the next player to choose.
The method then calls checkForWinner()
to determine if there is a winner. It checks for three squares selected in a horizontal, vertical, or diagonal line.
Using an array of arrays, the method checks if three squares match any winning combinations. If true, gameOver
is set to true, resetting the game on the next canvas click.
The method also draws a line and displays the winner's text representing the winning combination.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {
}
// The TicTacToe class represents the concrete implementation of the game.
class TicTacToe {
constructor(id) {
}
// The click method is called whenever the canvas is clicked.
// The method is used to check if the mouse clicked within one of the empty squares.
click(event) {
// Reset the game if it has ended.
if (this.gameOver) {
this.reset();
return;
}
// Retrieve the mouse position.
const x = event.offsetX;
const y = event.offsetY;
// Verify if one of the squares was clicked.
for (let square of this.squares) {
// Allow only empty squares to be clicked.
if (square.actor != null) continue;
// Validate if the mouse is inside the square.
if (x >= square.x && x <= square.x + square.width && y >= square.y && y <= square.height) {
// Assign the actor.
square.actor = this.actors[this.turn];
square.draw();
// Toggle the turn.
this.turn = (this.turn + 1) % this.actors.length;
// Determine if the game should end.
this.checkForWinner();
}
}
}
// The checkForWinner function determines the result of the game.
checkForWinner() {
// Determine whether the game has concluded.
const winnerCombinations = [
// Columns
[0, 1, 2], [3, 4, 5], [6, 7, 8],
// Rows
[0, 3, 6], [1, 4, 7], [2, 5, 8],
// Diagonals
[0, 4, 8], [2, 4, 6]
];
// Verify the combinations.
for (let i = 0; i < winnerCombinations.length; ++i) {
// Get combination
let combination = winnerCombinations[i];
// Get squares
let s1 = this.squares[combination[0]];
let s2 = this.squares[combination[1]];
let s3 = this.squares[combination[2]];
// Make sure the combination does not consist of three empty squares.
if (s1.actor != null) {
// Verify whether the three squares have the same actor.
if (s1.actor == s2.actor && s1.actor == s3.actor) {
// Set game over.
this.gameOver = true;
// Draw the combination line.
this.ctx.beginPath();
this.ctx.moveTo(s1.x + s1.width / 2, s1.y + s1.height / 2);
this.ctx.lineTo(s3.x + s3.width / 2, s3.y + s3.height / 2);
this.ctx.stroke();
// Draw winner text.
this.ctx.fillStyle = 'black';
this.ctx.font = '30px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(s1.actor + " wins!", this.canvas.width / 2, this.canvas.height / 2);
}
}
}
}
// The reset function initiates a new game session.
reset() {
}
}
</script>
</body>
</html>
Figure 8: Displays the implementation of the checkForWinner() method in the TicTacToe class.
The final part of the method checks if all squares have a non-null actor, indicating that the game is a draw. This check is performed after determining if there is a winner to ensure that the game is not mistakenly considered a draw when the last empty square is selected and a winner is found.
The reset method is responsible for restoring the properties of a TicTacToe instance to their initial values, preparing for a new game.
Firstly, the method clears the canvas by using the clearRect(x, y, width, height)
method of the ctx
property.
After clearing the canvas, the actor property of each square is set to null, and the squares are redrawn on the canvas.
Finally, the method resets the turn to 0, allowing players to take turns again, and sets gameOver to false. This indicates that the click()
method can assign players to squares and search for a new winner or a draw.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {
}
// The TicTacToe class represents the concrete implementation of the game.
class TicTacToe {
constructor(id) {
}
// The click method is called whenever the canvas is clicked.
// The method is used to check if the mouse clicked within one of the empty squares.
click(event) {
// Retrieve the mouse position.
const x = event.offsetX;
const y = event.offsetY;
// Verify if one of the squares was clicked.
for (let square of this.squares) {
// Allow only empty squares to be clicked.
if (square.actor != null) continue;
// Validate if the mouse is inside the square.
if (x >= square.x && x <= square.x + square.width && y >= square.y && y <= square.height) {
// Assign the actor.
square.actor = this.actors[this.turn];
square.draw();
// Toggle the turn.
this.turn = (this.turn + 1) % this.actors.length;
// Determine if the game should end.
this.checkForWinner();
}
}
}
// The checkForWinner function determines the result of the game.
checkForWinner() {
}
// The reset function begins a fresh game session.
reset() {
// Clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Reset all actors.
this.squares.forEach(squares => squares.actor = null);
// Draw the board.
this.squares.forEach(squares => squares.draw());
// Reset turn.
this.turn = 0;
// Reset game over.
this.gameOver = false;
}
}
</script>
</body>
</html>
Figure 9: Illustrates how the reset() method is implemented within the TicTacToe class.
The game is now fully prepared and ready for players to engage. To begin, simply add a new line below the class definitions to instantiate a fresh instance of the class. This will initialize all the necessary game components and set the stage for an exciting gameplay experience.
So go ahead, create a new instance and let the gaming fun begin!
<!DOCTYPE html>
<html lang="en">
<head>
<title>Educational - Tic-Tac-Toe</title>
</head>
<body>
<canvas id="canvas" width="320" height="320"></canvas>
<script>
// The Square contains position, size information and an actor.
class Square {}
// The TicTacToe class represents the concrete implementation of the game.
class TicTacToe {
// Create a new game.
new TicTacToe('canvas');
}
</script>
</body>
</html>
Figure 10: Demonstrates the process of initiating the Tic-tac-toe game.
You can find this code through the following link: https://github.com/marcosconci1/educational-tic-tac-toe. Additionally, you can also access my GitHub page through it.
Top comments (0)