Spelchan.com Logo

From Scratch Web Games: A Beginners Guide to Game Development using HTML, CSS, and JavaScript

Chapter 6.12: Project Solution

Creating the tic-tac-toe game is not difficult, but without the use of arrays (which we will be covering next chapter) it does have a lot of repeating of code. For that reason, when I am showing code fragments, most of the repetition will be removed, with just a few lines demonstrating what is happening. When we get to arrays in the next chapter, we will be re-writing this game as a demonstration of the advantages of using arrays.

Tic-Tac-Toe Screenshot

The first thing to do is to set up the CSS for the game. I have the instructions and game arranged into a flexbox container so it will fit nicely in the display. If there is room to have both the game and instructions in one row, that will be done otherwise the game will be on the top portion of the display and the instructions underneath.

.splitGameInst {
display:flex;
flex-flow: row wrap;
gap: 10px;
}

The game is made up of 9 tiles arranged in a grid, a message that indicates who’s turn it is to play as well as any win or tie messages, and a button for starting the game if the game is finished.

The CSS for this sets up the color scheme size, and fonts, with media queries used to set the size of the game display based on the size of the screen.

.TTTContainer {
display:block;
align-items: center;
justify-items: center;
background-color: navy;
width: 350px;
font-size: 22pt;
flex: 1 350px;
@media (min-height: 600px) and (min-width: 600px) {
width: 550px;
flex: 1 550px;
font-size: 32pt;
}
@media (min-height: 1100px) and (min-width: 1100px){
width: 950px;
flex: 1 950px;
font-size: 42pt;
}
}

The game also has to be set up. This uses a grid to hold the tiles that make up the game, with media queries used to determine the appropriate size for the grid component.

.TTTGame {
margin: 10px;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 10pt;
background-color: #000088;
width: 300px;
height: 300px;
@media (min-height: 600px) and (min-width: 600px){
width: 500px;
height: 500px;
}
@media (min-height: 1100px) and (min-width: 1100px){
width: 900px;
height: 900px;
}
}

There are additional CSS classes for the TTTCell, message, newGame and instructions, but these are all straightforward styling so are omitted from the text but are available in the repo.

The HTML portion of the game is fairly easy to set up as we just need to nest our elements appropriately and let the CSS handle the heavy lifting. Here is the relevant portion of my HTML code:

<h1>Tic-Tac-Toe</h1>
<div class="splitGameInst">
<div class="TTTContainer">
<div class="TTTGame">
<div id="t11" class="TTTCell">1</div>
<div id="t12" class="TTTCell">2</div>
<!—t13 through t32 omitted -->
<div id="t33" class="TTTCell">9</div>
</div>
<div id="message" class="message"></div>
<button id="newGame" class="newGame"
onclick="restartGame()">Start a new game</button>
</div>
<div class="instructions">
<h2>Tic Tac Toe Instructions</h2>
<p> <!-- instruction text omitted -->
</p>
</div>
</div>

The JavaScript to run everything first needs to be able to know which player is the current player and get the elements for all of the tiles on the board. A simple init() function will do this work.

let currentPlayer = 0;
let t11, t12, t13, t21, t22, t23, t31, t32, t33;

function init() {
// preload elements so don't have to constantly request
t11 = document.getElementById("t11");
t12 = document.getElementById("t12");
// t13 through t32 omitted
t33 = document.getElementById("t33");
// add mouse click support
t11.addEventListener("click", handleTileClick);
// t12 through t32 mouse listener code omitted
t33.addEventListener("click", handleTileClick);
// add keyboard support
document.addEventListener("keyup", handleKey)
}

The game is played using the mouse. My version has Keyboard support but gets this by using a feature that we won’t discuss until chapter 7. That said, the keyboard can be handled just like the click if you want to have keyboard support. Clicking simply gets information about the game, figures out which letter represents the player and the opponent, and makes sure that the tile selected is not already used. If a valid tile is selected then it sets the tile, calls our checkWin function and if the game isn’t won, calls to checkCat function to see if the game is tied which is also known as the cat’s game. Most games should end here.

function handleTileClick(e) {
let message = document.getElementById("message");
let newGame = document.getElementById("newGame");
let tile = e.target.innerText;
let playerTile = currentPlayer === 0 ? 'X' : 'O';
let opponentTile = currentPlayer === 0 ? 'O' : 'X';

if ((tile.charAt(0) !== 'X') && (tile.charAt(0) !== 'O')) {
e.target.innerText = playerTile
currentPlayer = currentPlayer === 0 ? 1 : 0;
if (checkWin()) { // player won
message.innerText = "Player " + playerTile + " WON!";
newGame.hidden = false;
} else if (checkCat()) { // nobody won
message.innerText = "It's the Cat's Game.";
newGame.hidden = false;
} else { // next player's turn
message.innerText = "Player " + opponentTile + " Turn...";
}
}
}

Checking to see if the game is won is handled by looking at all the possible winning lines and seeing if the values in that line are all the same. If you are using spaces, you would also need additional logic to make sure that the three tiles were not all empty but as I number the tiles, we know that unused tiles will all be distinct.

function checkWin() {
}return checkLine(t11, t12, t13) ||
}checkLine(t21, t22, t23) ||
checkLine(t31, t32, t33) ||
checkLine(t11, t21, t31) ||
checkLine(t12, t22, t32) ||
checkLine(t13, t23, t33) ||
checkLine(t11, t22, t33) ||
checkLine(t13, t22, t31) ;
}
function checkLine(a, b, c) {
}if ( (a.innerText === b.innerText) && (b.innerText === c.innerText) ) {
a.style.background = "#B00";
b.style.background = "#B00";
c.style.background = "#B00";
return true;
}}
}return false;
}

Because we know we will call checkWin before calling checkCat, we can simply see if there are any empty tiles remaining and if not then we know it is the cat’s game.

function checkCat() {
}return ( (t11.innerText !== "1") &&
}(t12.innerText !== "2") &&
// t13 through t32 omitted for space.
(t33.innerText !== "9") );
}

Finally, I have a restartGame function for handling the restarting of the game. It just resets all the tiles and the player to the starting condition. In the next chapter, we will be learning about arrays and classes which would have made creating this game a bit easier.

Chapter contents

Chapter 6 Contents

6.1 Layout Cheat Sheets

This chapter's summary cheat sheet.

6.2 The Layout problem

Why layout is complicated for web pages.

6.3 Display blocks

The basic display types.

6.4 Flexible Boxes

A box that contains other components and sizes them to fit the available space.

6.5 Grid

A flexible grid where you can lay out your design.

6.6 Grid Templates

Using templates to make laying out components in a grid easy.

6.7 Multiple columns

Using the columns attributes to easily allow for multiple columns.

6.8 Floating

Having images (or other blocks) have text wrap around them.

6.9 Position

There are other ways of placing elements by using the position attribute.

6.10 Media Queries

Altering CSS based on the properties of the device the page is being rendered on.

6.11 Project: Tic-Tac-Toe

The project for this chapter.

6.12 Project Solution

Solution for this chapter's project

← previous section
Project
next Chapter →
Chapter 7
Table of Contents