JavaScript Game High Score List


In this project we will create a high score list using:
  • JavaScript
  • Fetch API
  • JSON
  • PHP
The Fetch API is a highly adaptable and user-friendly alternative to XMLHttpRequest. We are adding a game score and a high score list to the previous project code: Make a JavaScript Dice Game.

dice
  images
  index.html
  style.css
  main.js
  scores.json
  dice.php

➼ Main Project Folder

➼ images - This is the folder that contains images used to create the dice game.

➼ index.html - This is the main page users will be directed to when entering your web address. HTML is the markup language that displays your content in the web browser.

➼ style.css - CSS formats HTML: color, size, shape, font ect.

➼ main.js - Here is where we will write our JavaScript. In the previous tutorial we had our JavaScript inside index.html. For this project we seperate the JS. We use JavaScript to write front-end logic, including using the Fetch API to read our JSON file.

➼ scores.json - JSON (JavaScript Object Notation) is a file format used to store data. JSON data is quick and easy to transport, so we will store our high score list data in scores.json.

➼ dice.php - PHP is an open-source server-side scripting language. The majority of the web is written in PHP. In this project we will use PHP to update our high score lists.

Project Code - Quick Copy

You can copy the complete project code below and paste it into the corresponding files.

Fetch API - Read JSON data

The first function we will write is get_scores. It takes in one parameter, a callback. A callback is a function that gets passed into another function. get_scores uses the Fetch API to retreive the data stored in scores.json.

Mouse Hover over the ➼ blue text in the descriptions to highlight the code decribed.

Code

const Errors = document.getElementById('error');

function get_scores (callback) {
  // High Score Data
  let file = "scores.json";

  // Fetch High Score Data
  fetch(file, {cache: 'no-cache'})
    .then(function(response) {
        //  If the response isn't OK
        if (response.status !== 200) {
          Errors.innerHTML = response.status;
        }
        // If the response is OK
        response.json().then(function(data) {
          let scores = JSON.stringify(data);
          console.log(scores);
          callback (scores);
        });
      })
    // If there is an error
    .catch(function(err) {
      Errors.innerHTML = err;
    });
}

Description

➼ function get_scores (callback) - get_scores is a user defined function that takes in one parameter: a callback.

➼ file - We declare the variable file and set it equal to our scores.json, which is the location of the file containing the high score data.

➼ fetch () - We pass file into the fetch method to access scores.json. By default fetch requests a cached. We also choose the option "no-cache". If the high score list has changed, we want it to download again before returning the data.

➼ response.status !== 200 - If the response status is not OK, we will display the response status in an element with the id error.

➼ response.json() - If the response is OK (200), we will take the HTTP response and turn it into a JSON string and store it in a variable called scores. We print that value to the console and pass it into the callback function that was passed into our parent function get_scores.

➼ .catch - If instead of a response fetch responds with an error, those errors will be displayed in the element with ID error.

console.log(scores) output

0: Object { name: "Hatnix", score: "9" }1: Object { name: "Caysind", score: "8" }2: Object { name: "Ambrosine", score: "7" }3: Object { name: "Kozross", score: "6" }4: Object { name: "Jookia", score: "5" }5: Object { name: "Arthur", score: "4" }6: Object { name: "John Smith", score: "3" }7: Object { name: "Sir Diealot", score: "2" }8: Object { name: "Commander Candy", score: "1" }9: Object { name: "Robert", score: "0" }length: 10

scores.json

[
  {"name":"Hatnix","score": 9},
  {"name":"Caysind","score": 8},
  {"name":"Ambrosine","score": 7},
  {"name":"Kozross","score": 6},
  {"name":"Jookia","score": 5},
  {"name":"Arthur","score": 4},
  {"name":"John Smith","score": 3},
  {"name":"Sir Diealot","score": 2},
  {"name":"Commander Candy","score": 1},
  {"name":"Robert","score": 0}
]

In summary, get_scores():

  1. Takes in one parameter-- a callback
  2. Fetched data from scores.json
  3. fetch() responded with scores.json's data as an HTTP response
  4. Used JSON.stringify to turn the HTTP request into a JSON string and stored that value in a variable called scores
  5. Passed scores into the callback function

JSON Data in an HTML List

Next, we will write a function called list_scores that will pass into get_scores. It will take in scores as its only parameter, then loop through the contents to place inside an HTML list element (ul). This function displays the data we fetched from scores.JSON into the high score list seen by players.

<h2>High
         Scores</h2><div id="error"></div><ol id="highscores"><li>Hatnix ... 9<li/><li>Caysind ... 8<li/><li>Ambrosine... 7<li/></ol><input id="lowscore" type="hidden"></div>

Code

const List = document.getElementById("highscores");

var list_scores = function (scores) {
  // turn scores JSON to a JavaScript object
  let object = JSON.parse(scores);

  // Store lowest high score for later
  let lowest_score = object[9].score;
  document.getElementById('lowscore').value = lowest_score;

  // loop through high scores
  for (let i=0; i<object.length; i++) {
    // console.log(object[i]);
    let li = document.createElement("LI");
    let text = document.createTextNode(object[i].name + " ... " + object[i].score);
    li.appendChild(text);
    List.appendChild(li);
    if (i===0) {
      li.setAttribute('class',"top1");
    }
    if (i===1) {
      li.setAttribute('class',"top2");
    }
    if (i===2) {
      li.setAttribute('class',"top3");
    }
  }
}

Description

➼ list_scores - Our function is stored in a variable called list_scores.

➼ let object - We are parsing our JSON string into a JavaScript object and storing it in a variable named object

➼ lowest_score - The object at index 9 is the lowest highscore on the highscore list. We are storing that value in a hidden input element with the ID 'lowscore'.

➼ for(){} - A JavaScript for loop is used to loop through the contents of object. Each element inside object (i.e., {"name":"Hatnix","score":9}) will run through the code in the block.

➼ li - We are creating a new list item (<li></li>) and storing it in a variable named li. The we are creating text (i.e., "Hatnix ... 9") and putting it inside the list item.

➼ append list - We are taking our newly created list item and placing it inside our list element (<ol id="highscores"></ol>).

➼ setAttribute - We are changing the class of the top three high scores.

In summary, list_scores():

  1. Takes in one parameter-- a JSON string
  2. Convers the JSON string into a JavaScript object stored in the variable object
  3. Loops through object to create list items containing the data for each high score, then sticks the list item inside the high score list.

index.html

Now let's take a look at our HTML file. I moved some things around and added a few elements since Dice Game Version 1.

<html>
  <!-- Page Head-->
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset="UTF-8">
    <title>Dice Roller Durby V2</title>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta name="description" content="Play a fun dice game. Try your luck and get on the highscore list!">
  </head>
  <!-- Page Body: on load execute list_scores()-->
  <body onload="get_scores(list_scores)">
    <h1 align="center">JavaScript Dice Game Version 2</h1>
    <div class="row" align="center">
    <!-- Column 1 -->
      <div class="col-3">
        <h2>Your Dice</h2>
        <!-- Player's Dice -->
        <img src="images/d1.png" id="mydice1">
        <img src="images/d1.png" id="mydice2"><br>
        <h2>Enemy"s Dice</h2>
        <!-- Enemey's Dice-->
        <img src="images/e1.png" id="hisdice1">
        <img src="images/e1.png" id="hisdice2">
        <div id="enemy_score"></div><br>
      </div>
      <!-- Column 2 -->
      <div class="col-3">
        <!-- Play button -->
        <button class="greenBut" onClick="throwdice()">Roll Dice</button>
        <!-- Game Form (myform)-->
        <form id="myform">
          Your name:<br>
          <!-- Player's name-->
          <input name="player_name" maxlength="20" class="greenBut" type="text" id="player_name" placeholder="Type Your Name" required>
          <!-- Player's Score -->
          <br>Your score:<br>
          <input name="player_score" class="greenBut" type="number" value=0 id="score" readonly></input><br></h3>
          <!-- Submit Button -->
          <button type="submit" class="greenBut">End Game</button>
        </form>
      </div>
      <!-- Column 3 -->
      <div class="col-3">
        <br><br>
        <!-- "message" image-->
        <img src="images/good-luck.gif" id="message" alt="Good Luck!"/>
      </div>
      <!-- Column 4 -->
      <div class="col-3" align="center">
        <h2>High Scores</h2>
        <!-- error container -->
        <div id="error"></div>
        <!-- High Score Lists -->
        <ol id="highscores"></ol>
        <!-- lowest high score hidden input -->
        <input id="lowscore" type="hidden">
      </div>
    </div>
    <script type="text/javascript" src="main.js"></script>
  </body>
</html>

Dice Game Version 2

Reload Data

We are about to write the code to submit After we submit myform, but before we do we will create a function called resetForm. After we submit myform, we want to reload the high score list and the player's score to get back at zero. resetForm will accomplish these tasks.

function resetForm () {
  while (List.hasChildNodes()) {
    List.removeChild(List.firstChild);
  }
  get_scores(list_scores);
  document.getElementById("score").value = 0;
  score=0;
}

In summary, resetForm():

  1. Loops throgh the HTML high score list and deletes all its <li> elements
  2. Fetches scores.json and remakes the high score list
  3. Sets the value of score to 0

Keeping Score

Since resetForm references our score variable, let's take a look at the code that determines the score. We begin by initializing the variable score at 0 (var score = 0;). If the total of the player's dice is higher than the enemy's dice, the player gets one point added to his score. If the enemy's total is greater than the player's total, then one point is subtracted from the player's score. If it is a tie, no points are gained or lost.

// player's score
var score = 0;
// "message" img alt text array
var side_alt = ["roll: 1", "roll: 2", "roll: 3", "roll: 4", "roll: 5", "roll: 6"];

function throwdice(){
  //create a random integer between 1 and 6
  let rand1 = Math.round(Math.random()*5) + 1;
  let rand2 = Math.round(Math.random()*5) + 1;
  let rand3 = Math.round(Math.random()*5) + 1;
  let rand4 = Math.round(Math.random()*5) + 1;

  // Set Image src
  document.getElementById("mydice1").src = "images/d" + rand1 + ".png";
  document.getElementById("mydice2").src = "images/d" + rand2 + ".png";
  document.getElementById("hisdice1").src = "images/e" + rand3 + ".png";
  document.getElementById("hisdice2").src = "images/e" + rand4 + ".png";

  // Set Image alt
  document.getElementById("mydice1").alt = side_alt[rand1];
  document.getElementById("mydice2").alt = side_alt[rand2];
  document.getElementById("hisdice1").alt = side_alt[rand3];
  document.getElementById("hisdice2").alt = side_alt[rand4];


  who_won(rand1,rand2,rand3,rand4);
}

function who_won(rand1,rand2,rand3,rand4){
  let player_points = rand1 + rand2 + 2;
  let enemy_points = rand3 + rand4 + 2;
  let giffy = winner(player_points,enemy_points);
  document.getElementById("message").src = "images/" + giffy;
  document.getElementById("message").alt = giffy;

  // set the value of the score input element to the variable score
  document.getElementById("score").value = score;
}

function winner(player, enemy) {
  if (player < enemy) {
    // subtract a point
    score = score-1;
    return "oof-looser.gif";
  }
  if (enemy < player) {
    // add a point
    score = score + 1;
    return "twerk-win.gif";
  }
  if (player == enemy) {
    return "equal.gif";
  }
}

If you have any questions about the code simulating the dice throws, please watch Make a JavaScript Dice Game (YouTube Video #1). If you have any questions about the score keeping please watch JavaScript Game Keeping Score - Dice Game (YouTube Video #2)

Fetch API - Submit Form

We are finally ready to submit myform! We use the Fetch API to send a post request back to our PHP script (dice.php).

    Code

const Myform = document.getElementById("myform");
Myform.addEventListener("submit", function (event) {
  // don't reload page
  event.preventDefault();
  var tenth_score = document.getElementById('lowscore').value;

  if (score > tenth_score) {
    document.getElementById("message").src = "images/highscore.gif";
    document.getElementById("message").alt = "You made it on the highscore list!!!";
  }
  else {
    document.getElementById("message").src = "images/good-luck.gif";
    document.getElementById("message").alt = "Good luck chump!";
  }
      //Form Data Object
    var formData = new FormData(this);

    // fetch request
    fetch ("dice.php", {
      method: "post",
      body: formData
    })
    .then (function (response){
      return response.text();
    })
    .then(function(text) {
      resetForm();
      console.log(text);
    })
    .catch(function (err) {
      Errors.innerHTML = err;
    })
});

Description

➼ addEventListener - We are listening in on myform for this user to click the submit button.

➼ preventDefault(); - Don't reload the page!

➼ score vs tenth_score - If the player's score is greater than the lowest highscore, the image with ID "message" will be changed to "highscore.gif". Otherwise, it goes back to "good-luck.gif".

➼ formData - We declare a variable named formData that stores form data from this form (#myform). A FormData object is used to send data in a key=>value format.

➼ fetch () - Finally, we use the Fetch API to send send our form data (formData) back to out PHP script (dice.php). The POST method is used.

➼ response - If we sucessfully receive an HTTP response back from dice.php, we convert it into plain text. Then we print the response to console. We also execute resetForm()

➼ error - If there is an error, we display the error in our element with ID errors.

In summary, the form submission code:

  1. Listenes for the user to click the submit button
  2. Doesn't reload the page
  3. Changes the src and alt attributes of the img element with ID = "message"
  4. Uses the Fetch API to send a POST request containing the data from "myform" to dice.php
  5. If the POST request is sucessful, displays the response to console
  6. If the POST request returns errors, displays the errors in the element with ID = "error"

dice.php

After finally submitting the form, PHP can process the data. dice.php will read the contents of scores.json and see if the player made the high score list. If he did, the data in scores.json gets updated, and PHP will print, "Howdy Partner!". If the player is not a high scorer, PHP will print "No high score".

     Code

<?php
  // myform data
  $player_name = filter_var($_POST[player_name], FILTER_SANITIZE_STRING);
  $player_score = (int)$_POST[player_score];

  // associative array
  $player_array = array("name"=>$player_name, "score"=>$player_score);

  // read scores.JSON and turn into a PHP associative array
  $highscoreJSON = file_get_contents("scores.json");
  $highscore_array = json_decode($highscoreJSON, true);

  // Declare variables
  $key = 0;
  $highscores = array();

  // If the player made it on the high score list
  if ($player_score > $highscore_array[9][score]) {
    foreach($highscore_array as $k => $value) {
      $score = $value[score];

      // The player has not beaten these scores
      if ($score >= $player_score) {
        $highscores[$k] = $value;
      }

      // The player has beat this score
      if ($score < $player_score) {
          $key = $k;  // current value index
          // add the player at this spot on the high score list
          $highscores[$k] = $player_array;
          // loop through the rest of the highscores, up to the 9th item
          for ($i = $key; $i < 9; $i++) {
            $highscores[$i + 1] = $highscore_array[$i];
          }
          break; // end the loop here
      }
    }

    // Parse $highscores into a json string and rewrite scores.json
    $jsonscores = json_encode($highscores);
    file_put_contents('scores.json', $jsonscores);

    // print "Howdy Partner!"
    echo "Howdy Partner!";
  }
  // If the player did not make it on the highscore list
  else {
    // Print "No high score"
    echo "No high score";
  }
?>

Description

➼ $_POST[] - The player's name and player's score sent from myform to PHP, and now we are storing them in variables named $player_name and $player_score, respectively. $player_name is sanitize with FILTER_SANITIZE_STRING and we ensure $player_score is an integer with (int).

➼ $player_array - $player_array is an associative array storing the player's name and score.

➼ $highscore_array - PHP is reading scores.json, then using json_decode to parse it into an associative array. That array is stored in a variable named $highscore_array.

➼ if($player_score > $highscore_array[9][score]) - The $highscore_array item indexed at [9] is the lowest highscore on the highscore this. If the player made it on the high score list, this code block will execute.

➼ foreach loop - We are looping through $highscore_array using a for each loop.

➼ if ($score >= $player_score) - As we loop through $highscore_array, if the current score ($score) is greater than or equal to the player's score ($player_score), we will add the current array item ($value) to $highscores. It will maintain the same index ($k) as it had in $highscore_array. This highscore hasn't changed its placement on the high score list.

➼ if ($score < $player_score) - The key of the first score that the player has beaten will be stored in the variable $key. $player_array gets added to $highscores at this index. Then we loop through the rest of the array (up to the 9th item), adding them to $highscores one place higher than they were in $highscores_array. The 10th item is no longer a high score.

➼ write scores.json - We take our newly made array ($highscores), parse it into a JSON string, and store it in a variable names $jsonscores. We then rewrite scores.json with $jsonscores.

➼ echo - If the player made it on the high score list we print "Howdy Partner!". As you work on the file, you can change the print statements to print whatever is useful at that stage of development.

➼ else - If the player did not make the high score list, we print "No high score".

In summary, dice.php:

  1. Places the POST data in an associative array
  2. Decodes scores.json into an associative array
  3. Compares the player's score to the existing highscore list
  4. Updates scores.json if the player made it on the highscore list

Testing in a LAMP Virtual Server

If you are unable to write files, the following command will grant permission:

$ chown -R www-data:www-data /var/www

The above command changes the owner to Apache. However, once you give /var/www/ to Apache, it may not want to give it back. To gain access, you can add your username to the www-data group:

$ sudo usermod -a -G www-data [your_username]

Recommended Tutorials

  • Make a JavaScript Dice Game: Our JavaScript Fetch API High Score List tutorial added scoring and a high score list to code in the Make a JavaScript Dice Game tutorial.
  • Build a Virtual LAMP Server: In order for the Fetch API and PHP code (or any other server code) to work, you must run it on a server. If you do not already have access to a server, you can quickly put it together with my tutorial.
  • Vanilla JavaScript Basics: Get a strong foundation in JavaScript. It will help you learn and code more efficently in all the popular JavaScript frameworks.
  • Beginner PHP Tutorials: Learn some basic PHP logic and syntax.
  • Build a JavaScript Random Number Generator: Learn to Generate Random Numbers within a user specified range.