Spelchan.com Logo

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

Chapter 4.19: Project Solution

The complete source code for this game is on GitHub, with this chapter only containing the most relevant parts of the code. One key thing to keep in mind is that the final code is the results of many iterations. I am writing this section in the way I developed the different parts.

Where's Wendy Icon

My first issue was trying to create a way of creating the grid that the player is playing on. While it would be possible to have rows of consecutive images and have each image tag have an id, the concern with this is that users can resize the page so if the page is too small then the tiles will wrap destroying the nature of the game. To prevent this, I opted to put the tiles in a table, which also gave me a grid. Another route that could have been taken is to have the grid as part of the tile images and simply set up a div with a set size. As the game is a grid game, I think the table is appropriate, but some CSS purists may incorrectly disagree with my decision.

The grid could be created by hand, but this would be a lot of work and would also lock the size of the game grid. Originally, I was going to have a set size grid and was only after the first version of the game was completed that I added the ability to change the size of the game grid based on difficulty levels. Because I had dynamically generated the grid, it was trivially easy to change my table building code to take width and height parameters as seen below.

function buildWendyTable(w, h) {
let innerString = "<table >\n";
for (let row = 1; row <= h; ++row) {
innerString += "<tr>";

for (let col = 1; col <= w; ++col) {
innerString += `<td><img id="r${row}c${col}" src="cells/Empty.png" onclick="selectTile(${col},${row});"/></td>`;
}

innerString += "</tr>\n"
}
let par = Math.ceil(Math.log2(Math.max(w, h)));
innerString += `</table><h2>Number of guesses: <span id='guessCount'>0</span> (par ${par})</h2>`;
innerString += '<<a href="#" class="fakeButton" onclick="startDifficultyMenu()">Exit game</a>'
wwDiv = document.getElementById("ww");
wwDiv.innerHTML = innerString;
}

As you can see, we are simply building a table. As we need to know which row and column a tile is in, when creating a tile we also create the onclick handler to call a selectTile(col, row) function. I use the row and column I am at to automatically fill in the function call to reflect the appropriate tile. My original plan was to have the row and column be in the name of the tile and use string manipulation to get this information out of the tile but half way through this approach realized there was the easier way, but having the tile id be set the way I did happened to help me out when I needed to find the element so I could change the image.

Selecting a tile simply calls the selectTile method that handles the logic of the game. Originally, this method just handled the game, but once I added a main menu and multiple difficulty levels, I also needed to determine if I should start a new game. Again, this is final code, it changed over time.

function selectTile(x, y) {
if (foundWendy)
startDifficultyMenu();
++guesses;
let guessCount = document.getElementById("guessCount");
guessCount.innerHTML = guesses;
let tImg = document.getElementById(`r${y}c${x}`);
tImg.src = determineTileImage(x, y);
}

The game logic is handled by determineTileImage(x,y). This uses the current position on the grid to determine what image to show for the arrow. This can be thought of as consisting of 3 steps which are used to build the name of the image to use for that tile. First, we check to see if the player has found Wendy. This simply returns the win tile and sets the foundWendy flag so we know the game is won. If we haven’t found Wendy, we need to determine which arrow image to use. This is a bit trickier, so take a look at the code and see if you can understand the logic before reading the next paragraph.

function determineTileImage(x, y) {
if ((x === wendyX) && (y === wendyY)) {
foundWendy = true;
return "cells/Win.png";
}
let imageName = "cells/";

if (y < wendyY)
imageName += "S";
else if (y > wendyY)
imageName += "N";

if (x < wendyX)
imageName += "E";
else if (x > wendyX)
imageName += "W";

return imageName + ".png";
}

We know that directions either consist of just the cardinal direction (N, W, E, S) or are made up of North or south combined with East or West. As North and South are always the first direction if it is a two-cardinal direction, then it makes sense to first figure out if there is a N/S component. It is possible that the vertical direction is correct in which case there is no N or S which is why we have an else if statement instead of an else. We don’t need to have an else here as the else would be adding an empty string to the imageName which doesn’t accomplish anything. Once we know the N/S component, we do the same thing to get the E/W component.

With the tile image set, we have a complete game. At this point we add niceties, such as a title screen. Our title screen simply has buttons for selecting the difficulty level of the game. We are replacing the grid with this title screen by taking advantage of the innerHTML property to replace the grid code with the title screen code.

Finally, some CSS rules were set up. Normally, these rules would be in a separate file, but for convenience, I am putting the CSS right into the .html file so everything needed to understand the game is present. The CSS code should be familiar to anybody who has read chapter 3 as it is the fake button logic that was used to make anchor tags look like buttons.

That is all there is to this game. Hopefully you were able to get your own version of this working. Next chapter we will be heading back to HTML and taking a look at forms. These were intended to be used to send information back to a server for processing, but with the advent of JavaScript can be used entirely on the client.

Chapter contents

Chapter 4 Contents

4.1 Cheat Sheets

A quick summary of the basics of JavaScript.

4.2 History of JavaScript

A brief look at how JavaScript was written in 10 days.

4.3 Comment Controversy

Comments. Why programmers don't write them, and how they should be written

4.4 Variables

Variables are used to store the state of a program.

4.5 (extra) How Computers Represent Data

Bits, Bytes, and data types.

4.6 Math

Math on the computer similar but some symbol differences.

Math functions

Various math operations can be used through the Math class.

4.8 Strings

Strings are what we call blocks of text and are used extensively.

4.9 Calculating true and false

Determining if a conditional expression is true or false

4.10 if (Conditional statements)

Conditional code using the if statement.

4.11 Nested conditions

If statements can contain other if statements, this is called nesting.

4.12 Switch statement

Switch statements are a way of replacing large number of else if statements.

4.13 Functions

Functions let you put common code into a named function that can be called anywhere.

4.14 Looping

Loops allow you to repeat sections of code until conditions are met.

4.15 Nested loops

Just like conditional statements, loops can be nested but this has some special considerations.

4.16 Accessing the Web page

Scripting languages give us the ability to dynamically change the web page.

4.17 Events

Reacting to the user actions is done by handling events.

4.18 Project: Where’s Wendy

Our project for this chapter is a grid search game.

4.19 Project: Where’s Wendy implementation

My solution to the Project.

← previous section
Project
next Chapter →
Coming July 28th.
Table of Contents