Hangman is a popular game for teaching programming fundamentals - it's simple, has only a small number of variables and simple rules. This simplicity also makes it a good way to explore different programming styles. In this article I'm going to develop a browser-based Hangman game written in the 'Functional Core, Imperative Shell'. The code for this game can be seen here and a live demo here.

Functional Core, Imperative Shell

Source: Destroy All Software

For those who haven't heard of it, 'Functional Core, Imperative Shell' is a programming approach mooted by Gary Bernhardt (see this talk for an explanation), which aims to marry the reliability offered by functional programming with the more 'real world' pragmatism offered by an imperative approach. It's well worth watching the talk (and the related videos from the Destroy All Software screencasts) to get a better understanding, but here's my take on it:

The functional core refers to classes that have immutable state, make decisions about objects, and have few dependencies. This makes them easy to test in isolation.

The imperative shell ties all these cores together, transmitting objects as values from one to the other to perform operations. It also provides the interaction with the outside world - user input, output, database and network calls.

It's well worth checking out the talk itself to get to grips with the ideas. For this article, I've written a simple, browser-based Hangman game in Javascript mimicking this style as a way of exploring how it can force us to think differently about software design.

Rules of the Game

The rules of hangman are this - We have to guess a word by guessing individual letters. We have a set number of lives and lose a life each time we make a wrong guess.

It's a pen and paper game, where the unknown word is displayed as dashes, with letters fill in as we guess them. A sketch of the scaffold is added to each time there's a wrong guess. If the sketch is completed before we complete the game, then we lose!

Thinking about this programmatically, we can see that the state of the game at any time can be represented through the use of a limited number of variables and operations on these variables.

At the start of the game we choose a word to be guessed.

to be guessed. We also have a number of lives , which is the total number of wrong guesses we are allowed.

, which is the total number of wrong guesses we are allowed. Finally we have our guesses , the letters that we have guessed so far.

, the letters that we have guessed so far. The game has three possible states - Victory (all letters guessed), Death (no more lives left) or Still Playing. All of these can be figured out using the above three variables.

It's worth emphasising that the lives, or maximum number of wrong guesses, and the word are set at the start of the game. The only thing that changes throughout the course of a game is the guessed letters, and everything else follows on from that.

So, let's think about some more variables.

The correct guesses are the letters in the guesses that are also contained in the word. If we have a set of guessed letters and a set of the word's letters, the correct guesses are the intersection of these sets.

are the letters in the guesses that are also contained in the word. If we have a set of guessed letters and a set of the word's letters, the correct guesses are the intersection of these sets. Similarly, the incorrect guesses are the guessed letters that are not contained in the word. So they are the difference of these sets.

are the guessed letters that are not contained in the word. So they are the difference of these sets. Game state - alive, dead, or has won. If the number of incorrect guesses is the same as the number of lives, then the player is dead. If the number of correct guesses is the same as the number of letters in the word, then the word must be completely guessed, and the player has won! :-)

Putting it into practice

The Game

Our most important class is the Game itself, an object which will hold all the information about the state of the game - what the word is, what our right and wrong guesses are, and whether we are alive, dead or have won the game.

Javascript has some lovely new set objects, so we can use these to simplify our code. First we need some intersection and difference utility methods.

var intersection = function(setA, setB){ var el_in_b = (x => setB.has(x)); return new Set([...setA].filter(el_in_b)); } var difference = function(setA, setB){ var el_not_in_b = (x => !setB.has(x)); return new Set([...setA].filter(el_not_in_b)); }

These methods combine the lovely Javascript arrow functions with the filter method, which will take an array as an argument and return a new array composed of each element for which the arrow function evaluates True. If you want to learn more about the Javascript Set object, check the docs here.

We're now ready to define our Game class. We'll do this by defining it as a function which returns itself. Because Javascript functions are first-class objects, we can use use them in much the same way as objects in more traditional OOP languages. Our Game function-object will take three parameters, and calculate five further variables that are dependent on these on instantiation.

var Game = function(lives, word, guesses){ this.lives = lives; this.word = word; this.guesses = guesses; this.word_letters = new Set(this.word); this.correct_guesses = intersection(this.guesses, this.word_letters); this.incorrect_guesses = difference(this.guesses, this.word_letters); this.is_dead = this.lives <= this.incorrect_guesses.size; this.has_won = this.correct_guesses.size == this.word_letters.size; this.addGuess = function(letter){ return Game(this.lives, this.word, this.guesses.add(letter)); } return this; }

Also worth noting is the addGuess function. In a more typical OOP approach, we might expect this to be a 'setter', i.e., a method that would modify the internal values of the object. For instance, we might write something like this:

this.addGuess = function(letter){ this.guesses.add(letter)); }

This modification of internal values would then mean that the other key variables would also need to be accessed via methods rather than as values, as the mutation will require dependent variables to be recalculated each time they are used. For instance, we might have Game.getCorrectGuesses() , which computes and returns the correct guesses each time it is run.

Instead, with the functional core style, when we add a guess, we return a whole new game object with the letter added to the guesses. The lives and the word stay the same, and the other dependent variables are automatically calculated. So Game.has_won , Game.is_dead , etc. are static variables - they are facts about the state of the game object, not methods that check this state.

Let's take a quick look at how this will work in practice. Here's a snippet from our interface module:

var lives = 5; var words = ['munificent','antelope','interlude', 'television', 'moribund', 'pabulum']; var word = chooseWord(words); var guesses = new Set([]); var game_obj = game.Game(lives, word, guesses);

That's the Game object instantiated on page load with a fixed number of lives, a word, and an empty set of guesses. We also need a way to update it as we get new guesses in. So we'll create a function to handle input and bind it to the keypress.

$(document).keypress(function(e){ handleInput(e); }); var handleInput = function(e){ if (!isLetter(e.key) || (game_obj.guesses.has(e.key))){ return; } game_obj = game_obj.addGuess(e.key); scr = screen.Screen(game_obj); drawScreen(scr); }

Our handleInput function performs some simple validation and uses the Game.addGuess function, which of course returns a whole new Game object with an updated set of guesses.

Now that we know how to keep track of the game, the other big thing we have to worry about is displaying this to the user.

The Screen

Since we'll be browser-based, all we really need are some strings to display to the user. At the minimum, a Hangman game needs:

The target word , with guessed letters displayed and blanks for the unguessed.

, with guessed letters displayed and blanks for the unguessed. The player's incorrect guesses to help them keep track of what they have guessed.

to help them keep track of what they have guessed. The gallows , a representation of how close the player is to losing the game.

, a representation of how close the player is to losing the game. A message to instruct the player on what to do.

As with the Game, we can do this with a Screen object, which will work out the above variables based on a Game object. These will be simple strings - ready to be added into our html.

var Screen = function(game){ this.msg = getMsg(game); this.word = getWord(game); this.gallows = gallows.stages[game.incorrect_guesses.size]; this.wrong_guesses = [...game.incorrect_guesses].join(' '); return this; }

The Screen object is created using a Game object, and the variables are defined by doing some calculations on one or more properties of this object. Screen.getMsg simply returns a message appropriate to the state of the game, while Screen.getWord gets the word for display, by splitting it into an array of letters (e.g., 'antelope' -> ['a', 'n', 't', 'e', 'l', 'o', 'p', 'e']) and maps each element to a function that returns an underscore if the letter is not guessed, the letter itself if it is.

var getWord = function(game){ var getLetter= function(letter){ return (game.guesses.has(letter) ? letter : "_"); } var display_letters = game.word.split('').map(getLetter); return display_letters.join(' '); }

And that's the Screen object. Handling this will just involve adding the relevant bits of the Screen object into our html. Our trivial drawScreen method will live in and be called from the interface module.

var drawScreen = function(screen){ $('#game-message').html(screen.msg); $('#gallows').html(screen.gallows); $('#display-word').html(screen.word); $('#guesses').html(screen.wrong_guesses); };

Remember, this interface module is the 'imperative shell', or management layer of our application.

var init = function(){ var lives = 5; var words = ['munificent','antelope','interlude', 'television', 'moribund', 'pabulum']; var word = chooseWord(words); var guesses = new Set([]); var game_obj = game.Game(lives, word, guesses); var scr = screen.Screen(game_obj); drawScreen(scr); $(document).keypress(function(e){ handleInput(e); }); var handleInput = function(e){ if (!isLetter(e.key) || (game_obj.guesses.has(e.key))){ return; } if (game_obj.is_dead || game_obj.has_won){ return; } game_obj = game_obj.addGuess(e.key); scr = screen.Screen(game_obj); drawScreen(scr); } }

This module is the overall manager of the sub-classes. It instantiates our Game object and passes that be composed as a Screen object, which calculates strings that can be added to the page to communicate to the user. We then wait for user input and repeat the process until the user wins or dies.

So that's our Hangman game! Our interface classes receives input, composes objects from our functional core classes, and finally produces output. It's a simple game, but I hope I've shown some of the advantages of this 'Functional Core, Imperative Shell' approach.