84 Matching Annotations
  1. Jun 2018
    1. options.error()

      An error occurred and we need to handle it. Call the error function callback.

    2. request.onerror

      Here, we define a method called onerror that is automatically called if the request encountered an error. Any error could be anything, but common errors are not having a URL to access, the URL not working, or trying to use an invalid HTTP requests.

    3. options.warning();

      We reached the server but it returned a warning and didn't complete the request. There's a million reasons this could have happened.

    4. request.responseText

      request.responseText is a property on the instance that contains a plain text version of the server's response to our request, if there was one.

    5. options.success

      Since the request was successful, call the success function callback.

    6. // Send the POST header if needed if (options.method.toLowerCase() === "post") { request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); }

      This whole block is for POST requests only. It is required for them to work. MDN has all the info you need. Explaining it here is beyond the scope of these annotations.

      However, one thing will be explained. Notice the line

      options.method.toLowerCase() === "post"
      

      This is input normalization. We're taking the value of the options.method, converting it to lowercase, then checking it against the string "post". If the comparison is true, we are making a POST request.

    7. if (request.readyState === 4 && request.status === 200) {

      This particular condition confirms that the request completed successfully. See MDN for more details.

    8. request.onload = function() {

      Here, we are defining a method on the instance called onload. The instance will automatically call this method once the request is made. This is another callback.

    9. options.data

      The data key is defined by the user. This is the data the request sends to the server.

    10. request.send

      Just as the instance we made has a method in it called open, there's a method called send. This is where we give it the data to send to the server as part of the request.

    11. options.url

      The url key is defined by the user. This is the URL the request needs to use.

    12. options.method

      The HTTP method to use to complete the request (usually GET or POST).

    13. request.open

      The instance we made has a method in it called open. This is where we give it the information needed to make the request.

    14. var request = new XMLHttpRequest();

      Create an instance of the XMLHttpRequest class for use to use.

    15. function() {}

      This is an empty function, sometimes called a "noop" or "no-op" (short for "no operation"). It does nothing. Remember that each response case needs a callback. The the user doesn't provide a callback, we create a noop function for XMLHttpRequest to call if that case is hit. We do this for every missing callback.

    16. options.error

      options.error is for when the AJAX request completely and utterly fails.

    17. // Define empty functions for the callbacks

      The next three conditionals follow the same "check if undefined and if so, define the key" structure as above. The difference is we are now defining callbacks, which is how we perform an additional action after the AJAX request is completed. If any conditional is true, it means the user has not defined a callback for that response case (explained shortly), requiring us to define one.

    18. options.success

      options.success is for when the AJAX request completes successfully.

    19. options.warning

      options.warning is for when the AJAX request reaches the target server but received an error back instead of the desired action.

    20. options.method = "POST";

      If the condition above was true, create the method key in the options object and assign it a value of "POST".

    21. === undefined

      undefined means the same thing as it does in maths: it doesn't exist or cannot happen. Division by zero is undefined.

      Here, the value of options.method is being checked if it is undefined. If so, it means that key does not exist in the object and the condition evaluates to true. If it does exist, the condition is false.

    22. options.method

      To access data in an object, we use dot notation. options.method means in the options object, access the method key and return its value.

    23. options

      We are specifying a parameter for this function: options. This is going to be in the form of an object, which is a key-value pair structure. This is how the user will specify how to make the AJAX request.

    24. microAjax

      We are creating a function called microAjax. It's basically jQuery.ajax. It is a small wrapper around the XMLHttpRequest JavaScript API.

    1. if (stristr($result['msg'], 'win') || stristr($result['msg'], 'over')) { $hangman->newGame();

      If the message we recieved from processGuess contains the the string "win" or "over" (the search is case insensitive), we have finished the game and we need to start a new one.

    2. } else if (isset($_POST['guess'])) {

      We don't have a POST key of pageLoad but do have a key of guess, meaning we are playing an existing game.

    3. echo json_encode([ 'hint' => $hangman->getHint(), 'word' => $hangman->displayWordStructure(), 'header' => $hangman->getHeader(), 'gallows' => $hangman->getGallows() ]);

      Here, we're getting the game's word, word hint, header message (word is/words are) and incorrect guess, putting them into a key-value array, converting it into JSON, then returning it to the JS. The methods being called is our public API, as defined in the class by using the public keyword on the respective methods.

    4. // White-listed character, do not make user guess it for ($i = 0; $i < $wordLen; $i += 1) { if (in_array($wordLower[$i], $this->whiteList)) { $wordLen -= 1; } }

      Psuedocode explanation:

      For every letter in the current word:
        if the current letter is not whitelisted:
          Decrease the letter count of the word,
          used to determine how many letters
          the user must guess to win
      
    5. public function displayWordStructure() {

      This function is probably the clearest when it comes to showing the development history. It's almost a straight duplicate of the displayWord method. In fact, it's actually a more limited version of displayWord. While displayWord generates the placeholders for unguessed, guessed, and whitelisted characters, this only generates the placeholders for unguessed and whitelisted characters, falling back to displayWord if there are any correctly guessed letters! Clearly my train of thought in how to write this step changed and I didn't go back and clean up my code.

      This is a change I highly recommend making. Rename displayWord to displayWordStructure, completely deleting the old displayWordStructure method, and updating all method calls. I've checked the code and everything will still run the same. You also may want to move the comment above the old displayWordStructure to the new one.

      As such, I'm not annotating this method. Read the annotations for displayWord again if you really want. 👌

    6. if (in_array($this->word[$i], $this->whiteList)) { $finalOutput .= $char;

      If the current letter ($this->word[$i], which should be replaced with $char) is in our whitelist of characters we don't want the user to guess, stick it into the placeholder.

    7. public function getGallows() { return count($this->incorrectLetters); }

      Another helper method used to get the number of letters that have been incorrectly guessed.

      Some improvements could be made here.

      • The function name could be clarified.
      • Instead of only returning the count of incorrect letters, maybe give a way for the user to see what letters have been incorrectly guessed.
    8. $this->guess = strtolower($guess);

      Whenever the user guesses a letter, we convert it to lowercase and store it in the class property guess.

      Why do we lowercase the user's input here? This is the start of a programming pattern called input normalization. There's multiple parts to the pattern, but the most basic and common part is taking the user's input and (if it's an alphabetic input) changing the case to upper or lowercase. Which one you choose is up to you. But why do we do this?

      You know from learning English that an uppercase A is a different letter than a lowercase a. Though they are both as, they serve different purposes based on their casing. When playing Hangman with friends, normally they don't force you to guess both the letter and its correct casing. They just ask for the letter regardless of case and tell you if it is in the word or not.

      This is the same way in programming. A is different than a and as such, not normalizing the input would force us to perform a case sensitive comparison later (which can get messy). We really don't care what case letter the user gives us as long as it is a letter. Therefore, we change the user's input to lowercase for easier comparison later.

      There are cases in programming were casing in user input matters. In those cases, this part of input normalization would be inappropriate. You as the developer must be able to interpret the requirements and implement them accordingly.

    9. public

      The public keyword indicates that this method can be (and probably is 😉) called from outside the class. Can also be applied to class properties.

    10. $wordList[$number];

      Use our randomly generated number to index the array and pick a word.This and the previous line can be replaced with array_rand.

    11. // Blank space

      lol old comment before I changed this to filter off a whitelist instead of only a space.

      https://www.youtube.com/watch?v=e-ORhEE9VVg

    12. $number = mt_rand(0, count($wordList) - 1);

      Very poor way of picking a random number within the bounds of the word list. Should be replaced with array_rand (I was not aware of this function at the time).

    13. }

      When writing PHP, if you have a file that is solely PHP, it is recommended that you do not add a closing PHP tag (?>). Doing so then having a bunch of whitespace after it can create some pretty weird errors. I lost a day of working time to this issue. Take it from an experienced PHP developer, save yourself lots of trouble and don't put the closing PHP tag.

    14. }

      Our check if the file was requested by a POST request ends here. As you can see, there is no more code after this. At this point, the special __destruct method defined in the class will be called to tear down and preserve the class state between guesses.

    15. echo json_encode($result);

      We're playing an existing game here, so we need to return the results of processGuess to display to the user.

    16. $result = $hangman->processGuess();

      Process the guess and see what happens. This could have been modified to accept the guessed letter and let it alter the class state instead of separately calling setGuess. It doesn't really matter.

    17. $hangman->setGuess($_POST['guess']);

      The value of the guess key will be the currently guessed letter. Use our public API method and change the class state to have the guessed letter.

    18. if (isset($_POST['pageLoad'])) {

      Inside our POST request, we are looking a key named pageLoad. If it exists, we are starting a new game.

    19. private

      The private keyword is used to prevent properties and methods in a class from being used outside the class.

    20. $hangman = new Hangman();

      Initialize an instance of the Hangman class. This will call the special __construct method defined in the class.

    21. if ($_SERVER['REQUEST_METHOD'] == 'POST') {

      This file is accessed by a POST AJAX request via JavaScript. I decided I wanted the script to respond only if it was requested by a POST request.

    22. }

      This is the ending of the Hangman class. 😃

    23. $messages['gallows'] = $this->getGallows();

      Get the number of incorrect guesses the user has made. This is where the weird function name comes in to reduce code purpose clarity.

    24. // Completely replace the first message

      I think this comment is a misnomer but I am not sure.

    25. else if (count($this->incorrectLetters) === 6) {

      Otherwise, if we've made six incorrect guesses, we lose the game.

      Why six? Because that's how many images I made for the gallows.

    26. if (count($this->correctLetters) === $wordLen) {

      If the length of the array of correct letters is equal to the number of letters in the word that the user needed to guess (which excludes any whitelisted characters), the user wins the game.

    27. // You're Winner!
    28. $messages['word'] = $this->displayWord();

      Get the placeholder structure for the current word, to be shown to the user.

    29. $this->incorrectLetters[] = $this->guess;

      Add the incorrectly guessed letter to the list of incorrect guesses. Unlike correct guesses, this only needs to happen once.

    30. } else if (!$isCorrectGuess) {

      The currently guessed letter has not been guessed at all and was determined to be an incorrect guess.

    31. for ($i = 0; $i < $letterOccurence; $i++) { $this->correctLetters[] = $this->guess; }

      For every occurrence of this letter in the word, add it to the array of correctly guessed letters. As an example, if the word is "pizza" and you guessed a "z", the letter "z" will be added to the list of correctly guessed letters two times because the letter "z" is in the word "pizza" twice.

    32. $letterOccurence = substr_count($wordLower, $this->guess);

      Count how many times the currently guessed letter appears within the current word. Can be a value of zero, meaning it doesn't occur.

    33. else if ($isCorrectGuess) {

      The currently guessed letter has not been guessed at all and was determined to be a correct guess.

    34. } else if (in_array($this->guess, $this->correctLetters)) {

      The currently guessed letter has already been correctly guessed.

    35. if (in_array($this->guess, $this->incorrectLetters)) {

      The currently guessed letter has already been incorrectly guessed.

    36. $messages = [];

      Key-value array that will contain any messages we should display to the user.

    37. public function processGuess() {

      The worst function in the code. It was originally written differently but randomly broke while working on an unrelated section, forcing me to rewrite it using a series of delicate and inefficient (as far as execution order) if/else if blocks that thankfully have not broke since. Follow along carefully.

    38. $isCorrectGuess = (bool) $letterOccurence && !in_array($this->guess, $this->whiteList);

      This is a compound condition being redundantly cast to a boolean. We confirm that the guessed letter appears at least once in the word and (&&) that the guessed letter is not in the whitelist. If both of these conditions are true, we consider this to be a correct guess.

    39. $wordLen = iconv_strlen($this->word);

      Get the length of the current word. Again, iconv_strlen is probably a bug waiting to happen.

    40. $wordLower = strtolower($this->word);

      Lowercase the current word for case-insensitive comparisons.

    41. public function displayWord() {

      This is the function that generates the placeholder text for the word. The point of Hangman is to fill in the letters before the man is hung. We know what to fill in because their are underscores used as placeholders for the letters and correctly guessed letters are dislayed. This function generates those placeholders.

    42. // Unguessed letter else { $finalOutput .= '<span>&nbsp;&nbsp;</span>';

      The current letter is not in the whitelist or correctly guessed letters, so display a placeholder.

    43. // Already guessed letters } else if (in_array(strtolower($char), $this->correctLetters)) { $finalOutput .= "<span>{$char}</span>";

      Here, we are checking if the current letter is in the list of correctly guessed words and displaying it if so. Note the call to strtolower on the current letter. Because we lowercased the user's input, we need to do the same for the letter because in_array is case-sensitive. Further note that we only lowercase in the condition and not when displaying the letter. We want a case-insensitive comparison but want to display the properly cased letter.

    44. return count(explode(' ', $this->word)) >= 2 ? 'The Words Are' : 'The Word Is';

      Lots of fun stuff in this one line.

      • The entire construct is a ternary operator. The format is condition ? true statement : false statement.
      • For the condition:
      • We take the current game word and split it on a space. This returns an array.
      • We count the number of elements in the resulting array.
      • If there are more than two elements, that means we have a game that uses multiple words.
      • If the condition evaluates to true, return the string 'The Words Are'.
      • If the condition evaluates to false, return the string 'The Word Is'.
    45. for ($i = 0; $i < iconv_strlen($this->word); $i += 1) {

      Iterate through every character in the current game word. iconv_strlen is almost certainly a bug waiting to happen. Getting the length of a string in PHP... is not the most straightforward. Changing it to strlen is probably a good idea.

    46. $finalOutput = '';

      This is the string that will eventually contain all of the placeholder.

    47. public function getHint() { return $this->hint; }

      This is a helper method used to (potentially) access the word hint outside the class because the property is currently set as private. This method could be removed completely by changing the $hint property to public and letting outside code access it directly. Having this method is a layer of abstraction and allows us to manage the class internals as we want while not breaking the public API.

    48. $_SESSION['clearCache'] = true;

      This is where we set that session key that indicates if we are starting a new game or not. The function name and resulting session key name indicates a poor naming choice for the key should probably be renamed to newGame for clarity.

    49. $wordList = json_decode(file_get_contents('words/word-list.json'), true);

      Multiple things happening here.

      • There is a JSON file located at words/word-list.json that contains all our words and hints.
      • We read the file contents.
      • We then immediately convert the JSON into a PHP array.
    50. $_SESSION['gameWord'] = $this->word; $_SESSION['gameHint'] = $this->hint; $_SESSION['correctLetters'] = $this->correctLetters; $_SESSION['incorrectLetters'] = $this->incorrectLetters;

      This is the exact opposite of what we did a second ago. Here, we are taking the current word, hint, and guessed letters and putting them into the session.

    51. __destruct

      Special class method called on class deinitialization. Used to tear down or preserve a class's state.

    52. $this->word = $_SESSION['gameWord']; $this->hint = $_SESSION['gameHint']; $this->correctLetters = $_SESSION['correctLetters']; $this->incorrectLetters = $_SESSION['incorrectLetters'];

      Get the game's word, hint, and lists of guessed letters from the session and put them back into class properties for easier use.

    53. } else {

      Obviously, if the above condition was if we were starting a new game, this block is for when we are playing an existing game.

    54. unset($chosenWord); unset($_SESSION['clearCache']);

      Delete the $chosenWord variable (reason unknown) as well as the clearCache session key. Failing to remove the latter will always cause a new word to be picked on every guess.

    55. $chosenWord = $this->selectRandomWord(); $this->word = $chosenWord['word']; $this->hint = $chosenWord['hint'];

      Since we're starting a new game, we need a word to guess. We get this by calling the class method selectRandomWord (we'll see this later). It returns a key-value pair, which we unpack into the proper class properties.

    56. if (isset($_SESSION['clearCache']) || !isset($_SESSION['gameWord'])) {

      Here the session we started comes into play. We are accessing two session keys: clearCache and gameWord. We check if the first key is set or (||) that an existing word has not been set. In either case, it means we're starting a new game.

    57. __construct

      Special class method called on class initialization. Used to set up a class's state.

    58. $word, $hint, $guess; private $whiteList = ['-', ' ']; private $correctLetters = []; private $incorrectLetters = [];

      L1: Set up three variables for use later. Names should be clear, but just in case...

      • The current word to be guessed
      • The word's hint
      • The user's current letter guess.

      L2: To make it easier for the user, do not make them guess if a word (or words) have a space or dash in them. These are printed when the word structure is shown to the user.

      L3-4: Create empty arrays that track what letters have been guessed, both correctly and incorrectly.

    59. class Hangman {

      A class was used to keep everything contained and to avoid global variables (which are a mess in PHP).

    60. session_start();

      Sessions are a basic part of the Web. Sessions allow us to persist data. Don't use them for everything, but in this game's case, it works. We start a session as the first thing we do so we can persist data between guesses.