Building your Wheres Waldo game

Wheres Waldo, some may call it debugging for the layman, is a game about staring at an image looking for a man in a red and white striped outfit, named Waldo. I’ll be walking through my approach to building the popular game, and how I approach the project.

Defining the approach

So, we know we want a web game of Waldo, and that entitles a few things. One is the very fundamental aspect of Waldo, finding him! To accomplish this the plan is to get where the user clicked, then send it to the server to see if it is where Waldo is hiding. The second thing we will need is a timer that will start when the game does and end once Waldo is found. The last thing we will implement is a scoreboard with the high scores of players.

So our broad goals are comprised of the following…

  • Location validation
  • Timing
  • Score board

Setting up the html and css

So before we can add functionality we first need an actual page. Let us start with the html.

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<title>Wheres Waldo?</title>
	<link rel="stylesheet" href="css/styles.css">
	<script type="text/javascript" src="scripts/waldo.js"></script>
</head>

<body>
	<div id="startScreen">
		<h1>Wheres Waldo?</h1>
		<p>
			Click the button to start searching.<br>
			By the way, you are timed.
		</p>
		<div id="go">
			<p id="goText">START</p>
		</div>
		<div id="playerForm">
			<p>Enter your name.</p>
			<input type="text" placeholder="Player">
			<input type="submit">
		</div>
	</div>

	<div id="gameData">
		<p id="rspnse"></p>
		<p id="timer">0.00</p>
	</div>

	<img src="media/maps_future.jpg">
	<div class="highlighter"></div>

	<div id="endModal" style="display:none">
		<h2>You won!</h2>
		<p>It took you <span id="yourTime"></span> seconds to find waldo.</p>
		<table style="border: 1px solid white">
			<caption>Highscores</caption>
			<thead>
				<tr>
					<th>Name</th>
					<th>Score</th>
				</tr>
			</thead>
			<tbody id="topPlayers">
			</tbody>
		</table>
		<a href="index.html">PLAY AGAIN?</a>
	</div>
</body>
</html>

In the head we have the link to the css and the JavaScript, you may need to change the path if you don’t wish to follow my folder layout. First thing in the body is the start screen modal, containing a form and a start link, we will write functionality to get the users name and start the game later. Next is the gameData, here we will have the timer and the text telling whether Waldo was found. Then we have the actual image, followed by an invisible ‘highlighter’ div. Lastly we have the end modal with the users time and the high scores.

Now we need the styling to compliment the layout and make our modals work.

body{
	background-color: red;
	color:white;
	margin:0px;
}
img{
	display: block;
	margin:auto;
	height:600px;
	width:1260px;
}
h1{
	margin-top: 16%;
	text-align: center;
	text-shadow: 2px 2px black;
	font-size: 60px;
	letter-spacing: 20px;
}
p{
	text-align: center;
}
table{
	width:inherit;
	margin:12px auto;
}
thead{
	color:royalblue;
}
a{
	color:red;
}
#go{
	height: 60px;
	width: 140px;
	margin: 50px auto;
	background-color: black;
	border-radius: 10px;
}
#go:hover{
	background-color: white;
	color:black;
}
#goText{
	padding: 20px;
}
#startScreen{
	position: fixed;
	top:0px;
	margin:auto;
	width:100%;
	height:100%;
	background-color: red;
	z-index: 999;
}
#gameData{
	background-color: white;
	width:98%;
	margin:1% auto;
	border:12px groove rgb(220,220,220);
	height: 30px;
	color:black;
	font-size: 20px;
	font-weight: bold;
}
#rspnse{
	margin: initial;
	text-decoration: underline;
	text-align: initial;
	margin-left: 220px;
	letter-spacing: 8px;
}
#endModal{
	background: black;
	border:1px solid white;
	border-radius: 20px;
	position: fixed;
	padding: 10px 45px;
	top: 25%;
	left: 47%;
	width: 200px;
	margin-left: -100px;
	text-align: center;
	z-index: 999;
}
#timer{
	position: absolute;
	top: 7px;
	right: 220px;
	letter-spacing: 4px;
}
#playerForm{
	background: black;
	border: 2px solid white;
	border-radius: 30px;
	position: fixed;
	top: 281px;
	left: 34%;
	padding: 109px;
}
.highlighter{
	display: none;
	border:6px solid red;
	box-shadow: 1px 2px black;
	border-radius: 100%;
	position:absolute;
	width:46px;
	height:46px;
}

Alright, now we have a page with all the bells and whistles, except the bells and whistles don’t work. Now we can move over to the JavaScript to make them work.

JavaScript

JavaScript allows the page to change, so with that in mind we must ask what we want to change? We want the staring name form to disappear, the start modal to disappear, and a visual indicator of where we clicked on the photo. How we will do this is via the DOM and event listeners.

window.addEventListener("load", function(){

  submit = document.getElementById("playerForm").children[2];
  start = document.getElementById("go");
  picture = document.getElementsByTagName("img")[0];

  submit.addEventListener("click", setPlayersName);
  start.addEventListener("click", startGame);
  picture.addEventListener("click", didTheyFindWaldo);
  picture.addEventListener("click", highlight); 

});

In this code we start by adding an event listener to the window, the listener listens for the ‘load’ event and once it happens it executes the function. We do this so that we can add events to the nodes1 of html, because without the load event it will try to assign an event to a html element that doesn’t yet exist. Then we set variables; ‘submit’ is equal to the DOM node of the submit button, ‘start’ is the node of the start button on the start modal, and ‘picture’ is the node of the wheres Waldo image. Next we add click event listeners to them. So now all we need is to define the functions.

Functions

getPlayersName
function setPlayersName(e){
	input = e.target.parentElement.children[1].value;

	if (input.match(/[a-z]/gi)){
		name = input.replace(/[^a-z]/gi, "");
		e.target.parentElement.style.display="none";
	}
	else{
		alert("Enter a valid name");
	};
};

Here we define a function called ‘getPlayersName’ and it has e in its parameters but, what is e? Well, e is the event that triggered the function, aka the click event. Next we define the variable ‘input’ as the text input of the form using the DOM. Following that is a conditional block that checks the users input using a regular expression2. So if the input contains any character a-z, upper or lowercase, it continues by setting a variable ‘name’ to the input after removing any non a-z characters, and finally sets the display to none using the DOM. Alternatively if input doesn’t match the regex it sends an alert to the user. So, now once the form is gone the modal is still there with a start button, and the process is similar to the one above.

startGame

Now we will define start game but, what should it do. First off it should close the start modal and it should start the timer.

function startGame(){
  document.getElementById("startScreen").style.display = "none";
  startTimer();
};

function startTimer(){
  myTimer = setInterval(upTimer, 10);
};

function upTimer(){
  newTime = parseFloat(document.getElementById("timer").textContent) + 0.01;
  document.getElementById("timer").textContent = newTime.toFixed(3);
};

It looks intimidating but its not too bad. Function startGame begins by setting the start modal display to none, then calls upon the function startTimer. That function then sets a variable ‘myTimer’ equal to an interval that calls the function upTimer every 10 milliseconds. Down the rabbit hole one more time to the function upTimer, it first selects the timer in the html, gets its numerical value, adds 0.01 ‘seconds’ to it, and lastly sets the timers value to the new count. Viola! the game now starts, and has a timer!

highlight
function highlight(e){
  highlighter = document.getElementsByClassName("highlighter")[0]
  highlighter.style.display = "block"
  highlighter.style.top = e.pageY - 26 +"px"
  highlighter.style.left = e.pageX - 26 + "px"
  setTimeout(unhighlight,750);
};

function unhighlight(){
  highlighter.style.display = "none"
};

So when the image is clicked on this function is executed. Highlight first selects the highlighter div, sets its display from none to block, and then sets its top and bottom position to the x and y of the users click, minus half of the divs width/height. After the div is shown and moved, it sets a timeout that executes the unhighlight function after 0.75 seconds. Unhighlight just sets the display back to none.

didTheyFindWaldo

At this point every thing has been happening in JavaScript and the DOM but, we need to store Waldo’s location out of the hands of the user in case they cheat. This function, didTheyFindWaldo, checks if the user found Waldo and needs the actual location to compare. Thus, we must send the users click server-side to check.

function didTheyFindWaldo(e){
  x = e.pageX;
  y = e.pageY;
  params = "xpos=" + (x - e.target.offsetLeft) + "&ypos=" + (y - e.target.offsetTop);
  sendRequest("POST", "scripts/tony_stark.php",params, showResponse);
};

function sendRequest(method, phpFile, params, afterLoad){
  request = new XMLHttpRequest();
  request.open(method, phpFile, true);
  request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 
  request.send(params);
  request.addEventListener("load", afterLoad);
};

The function starts by setting the x and y of the users click, relative to the entire page, to respective variables. Then we define our query string in the variable params with the users x and y, relative to the image, passed into it. Then we get into the bridge that connects us to the server-side script, the sendRequest function. It takes four params, the method of sending (post/get), the server script’s path, params, and a function to do with the response. In my case I send a request via post method to scripts/tony_stark.php, with the users x/y input, and tell it to run the showResponse function after coming back from the server. So now we have sent a request with the query string “xpos=#number&ypos=#number”. So, let’s write our server script to validate the click!

PHP

tony_stark

Create a file in the scripts folder called tony_stark.php and open the file to write it. Now we are in the server-side with our user’s click in the $_POST global variable, and Waldo’s location stored in a csv file, example: row 1 => 250,430 . We already have the user’s data store in $_POST, so we need to read the csv and run some comparisons.

<?php

$file = '../data/locations.csv';
$f = fopen($file, 'r');
$text = fread($f, filesize($file));
fclose($f);

$locationOfWaldo = str_getcsv($text);
echo checkCoords($locationOfWaldo);

function checkCoords($loW){
  if (withinRange($loW, 'x') && withinRange($loW, 'y')){
    return "√ WALDO FOUND!";
  }
  else{
    return respondWrong();
  };
};

function withinRange($loW, $xORy){
  $axis = isItXorY($xORy);
  if ($_POST[$xORy."pos"] > $loW[$axis] - 10 && $_POST[$xORy."pos"] < $loW[$axis] + 10){
    return true;
  }
  else{
    return false;
  };
};

function isItXorY($xy){
  if ($xy == "x"){
    return 0;
  }
  else if ($xy == "y"){
    return 1;
  };
};

function respondWrong(){
  $snarks = ["So close","Warmer...","Colder...","Are you blind?","That's not Waldo.","Show me Waldo!",
  "Survey says...","This is not the Waldo you're looking for.","Your Waldo is in another castle.",
  "If Waldo were a snake..."];
  return $snarks[rand(0, 9)];
};

?>

We start by opening and reading the csv, then store that text into a variable $text. Using the method str_getcsv, we turn the string “250,430” into an array [250,430], and store it in the variable $locationOfWaldo. That variable is then passed into our function checkCoords, and the server will echo the result as the server’s response.

Functions

checkCoords
<?php
...

function checkCoords($loW){
  if (withinRange($loW, 'x') && withinRange($loW, 'y')){
    return "√ WALDO FOUND!";
  }
  else{
    return respondWrong();
  };
};

...
?>

In checkCoords, it passes the csv array into the withinRange function, once with the string “x” and another with the string “y”. If both instances return true, the checkCoord function returns the string “√ WALDO FOUND!” and the server script is done. But if both instances are false, the return value is the return value from the respondWrong function.

withinRange
<?php
...

function withinRange($loW, $xORy){
  $axis = isItXorY($xORy);
  if ($_POST[$xORy."pos"] > $loW[$axis] - 10 && $_POST[$xORy."pos"] < $loW[$axis] + 10){
    return true;
  }
  else{
    return false;
  };
};

...
?>

WithinRange receives the location array, and a string of x or y. Using the itItXorY function it determines if this instance is measuring the x or y axis, and then sets the return value to the variable $axis. Next we enter into a conditional block that compares the value of the “x/ypos” query key to the actual x/y location value. If the query value is within 10px from the actual position it returns true, otherwise returns false.

isItXorY
<?php
...

function isItXorY($xy){
  if ($xy == "x"){
    return 0;
  }
  else if ($xy == "y"){
    return 1;
  };
};

...
?>

The function receives one argument, a string. If the string is ‘x’ it returns 0, else if it is ‘y’ returns a value of 1.

respondWrong
<?php
...

function respondWrong(){
  $snarks = ["So close","Warmer...","Colder...","Are you blind?","That's not Waldo.","Show me Waldo!",
  "Survey says...","This is not the Waldo you're looking for.","Your Waldo is in another castle.",
  "If Waldo were a snake..."];
  return $snarks[rand(0, 9)];
};

...
?>

Defines $snarks as an array of strings containing sassy responses, then returns an item from the array at random.

Now we have our server-side script that reads and compares the users location to the actual location, stored in a csv file. The response is either the Waldo found string or a snarky remark, and all we need is to return to our JavaScript.

Returning to JavaScript

Alright, we are back in the world of JavaScript now with our response from the server. If you recall to our sendRequest function, it took four parameters, the last being a function to execute once it received a response. So, now we can goto our showResponse function.

function showResponse(e){
  document.getElementById("rspnse").innerHTML = e.target.response;
  if (e.target.response.includes("√")){
    endGame();
  }
};

ShowResponse will first select the node in our html with the id rspnse, this node is located in the gameData along with the timer. It then will change the text to the servers response, either Waldo found or a random snarky response, for the user to see. It then checks if it has the unique check mark in the victory response. If so, it moves onto endGame, else, it does nothing and continues. Now, we need to define endGame.

function endGame(){
  stopTimer();
  time = getTime();
  params = "name=" + name + "&score=" + time;
  sendRequest("POST","scripts/leaderboard.php",params, generateScoreboard);
  document.getElementById("endModal").style.display = "block";
};

At this point the user has found Waldo so, we can stop the timer. Then we’ll need the time took to find Waldo so we can write it into the scoreboard. The time is stored in the time variable. Next we define params for our request to the next server script. We attach the time and name for this user. Send the request and then show the endgame modal. Alas, there are more functions we have not defined yet.

function stopTimer(){
  clearInterval(myTimer);
};

Simply grabs the variable storing our timer interval and clears it.

function getTime(){
  time = document.getElementById("timer").textContent;
  document.getElementById("yourTime").textContent = time;
  return time;
};

The timer is now stopped and we can now get the timer’s text and use that as the players score. First, we will select the timer using the DOM and store its text into the time variable. Second, we will insert the time into the end modal’s body. Finally we will return the string of the time.

Now we have the time it took to find Waldo, and we have sent another request to leaderboard.php so, let’s hop back server-side to write our second script.

Back to php

So we have in our html an empty score board table, and we’ve just sent over a query string with a name and a score. We need to first write the sent information into a data file of names/scores and then send back the top ten names/scores to the JavaScript so, that it can populate our html using the DOM.

Leader board

So lets make another csv, with the format following (name,score), and we need a function to add a new score entry. After writing the new score we need a function to get all the scores and package it all up for the JavaScript, this can be done using JSON text formating.

<?php

addNewScore();
$responseJSON = convertCSVtoJSON();
$responseJSON[strrpos($responseJSON, ",")] = " ";
echo $responseJSON;

function addNewScore(){
    if (is_numeric($_POST["score"])){
        $newScoreRow = $_POST["name"].",".$_POST["score"]."\n";
        file_put_contents("../data/highscores.csv", $newScoreRow, FILE_APPEND);
    };
};

function convertCSVtoJSON(){
  if (($handle = fopen("../data/highscores.csv", "r")) !== FALSE) {
    $response = "[";
    while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { 
      $response = $response." {";
      for ($i=0; $i < count($data); $i++) {
        if($i == 0){
          $response = $response.'"name": "'.$data[$i].'",';
        }
        else{ 
          $response = $response.' "score": "'.$data[$i].'"';
        }
      }
      $response = $response."},";
    }
    $response = $response."]";
    fclose($handle);
  };
  return $response;
};

?>

First off we add the new score into our scores csv, then we call on convertCSVtoJSON which will read the csv and turn it into JSON text. More in detail below.2

Functions

addNewScore

This function accesses the $_POST parameters and begins by checking if the score is a number, just in case so it doesn’t break the data structure. If it is it proceeds with adding the score. When adding it sets the variable $newScoreRow to (name + “,” + score +”\n”) from $_POST, now we have a csv formated row to add. Now we tell the program to append that $newScoreRow string to the high scores csv, and we’ve written in the new score!

That covers the first half of the script’s functionality, now let’s look at the remaining functions responsible for packaging the csv up and sending it to JavaScript.

convertCSVtoJSON

We need to read and convert our csv formated text into JSON formated text. This function does that for us and starts by opening the csv file in read mode, if the file isn’t empty. Then we define $response as a string equal to “[”, this is the very first part of converting into JSON format. This variable will be added to as we loop through the files contents. So, with the file open we now set $data to fgetcsv(…). This will go line by and set data equal to that line but, fgetcsv will first get the line split it on the comma and return the array. So, $data actually will equal one line of the file as an array of each field.

For Example: The following line of csv “Nate,8.130” would, after fgetcsv, return as [“Nate”,”8.130”].

As long as $data doesn’t return false it will loop through each item in the current line. When $data is not false, it adds “{“ to response for a new object containing name and score. Then it loops through the two items on the line. If it is the first it adds ‘“name”: “’.$data[$i].’”,’ to the response string, but if not it adds the second item proceeded by ‘“score”:’. Then it hits the end of the row and adds “}” to finish the object representing the row. It repeats this for each row. Once there are no more row in the csv it adds a “]” to close all the row objects in an array, closes the file, and returns the $response.

$response would be a json formated string such as:
“[{‘Nate’: ‘8.130’}, {‘Devon’: ‘9.120’}, {‘Spencer’: ‘10.320’},]”


Now, we have a nice JSON response thats perfect to send now, right? Almost, if left in this state there would be an error in parsing the JSON, because we have a rogue comma at the end.

<?php
...

$responseJSON[strrpos($responseJSON, ",")] = " ";
echo $responseJSON;

...
?>

Lines 5 and 6, shown here, take that JSON string and use strrpos($responseJSON, “,”) to find the index of that last comma. With that index we get $responseJSON[indexNum] aka the position of that last comma. Now, we have targeted that comma and lastly we’ll just turn it into a space. No more parsing error!

Wrapping up JavaScript

Okay we’re in the home stretch what’s left is parsing the string response ,sorting least to greatest, and filling in our html table. We left off on generateScoreboard and we’ll start defining it.

function generateScoreboard(e){
  allScores = parseJ(e.target.response);
  sortByHighest(allScores);
  fillHTML(allScores);
};

This is the after load function for our request in endGame so, we can access the response at e.target.response. The response is still a string though so we can’t treat it like an array of objects properly, to do that we first use the function parseJ and store the return value in the allScores variable. Now it is an array of objects. We now pass it into sortByHighest a function that organizes the objects least to greatest based on their ‘score’ property’s value. Finally it passes the sorted array into fillHTML to populate the html.

function parseJ(stringJSON){
  return JSON.parse(stringJSON);		
};

Simply takes the passed string, parses it for json, and returns the array/object.

function sortByHighest(scoreJSON){
  scoreJSON.sort( function(a,b){ return a["score"] - b["score"] });
};

Takes in the array of objects and uses the sort method with a anonymous compare function3 in the parameters, based off the “score” property.

function fillHTML(data){
  stringHTML = '';
  for (i = 0; i < data.length && i < 10; i++){
    stringHTML += "<tr><td>" + data[i]["name"] + "</td><td>" + data[i]["score"] + "</td></tr>";
  };
  document.getElementById("topPlayers").innerHTML = stringHTML;
}

Receives the array of objects and initially sets a variable ‘stringHTML’ to an empty string. This variable will be concatenated to as we loop through our array of objects. We then begin looping for the length of objects in the array or until we hit 10 items. Each time it loops we take the current object’s properties ‘name/score’ putting each in between td tags, and then both between tr tags. Every time adding the tr tag string to the stringHTML variable. Once done looping set the html formated string to the innerHTML of the empty table. Thus, populating our high score list.

Now you have your own wheres Waldo web app

Congrats! You’ve done it. What is it? Well, it is writing a working game of wheres Waldo with server integration, and databases. Using AJAX and JSON you’ve made a truly dynamic page, feel free to build upon this foundation, or change the existing foundation.

Footnotes

  1. In the DOM simply an HTML element, in JavaScript an object representing a DOM node. 

  2. A sequence of characters that define a search pattern. 

  3. A compare function is the optional parameter for the sort method and can be used to change the default sorting order. The default is according to string Unicode points. On how it works, I’d recommend checking out this stack overflow.