Search justacoding.blog. [Enter] to search. Click anywhere to close.

April 11th, 2022

Create A Game Of Hangman In React

Hangman is a particularly suitable game for beginners to look at replicating or recreating in React (or within JavaScript in general, in fact).

This is because the fundamentals of the game revolve around some very common web-dev operations, most notably; the searching of strings.

On top of this, the developer will have the opportunity to manage some basic game state (game started, game ended and so on) as well as handle the other necessary mechanisms and interactions required by this game.

You can see an example of the React Hangman game over at this JSFiddle link.

It’s a basic, unstyled demonstration, but it’s enough to get you started!

With that said, let’s take a detailed look at the example provided.

The main Hangman component

Even for a simple React game such as this one, there are often a multitude of different ways to structure and set out the component(s) within your project.

There is often a degree of preference with regards to this, but there are also some important concepts and considerations to be aware of during your approach.

In this case, I’ve opted to go for a “main” component that is primarily responsible for handling the state of the game.

So the Hangman component has these responsibilities in particular:

  • Selects a target word from the dictionary of potential words
  • Controls the “state” of the game, ie. if it’s in progress, if it has ended and so on
  • Controls the list with regards to which letters have been selected by the player
  • Controls how many (incorrect) guesses are remaining

This can be considered to be the core functionality of the hangman game itself; this component is responsible for this. It’s the backbone of the game.

For everything else, we can delegate functionality (or more accurately, presentation) down to less intelligent (or “dumb”) components.

Let’s take a look at some of these components next.

The “screen” components

These components are examples of “dumb” or “smart” components. They look a bit like this:

const StartGameScreen = ({ start }) => {
  return (
    <div className="container">
      <h3>Hangman</h3>
      <button onClick={start}>Start game</button>
    </div>
  )
}

They are labelled as such because they typically don’t maintain their own internal state, and they aren’t really directly responsible for executing specific functionality or logic within the app.

They should be thought of more as simple buildings blocks that are used primarily to output or present elements on the screen.

It’s often important to distinguish between dumb and smart components in your React applications, this can lead to a better overall design and structure that’s easier to scale and reason about in the long run!

The various “screen” components I am using are as follows:

  • StartGameScreen
  • GameCompleteScreen
  • GameFailedScreen

Each of these components receive the necessary props required in order to carry out their particular task.

Hopefully it’s fairly straightforward and clear with regards to what each of these components does in particular!

The main logic/functionality required by the game

Now we’ve briefly summarised what each of the components is responsible for, let’s run through how this simple React hangman game actually functions!

1. The game is started

I touched briefly on the various states of the game earlier on.

As you can see, I’m using some simple strings to denote which state the game should currently be in, these strings being:

  • GAME_READY_TO_BEGIN
  • GAME_IN_PROGRESS
  • GAME_COMPLETE__FAIL
  • GAME_COMPLETE__SUCCESS

The gameStatus variable is updated with the relevant string each time, and we can use this to inform our application with regards to what elements should be visible, what functionalities should be actionable and so on.

This is a simple but effective way to handle game state and control the flow of the game.

As you can see, to actually begin the game of hangman — I have a button that sets gameStatus to GAME_IN_PROGRESS:

setGameStatus("GAME_IN_PROGRESS")

2. A word is selected

Given a dictionary of words (predictably labelled words) we need to initially select a random word from this list.

We can do this by using the floor and random methods on the Math object:

setTargetWord(words[Math.floor(Math.random() * words.length)])

You’ll notice that I’m not storing an array of words here, but instead objects that each contain the specific word itself — but also a hint. This will be used within the user interface to make the word easier to guess for the player should that option be enabled.

const words = [
  // ...
  {
    word: "javelin",
    hint: "a throwing weapon"
  }
]

3. The interface is constructed in order for the player to begin identifying the word

With a word selected and in play, it’s now time to output the various elements required by the player to actually play the game of hangman (identify the obscured word).

These various elements are as follows:

  • An indicator with regards to how many guesses are remaining
  • The word itself, or rather, an indicator of how many letters are present in the word
  • The hint for the selected word
  • All letters in the alphabet, selectable in order to “guess” a specific letter

These elements form the entirety of the game, this is hangman in its most simplistic form.

4. The player makes a guess

Of course, our simple React hangman game would not be complete if the player couldn’t actually make guesses at the target word!

For this, we need to attach some functionality to each of the letter elements. Upon clicking a letter, for instance the letter E, we need to scan the target word for this letter:

const makeGuess = (letter) => {
  if (guessedLetters.includes(letter)) {
    return
  }
  if (!targetWord.word.includes(letter)) {
    setGuessesRemaining(guessesRemaining => guessesRemaining - 1)
  }
  setGuessedLetters(guessedLetters => [...guessedLetters, letter])
}

If present, display the letter E in the correct position(s) within the word.

If not present, we need to decrement the guessesRemaining value. Unfortunately for them, the player just got one step closer to losing the game!

The makeGuess function above sets the required items in state, and we also have a useEffect hook that’s used to react to these state changes:

useEffect(() => {
  if (guessesRemaining === 0) {
    setGameStatus("GAME_COMPLETE__FAIL")
  }

  if (targetWord) {
    if (targetWord.word.split("").find(letter => !guessedLetters.includes(letter)) == null) {
      setGameStatus("GAME_COMPLETE__SUCCESS")
    }
  }
}, [targetWord, guessedLetters, guessesRemaining])

As the state updates are asynchronous (the updates invoked via the makeGuess function above) it’s better to react to the update state in a useEffect hook like this. This way, we can be sure that the asynchronous nature of state updates don’t meddle with our functionality in unexpected/undesired ways!

5. The player makes more guesses…

The game is “in progress” now, and the player is trying their best to identify the target word!

6. The game ends (successfully or otherwise)

A series of guesses will lead to one of two possible outcomes:

  • The player “wins” the game, ie. they correctly guess the target word
  • The player “loses” the game, ie. they didn’t identify the target word and ran out of guesses (or lives)

Both of these states are tracked in the same way, via the gameStatus variable and some simple string comparisons:

if (gameStatus === "GAME_COMPLETE__FAIL") {
  return <GameFailedScreen restart={startGame} />
}
  
if (gameStatus === "GAME_COMPLETE__SUCCESS") {
  return <GameCompleteScreen restart={startGame} />
}

There’s some simple logic to maintain the correct game state after each and every interaction, as you can see.

Upon reaching either of these states, we display the relevant screen to the player, and that effectively concludes the game.

The player has the option to restart the game, whereby the exact same process repeats again — only with a (hopefully) different randomly-generated word this time!

Improving the game with more features

Simple games like this are great because they provide a good platform on which to apply difference features and enhancements.

Here are some examples of some additional improvements you can make to this React hangman game to make it more interesting.

Enforce a time limit for the player

You could enforce a time limit on the game.

In this scenario, the player would need to complete the game in under a minute, for instance.

You could display a timer component in the top right portion of the screen which would count down from 60 to 0. Reaching 0 before the target word is identified would trigger the same GAME_COMPLETE__FAILED gameState!

Time the player

Similarly, you could time the player.

Based on how quickly the word was identified, a modifier would be applied that would increase the player’s score by a specific percentage. Adding the concept of a “score” (rather than just simple win or lose) would be a requirement in order to integrate this feature in particular.

Add the concept of difficulty

Longer words would typically be more difficult to identify (especially in an allotted time). This could be used to forge the idea of a “difficulty” setting within the game.

Fetch your words from an API or some other external source

Of course, in the real world, you’ll likely want to have a lot of words in your dictionary to begin with.

This will obviously make it less likely for players to receive “duplicate” words when playing your game.

One way of achieving this would be to load your words dynamically from an external source, so it’d be a matter of identifying such a source and then integrating that into your application!

In closing

I hope you’ve enjoyed this article!

Our final React hangman game is of course fairly straightforward, but projects such as these ones can serve as a fantastic learning opportunity.

If you’re interested in more React code examples, please do feel free to check out our Code Examples category.

Thanks for reading!

Have any questions? Ping me over at @justacodingblog
← Back to blog