Select Page

[Part of the Speaking Javascript project.]

This is the first of two vanilla JS projects I did for my recent front end developer course on Skillcrush.com. I’m going to describe how the user experiences the program. Then I will create a logical overview of the program by listing the DOM variables, the global variables, and the functions; and explaining how those components work together to create the user’s experience. Then I will write out each function and explain how it works internally, as well as its generic functionality (how it can be used in other projects).

The idea is to start growing a sense for how all the pieces of JS programs fit together to create user experiences.

User Experience

The display has a spot for messages to update the user about the game; a spot for the word as it is being guessed (if a letter in the word has not yet been guessed by the user, than that letter is replaced by a dot); a spot to display all the unsuccessfully guessed letters; a spot where the program informs the user how many guesses he has left; a spot where the user can input a letter as a guess (if the letter is in the word, it is added in the correct place(s) in the guessed-word display; if not, the message informs the user that the letter is not in the word); and a guess button that the user can press to input a guess; and a button (that replaces the guess button when the game has ended) to restart the game.

DOM variables

guessedLetters — connects to where the list of unsuccessfully guessed-letters are displayed
guessButton — connects to the button that inputs the guess made within guessedLetters
guessButton — the same button is called “Start Again” when the game is over; then it can be used to start a new game.
inputLetter — connects to the field where user’s input the letter they are guessing
wordInProgress — connects to where the word that is being guessed is displayed (if a letter’s not yet been guessed, that place is held by a circle).
remainingGuessesDisplay — connects to paragraph around the span that displays the number of remaining guesses.
numberRemainingGuesses — connects to the span where the number of remaining guesses is displayed.
message — connects to the top paragraph where the program updates the user about the game.
playAgain — connects to the button that restarts the game.

Global Variables

  • let word = “magnolia”;
  • let guessedLettersArray = [ ];
  • let remainingGuesses = 8;

Questions: Why do they use ‘let’ instead of ‘const’ for all the global variables? How were the global variables chosen?

Functions

getWord — An async function that fetches a list of commonly used words. It sends the list of words as an array to the selectRandomWord function.

selectRandomWord — Randomly selects a word from the list getWord gave it.  Passes that random word to the addCircles function.

addCircles — Creates the initial display for the word the user is trying to guess. All the letters are replaced by circles, so the user can see how many letters are in the word.

guessButton.addEventListener — Captures the value of the input letter. Passes that value to the validate function. If the input letter is a valid guess, the validate function then passes the input letter back to this guessButton.addEventListener function, which then sends the input letter to the makeGuess function. The function then empties the input-letter field, so the user can use it to guess another letter.

validate — Checks to see if the input is not blank and if it is one and only one letter. If no, an error message is posted in the message area. If yes, the validate function passes the input letter value back to guessButton.addEventListener.

makeGuess — Makes sure the input letter value has not already been guessed by the user. If it has, an error message is posted in the message area. If it hasn’t, the letter is added to the guessedLettersArray, and the guessed letter is also passed to the remainingGuessesCounter, and then guessedLettersArray is passed to updateWordInProgress, and, finally, the showGuessedLetters function is called.

remainingGuessesCounter — Counts how many guesses the user has left. The remainingGuessesCounter function checks to see if the guessed letter is in the word and updates the message area with that information. The function also checks to see if the user has run out of guesses. If they have, the message area informs them that they lost the game and what the word was. Finally, if the guessed letter is in the word, the numberRemainingGuesses variable and display info are updated with the current number of remaining guesses.

updateWordInProgress — Compares the updated guessedLettersArray to the word. For every letter in the word, if it is is included in the guessedLettersArray, that letter is placed in the correct spot within the word, and if the letter is not included in the guessedLettersArray, that spot in the word is filled with a circle. The updateWordInProgress function then updates the wordInProgress variable / display. Finally, the function calls the didPlayerWin function, passing it the most recent wordInProgress array.

showGuessedLetters — Uses the global variable guessedLettersArray to update the list of letters that have been guessed. This list is displayed via the guessedLetters variable and display area.

didPlayerWin — Checks the evolvingGuess variable from the updateWordInProgress function against the word to see if the current guess matches the word. If yes, the player wins and is informed of their great victory. And then the startOver function is called. If the evolvingGuess does not match the word, nothing happens in this function and the game continues because the player is still able to make another guess.

startOver — Hides the remainingGuessesDisplay and the guessButton. Unhides the playAgain button. 

playAgainButton.addEvent Listener — Hides the “win” class from the message variable / display. Sets remainingGuesses back to 8. Sets the text of the numberRemainingGuesses back to “8 guesses”. Sets wordInProgress variable back to an empty string. Sets message’s inner text back to an empty string. Sets the guessedLettersArray back to an empty array. Sets the guessedLetters inner text back to an empty string. Unhides the guessButton. Hides the PlayAgainButton. Unhides the remainingGuessesDisplay. Calls the getWord function.

Discussion of App Flow

Let’s come back to this. Now I want to look at the code snippets

The Functions in Detail

const getWord = async function(){
const request = await fetch("https://gist.githubusercontent.com/skillcrush-curriculum/7061f1d4d3d5bfe47efbfbcfe42bf57e/raw/5ffc447694486e7dea686f34a6c085ae371b43fe/words.txt");
const words = await request.text();
const wordsArray = words.split("\n");

An Async function. Can be used to get data from APIs. Also turns the data into text. And uses the .split method to split a string into substrings at the newline (“\n”). To create an array of substrings out of one long string where line endings are coded into the long string (if I am understanding this correctly). 


const selectRandomWord = function(wordsArray) {
const randomIndex = Math.floor(Math.random() * wordsArray.length);
word = wordsArray[randomIndex].trim();
addCircles(word);
};

Useful Aspects: Generate a random index to randomly select an item in an array. Trim all white space off of an item. In this case, the selected item is a word, so by trimming all the white space, it becomes a tidy array of all the individual letters in the word.


const addCircles = function (word) {
const circles = []
for (let letter of word) {
circles.push("● ");
}
wordInProgress.innerText = circles.join("");
};

Useful Aspects: Use a for of loop to loop through an array. Replace every item in the array with a symbol. Join the symbols together to form a string.


guessButton.addEventListener("click", function (e) {
e.preventDefault();
//get value of guessed letter and validate it
const guess = inputLetter.value;
const goodGuess = validate(guess);
//if letter passes the validation, make a guess
if (goodGuess) {
makeGuess(guess);
}
//make the input Letter box blank again
inputLetter.value = "";
});

click even listener on a button that calls a function passing the event (the click) as the parameter.
e.preventDefault(); keeps the button from its default function. Why is that necessary? Something to do with using a form?
Get what was input with inputThing.value.
You can also use inputThing.value = YouPickTheNewValue to set the value that is displayed — with for example ” ” if you want to display nothing in that field.
Note that if (something) {} only returns anything if the something is True / exists.
In this case, the guess sent to the validate function is deemed valid by the function, so the validate function returns something (it returns the input guess value) and validate(guess) resolves to True. Otherwise, the validate function does not return a value (but only kicks up error messages), and validate(guess) resolves to False. Since goodGuess = validate(guess), goodGuess is T if v(g) is T and F if v(g) is F. 


const validate = function (input) {
// create a variable that contains/is all letters a-z and A-Z
const acceptedLetter = /[a-zA-Z]/;
if (input.length === 0) {
message.innerText = "You didn't enter anything. Please enter a letter."
} else if (input.length > 1) {
message.innerText = "Please enter only one letter."
} else if (!input.match(acceptedLetter)) {
message.innerText = "That's not a letter! Please enter a letter."
} else {
// if the guess makes it all the way to here, this function has a result and the variable goodGuess is True
return input;
}
};

Uses regex expressions and the .match method to check to see if the user put in a letter.
Uses input.length to make sure the user input one and only one characher
If the input passes all those tests, the function returns the input, so the validate() function with an input of input resolves to True. So validate(input) in guessButton.addEventListener() resolves to True.


// make the guess
const makeGuess = function(guess) {
guess = guess.toUpperCase();
//they used guessedLettersArray.includes(allCapsGuess)
if (guessedLettersArray.indexOf(guess) !== -1) {
message.innerText = "You already guessed that letter. Please try again."
} else {
// the following is done when the guess is both valid and a new letter:
guessedLettersArray.push(guess);
remainingGuessesCounter(guess);
updateWordInProgress(guessedLettersArray);
showGuessedLetters();
}
};

Uses the .toUpperCase() method to compare apples to apples.
Uses the fact that if an index doesn’t have a value, indexOf() returns -1 with the NOT operator to weed out letters that were already guessed.
You could do the same thing more easily with if (guessedLettersArray.includes(guess))
The guess variable got sent to this function because it was a valid entry, and the function has confirmed that it is also not a repeat of an old guess. So now the function has several outputs: 1. Uses the .push method to add the guessed letter to the guessedLettersArray; 2. Passes the guessed letter to the remainingGuessesCounter; 3. Passes the newly updated guessedLettersArray to the updateWordInProgress function. Calls the showGuessedLetters() function. Note that it doesn’t pass anything to the showedGuessedLetter() function. This is because the sGL function automatically calls the guessedLettersArray() when it is called and the gLA is a global variable.


// create a list to show all the guessed letters
const showGuessedLetters = function () {
// Clear the list first
guessedLetters.innerHTML = "";
for (const letter of guessedLettersArray) {
const li = document.createElement("li");
li.innerText = letter;
guessedLetters.append(li);
}
};

Uses the .innerHTML method to set the guessedLetters variable and display to an empty string.
Uses a for of loop to go over every item in an array (in this case every letter in the array), and uses the document.createElement and .innerText methods to create a list item with that letter inside of it. 
The function then uses the .append method to add all the list items to the guessedLetters variable, which is a list.
So the function is useful for building HTML lists out of arrays.


const updateWordInProgress = function(guessedLettersArray) {
const wordUpper = word.toUpperCase();
const lettersInWord = wordUpper.split("");
const evolvingGuess = [];
for (let letter of lettersInWord) {
if (guessedLettersArray.includes(letter)) {
evolvingGuess.push(letter);
} else {
evolvingGuess.push("● ");
};
};
// evolvingGuess is an array with all the guessed letters in the word in their proper order,
// with dots in place of letters for all the spots where the player has not yet guessed the correct letter.
// here we turn that array into a word with circles for all the unguessed letters
wordInProgress.innerText = evolvingGuess.join("");
didPlayerWin(wordInProgress);
};

Capitalizes all the letters in the word variable, which is global and so does not have to be passed to the function.
Uses the .split method with (“”) to split a word into and array of letters. You can use the .split method with (” “) to split at spaces and thus, for example, split a sentence into wrods.
Uses a for of loop to go over every letter in the array created by the .split method. And uses if and the .includes method to check to see if that letter from the correct word is inside of the guessedLettersArray. If it is, the letter is inserted in the correct spot in the evolvingGuess array. If a given letter in the correct word is not included in the guessedLettersArray, the spot for that letter is filled in with a circle.
In this way, we create an array of all the letters in word in the correct place, with any letters that were not guessed by the user replaced with a circle.
With the .join method with (“”) the characters are joined together into a string with no spaces. That is: they become a word.


// counting how many guesses the player has left
// this function also updates the message to tell the player whether or not their guessed letter is in the word.
const remainingGuessesCounter = function(guess) {
const wordUpper = word.toUpperCase();
const allCapsGuess = guess.toUpperCase();
if (!wordUpper.includes(allCapsGuess)) {
message.innerText = `Nope! ${allCapsGuess} is not in the word!`;
remainingGuesses -= 1;
} else {
message.innerText = `Yes! ${allCapsGuess} is in the word!`;
};
// make sure the player isn't out of guesses
if (remainingGuesses === 0) {
message.innerText = `Sorry. ${allCapsGuess} isn't in the word. That was your last guess. Game over! The word was ${wordUpper}.`;
// if player loses, game ends
startOver();
} else {
numberRemainingGuesses.innerText = `${remainingGuesses} guesses`;
};
};

The first interior function checks whether or not the most recent guess is in the word — preliminarily with .toUpperCase (to compare apples to apples) — and primarily with the if clause and NOT operator combined with the .includes method
As part of that interior function, the user is informed whether or not their guess is in word; and the remainingGuesses variable is reduced by one with -=The second interior function uses an if clause and === 0 to check to see if the player still has guesses left.
If no, game ends. If yes, the numberRemainingGuesses.innerText is updated.

I’m going to come back to this later for the didPlayerWin, startOver, and playAgain functions.  They mostly reset the DOM and otherwise set things back to the initial states. Now I want to move onto the other JavaScript project. Because I want to create a list of all the tricks I’ve learned from both projects and then use that to sketch up a new project.

Project ideas: A Presocratic Wisdom dispenser. Wisdom dispensers using other classic texts. A conversation generator. It would be fun to do a What Would Jesus say conversation generator. You could either write the sentences yourself or pull them from other classic texts. And then draw randomly from a list of Jesus’s statements. An app that takes two sentences in two different languages and uses a translation service to see how many words match in the two sentences. This could be used to test translations for closeness. With that app we could then build a machine to match up translations with their original texts. 

[Part of the Speaking Javascript project.]Copyright: AM Watson