/lab2

Primary LanguageElixir

Hangman—The first iteration

We're going to be building increasingly sophisticated versions of a Hangman game. This will let us explore Elixir syntax, libraries, modules, processes, projects, and applications. We'll also use it as the basis of our Phoenix programming.

In this first iteration we'll implement the code that runs the game. This code selects a word to be guessed. It then accepts letters and scores them against the target word, reporting the result.

The goal is to write this module without using any conditional constructs: if, cond, or case. Instead, focus on writing reducers: functions that transform state.

I've created a user interface to this code, so you'll be able to play Hangman with your code.

Where to Find Stuff

Although we haven't talked about this yet, I decided to use the standard Elixir project structure for this application—it'll make it easier as we go forward. Don't worry about it too much for now.

.
├── README.md
├── assets
│   ├── word lists . . .
├── config
│   └── config.exs
├── lib
│   ├── hangman
│   │   ├── dictionary.ex
│   │   ├── game.ex              ☜ your code goes here
│   │   └── human_player.ex
│   └── hangman.ex
├── mix.exs
└── test
    ├── hangman_test.exs
    └── test_helper.exs

The source code is in the directory lib/hangman/. You'll be working in the file game.ex. The documentation at the top of that file tells you what you need todo.

How to Run Stuff

Keep a shell open in this directory (the one holding the README.md file).

Although your editor will probably be able to do this, you can compile your code from the command prompt using

 $ mix compile                   # this compiles files that changed

 $ mix do clean, compile         # this does a fresh compile

There are a suite of tests for the Game module. You can run them with

 $ mix test

(This will also compile your code.)

You can interact with your code using

 $ iex -S mix

This brings up mix with all the project's code preloaded. You can call functions in your code using

 iex> Hangman.Game.«func(params)»

After your code is written, you can use the prewritten user interface:

$  mix run -e Hangman.HumanPlayer.play

The Game Module

Here's the documentation that's also included at the top of the Game module (the module you'll be working on)

This is the backend for a Hangman game. It manages the game state. Clients make moves, and this code validates them and reports back the updated state.

Our API is

  • game = Hangman.Game.new_game
  • { game, status } = make_move(game, guess)

and the auxiliary functions

  • status = get_status(game)
  • {game, status} = reset_game(game)

In this api, game is the opaque (internal) representation of the state of play maintained by your module. status is the external representation of that state, used by clients of your module. It tells them thinks like the number of turns left, the word to be guessed with the letters the client got right filled in, and so on.

Two functions, make_move and reset_game, can change the internal state of the game. As a result, they return a new game value.

There's more documentation for each function inline in lib/hangman/game.ex

Example of use

Here's this module being exercised from an iex session. For all but the first line we only show the resulting status (not the value of the game variable).

iex> alias Hangman.Game
Hangman.Game
#
iex> game = Game.new_game("wombat")
#
iex> { game, status } = Game.make_move(game, "a"); status
%{game_state: :in_progress, guess_state: :good_guess, last_guess: "a",
  letters: ["_", "_", "_", "_", "a", "_"], turns_left: 10}
#
iex> { game, status } = Game.make_move(game, "b"); status
%{game_state: :in_progress, guess_state: :good_guess, last_guess: "b",
  letters: ["_", "_", "_", "b", "a", "_"], turns_left: 10}
#
iex> { game, status } = Game.make_move(game, "c"); status
%{game_state: :in_progress, guess_state: :bad_guess, last_guess: "c",
  letters: ["_", "_", "_", "b", "a", "_"], turns_left: 9}
#
iex> { game, status } = Game.make_move(game, "d"); status
%{game_state: :in_progress, guess_state: :bad_guess, last_guess: "d",
  letters: ["_", "_", "_", "b", "a", "_"], turns_left: 8}
#
iex> { game, status } = Game.make_move(game, "a"); status
%{game_state: :in_progress, guess_state: :already_guessed, last_guess: "a",
  letters: ["_", "_", "_", "b", "a", "_"], turns_left: 8}
#
iex> { game, status } = Game.make_move(game, "w"); status
%{game_state: :in_progress, guess_state: :good_guess, last_guess: "w",
  letters: ["w", "_", "_", "b", "a", "_"], turns_left: 8}
#
iex> { game, status } = Game.make_move(game, "o"); status
%{game_state: :in_progress, guess_state: :good_guess, last_guess: "o",
  letters: ["w", "o", "_", "b", "a", "_"], turns_left: 8}
#
iex> { game, status } = Game.make_move(game, "m"); status
%{game_state: :in_progress, guess_state: :good_guess, last_guess: "m",
  letters: ["w", "o", "m", "b", "a", "_"], turns_left: 8}
#
iex> { _game, _status } = Game.make_move(game, "t"); status
%{game_state: :won, guess_state: :good_guess, last_guess: "t",
  letters: ["w", "o", "m", "b", "a", "t"], turns_left: 8}