← All series

Series contents

Log in to track your progress

  1. 1. Welcome to Alchemist Camp Episode
  2. 2. Lesson 1: The guessing game Episode
  3. 3. Solution 1: The Greeter Episode
  4. 4. Lesson 2: Word Count Episode
  5. 5. Word Count ULTIMATE EDITION! Episode
  6. 6. Minimal Todo List Part 1 Episode
  7. 7. Elixir lists vs Elixir tuples Article
  8. 8. The Image Snatcher Episode
  9. 9. Minimal Todo List Part 2 Episode
  10. 10. Elixir maps made effortless Article
  11. 11. Fibonacci with tail recursion Episode
  12. 12. Mini Markdown Episode
  13. 13. Extending Mini Markdown Episode
  14. 14. Alchemy Markdown Episode
  15. 15. Using mix to modularize and add tests Episode
  16. 16. Command line applications with escript Episode
  17. 17. Caboose, a simple Unix tail clone Episode
  18. 18. Game board (Tictac Part 1) Episode
  19. 19. Structs: Maps with checks and default values Article
  20. 20. Deck generation, Pythagorean Triplets and more Episode
  21. 21. Whiteboarding the Tictac state machine Episode
  22. 22. Game state machine (Tictac Part 2) Episode
  23. 23. Modelling the memory game Episode
  24. 24. Finishing Tictac and making a CLI Episode
  25. 25. Threatened by rooks, bishops and knights Episode
  26. 26. A worker to log stats from a YouTube’s API Episode
  27. 27. Constructing bitmap files with Elixir Episode
  28. 28. Making bitmaps with color palettes Episode
  29. 29. Flattening lists recursively in Elixir Episode

Game state machine (Tictac Part 2)

In a previous episode, we built a representation of a tic-tac-toe board and its squares. Now it's time to make a state machine for our tic-tac-toe game.

Tracking the state of a tic-tac-toe game

There are a few ways to look at the state of a game. At the highest level, we need to know if a game is being set up, is progress, or completed, etc. We'll call this the status of the game. Digging in a bit more deeply, to fully represent a game, we need to know the state of the board and whose turn it is. Finally, there are some properties that can be computed from the above information but are useful enough to deserve a top level representation. Here's the structure we'll use for Tictac.State:

  • status: can be :initial, :choose_p1, playing, game_over or winner_reported
  • over: true if the game is over, false otherwise
  • turn: whose turn it is to play next (nil, :x or :o)
  • winner: the winner of the current game (nil, :x, :o or :tie)
  • board: a map of all the squares on the board as keys and :empty, :x or :o as values
  • ui: a hook to the current UI being used by the game

All new games will start with a status of :initial, a board of all empty squares, whatever UI hook is passed into it at creation time and nil values for everything else.

Using events to transition between states

In order to transition from one state to another, our game will have to recognize various events, such as a first player being chosen, a player taking a turn or the game being won. The safest and easiest way to do this is by making a many-headed function that takes game state and event tuples, e.g. event(%State{status: :initial} = state, {:choose_p1, :x}) will be matched by a function that checks that :x is a valid player, updates the game state's :status to :playing and :turn to :x.

Other event tuples such as {:check_for_winner, :o} simply don't don't have a matching function head where state matches %State{status: :initial} because tic-tac-toe doesn't allow for players to win before it's decided who will play first. Those and similar calls will be caught by a default definition for &event/2 that catches any unmatched state and actions and returns an :invalid_state_transition error.

Allowed transitions (status, action) -> new status

  • (:initial, :choose_p1) -> :playing
  • (:playing, :play) -> :playing (with the state of :turn toggled to the other player and the board updated)
  • (:playing, :check_for_winner) -> :playing
  • (:playing, :check_for_winner) -> :game_over
  • (:playing, :game_over?) -> :playing
  • (:playing, :game_over?) -> :game_over
  • (:game_over, :game_over? -> :game_over
  • (:game_over, :report_outcome) -> :winner_reported (not implemented)

Any other transitions will result in an error. Note that the :game_over? action is a convenience and isn't needed if :check_for_winner is written in such a way that it will also handle full boards where neither player has won.

Implementing the game logic

With the above state transitions in place, the remaining non-UI logic to be implemented is the actions. Choosing a player 1 is trivial, but playing a turn and checking to see if there is a winner each warrant creating helper functions. The following episode in this series implements that logic and builds a CLI.

Challenge:

Model another game you know as a state machine. It could be a card game like Hearts or Blackjack, another board game or even a children's game like Capture the Flag.

(example solution here)

Open standalone page ↗

No Comments