You are on page 1of 224

Toy Robot - The Elixir Version

Ryan Bigg
This book is for sale at http://leanpub.com/elixir-toyrobot

This version was published on 2021-01-18

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process.
Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations
to get reader feedback, pivot until you have the right book and build traction once you do.

© 2019 - 2021 Ryan Bigg


Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i

1. Introducing the Toy Robot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Simplifying the problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Creating the application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
The MOVE command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Moving west . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Moving north and south . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Moving in the right direction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Turn left! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Turn right! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

2. Catching bugs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Finding the bugs within . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Regression testing the turn_right function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Regression testing the move function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Regression testing the move_west function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Regression testing the move_north and move_south functions . . . . . . . . . . . . . . . . . . . . 49
Jumping back to the manual test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Reflections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

3. Placing the robot on a table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58


The Table module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
The missing link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

4. Building our simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66


Moving a robot, within a simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Turning a robot, within a simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Reporting the robot’s position . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

5. Reading and handling commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76


The Command Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
The Command Runner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Piecing it all together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

6. Building the CLI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105


Reading commands from a file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Verifying the robot’s behaviour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
CONTENTS

Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

I The Toy Robot Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124

7. A Single, Supervised Player . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125


A single player . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Let it crash! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Watching processes with supervisors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Giving names to players . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

8. Multiplayer Toy Robot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161


Prelude: a short refactor involving Player and tables . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Creating a Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
Handling an invalid placement (off the board) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Handling an invalid placement (square occupied) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
A refactoring interlude: Breaking Server apart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Preventing robots from colliding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Preventing robots from respawning in occupied spaces . . . . . . . . . . . . . . . . . . . . . . . . . 200
The End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Homework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
Introduction
Thank you for reading The Toy Robot.
If you find any misteaks while reading this book, please email them to me@ryanbigg.com.
If you have a problem to do with your code, then please put the code on GitHub and link me to it in the
email so I can clone it and attempt to reproduce the problem myself. If you don’t understand something,
then it’s more likely that I’m the idiot and rushed it when I wrote it. Let me know!
This far into the book and there’s already one error. You should expect that it is not alone. It has friends
and their ways are devious. They are coming after your perception of reality. Beware.

The “Toy Robot” exercise is a famous exercise which is commonly used as an interview exercise for
programming jobs. It was originally developed by a Melbourne-based developer, Jon Eaves. He has a blog
post about it here1 .
The description of the exercise is fairly simple, but it has some underlying complexity which can lead to
some difficulties for anyone attempting it.
The main idea is that there’s a robot which moves around on a table that’s 5 x 5 units. If we looked at the
table from a top-down perspective, it might look like this:

Tabletop

1 https://joneaves.wordpress.com/2014/07/21/toy-robot-coding-test/
Introduction ii

The robot can move forward one space in the direction it’s facing, and it can turn to face a new direction.
The robot is supposed to ignore any commands that would make it fall off the table. The robot can also tell
us where it is on the tabletop and which direction it’s facing. The robot receives a list of commands of what
actions to take from a file that we specify.
It sounds simple, right? Well, you’re about to read about 40,000 words about this “simple” problem. I
think the term “simple” is best left for elegant one-liners, and not interview exercises with trickied buried
complexity such as the Toy Robot. There’s an extra 20,000 words in a “bonus” extension set of chapters at
the end of this book too.
In this book, I’m going to go through how I would implement the Toy Robot exercise myself in Elixir, just
for demonstration purposes. I’ll be building it up piece-by-piece with the overall aim to be to help anyone
reading how to think through and implement this problem. This book is not intended to be the ultimate
100% perfect and make people weep with joy at the elegance of the solution type of deal. It is my approach to
the Toy Robot problem and I’m sharing it with you in the hope you may gain something from it. There may,
nay, WILL be better ways to write what I have written here. It is up to you find out what they are. (And then
to tell me so that I too may learn!)
While writing this book I’ve actually revised my approach to the problem several times. I have done this
exercise a few times myself before, for job interviews, for fun, and for the other Toy Robot book (Ruby
version)2 . Each time I’ve done it I’ve refined the approach taken.
This book is no exception; the code inside it has gone through several revisions while I worked out how
to structure the code and explain my thought process. What you’re seeing here is much closer to a final
revision rather than a first draft. Such is the power of a book that can be very easily edited ;) Chapter 5
was especially tricky in this book and required an almost complete rewrite during the writing process. Then
along came Chapter 8 and it needed FOUR rewrites!

The original version of this book was written as a blog post, but it grew into 20,000+ words and so it deserved
to be published in book-form. That first version uses the Ruby programming language and the wonderful
RSpec testing framework. If you’d like to, you can get a free copy by going to this link:
https://leanpub.com/toyrobot/c/BeenThereDoneThat
This second version focusses on Elixir and its testing framework, ExUnit. This book is not a carbon-copy of
the Ruby book, with the Ruby bits taken out and Elixir bits put in. There are things contained within this
book which are Elixir-specific, particularly how Elixir handles concurrency with things like GenServer and
DynamicSupervisor. Ruby and Elixir are two different languages with different paradigms and if you read both
books you would gain a great appreciation of those differences. In particular:

• In Ruby, data is mutable (changeable), but in Elixir data is immutable (unchangeable)


• In Ruby, you have objects that contain state, but in Elixir you have data and functions. To have
something keeping a track of state in Elixir you would need to start a separate process, which is
something we cover in Chapter 7 of this book.

2 https://leanpub.com/toyrobot
Introduction iii

In this book, I’ll be assuming that you have a base-level of Elixir knowledge. If you’ve gone through my
other Elixir book, Joy of Elixir3 , you’ll have the right amount of knowledge. You’ll know about lists, maps,
functions, and modules. All things that we’ll use a lot of in this book. Consider this Toy Robot book a spiritual
sequel to Joy of Elixir.
If you haven’t gone through that book but have learned Elixir elsewhere, then I would expect you to have
done enough reading about Elixir to know your modules from your functions, as well as lists and maps too.
I am also going to assume a base-level understanding of test-driven design.
I will try not to assume too much Elixir knowledge, but I may accidentally skip over some things. If I do,
please shoot me an email at me@ryanbigg.com if you don’t understand anything. I’ll try to explain it better
in a reply, and then once I know you understand it I can work it back into the book.
If you would like to the final code of this book, it is available for free on GitHub:
https://github.com/radar/toy_robot_elixir.

Writing a book is not a solitary endeavour. There’s always more people involved than just the name on the
cover.
The Ruby version of this book started out as a blog post that I was going to put up at ryanbigg.com. When
I started writing it, I thought the blog post would total around 10,000 words. After all, how much could I
possibly have to say about the toy robot exercise? Apparently I had about 20,000 words to write about the
toy robot exercise.
This is quite a long blog post! A friend of mine (Phil Arndt) and my wife (Sharon) both suggested I should
make it a book and sell it and so here it is – available on Leanpub just for you.
The idea for that original blog post came up after a few discussions with several fledging developers here in
Melbourne. The one that triggered the idea to write the blog post was Mel Kaulfauss, but I remember talking
to two others at least (Ashley Petit and Owen Schebella) about the Toy Robot too. There were probably
others, but Mel was the one who triggered the thought to write the post in the first place. Thank you, Mel.
I would like to also thank my Junior Engineering Program (JEP) cohort that proof-read a beta version of this
Elixir book as a part of our JEP work at Culture Amp. They asked me some tricky questions and even helped
me see that I was making things too complex sometimes. These wonderful people are: Caroline Bambrick,
Caroline Setiawan, Jake Pitman, Jody Vandenshrick, Kasia Misirli, Nick Mills, Nick Wolf, Rachel Johnson
and Tanim Mahmud.
I would also like to thank Brenton Annan for his wise suggestions around structuring GenServers within
Elixir. Whenever you see code in this book involving a GenServer, you have Brenton to thank for the clarity
and conciseness of that code.

With all that said, let’s start building our toy robot!

3 https://joyofelixir.com
1. Introducing the Toy Robot
The Toy Robot exercise sometimes gets given different names, like the “Mars Rover” exercise, or “Pacman”.
For simplicity’s sake, we’ll call it the “Toy Robot” here as that’s its most common name. In keeping with
tradition, we’ll also use the most common description of the problem. Here it is:

The Toy Robot Exercise


The application is a simulation of a toy robot moving on a square tabletop, of dimensions 5 units
x 5 units. There are no other obstructions on the table surface. The robot is free to roam around
the surface of the table. Any movement that would result in the robot falling from the table is
prevented, however further valid movement commands are still allowed.

The application reads a file using a name passed in the command line, the Ifollowing commands
are valid:

1 PLACE X,Y,F
2 MOVE
3 LEFT
4 RIGHT
5 REPORT

• PLACE will put the toy robot on the table in position X,Y and facing NORTH SOUTH, EAST
or WEST.
• The origin (0,0) is the SOUTH WEST most corner.
• All commands are ignored until a valid PLACE is made.
• MOVE will move the toy robot one unit forward in the direction it is Icurrently facing.
• LEFT and RIGHT rotates the robot 90 degrees in the specified direction Iwithout changing
the position of the robot.
• REPORT announces the X,Y and F of the robot.

The file is assumed to have ASCII encoding. It is assumed that the PLACE command has only one
space, that is PLACE 1, 2, NORTH is an invalid command. All commands must be in uppercase, all
lower and mixed case commands will be ignored.
Introducing the Toy Robot 2

Ultimately, this description wants us to build an application that will take a list of commands such as:

commands.txt

1 PLACE 0,0,NORTH
2 MOVE
3 RIGHT
4 MOVE
5 LEFT
6 MOVE
7 REPORT

Once the application receives these commands, it should run a simulation for a toy robot which obeys these
commands and then if the list of commands includes a REPORT command, the application will then tell us
where the robot is:

1 $ toyrobot commands.txt
2
3 Robot is currently at (1, 2) and it's facing NORTH

In this example, the two coordinates refer to the robot’s EAST and NORTH coordinates respectively. The robot
started at (0, 0) and was facing NORTH. The robot then moves (to (EAST=0, NORTH=1) or (0, 1) for short), turns
right, moves again (to (1, 1)), turns left, and moves a final time (to (1, 2)) before reporting its location. The
robot tells us that it has the coordinates of (EAST=1, NORTH=2), as it moved EAST once and NORTH twice given
the commands that we provided it.
That may seem like a lot to take in at the moment, and that’s because it is! Clocking in at nearly 250 words,
that problem description is quite a lot to process all at once. So we won’t be processing this all at once.
We’ll be breaking it down into little chunks that we can tackle, one at a time. When we’ve implemented one
chunk, we’ll move onto the next one. Eventually, we’ll chain these chunks together and make our toy robot
move, turn and report!

Simplifying the problem

Before we write any code, we should break this problem down into little chunks that we can solve piece by
piece. We know that our robot needs to be placed and once it’s placed it needs to respond to commands to
move, turn and report its location. We should pick out one instruction from this set and implement that.
But first, let’s visualise what the robot and the tabletop might look like:
Introducing the Toy Robot 3

Tabletop

The grid represents our tabletop, and the little icon in the middle is our robot. The little “turret” on our robot
indicates which direction its facing. This is so far a good visualisation of the problem that we’re trying to
work out.
If our robot is given a MOVE command, then it should “move one unit forward in the direction it is currently
facing.” A single MOVE command issued to our robot on the tabletop would mean that the robot would end
up like this:

Tabletop

You can probably guess what several MOVE commands in a row will do to the robot: it will keep obeying those
Introducing the Toy Robot 4

commands and moving up the table, in the direction it is facing.


The MOVE instruction will be fairly easy to implement, since it is just about moving the robot to a new location.
Let’s say in the above diagrams that the robot started at (0, 0) and that the MOVE command moved it to (0,
1). Another MOVE command would move it to (0, 2) and so on.

Now what if our robot could only move in a straight line? If we thought of the starting position of the robot
as 0, then a MOVE command would mean that the robot would then move to 1, and another MOVE command
would mean that the robot would then move to 2.
A good way of representing this straight-line-with-numbers-on-it would be a number line:

Toy Robot starting at 0

The “robot” will initially start at 0, and a MOVE command will move the “robot” to the right one number. So
if the instructions were just:

1 MOVE
2 MOVE
3 MOVE

The robot would then end up on 3:

Toy Robot moved to 3

By making our “tabletop” temporarily into a single line, it really simplifies our problem. Our robot now only
has two directions it will move: “forward” or “backward” along the number line. To simplify it further, we
will choose to write code that will only mean our robot can move “forward” to start with.
So this is where we’ll start: with the MOVE command and a number line instead of a table top. We’re going to
ignore PLACE, LEFT, RIGHT and REPORT commands for the time-being. PLACE specifies an X, Y, and facing direction
for our robot, but thanks to this simplification of our problem, we don’t have any concept of X, Y or facing
direction for the robot. All we have is its position on the number line. We’ll assume a starting place of 0
for the time being. We’ll ignore the LEFT and RIGHT commands for the time being, as they don’t make sense
when the robot can only move two directions along the number line.
We will take a look at implementing these two commands – moving forward and backward – in this much
simpler environment, but before we can do that we will need to create an Elixir application.
Introducing the Toy Robot 5

Creating the application

Before we can make our robot do anything at all, we’ll need to create an Elixir application directory to put
our code in. The best way to generate one of these is by running the mix new command, like this:

1 mix new toy_robot

This directory generated by mix new will provide us a for building out our toy robot, and it even comes with a
test directory (spec), which we’ll be making great use of, to make sure our robot does what we want it to do.
We’ll be test-driving this exercise because, like many other projects, this one will be evolving over time and
changes that we make to it may break functionality. By having the tests in place and regularly checking their
status, we can make sure that each part of our project is functioning the way it should be. If a test indicates
something broke, we can stop what we’re doing and go and fix it. We will see plenty of these examples in
this book!
It may be tempting to write this code without any tests and play around with it yourself. By all means, go
ahead. I am choosing to write tests here in this book because it is what would be expected if this code was
submitted as part of an interview process. Given that the Toy Robot exercise is commonly used in interviews,
it would make sense for this walk through to follow the same kind of style too.
Additionally, by writing the test first it gives us time to think about our design of the code before we begin
writing it. If the test is hard to write, then the code will be hard to write too. We should be trying to think
about what is the next smallest step to build out our application, and we should also take small steps to
make the tests pass.
Consider all of this practice for interviewing!
To begin with our exercise, we’ll remove the placeholder tests that live at test/toy_robot_test.ex. We’re
not going to need these tests, as they just demonstrate how we can test our code. This book will do this
extensively anyway. We’ll remove this test/toy_robot_test.exs file.
With the application now setup, we can continue with implementing this MOVE command.

The MOVE command

The first thing that we’re going to implement is the ability for our robot to move right along our number
line. The noun of the thing that we’re talking about here is “robot”, and so it would then follow that our
code belongs in a module called Robot. Finding the nouns in a system is a good way to figure out where to
start.
The application we generated is called ToyRobot, so the full module name would be ToyRobot.Robot. Before we
go creating this module, let’s create a new file called test/toy_robot/robot_test.exs that will be used to test
this module. We’ll put this code in this file:
Introducing the Toy Robot 6

test/toy_robot/robot_test.exs

1 defmodule ToyRobot.RobotTest do
2 use ExUnit.Case
3 doctest ToyRobot.Robot
4 end

With this new test file, we’ve got a choice to make. Do we choose to write the tests in this file, or would we
write some doctests within the ToyRobot.Robot module? I personally like writing a short few examples in the
documentation to indicate how a function should be used, and then expanding on those tests by writing
additional ones in the test file itself. If we bunch all the tests into documentation, then the documentation
would become hard to read. So let’s start by writing a single doctest.
If we want to find an example for these, we can look in lib/toy_robot.ex. When we generate a new Mix project,
a file like this is created:

lib/toy_robot.ex

1 defmodule ToyRobot do
2 @moduledoc """
3 Documentation for ToyRobot.
4 """
5
6 @doc """
7 Hello world.
8
9 ## Examples
10
11 iex> ToyRobot.hello()
12 :world
13
14 """
15 def hello do
16 :world
17 end
18 end

This module defines documentation for the ToyRobot module itself, as well as a hello function within that
module, which just returns the atom :world. The documentation for this function is a doctest. We can know
if it’s a doctest by looking for any code blocks in it that use iex>. This is what Elixir will look for to determine
if a documentation block is pure documentation, or whether its testable documentation.
When we run mix test, the examples within the documentation will be evaluated and checked for correctness.
This test asserts that when we call ToyRobot.hello(), that :world is returned.
We do not need anything from this file, and so we can delete it completely. It’s helpful just to see what an
example doctest looks like.
Before we can write a doctest, we need to have a module and a function. Let’s create one now, along with
some documentation:
Introducing the Toy Robot 7

lib/toy_robot/robot.ex

1 defmodule ToyRobot.Robot do
2 @doc """
3 Moves the robot forward one space.
4
5 ## Examples
6
7 iex> alias ToyRobot.Robot
8 ToyRobot.Robot
9 iex> robot = %{position: 0}
10 %{position: 0}
11 iex> robot |> Robot.move
12 %{position: 1}
13 iex> robot |> Robot.move |> Robot.move |> Robot.move
14 %{position: 3}
15 """
16 def move(robot) do
17 end
18 end

This function will take a robot argument, and then according to the documentation the function should
increase the robot’s position by 1 each time the function is called. The function receives a map with a
:position key and also returns a map in the same shape, and so we can pipe-chain several calls of Robot.move/1
together.
The first test moves the robot once with Robot.move/1 and asserts that its final position is 1. The second test
asserts that when we move the robot three times – by pipe-chaining Robot.move/1 three times – the robot’s
position is 3. When the robot does that, we’re expecting that the returned value from this function is a map
that indicates that the position is 3. If we speak about it in terms of a robot moving along a line, our robot
will end up here on the line:

Toy Robot moved to 3

Before writing any code, we should first validate that the doctest we just wrote fails. This test is intended
to prove that our code works and if our test worked already, how could we know that it was really testing
that code in the first place? It’s always a good idea to make sure by running the test before writing the code.
We’ll run the test with this command:

1 mix test

The test will tell us this:


Introducing the Toy Robot 8

1 1) doctest ToyRobot.Robot.move/1 (1) (ToyRobot.RobotTest)


2 test/toy_robot/robot_test.exs:3
3 Doctest failed
4 code: robot |> ToyRobot.Robot.move === %{position: 1}
5 left: nil
6 right: %{position: 1}
7 stacktrace:
8 lib/toy_robot/robot.ex:7: ToyRobot.Robot (module)

This error tells us that the Robot.move/1 function can be called successfully, but the returned value from that
function is nil, not a map as we’re expecting. We need to change this function to make this first test pass,
but we must be careful to make this change in a way that would make the second test pass too.
One quick solution would be to return a map from move with a position value incremented, like this:

lib/toy_robot/robot.ex

1 def move(robot) do
2 %{position: robot.position + 1}
3 end

The solution doesn’t need to be perfect, it just needs to work and the way we validate if it works is by running
our tests. So let’s do that by running mix test now.

1 1 doctest, 0 failures

Everything is working correctly, as per our tests. Our robot is now able to “move” along the number line.
However, our robot currently only moves one direction along the number line, but we need to keep in
mind that the robot will eventually need to move up, down, left and right along the “tabletop”, as per our
instructions.

It’s worth pointing out that the description of the problem doesn’t refer to these directions as up,
down, right and left, but rather NORTH, SOUTH, EAST or WEST. So we should be careful to do the same.
From now on we’ll be using these compass directions, instead of the regular directions of “up”,
“down”, “right” and “left” respectively.

This will make it easy for us later on as we won’t need to think that north means up, west means
left, east means right and south means down.

What we have here now is a robot that can move right (or as we’re now saying: EAST) along our number line.
Rather than implementing the remaining three movement directions (WEST, NORTH and SOUTH) all at the same
time, let’s just take it slow. Let’s make it so that the robot can move “west” along our number line.
Introducing the Toy Robot 9

Moving west

Our robot should be able to move “west” (left) as well as “east” (right) along the number line:

Toy Robot moving west

Before we do that, we’ll change our code so that the move function is called move_east; just so it’s different to
the function which will move our robot WEST, move_west. Let’s change the move function in the doctests first,
so that we have some failing tests to assert that our code changes will be correct. Let’s change the doctests
in lib/toy_robot/robot.ex:

lib/toy_robot/robot.ex
1 @doc """
2 Moves the robot east one space.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %{position: 0}
9 %{position: 0}
10 iex> robot |> Robot.move_east
11 %{position: 1}
12 iex> robot |> Robot.move_east |> Robot.move_east |> Robot.move_east
13 %{position: 3}
14 """

After these changes are made, our test will break:

1 1) doctest ToyRobot.Robot.move/1 (1) (ToyRobot.RobotTest)


2 test/toy_robot/robot_test.exs:3
3 Doctest failed:
4 got UndefinedFunctionError with message
5 "function ToyRobot.Robot.move_east/1 is undefined or private"
6 code: alias ToyRobot.Robot
7 robot = %{position: 0}
8 robot |> Robot.move_east
9 robot |> Robot.move_east |> Robot.move_east |> Robot.move_east
10 stacktrace:
11 (toy_robot) ToyRobot.Robot.move_east(%{position: 0})
12 (for doctest at) lib/toy_robot/robot.ex:7: (test)

This is because we’ve updated the documentation, but “forgot” to update the function’s name. Let’s change
it now:
Introducing the Toy Robot 10

lib/toy_robot/robot.ex

1 def move_east(robot) do
2 %{position: robot.position + 1}
3 end

Running our tests again will show that they’re working:

1 1 doctest, 0 examples

We’re now at a good spot where we can add our function that moves the robot to the west of the number
line. We’ve got some tests ensuring that the current move_east behaviour will remain the same, even if we
make these new changes to the code. If the robot is issued an instruction to move west, it should indeed
move west along the line. Three “move west” instructions on a new robot should put the robot here on the
line:

Toy Robot at -3

Let’s add a new empty function and doctests for this to lib/toy_robot/robot.ex:

lib/toy_robot/robot.ex

1 @doc """
2 Moves the robot west one space.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %{position: 0}
9 %{position: 0}
10 iex> robot |> Robot.move_west
11 %{position: -1}
12 iex> robot |> Robot.move_west |> Robot.move_west |> Robot.move_west
13 %{position: -3}
14 """
15 def move_west(robot) do
16 end

When we run this new test, it’ll tell us that the function is returning nil:
Introducing the Toy Robot 11

1 1) doctest ToyRobot.Robot.move_west/1 (2) (ToyRobot.RobotTest)


2 test/toy_robot/robot_test.exs:3
3 Doctest failed
4 code: robot |> Robot.move_west === %{position: -1}
5 left: nil
6 right: %{position: -1}
7 stacktrace:
8 lib/toy_robot/robot.ex:24: ToyRobot.Robot (module)

The move_east function increments the position of the robot, and so the move_west function should decrement
the position of the robot. That’s what our doctests say it should do too. Let’s add the code to the move_west
function now:

lib/toy_robot/robot.ex, move_west

1 def move_west(robot) do
2 %{position: robot.position - 1}
3 end

Running our tests again will once again show that everything is happy:

1 2 doctests, 0 failures

Great! Our robot can now move in two of the four directions: EAST and WEST. Let’s now undo a stage of the
simplification of our problem a little by introducing a second number line; turning one number line into
two intersecting number lines.

Moving north and south

To make our robot move NORTH, SOUTH, EAST and WEST we’ll move the robot along two intersecting number lines,
shown here as a grid with coordinates representing each intersecting position on our number lines:
Introducing the Toy Robot 12

The Grid

This kind of thing is often referred to as a Cartesian plane by mathematicians and other people who like
sounding smart. In simpler terms, it is a coordinate grid generated by two intersecting number lines.
We refer to the horizontal rows of the grid as x, and the vertical as y typically when using a Cartesian plane,
but for this problem we’ll refer to them as east and north in ToyRobot.Robot. These variables will indicate the
current east and north position of our robot. If our robot was to move forward from its current position, it
would end up on the grid spot marked (2, 3), which is a short way of writing (x = 2, y = 3), or in our terms
(east = 2, north = 3).

The (0, 0) point of our grid is the “south west” (bottom left) corner because this is what our problem
description tells us it should be:

The origin (0,0) is the SOUTH WEST most corner.

Incidentally, by doing this it will make it easier for us to determine if our robot is going to go outside the
boundaries of the table, which is important because the problem description says this:

Any movement that would result in the robot falling from the table is prevented

More on that later. Let’s get our move actions all working first.
When we tell our robot to move_east, it will increase the value of east, and when we tell it to move_west it will
decrease the value of east. Similarly, if we tell the robot to move north, then we should increase the value
of north, and if we tell it to move south then we’ll decrease the value of north.
Let’s work in small increments towards our goal. We currently have one variable representing the position
of the robot on the table: position. Now we’re going to need another one so we can represent the “north-
ness” of our robot. We could call this new one north_position, and rename our existing one to east_position,
but that’s a lot of typing. So let’s now call them east and north to keep things simple.
Introducing the Toy Robot 13

To start with, we’ll rename the position variable to east, so that the name more accurately represents the
data that the variable contains. Let’s first update our doctests in lib/toy_robot/robot.ex to use east, instead of
position. This is a simple find-and-replace operation in our code, and will result in the lib/toy_robot/robot.ex
file looking like this:

lib/toy_robot/robot.ex

1 defmodule ToyRobot.Robot do
2 @doc """
3 Moves the robot east one space.
4
5 ## Examples
6
7 iex> alias ToyRobot.Robot
8 ToyRobot.Robot
9 iex> robot = %{east: 0}
10 %{east: 0}
11 iex> robot |> Robot.move_east
12 %{east: 1}
13 iex> robot |> Robot.move_east |> Robot.move_east |> Robot.move_east
14 %{east: 3}
15 """
16 def move_east(robot) do
17 %{east: robot.east + 1}
18 end
19
20 @doc """
21 Moves the robot west one space.
22
23 ## Examples
24
25 iex> alias ToyRobot.Robot
26 ToyRobot.Robot
27 iex> robot = %{east: 0}
28 %{east: 0}
29 iex> robot |> Robot.move_west
30 %{east: -1}
31 iex> robot |> Robot.move_west |> Robot.move_west |> Robot.move_west
32 %{east: -3}
33 """
34 def move_west(robot) do
35 %{east: robot.east - 1}
36 end
37 end

Running our tests will show that this change did not break any of our code:

1 2 doctests, 0 failures

Great! Our next step will be to add one of the functions for moving NORTH or SOUTH. Given that I said NORTH
first, let’s do that one. We’re old hands at these sorts of tests now, so writing them should be pretty
Introducing the Toy Robot 14

straightforward for us. These two tests will be very similar to the ones for move_west and move_east functions.
Similar enough that we could copy the code and replace a few variable names:

lib/toy_robot/robot.ex
1 @doc """
2 Moves the robot north one space.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %{north: 0}
9 %{north: 0}
10 iex> robot |> Robot.move_north
11 %{north: 1}
12 iex> robot |> Robot.move_north |> Robot.move_north |> Robot.move_north
13 %{north: 3}
14 """
15 def move_north(robot) do
16 %{north: robot.north + 1}
17 end

Our code for moving north isn’t that different from moving east or west. Instead of changing the east value,
we’re changing the north value. Let’s validate that this code is working by running our tests again:

1 3 doctests, 0 failures

Excellent. We now have just one more direction to implement (move_south) before our robot can move in all
the directions it is supposed to. Let’s copy the move_north function, and make a few changes to it:

lib/toy_robot/robot.ex
1 @doc """
2 Moves the robot south one space.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %{north: 0}
9 %{north: 0}
10 iex> robot |> Robot.move_south
11 %{north: -1}
12 iex> robot |> Robot.move_south |> Robot.move_south |> Robot.move_south
13 %{north: -3}
14 """
15 def move_south(robot) do
16 %{north: robot.north - 1}
17 end

This time, our robot should move south. That’s our last direction. Let’s see if the robot is moving correctly:
Introducing the Toy Robot 15

1 4 doctests, 0 failures

Yes! The robot can now move in all 4 directions. We have implemented our first rule:

• MOVE will move the toy robot one unit forward in the direction it is currently facing.

Well, most of that rule. We haven’t yet considered the “in the direction it is currently facing” part. We’ve
got a pretty good base to build on at the moment (thanks a lot to our doctests!), and we can very likely add
in facing support with very little fuss. Thanks to the tests that we’ve written, we’ll also be able to ensure
that our robot is performing as normal just by running these tests again.

Our code is not perfect (yet!)

It is important to point out here that there are definitely bugs within this code. Rather than be a deeply-
enigmatic author about it, let me show you an example that would most certainly break:

1 iex> alias ToyRobot.Robot


2 ToyRobot.Robot
3 iex> robot = %{north: 0}
4 %{north: 0}
5 iex> robot |> Robot.move_east

This code has a robot map which only tracks the north value, not the east value. The Robot.move_east/1
function is then called on this map, which will then show this error if we ran this code in a doctest:

1 Doctest failed: got KeyError with message "key :east not found in: %{north: 0}"
2 code:
3 alias ToyRobot.Robot
4 robot = %{north: 0}
5 robot |> Robot.move_east

We’re writing code here to make little things work. There is no intention yet to make our code work in all
cases. That’ll come later. We’re going to progressively build this toy robot implementations out, and then
fix bugs just like this one when they become true showstoppers.

We’re now tracking the NORTH and EAST position of the robot, and the next thing that we need to support is
the facing direction of the robot. Let’s take a look at that.

Moving in the right direction

The facing direction is important because the robot has to move in the direction it is currently facing, and
when the robot reports it needs to be able to tell us what direction it is currently facing. Later on, we’ll also
need to know what direction the robot is currently facing in order to know which direction to turn to when
the robot is asked to turn left or right. If the robot is facing NORTH and is asked to turn left, then it should
face WEST. If it is asked to turn right, then it should face EAST.
Introducing the Toy Robot 16

We’ve been working on moving the robot so far in all four of our directions, and so lets stick with that and
make it so that the robot moves in the direction its currently facing. To start with, we’re going to need to
track the direction the robot is facing in a similar way to how we’ve been tracking the robot’s north and east
positions. We’ll do this with an extra key on our maps to indicate the robot’s position:

1 %{north: 0, facing: :north}

Then based on the direction the robot is facing, it will be able to work out which direction to move in. If
the robot is facing NORTH, then a MOVE command should move the robot north. We’re going to have to become
cleverer about how we call the move_* functions. It now makes sense to combine the movement of our robot
into one function called move. Here’s how we’ll start, by putting this function at the top of the ToyRobot.Robot
module:

lib/toy_robot/robot.ex
1 @doc """
2 Moves the robot forward one space in the direction it is facing.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %{north: 0, facing: :north}
9 %{north: 0, facing: :north}
10 iex> robot |> Robot.move
11 %{north: 1}
12 """
13 def move(robot) do
14 end

We’re putting this function at the top, above all the other functions, because it will supersede the other
move_* functions soon. We’ll turn those move_* functions into private functions shortly. When we ask the
robot to move later on, we’ll call this Robot.move/1 function and it will move the robot in the direction it is
facing by using those move_* functions.
We’ve now added a new doctest, and so we should validate that this test fails before we implement this
function. When we run this test, we’ll see this error:

1 test/toy_robot/robot_test.exs:3
2 Doctest failed
3 code: alias ToyRobot.Robot
4 robot = %{north: 0, facing: :north}
5 robot |> Robot.move === %{north: 1}
6 left: nil
7 right: %{north: 1}
8 stacktrace:
9 lib/toy_robot/robot.ex:7: ToyRobot.Robot (module)

Great, our test is failing. Now we need to make it work. This test is only checking if the robot can move
north, and so we could make it pass with a simple definition of move:
Introducing the Toy Robot 17

lib/toy_robot/robot.ex

1 @doc """
2 Moves the robot forward one space in the direction it is facing.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %{north: 0, facing: :north}
9 %{north: 0, facing: :north}
10 iex> robot |> Robot.move
11 %{north: 1}
12 """
13 def move(robot), do: robot |> move_north

As long as all we wanted to do was to move north, this function definition would be perfect. But we also
want to move east, south and west. When we run the tests, we’ll see that everything works:

1 5 doctests, 0 failures

But we know that not everything works. If we tried to move our robot east, south or west, our code would
misbehave: it would move the robot north instead of any of those other directions. To prove that such a bug
exists, we have two choices. We could choose to write more doctests to cover the other directions, or we could
write some tests inside of test/toy_robot/robot_test.exs. My preference is for the latter, because writing more
doctests here would not increase the value of the documentation. People who read the documentation could
probably intuit how move should work based on the documentation that is already there for when the robot
is facing north.
Therefore, we’ll put these tests in a separate file, starting with another test for facing & moving north, as
well as an additional test for facing + moving east. We’ll cover the other two directions in a short while.

test/toy_robot/robot_test.exs

1 defmodule ToyRobot.RobotTest do
2 use ExUnit.Case
3 doctest ToyRobot.Robot
4 alias ToyRobot.Robot
5
6 describe "when the robot is facing north" do
7 setup do
8 {:ok, %{robot: %{north: 0, facing: :north}}}
9 end
10
11 test "it moves one space north", %{robot: robot} do
12 robot = robot |> Robot.move
13 assert robot.north == 1
14 end
15 end
16
Introducing the Toy Robot 18

17 describe "when the robot is facing east" do


18 setup do
19 {:ok, %{robot: %{east: 0, facing: :east}}}
20 end
21
22 test "it moves one space east", %{robot: robot} do
23 robot = robot |> Robot.move
24 assert robot.east == 1
25 end
26 end
27 end

These each exist within a describe block. Each describe block sets up a robot facing either north or east.
When we run these tests, we should get a robot that moves one “step” in its respective direction.
When we run these tests with mix test, we’ll see it fail like this:

1 1) test when the robot is facing east it moves one space east (ToyRobot.RobotTest)
2 test/toy_robot/robot_test.exs:22
3 ** (KeyError) key :north not found in: %{east: 0, facing: :east}
4 code: robot = robot |> Robot.move
5 stacktrace:
6 (toy_robot) lib/toy_robot/robot.ex:63: ToyRobot.Robot.move_north/1
7 test/toy_robot/robot_test.exs:23: (test)

This is the second time we’ve seen this sort of bug, where our robot maps have been missing either the north
or east keys. We talked about it a little at the end of the last section. We said that we wouldn’t care about it
too much unless it’s becoming a showstopper. It’s not quite a showstopper, but we’ve seen this sort of issue
crop up a few times, and so we should dedicate some time to fixing it.
The issue here is that we’ve been fairly lax with how we’ve defined maps. We’ve sometimes neglected to
include either the north key, or the east key. We haven’t been consistent about providing both keys. For our
code to work correctly in all cases, we need to enforce that our move function takes a map that has both the
north and east keys. The best way to do this would be to move away from using a map to represent our robot
and to move to using a struct instead. Think of a struct as a named map that has a limited set of keys, and
on top of that a struct can have default values for its keys.
Let’s add a defstruct to the ToyRobot.Robot module to define this struct:

lib/toy_robot/robot.ex

1 defmodule ToyRobot.Robot do
2 defstruct [north: 0, east: 0, facing: :north]
3 ...

This one line will define a struct for the ToyRobot.Robot module, which we can now use to represent our robot.
While we’re in this lib/toy_robot/robot.ex file, let’s update all the code here to use this struct, rather than a
regular map. Let’s start with the move/1 function:
Introducing the Toy Robot 19

lib/toy_robot/robot.ex

1 @doc """
2 Moves the robot forward one space in the direction it is facing.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %Robot{north: 0, facing: :north}
9 %Robot{north: 0, facing: :north}
10 iex> robot |> Robot.move
11 %Robot{north: 1}
12 """
13 def move(%__MODULE__{} = robot), do: robot |> move_north

This move/1 function and its related documentation has now been updated to use the ToyRobot.Robot struct,
rather than a plain map. It’s important to note that when we are testing the return value of each of these
lines, we don’t have to supply all of the keys of the struct. We just need to return the ones that we care about.
If we wanted to be super-explicit about it, we could write:

lib/toy_robot/robot.ex

1 @doc """
2 Moves the robot forward one space in the direction it is facing.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %Robot{north: 0, facing: :north}
9 %Robot{north: 0, east: 0, facing: :north}
10 iex> robot |> Robot.move
11 %Robot{north: 1, east: 0, facing: north}
12 """
13 def move(%__MODULE__{} = robot), do: robot |> move_north

But that’s extra typing for something that I think isn’t worth it. I’ll leave the documentation short for now,
but you can choose to write it out if it would help you.
It’s worth talking about the %__MODULE__ syntax within the move/1 function definition too. This is a short way
of writing the name of the current module. The alternative would be to write out %ToyRobot.Robot{}, and
that’s slightly longer than %__MODULE__{}, so we should prefer %__MODULE__ here.
Next up, let’s update move_east in the same sort of way:
Introducing the Toy Robot 20

lib/toy_robot/robot.ex

1 @doc """
2 Moves the robot east one space.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %Robot{east: 0}
9 %Robot{east: 0}
10 iex> robot |> Robot.move_east
11 %Robot{east: 1}
12 iex> robot |> Robot.move_east |> Robot.move_east |> Robot.move_east
13 %Robot{east: 3}
14
15 """
16 def move_east(robot) do
17 %Robot{east: robot.east + 1}
18 end

We’ve mostly changed the documentation for this function, but it’s important to know that the code inside
the function has also changed from returning a map to returning a struct from the module. We can make
this code a little shorter by aliasing the module at the top of this file:

lib/toy_robot/robot.ex

1 defmodule ToyRobot.Robot do
2 alias ToyRobot.Robot
3 ...

Then we could change this code inside move_east to this:

lib/toy_robot/robot.ex

1 def move_east(robot) do
2 %Robot{east: robot.east + 1}
3 end

With that change in place, we can make similar changes to the other move functions, move_west, move_north,
and move_south:
Introducing the Toy Robot 21

lib/toy_robot/robot.ex

1 @doc """
2 Moves the robot west one space.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %Robot{east: 0}
9 %Robot{east: 0}
10 iex> robot |> Robot.move_west
11 %Robot{east: -1}
12 iex> robot |> Robot.move_west |> Robot.move_west |> Robot.move_west
13 %Robot{east: -3}
14 """
15 def move_west(robot) do
16 %Robot{east: robot.east - 1}
17 end
18
19 @doc """
20 Moves the robot north one space.
21
22 ## Examples
23
24 iex> alias ToyRobot.Robot
25 ToyRobot.Robot
26 iex> robot = %Robot{north: 0}
27 %Robot{north: 0}
28 iex> robot |> Robot.move_north
29 %Robot{north: 1}
30 iex> robot |> Robot.move_north |> Robot.move_north |> Robot.move_north
31 %Robot{north: 3}
32 """
33 def move_north(robot) do
34 %Robot{north: robot.north + 1}
35 end
36
37 @doc """
38 Moves the robot south one space.
39
40 ## Examples
41
42 iex> alias ToyRobot.Robot
43 ToyRobot.Robot
44 iex> robot = %Robot{north: 0}
45 %Robot{north: 0}
46 iex> robot |> Robot.move_south
47 %Robot{north: -1}
48 iex> robot |> Robot.move_south |> Robot.move_south |> Robot.move_south
49 %Robot{north: -3}
50 """
51 def move_south(robot) do
52 %Robot{north: robot.north - 1}
Introducing the Toy Robot 22

53 end

These changes might look good to our eyes, but what do the tests have to say? Let’s run them with mix test
and find out.

1 1) test when the robot is facing north it moves one space north (ToyRobot.RobotTest)
2 test/toy_robot/robot_test.exs:11
3 ** (FunctionClauseError) no function clause matching in ToyRobot.Robot.move/1
4
5 The following arguments were given to ToyRobot.Robot.move/1:
6
7 # 1
8 %{facing: :north, north: 0}
9
10 Attempted function clauses (showing 1 out of 1):
11
12 def move(%ToyRobot.Robot{} = robot)
13
14 code: robot = robot |> Robot.move
15 stacktrace:
16 (toy_robot) lib/toy_robot/robot.ex:17: ToyRobot.Robot.move/1
17 test/toy_robot/robot_test.exs:12: (test)
18
19 2) test when the robot is facing east it moves one space east (ToyRobot.RobotTest)
20 test/toy_robot/robot_test.exs:22
21 ** (FunctionClauseError) no function clause matching in ToyRobot.Robot.move/1
22
23 The following arguments were given to ToyRobot.Robot.move/1:
24
25 # 1
26 %{east: 0, facing: :east}
27
28 Attempted function clauses (showing 1 out of 1):
29
30 def move(%ToyRobot.Robot{} = robot)
31
32 code: robot = robot |> Robot.move
33 stacktrace:
34 (toy_robot) lib/toy_robot/robot.ex:17: ToyRobot.Robot.move/1
35 test/toy_robot/robot_test.exs:23: (test)

It seems like all of our doctests are passing. The tests that are not passing are both coming from test/toy_-
robot/robot_test.exs. The two setup blocks that we have in this test file setup plain maps to represent the
robot, whereas our functions are now expecting structs. If Elixir was a statically typed language, we would
probably get better error messages to indicate this. For now, we have our intuition and brains.
We can change the two setup blocks within this test file to use the struct to fix this issue:
Introducing the Toy Robot 23

test/toy_robot/robot_test.exs

1 defmodule ToyRobot.RobotTest do
2 use ExUnit.Case
3 doctest ToyRobot.Robot
4 alias ToyRobot.Robot
5
6 describe "when the robot is facing north" do
7 setup do
8 {:ok, %{robot: %Robot{north: 0, facing: :north}}}
9 end
10
11 test "it moves one space north", %{robot: robot} do
12 robot = robot |> Robot.move
13 assert robot.north == 1
14 end
15 end
16
17 describe "when the robot is facing east" do
18 setup do
19 {:ok, %{robot: %Robot{east: 0, facing: :east}}}
20 end
21
22 test "it moves one space east", %{robot: robot} do
23 robot = robot |> Robot.move
24 assert robot.east == 1
25 end
26 end
27 end

This will fix one of our tests, but we’ll see that the “moves one space east” test is still broken:

1 1) test when the robot is facing east it moves one space east (ToyRobot.RobotTest)
2 test/toy_robot/robot_test.exs:22
3 Assertion with == failed
4 code: assert robot.east() == 1
5 left: 0
6 right: 1
7 stacktrace:
8 test/toy_robot/robot_test.exs:24: (test)

This is because our move/1 function only ever moves the robot north.

lib/toy_robot/robot.ex

1 def move(%__MODULE__{} = robot), do: robot |> move_north

We need to change this function to move the robot in whatever direction it is currently facing as per the
instructions. If it’s facing north, it needs to move north. If it’s facing east, it needs to move east. Let’s
account for at least these two directions by changing the function’s definition to this:
Introducing the Toy Robot 24

lib/toy_robot/robot.ex

1 def move(%Robot{facing: :north} = robot), do: robot |> move_north


2 def move(%Robot{facing: :east} = robot), do: robot |> move_east

When we run our tests once more, we’ll see that they’re now completely satisfied:

1 5 doctests, 2 tests, 0 failures

There are still two directions that we have not accounted for, west and south. Let’s add more tests for these
two directions:

test/toy_robot/robot_test.exs

1 describe "when the robot is facing south" do


2 setup do
3 {:ok, %{robot: %Robot{north: 0, facing: :south}}}
4 end
5
6 test "it moves one space south", %{robot: robot} do
7 robot = robot |> Robot.move
8 assert robot.north == -1
9 end
10 end
11
12 describe "when the robot is facing west" do
13 setup do
14 {:ok, %{robot: %Robot{east: 0, facing: :west}}}
15 end
16
17 test "it moves one space west", %{robot: robot} do
18 robot = robot |> Robot.move
19 assert robot.east == -1
20 end
21 end

These two tests should make sure that our robot can move south and west. If we run these tests, they will
fail:
Introducing the Toy Robot 25

1 1) test when the robot is facing west it moves one space west (ToyRobot.RobotTest)
2 test/toy_robot/robot_test.exs:44
3 ** (FunctionClauseError) no function clause matching in ToyRobot.Robot.move/1
4
5 The following arguments were given to ToyRobot.Robot.move/1:
6
7 # 1
8 %ToyRobot.Robot{east: 0, facing: :west, north: 0}
9
10 Attempted function clauses (showing 2 out of 2):
11
12 def move(%ToyRobot.Robot{facing: :north} = robot)
13 def move(%ToyRobot.Robot{facing: :east} = robot)
14
15 code: robot = robot |> Robot.move
16 stacktrace:
17 (toy_robot) lib/toy_robot/robot.ex:17: ToyRobot.Robot.move/1
18 test/toy_robot/robot_test.exs:45: (test)
19
20 2) test when the robot is facing south it moves one space south (ToyRobot.RobotTest)
21 test/toy_robot/robot_test.exs:33
22 ** (FunctionClauseError) no function clause matching in ToyRobot.Robot.move/1
23
24 The following arguments were given to ToyRobot.Robot.move/1:
25
26 # 1
27 %ToyRobot.Robot{east: 0, facing: :south, north: 0}
28
29 Attempted function clauses (showing 2 out of 2):
30
31 def move(%ToyRobot.Robot{facing: :north} = robot)
32 def move(%ToyRobot.Robot{facing: :east} = robot)
33
34 code: robot = robot |> Robot.move
35 stacktrace:
36 (toy_robot) lib/toy_robot/robot.ex:17: ToyRobot.Robot.move/1
37 test/toy_robot/robot_test.exs:34: (test)

These two tests are failing because we do not support the south and west facing directions in our move
function yet. Let’s change our move function to support these directions:

lib/toy_robot/robot.ex

1 def move(%Robot{facing: :north} = robot), do: robot |> move_north


2 def move(%Robot{facing: :east} = robot), do: robot |> move_east
3 def move(%Robot{facing: :south} = robot), do: robot |> move_south
4 def move(%Robot{facing: :west} = robot), do: robot |> move_west

If we re-run our tests again, we’ll see that they’re happy:


Introducing the Toy Robot 26

1 5 doctests, 4 tests, 0 failures

Ah, but are we happy? Is the move function the cleanest we could make it? I would say it isn’t. The move
function could probably be written a little neater. We used pattern matching in the definition of the function,
but we could probably replace that with a case statement instead. Let’s try it and see how it looks:

lib/toy_robot/robot.ex

1 def move(%Robot{facing: facing} = robot) do


2 case facing do
3 :north -> robot |> move_north
4 :east -> robot |> move_east
5 :south -> robot |> move_south
6 :west -> robot |> move_west
7 end
8 end

This reduces the repetition from the head of the function, moving the pattern logic code into the function
body itself. I like this approach more.
One other thing to consider in the wider scope of this module’s code is whether or not the move_* functions
should be exposed publicly. I would say that they shouldn’t be exposed. Any movement of the robot should
be done purely through the move function. Therefore, I think we could reduce this module’s code to this:

lib/toy_robot/robot.ex

1 defmodule ToyRobot.Robot do
2 alias ToyRobot.Robot
3 defstruct [north: 0, east: 0, facing: :north]
4
5 @doc """
6 Moves the robot forward one space in the direction it is facing.
7
8 ## Examples
9
10 iex> alias ToyRobot.Robot
11 ToyRobot.Robot
12 iex> robot = %Robot{north: 0, facing: :north}
13 %Robot{north: 0, facing: :north}
14 iex> robot |> Robot.move
15 %Robot{north: 1}
16 """
17 def move(%__MODULE__{facing: facing} = robot) do
18 case facing do
19 :north -> robot |> move_north
20 :east -> robot |> move_east
21 :south -> robot |> move_south
22 :west -> robot |> move_west
23 end
24 end
25
26 defp move_east(robot) do
Introducing the Toy Robot 27

27 %Robot{east: robot.east + 1}
28 end
29
30 defp move_west(robot) do
31 %Robot{east: robot.east - 1}
32 end
33
34 defp move_north(robot) do
35 %Robot{north: robot.north + 1}
36 end
37
38 defp move_south(robot) do
39 %Robot{north: robot.north - 1}
40 end
41 end

We have made the move_* functions private, and now this module only exposes a move function. The
documentation was removed from these private functions, because Elixir does not expose documentation
for private functions. It doesn’t make sense to keep that documentation around.
You might think that we have lost those valuable tests we wrote. We have not lost the tests for these
functions; we wrote similar ones in test/toy_robot/robot_test.exs that test exactly the same things. We have
simplified our code and it has cost us barely anything. We did not lose anything.
Running the tests will now show that the doctest for move/1 passes, as do the other 4 tests that check that
move can move in any one of the four facing directions:

1 1 doctest, 4 tests, 0 failures

What we’ve now done is implemented the first command for our robot completely: the MOVE command. The
robot will now move in the direction it is currently facing when it is asked to move.
What we’ve also done is provided a wonderful framework in the ToyRobot.Robot module to add additional
functionality to, and we have a great test suite foundation consisting of 5 tests that ensures our robot will
always work the way that we want it to. Thanks to this framework and test suite combination, adding more
features will be very easy.
Out of the commands to be processed, we have these three left:

• PLACE will put the toy robot on the table in position X,Y and facing NORTH, SOUTH, EAST
or WEST.
• LEFT and RIGHT rotates the robot 90 degrees in the specified direction without changing
the position of the robot.
• REPORT announces the X,Y and F of the robot.

We don’t yet have a concept of a table, and so we can ignore PLACE for now. What we do have is a robot that
can face a certain direction. So let’s look at making our robot obey the LEFT and RIGHT commands next.
Introducing the Toy Robot 28

Turn left!

Our robot has a concept of the direction it is facing in the facing key of the ToyRobot.Robot struct, but our
code doesn’t know how to change that key’s value to a different value once it has been initially set. This is
what will be required if our robot is to understand what LEFT and RIGHT actually mean.
The initial description of our problem says this about the LEFT and RIGHT commands:

• LEFT and RIGHT rotates the robot 90 degrees in the specified direction without changing
the position of the robot.

We could go ahead and track the exact degrees the robot is facing – either 0, 90, 180 or 270 – but the PLACE
command says this:

• PLACE will put the toy robot on the table in position X,Y and facing NORTH, SOUTH, EAST
or WEST.

We know that WEST is 90 degrees anti-clockwise from NORTH, and EAST is 90 degrees clockwise and so we can
simply track which direction out of NORTH, SOUTH, EAST or WEST the robot is facing, rather than the degrees.
Let’s start off by just implementing the turn_left function for our robot, so that it can turn left to face a
new heading. We’re going to continue our theme of starting with a doctest so that we have documentation
available for our public functions. We’ll put this new function just under the move/1 function, but above the
private move functions. This is because it is best practice to group public and private functions together.

lib/toy_robot/robot.ex

1 @doc """
2 Turns the robot left.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %Robot{facing: :north}
9 %Robot{facing: :north}
10 iex> robot |> Robot.turn_left
11 %Robot{facing: :west}
12 """
13 def turn_left(robot) do
14 end

This doctest takes a robot that faces north and then asserts that when we call the Robot.turn_left/1 function
that the robot ends up facing west. When we run this test, we’ll see it fail because we haven’t implemented
the code inside this method:
Introducing the Toy Robot 29

1 1) doctest ToyRobot.Robot.turn_left/1 (2) (ToyRobot.RobotTest)


2 test/toy_robot/robot_test.exs:3
3 Doctest failed
4 code: robot |> Robot.turn_left === %Robot{facing: :west}
5 left: nil
6 right: %ToyRobot.Robot{east: 0, facing: :west, north: 0}
7 stacktrace:
8 lib/toy_robot/robot.ex:31: ToyRobot.Robot (module)

The simplest way to make this test pass is to change our function to return a robot that is now facing west:

lib/toy_robot/robot.ex
1 def turn_left(robot) do
2 %Robot{facing: :west}
3 end

This will make our doctest happy:

1 2 doctests, 4 tests, 0 failures

But we can tell just from looking at this code that it’s not very good. It only handles a robot that turns left
from facing north. Realistically, the code should handle a robot facing any direction and then turning left.
To test that these movements would work we should write some additional ExUnit tests in to test/toy_-
robot/robot_test.exs, just like we did for testing the move function and all its directions that it could move
in.
Let’s start with a test for when the robot is facing north, placing it within the describe block for “when the
robot is facing north”:

lib/toy_robot/robot_test.exs
1 describe "when the robot is facing north" do
2 setup do
3 {:ok, %{robot: %Robot{north: 0, facing: :north}}}
4 end
5
6 test "moves one space north", %{robot: robot} do
7 robot = robot |> Robot.move
8 assert robot.north == 1
9 end
10
11 test "turns left to face west", %{robot: robot} do
12 robot = robot |> Robot.turn_left
13 assert robot.facing == :west
14 end
15 end

This test is the same as our doctest. It asserts that when our robot is facing north and is asked to turn left,
that it then faces west. The doctest is passing, and so our new test should be passing too. Let’s check:
Introducing the Toy Robot 30

1 2 doctests, 5 tests, 0 failures

Okay, that one’s working. Let’s try a different direction now. The one that’s next in the file is if the robot is
facing east. If the robot is facing east and it turns left, it should then be facing north. Let’s write this within
the existing describe for “when the robot is facing east”:

lib/toy_robot/robot_test.exs

1 describe "when the robot is facing east" do


2 setup do
3 {:ok, %{robot: %Robot{east: 0, facing: :east}}}
4 end
5
6 test "it moves one space east", %{robot: robot} do
7 robot = robot |> Robot.move
8 assert robot.east == 1
9 end
10
11 test "turns left to face north", %{robot: robot} do
12 robot = robot |> Robot.turn_left
13 assert robot.facing == :north
14 end
15 end

This time when we run our tests, we will now have a failing test to fix up:

1 1) test when the robot is facing east turns left to face north (ToyRobot.RobotTest)
2 test/toy_robot/robot_test.exs:32
3 Assertion with == failed
4 code: assert robot.facing() == :north
5 left: :west
6 right: :north
7 stacktrace:
8 test/toy_robot/robot_test.exs:34: (test)

Our robot has, seemingly, turned too far left! It is now facing WEST, when it should be facing NORTH. We know
the reason for this: our robot only ever faces WEST when it turns left.
To fix this test, we’re going to have to be cleverer with our turn_left/1 function. Let’s update this function
now to turn a robot that is facing either NORTH or EAST:
Introducing the Toy Robot 31

lib/toy_robot/robot.ex
1 def turn_left(%Robot{facing: facing}) do
2 case facing do
3 :north -> %Robot{facing: :west}
4 :east -> %Robot{facing: :north}
5 end
6 end

This code should make our test pass. Let’s run it and find out:

1 2 doctests, 6 tests, 0 failures

Great! Let’s add two more tests now in this same file. These tests will test what happens if the robot is facing
south, or west.

test/toy_robot/robot_test.exs
1 describe "when the robot is facing south" do
2 setup do
3 {:ok, %{robot: %Robot{north: 0, facing: :south}}}
4 end
5
6 test "it moves one space south", %{robot: robot} do
7 robot = robot |> Robot.move
8 assert robot.north == -1
9 end
10
11 test "turns left to face east", %{robot: robot} do
12 robot = robot |> Robot.turn_left
13 assert robot.facing == :east
14 end
15 end
16
17 describe "when the robot is facing west" do
18 setup do
19 {:ok, %{robot: %Robot{east: 0, facing: :west}}}
20 end
21
22 test "it moves one space west", %{robot: robot} do
23 robot = robot |> Robot.move
24 assert robot.east == -1
25 end
26
27 test "turns left to face south", %{robot: robot} do
28 robot = robot |> Robot.turn_left
29 assert robot.facing == :south
30 end
31 end

When we run these tests, we’ll see that the turn_left function does not know how to handle the cases where
our robot is facing either SOUTH or WEST:
Introducing the Toy Robot 32

1 1) test when the robot is facing west turns left to face south (ToyRobot.RobotTest)
2 test/toy_robot/robot_test.exs:64
3 ** (CaseClauseError) no case clause matching: :west
4 code: robot = robot |> Robot.turn_left
5 stacktrace:
6 (toy_robot) lib/toy_robot/robot.ex:39: ToyRobot.Robot.turn_left/1
7 test/toy_robot/robot_test.exs:65: (test)
8
9 2) test when the robot is facing south turns left to face east (ToyRobot.RobotTest)
10 test/toy_robot/robot_test.exs:48
11 ** (CaseClauseError) no case clause matching: :south
12 code: robot = robot |> Robot.turn_left
13 stacktrace:
14 (toy_robot) lib/toy_robot/robot.ex:39: ToyRobot.Robot.turn_left/1
15 test/toy_robot/robot_test.exs:49: (test)

To fix these tests, we’ll need to change our turn_left function to handle these two other facing directions.
We can do that with this code:

lib/toy_robot/robot.ex

1 def turn_left(%Robot{facing: facing}) do


2 case facing do
3 :north -> %Robot{facing: :west}
4 :east -> %Robot{facing: :north}
5 :south -> %Robot{facing: :east}
6 :west -> %Robot{facing: :south}
7 end
8 end

Now when we run our tests again, they’ll be happy:

1 2 doctests, 8 tests, 0 failures

Our robot is now able to turn left from any direction it is facing, as evidenced by our tests all being green.
Let’s take a look at making our robot turn right now.

Turn right!

To make sure our robot will behave when it is asked to turn right, we should write some tests. Turning right
isn’t really all that different from turning left, so we should follow much the same pattern we did in the last
section. We’ll do it a little quicker this time, because we’re old hands at turning robots now. Let’s start with
a doctest still:
Introducing the Toy Robot 33

lib/toy_robot/robot.ex

1 @doc """
2 Turns the robot right.
3
4 ## Examples
5
6 iex> alias ToyRobot.Robot
7 ToyRobot.Robot
8 iex> robot = %Robot{facing: :north}
9 %Robot{facing: :north}
10 iex> robot |> Robot.turn_right
11 %Robot{facing: :east}
12 """
13 def turn_right(robot) do
14 end

A robot that is asked to turn right while facing north should end up pointing east. Well, that’s easy enough
to reason about. We’ll see our test break here because we haven’t implemented this function yet:

1 1) doctest ToyRobot.Robot.turn_right/1 (3) (ToyRobot.RobotTest)


2 test/toy_robot/robot_test.exs:3
3 Doctest failed
4 code: robot |> Robot.turn_right === %Robot{facing: :east}
5 left: nil
6 right: %ToyRobot.Robot{east: 0, facing: :east, north: 0}
7 stacktrace:
8 lib/toy_robot/robot.ex:52: ToyRobot.Robot (module)

We’ll fix this doctest up by making our turn_right function return a robot that is facing east:

lib/toy_robot/robot.ex

1 def turn_right(robot) do
2 %Robot{facing: :east}
3 end

This will make our test pass:

1 3 doctests, 8 tests, 0 failures

But of course, we’re ignoring the direction the robot is currently facing again! Deja vu!
Let’s add in some tests for all the directions to test/toy_robot/robot_test.exs:
Introducing the Toy Robot 34

test/toy_robot/robot_test.exs

1 describe "when the robot is facing north" do


2 setup do
3 {:ok, %{robot: %Robot{north: 0, facing: :north}}}
4 end
5
6 ...
7
8 test "turns right to face east", %{robot: robot} do
9 robot = robot |> Robot.turn_right
10 assert robot.facing == :east
11 end
12 end
13
14 describe "when the robot is facing east" do
15 setup do
16 {:ok, %{robot: %Robot{north: 0, facing: :east}}}
17 end
18
19 ...
20
21 test "turns right to face south", %{robot: robot} do
22 robot = robot |> Robot.turn_right
23 assert robot.facing == :south
24 end
25 end
26
27 describe "when the robot is facing south" do
28 setup do
29 {:ok, %{robot: %Robot{north: 0, facing: :south}}}
30 end
31
32 ...
33
34 test "turns right to face west", %{robot: robot} do
35 robot = robot |> Robot.turn_right
36 assert robot.facing == :west
37 end
38 end
39
40 describe "when the robot is facing west" do
41 setup do
42 {:ok, %{robot: %Robot{north: 0, facing: :west}}}
43 end
44
45 ...
46
47 test "turns right to face north", %{robot: robot} do
48 robot = robot |> Robot.turn_right
49 assert robot.facing == :north
50 end
51 end
Introducing the Toy Robot 35

Running these tests will show that they’re all failing because our robot ends up facing east, rather than the
expected direction:

1 1) test when the robot is facing south turns right to face west (ToyRobot.RobotTest)
2 test/toy_robot/robot_test.exs:63
3 Assertion with == failed
4 code: assert robot.facing() == :west
5 left: :east
6 right: :west
7 stacktrace:
8 test/toy_robot/robot_test.exs:65: (test)
9
10 2) test when the robot is facing west turns right to face north (ToyRobot.RobotTest)
11 test/toy_robot/robot_test.exs:84
12 Assertion with == failed
13 code: assert robot.facing() == :north
14 left: :east
15 right: :north
16 stacktrace:
17 test/toy_robot/robot_test.exs:86: (test)
18
19 3) test when the robot is facing east turns right to face south (ToyRobot.RobotTest)
20 test/toy_robot/robot_test.exs:42
21 Assertion with == failed
22 code: assert robot.facing() == :south
23 left: :east
24 right: :south
25 stacktrace:
26 test/toy_robot/robot_test.exs:44: (test)

Let’s change this turn_right function to correctly orient our robot for all directions:

lib/toy_robot/robot.ex
1 def turn_right(%Robot{facing: facing}) do
2 case facing do
3 :north -> %Robot{facing: :east}
4 :east -> %Robot{facing: :south}
5 :south -> %Robot{facing: :west}
6 :west -> %Robot{facing: :north}
7 end
8 end

With these changes, all of our robot’s tests will now be passing:

1 3 doctests, 12 tests, 0 failures

Our robot is now able to move, turn left and to turn right. We now have three of the commands taken care
of: MOVE, LEFT and RIGHT. We have PLACE X,Y,F and REPORT to do. And that’s just the commands side of things!
Before we jump into tackling these commands, I want to talk about our code some more. In particular, I
want to talk about bugs.
2. Catching bugs
When we completed the move function, hinted that our code wasn’t perfect (yet!); that there were bugs in
our code. The code I used to demonstrate one such bug was this:

1 iex> alias ToyRobot.Robot


2 ToyRobot.Robot
3 iex> robot = %{north: 0}
4 %{north: 0}
5 iex> robot |> Robot.move_east

The way this code would break is that the move_east function would try to increase the value of the east key,
but on that robot map, the east key does not exist. This would cause the move_east function to break:

1 Doctest failed: got KeyError with message "key :east not found in: %{north: 0}"
2 code:
3 alias ToyRobot.Robot
4 robot = %{north: 0}
5 robot |> Robot.move_east

The move function has superseded the move functions since then, and not only that but we now use a struct
to represent our robot. The times, they are a changin’. To move a robot east, our code would now be:

1 alias ToyRobot.Robot
2 robot = %Robot{facing: :east}
3 robot |> Robot.move

Go ahead and try this out in an iex -S mix session now. You will see that this code does indeed work. We
have tests covering this exact circumstance over in test/toy_robot/robot_test.exs. All of our tests are green
and so we could be lured into a false sense of security that our code is perfect in every which way. We have
proved that our robot can move in all four compass directions, and can turn left or right when they’re facing
any one of those directions.
But our ideal of our code being “perfect”, isn’t true.
What is true is that each of our functions – move/1, turn_left/1 and turn_right/1 – work well independently,
but we have not really tested these together. The point of the robot exercise is to ingest a list of commands,
and then to simulate the robot moving around a table. This process would involve calling these functions
in any order, and any number of times. This is not something we have tested for.
And so our code is faulty. As the author, I can definitively say such things because I’ve planned for them. I
hope you’ve noticed, or gained an inkling, that our code isn’t quite right. Let’s get it out in the open now: our
code is wrong. I intentionally made it that way to demonstrate that what looks like pretty thorough testing
can mask bugs. The fact is, we have only been testing these functions in isolation. It is time we started to
test these functions together and to fix any problems that crop up. And there will be problems.
Catching bugs 37

Finding the bugs within

Let’s start by chaining all three of our functions together in different ways. Let’s say we’ve received a
moderately complex set of instructions like this:

1 PLACE 0,0,NORTH
2 MOVE
3 RIGHT
4 MOVE
5 LEFT
6 MOVE
7 MOVE
8 LEFT
9 MOVE
10 RIGHT
11 RIGHT
12 MOVE
13 MOVE
14 MOVE

Where might we expect our robot to end up? We could code this up, but as we’ve just established: our code
is broken. It might help if we drew this up instead, so we can visualize where the robot should end up. Here’s
where our robot starts on the grid:

Robot starts at 0, 0

And then given this set of instructions combined with our wonderful drawing skills, we can expect the robot
to end up at this position:
Catching bugs 38

Robot moves to 3, 3

That position we would call (3, 3), or 3 EAST, 3 NORTH, and it should be facing EAST. So, does the robot end
up there if we translated these instructions into function calls? Let’s try it and find out.
We don’t have a place function at the moment for our robot, but we could simulate one by building a struct
with the relevant fields. Let’s start out by creating a new file called test.exs at the root of our project:

test.exs
1 alias ToyRobot.Robot
2 %Robot{east: 0, north: 0, facing: :north}

We do have functions for the MOVE, LEFT and ‘RIGHT actions though, so let’s write those out, using pipes to
chain the functions together:

test.exs
1 alias ToyRobot.Robot
2
3 %Robot{east: 0, north: 0, facing: :north}
4 |> Robot.move
5 |> Robot.turn_right
6 |> Robot.move
7 |> Robot.turn_left
8 |> Robot.move
9 |> Robot.move
10 |> Robot.turn_left
11 |> Robot.move
12 |> Robot.turn_right
13 |> Robot.turn_right
14 |> Robot.move
15 |> Robot.move
16 |> Robot.move
17 |> IO.inspect

The final line in this file uses IO.inspect to get the final result of our robot. We might expect our REPORT
command to perform this action for us later on. We’re going to use it here to see where our robot ends up
after we issue all these function calls. According to this diagram, here’s the actions our robot should take:
Catching bugs 39

Robot moves to 3, 3

Our robot should finish at (3, 3) on our board, and it should be facing EAST. Does it? The next step here is
to try and run this little file to see what happens. We can do this with the mix run command:

1 mix run test.exs

When we run this command, we’ll see this output:

1 %ToyRobot.Robot{east: 0, facing: :north, north: 1}

Our robot certainly does not end up at (3, 3), facing EAST. Out of the 3 variables that our robot has, all 3 are
wrong. What went wrong here? We’re not sure, but debugging Elixir code is as simple as stepping through
each function call to see what’s happening. Let’s take a look at how we might do that here.
We can start by seeing if the first function of this test moves our robot correctly. Let’s change the top of this
test.exs file to this:

test.exs

1 alias ToyRobot.Robot
2
3 %Robot{east: 0, north: 0, facing: :north}
4 |> Robot.move
5 |> IO.inspect
6 ...

The great thing here about putting IO.inspect in the middle of the pipe chain is that IO.inspect takes in an
argument and will return the same argument. This means we can throw it in the middle of a pipe chain like
this and get to see the shape of our data, or in this case, the position of our robot.
When we do this and then run the script again, we’ll see two pieces of information on our screen:
Catching bugs 40

1 %ToyRobot.Robot{east: 0, facing: :north, north: 1}


2 %ToyRobot.Robot{east: 0, facing: :north, north: 1}

Both of these lines are from the two IO.inspect calls in our file. The first one comes right after the first
Robot.move call in our file, which should move the robot NORTH one space. We can see from the output from
IO.inspect that the robot has indeed moved NORTH one space, from its starting position at (0, 0) to (0, 1).
This is the correct behaviour for our robot, and so that part of our program is correct, or at least we can
assume so for the time being.
Let’s move that IO.inspect down one line, so that the top of our file now looks like this:

test.exs

1 alias ToyRobot.Robot
2
3 %Robot{east: 0, north: 0, facing: :north}
4 |> Robot.move
5 |> Robot.turn_right
6 |> IO.inspect
7 ...

Let’s run this file again and see what happens. Here’s what we’ll now see for our first IO.inspect call:

1 %ToyRobot.Robot{east: 0, facing: :east, north: 0}

Our robot should turn right to face east – which it does. The robot should still be at (0, 1) after that earlier
move function call, but it is back to (0, 0). Well, that isn’t right. We should take a look at our turn_right
function to see if we can discover why that this incorrect behaviour is happening.

lib/toy_robot/robot.ex

1 def turn_right(%Robot{facing: facing}) do


2 case facing do
3 :north -> %Robot{facing: :east}
4 :east -> %Robot{facing: :south}
5 :south -> %Robot{facing: :west}
6 :west -> %Robot{facing: :north}
7 end
8 end

Our automated Elixir doctests and ExUnit tests for this function are all working, but our manual test shows
that this function is doing the wrong thing.
If we take a closer look at this function, we will see that it takes a Robot struct as an argument, and then
returns a new Robot struct. What this code fails to do is acknowledge that the original Robot struct may have
values for its north and east keys that we wish to keep around. Defining a new Robot struct is right, but it
must be based off the original struct in order to be correct. The north and east values must be maintained.
Catching bugs 41

Now that we have identified an issue, you might think that the next step is to go right ahead and fix this
issue. That would be a sensible thing to do, after all. You’re right, it would be sensible. But what I would like
us to take the time to practice here is a process called regression testing.
Regression testing is what we use when we find a bug in our system. We write a test (or multiple tests, for
tricky bugs!) to assert that a bug does not occur under a set of given conditions. That test will fail when we
first write it, because the bug will still be happening. Then we set about fixing the bug. We know that the
bug is properly fixed when the tests are green.
The bug in this case here is that our robot does not maintain the north or east values when it is turning right.
The given conditions are around the robot having moved from (0, 0). We want the robot to remember its
place.

Regression testing the turn_right function

So before we dive in and fix this bug, let’s first write a regression test for the bug to assert that this bug never
occurs again. We’ll put this new describe block directly under the first describe block, the one for “when the
robot is facing north”:

test/toy_robot/robot_test.exs

1 describe "when the robot is facing north, and it has moved forward a space" do
2 setup do
3 {:ok, %{robot: %Robot{north: 1, facing: :north}}}
4 end
5
6 test "turns right to face east", %{robot: robot} do
7 robot = robot |> Robot.turn_right()
8 assert robot.facing == :east
9 assert robot.north == 1
10 end
11 end

In this new describe block, we set up our robot to be positioned at NORTH=1, and it is facing north. In the test
itself, we make our robot turn right through the turn_right/1 function, and then assert that its new facing
direction is :east, and that its north value has been maintained.
When we run our tests with mix test, we’ll see that it fails:

1 1) test when the robot is facing north,


2 and it has moved forward a space turns right to face east
3 (ToyRobot.RobotTest)
4
5 test/toy_robot/robot_test.exs:32
6 Assertion with == failed
7 code: assert robot.north() == 1
8 left: 0
9 right: 1
10 stacktrace:
11 test/toy_robot/robot_test.exs:35: (test)
Catching bugs 42

The point of a regression test is to first confirm that the bug is happening. The way we do that is with this
failing test. We have proved here that this bug is happening.
Now that we have proved that the bug is indeed happening, the way to fix this is straight forward. What we
need to do is to maintain the existing values from the map in the turn_right/1 function. We can do this by
using Elixir’s map piping to merge the original struct’s values and the new struct’s values together:

lib/toy_robot/robot.ex

1 def turn_right(%Robot{facing: facing} = robot) do


2 case facing do
3 :north -> %Robot{robot | facing: :east}
4 :east -> %Robot{robot | facing: :south}
5 :south -> %Robot{robot | facing: :west}
6 :west -> %Robot{robot | facing: :north}
7 end
8 end

Given that there’s now a lot of repetition in our code, we could probably simplify. Let’s try doing this and
see what we think:

lib/toy_robot/robot.ex

1 def turn_right(%Robot{facing: facing} = robot) do


2 new_facing = case facing do
3 :north -> :east
4 :east -> :south
5 :south -> :west
6 :west -> :north
7 end
8
9 %Robot{robot | facing: new_facing}
10 end

Our turn_right function still takes a Robot struct and returns one. The difference is now that it will merge the
keys from the original struct into the new one. We’ve simplified this code by making our case statement just
about determining what the new facing value should be, and then moving the Robot struct / map updating
code to the end of the function. I prefer the function written this way.
A good way to make sure we haven’t broken anything is to run our tests with mix test:

1 3 doctests, 13 tests, 0 failures

Great! We haven’t broken anything with this change to the turn_right/1 function. But is this the cleanest
code we could write? This is definitely a question we should routinely ask ourselves when we end up with
all the tests happy once again: is this the best it could be right now? We’re using the map updating, but how
about other options that would do the same thing, like Map.update/2? Let’s try it and see if we like it better.
Catching bugs 43

lib/toy_robot/robot.ex

1 def turn_right(%Robot{facing: facing} = robot) do


2 new_facing = case facing do
3 :north -> :east
4 :east -> :south
5 :south -> :west
6 :west -> :north
7 end
8
9 robot |> Map.update(facing: new_facing)
10 end

This code is slightly longer than the previous way we were doing it with the map updating. It’s less explicit
about what is being returned from this function. Is it a map? Or is it a struct? I personally like the old way
better. It gives a much clearer indication that what is being returned is a struct. Let’s swap it back now:

lib/toy_robot/robot.ex

1 def turn_right(%Robot{facing: facing} = robot) do


2 new_facing = case facing do
3 :north -> :east
4 :east -> :south
5 :south -> :west
6 :west -> :north
7 end
8
9 %Robot{robot | facing: new_facing}
10 end

Our next step is to make sure that our test.exs file is also happy now. Let’s run it with mix run test.exs.

1 %ToyRobot.Robot{east: 0, facing: :east, north: 1}

We’ll now see that the first IO.inspect call is correct. Our robot is now at (0, 1) and is facing EAST. This means
that both our move/1 and turn_right/1 functions have worked as intended. It looks like we’ve fixed one bug,
but perhaps there are more. It would be a good idea to walk through the remainder of our test.exs file so
that we can make sure there are no more bugs.
Let’s move that IO.inspect down one more line so that the top of the file now looks like this:
Catching bugs 44

test.exs

1 alias ToyRobot.Robot
2
3 %Robot{east: 0, north: 0, facing: :north}
4 |> Robot.move
5 |> Robot.turn_right
6 |> Robot.move
7 |> IO.inspect

Our robot should now move one space NORTH, turn right, and then move one space EAST. If we were to draw
this up on a piece of paper, we should have a robot that looks like this:

Robot at (1,1) facing east

We don’t have an easy way of visualizing what our robot actually looks like, but we do have a pretty simple
struct and our brains. Let’s run this script once more with mix run test.exs and see where our robot ends up:

1 %ToyRobot.Robot{east: 1, facing: :north, north: 0}

Our robot is supposed to end up at (1, 1), facing EAST, but it ends up at (1, 0), facing NORTH! Something is
yet again wrong with our code. This time, it has to do with our Robot.move/1 function. Just as we did for the
turn_right/1 function, let’s start by writing a regression test for this misbehaviour.

Regression testing the move function

This regression test should match the same conditions as our test.exs script. These conditions are: a robot
has moved forward a space and turned right. Let’s add a new describe block under the “when the robot is
facing east” block, and put this setup block inside it:
Catching bugs 45

test/toy_robot/robot_test.exs

1 describe "when the robot is facing east, and it has moved north once" do
2 setup do
3 {:ok, %{robot: %Robot{north: 1, facing: :east}}}
4 end
5 end

This is the state of our robot right before it tries to move east. When this movement happens, we’re expecting
the robot to end up at (1,1) and it should continue to face east. So let’s write a test to that effect within this
new describe block:

test/toy_robot/robot_test.exs

1 describe "when the robot is facing east, and it has moved north once" do
2 setup do
3 {:ok, %{robot: %Robot{north: 1, facing: :east}}}
4 end
5
6 test "moves east one space", %{robot: robot} do
7 robot = robot |> Robot.move()
8 assert robot.north == 1
9 assert robot.east == 1
10 assert robot.facing == :east
11 end
12 end

When we run this test with mix test, we’ll see it fail:

1 1) test when the robot is facing east, and it has moved north once moves east one space (ToyRobot.RobotTes\
2 t)
3 test/toy_robot/robot_test.exs:65
4 Assertion with == failed
5 code: assert robot.north() == 1
6 left: 0
7 right: 1
8 stacktrace:
9 test/toy_robot/robot_test.exs:67: (test)

Great! We’ve now been able to reproduce the bad behaviour. Let’s take a closer look at what this function is
doing to get to the bottom of this misbehaviour:
Catching bugs 46

lib/toy_robot/robot.ex

1 def move(%Robot{facing: facing} = robot) do


2 case facing do
3 :north -> robot |> move_north
4 :east -> robot |> move_east
5 :south -> robot |> move_south
6 :west -> robot |> move_west
7 end
8 end

Nothing in particular looks suspicious here. Our move function delegates the movement of the robot to the
other move_* functions. Given that our robot is facing east, we can determine that the function that is being
called here is the move_east function. Let’s take a look at that one:

lib/toy_robot/robot.ex

1 defp move_east(robot) do
2 %Robot{east: robot.east + 1}
3 end

We’ve now come full circle, back to where it all began: the move_east function. This function takes a robot as
an argument, and then returns a new Robot struct based off that argument, increasing the robot.east value
by 1.
Did you notice the issue here? It’s the same one that plagued us with turn_right/1! We’re returning an
entirely new struct and ignoring any other keys that may have been present on the struct. Let’s update
the code now to use map updating. While we’re here, we’ll change the argument of the function to a Robot
struct, just to be safe:

lib/toy_robot/robot.ex

1 defp move_east(%Robot{} = robot) do


2 %Robot{robot | east: robot.east + 1}
3 end

Okay, that is looking a lot better! If you look a little further down in the file, you’ll see that we’ve made the
same mistake for the other move_* functions too:
Catching bugs 47

lib/toy_robot/robot.ex

1 defp move_west(robot) do
2 %Robot{east: robot.east - 1}
3 end
4
5 defp move_north(robot) do
6 %Robot{north: robot.north + 1}
7 end
8
9 defp move_south(robot) do
10 %Robot{north: robot.north - 1}
11 end

We’ll get to fixing those in just a moment. Let’s make sure we’ve definitely fixed this one first. The first way
we can do that is by running all the ExUnit tests to validate that we haven’t broken any existing behaviour.
Let’s do that now with mix test.

1 3 doctests, 14 tests, 0 failures

Great, everything is still working according to those tests. How about our little test script? We’ll run that
now with mix run test.exs. Here’s what we’ll now see:

1 %ToyRobot.Robot{east: 1, facing: :east, north: 1}

Our robot is now correctly located at (1, 1), and it is correctly facing EAST! We have fixed the second bug
that we found in our application.
We noticed not that long ago that our other move_* functions probably suffer from the same bug as the
move_east function. We noticed this just by looking at the code for these functions:

lib/toy_robot/robot.ex

1 defp move_east(%Robot{} = robot) do


2 %Robot{robot | east: robot.east + 1}
3 end
4
5 defp move_west(robot) do
6 %Robot{east: robot.east - 1}
7 end
8
9 defp move_north(robot) do
10 %Robot{north: robot.north + 1}
11 end
12
13 defp move_south(robot) do
14 %Robot{north: robot.north - 1}
15 end
Catching bugs 48

Notice how the move_west, move_north and move_south functions all ignore the keys from the robot argument
that is passed in? This means that all of these functions will have the same problem that we just fixed in
move_east. We could very easily jump into fixing these functions. We know what the problem is and definitely
know how to fix it.
But we’re responsible developers and responsible developers write regression tests for bugs that they
encounter! So we’re going to take a little bit of time now to write three new regression tests, one for each of
move_west, move_north and move_south. The great thing about this is that we already have the test infrastructure
in place to allow us to do this relatively easily.

Regression testing the move_west function

Let’s start out with the move_west function. First, we must see if this function has the same bad behaviour that
our move_east function did. The way we can see this is to copy the regression tests we did for the move_east
function, and change them slightly so that the robot is moving west. Here’s what we’ll end up with:

test/toy_robot/robot_test.exs
1 describe "when the robot is facing west, and it has moved north once" do
2 setup do
3 {:ok, %{robot: %Robot{north: 1, facing: :west}}}
4 end
5
6 test "moves west one space", %{robot: robot} do
7 robot = robot |> Robot.move()
8 assert robot.north == 1
9 assert robot.east == -1
10 assert robot.facing == :west
11 end
12 end

This test does what it says its going to do. When the robot is facing west and it has already moved north a
space, then it should remain 1 space NORTH, but also move one space WEST and it should then still be facing
WEST.

Let’s see if this test works by running mix test:

1 1) test when the robot is facing west,


2 and it has moved north once moves west one space (ToyRobot.RobotTest)
3
4 test/toy_robot/robot_test.exs:120
5 Assertion with == failed
6 code: assert robot.north() == 1
7 left: 0
8 right: 1
9 stacktrace:
10 test/toy_robot/robot_test.exs:122: (test)

Our code does not remember that the robot was one space NORTH! We have confirmed that move_west is broken
in the same way that move_east was. Let’s remedy this function now:
Catching bugs 49

lib/toy_robot/robot.ex

1 defp move_west(%Robot{} = robot) do


2 %Robot{robot | east: robot.east - 1}
3 end

The move_west function now accepts the Robot struct, and uses the map updating syntax to merge the existing
struct’s keys, with a new value for the east key. This should fix our tests now. Let’s run mix test and find out:

1 3 doctests, 15 tests, 0 failures

Great! Everything is once again happy. We now have a regression test for the move_west function. Let’s quickly
write some tests for the move_north and move_south functions, and then we’ll continue our work on ensuring
that test.exs is working correctly.

Regression testing the move_north and move_south functions

We’ve now got move_east and move_west working correctly, and we should now have a pretty good idea of
how to get move_north and move_south working as well. Let’s jump straight in to writing a regression test for
move_north now:

test/toy_robot/robot_test.exs

1 describe "when the robot is facing north, and it has moved east once" do
2 setup do
3 {:ok, %{robot: %Robot{east: 1, facing: :north}}}
4 end
5
6 test "moves north one space", %{robot: robot} do
7 robot = robot |> Robot.move()
8 assert robot.north == 1
9 assert robot.east == 1
10 assert robot.facing == :north
11 end
12 end

With this test, we’re asserting that when the robot has moved EAST once, and is then facing NORTH, a single
move function call will move the robot to (1, 1) and leave it facing NORTH. When we run this test with mix test,
we’ll see it break:
Catching bugs 50

1 1) test when the robot is facing north, and it has moved east once moves north one space (ToyRobot.RobotTe\
2 st)
3 test/toy_robot/robot_test.exs:65
4 Assertion with == failed
5 code: assert robot.east() == 1
6 left: 0
7 right: 1
8 stacktrace:
9 test/toy_robot/robot_test.exs:68: (test)

Okay, great. With the regression test in place for moving northwards, let’s fix this move_north function to
behave correctly:

lib/toy_robot/robot.ex

1 defp move_north(%Robot{} = robot) do


2 %Robot{robot | north: robot.north + 1}
3 end

The changes that we made to this move_north function are the same sort of changes we made to the move_west
and move_east functions, and so they should be familiar.
We could assume that things are going to work after this change, but it’s a good idea to run the tests to
confirm that. Let’s run the test again to double check that things are now working:

1 3 doctests, 16 tests, 0 failures

Good! Everything is working once again.


We have one more function to write a regression test for: move_south. Let’s do that quickly now. We’re old
hands at this by now.

test/toy_robot/robot_test.exs

1 describe "when the robot is facing south, and it has moved east once" do
2 setup do
3 {:ok, %{robot: %Robot{east: 1, facing: :south}}}
4 end
5
6 test "moves south one space", %{robot: robot} do
7 robot = robot |> Robot.move()
8 assert robot.north == -1
9 assert robot.east == 1
10 assert robot.facing == :south
11 end
12 end

Running this test will show that moving south is broken:


Catching bugs 51

1 1) test when the robot is facing south,


2 and it has moved east once moves south one space (ToyRobot.RobotTest)
3
4 test/toy_robot/robot_test.exs:146
5 Assertion with == failed
6 code: assert robot.north() == 1
7 left: -1
8 right: 1
9 stacktrace:
10 test/toy_robot/robot_test.exs:148: (test)

Let’s fix up the move_south function:

lib/toy_robot/robot.ex

1 defp move_south(%Robot{} = robot) do


2 %Robot{robot | north: robot.north - 1}
3 end

To confirm our changes are correct, we’ll run the tests:

1 3 doctests, 17 tests, 0 failures

All good! We’ve now made sure that our move_* functions are now correctly maintaining the values of north,
east and facing when they operate. We’re almost done here, we just need to make sure that our manual test
file, test.exs actually operates correctly.

Jumping back to the manual test

We’ve spent a bit of time now writing regression tests for the functions, and so we might’ve forgotten what
led us here: our manual testing file, test.exs. Here’s where that file stands at the moment:

test.exs

1 alias ToyRobot.Robot
2
3 %Robot{east: 0, north: 0, facing: :north}
4 |> Robot.move
5 |> Robot.turn_right
6 |> Robot.move
7 |> IO.inspect
8 |> Robot.turn_left
9 |> Robot.move
10 |> Robot.move
11 |> Robot.turn_left
12 |> Robot.move
13 |> Robot.turn_right
14 |> Robot.turn_right
Catching bugs 52

15 |> Robot.move
16 |> Robot.move
17 |> Robot.move
18 |> IO.inspect

We were in the process of moving that top-most IO.inspect further down our file to assert that our robot
ends up at a particular location, facing a certain direction. We would expect our robot to end up here:

Robot moves to (3, 3), facing EAST

The robot should arrive at (3, 3), and it should be facing EAST. We’ve fixed a lot of buggy code in our
application so far, and maybe, just maybe, we might’ve fixed our application enough to make this test work.
So without further ado, let’s run this test.exs file and check its output:

1 %ToyRobot.Robot{east: 1, facing: :east, north: 1}


2 %ToyRobot.Robot{east: 2, facing: :east, north: 0}

The final IO.inspect in test.exs, should tell us that the robot is at (3, 3), and the robot is facing NORTH. Instead,
it tells us that the robot is at (2, 0), and its facing EAST. Something is still wrong in our code!
If we refresh our memories, we will remember that the first IO.inspect call in this code outputs the correct
value for its position: the robot is at (1, 1) and its facing EAST. So we know that everything above this line
is correct.
Let’s see what happens when we move that IO.inspect line down one line, moving it to directly after the
turn_left function:
Catching bugs 53

test.exs

1 alias ToyRobot.Robot
2
3 %Robot{east: 0, north: 0, facing: :north}
4 |> Robot.move
5 |> Robot.turn_right
6 |> Robot.move
7 |> Robot.turn_left
8 |> IO.inspect
9 ...

We can check out what the turn_left function would do to our robot with a quick run of mix run test.exs:

1 %ToyRobot.Robot{east: 0, facing: :north, north: 0}


2 %ToyRobot.Robot{east: 2, facing: :east, north: 0}

Oh dear! That doesn’t look right at all. The turn_left function call has resulted in the east and north values
for the robot being reset back to 0. That isn’t right!
While we have fixed the turn_right and move_* functions in our application, we have probably neglected to
fix the turn_left function. We wrote a regression test for the turn_right function in test/robot_test.exs. It
looks like this:

test/toy_robot/robot_test.exs

1 describe "when the robot is facing north, and it has moved forward a space" do
2 setup do
3 {:ok, %{robot: %Robot{north: 1, facing: :north}}}
4 end
5
6 test "turns right to face east", %{robot: robot} do
7 robot = robot |> Robot.turn_right()
8 assert robot.facing == :east
9 assert robot.north == 1
10 end
11 end

This seems like a sensible place to put a regression test for turn_left too, so let’s add a new test here for that
function:
Catching bugs 54

test/toy_robot/robot_test.exs

1 describe "when the robot is facing north, and it has moved forward a space" do
2 setup do
3 {:ok, %{robot: %Robot{north: 1, facing: :north}}}
4 end
5
6 ...
7
8 test "turns left to face west", %{robot: robot} do
9 robot = robot |> Robot.turn_left()
10 assert robot.facing == :west
11 assert robot.north == 1
12 end
13 end

Running this test with mix test will show that the north value is indeed reset back to 0 incorrectly:

1 1) test when the robot is facing north, and it has moved forward a space
2 turns left to face west (ToyRobot.RobotTest)
3
4 test/toy_robot/robot_test.exs:38
5 Assertion with == failed
6 code: assert robot.north() == 1
7 left: 0
8 right: 1
9 stacktrace:
10 test/toy_robot/robot_test.exs:41: (test)

Now that we have a regression test in place, let’s take a look at the turn_left function in lib/toy_-
robot/robot.ex:

lib/toy_robot/robot.ex

1 def turn_left(%Robot{facing: facing}) do


2 case facing do
3 :north -> %Robot{facing: :west}
4 :east -> %Robot{facing: :north}
5 :south -> %Robot{facing: :east}
6 :west -> %Robot{facing: :south}
7 end
8 end

This function takes a Robot struct and then returns a new Robot struct, with a facing value dependent on the
original facing value. :north becomes :west, and so on. This function neglects to keep the existing north and
east values, which is wrong.

Let’s fix this function now, taking inspiration from the turn_right function:
Catching bugs 55

lib/toy_robot/robot.ex

1 def turn_left(%Robot{facing: facing} = robot) do


2 new_facing = case facing do
3 :north -> :west
4 :east -> :north
5 :south -> :east
6 :west -> :south
7 end
8
9 %Robot{robot | facing: new_facing}
10 end

These are the same sort of changes that we performed on the turn_right function. We simplified the case
statement to just return an atom based on the original facing direction, and then we update the Robot struct
using map updating as the final line of the function.
This code looks good to me. Let’s run the test to see if it’s working:

1 3 doctests, 18 tests, 0 failures

Everything’s working once again, at least according to our tests.


Let’s revisit test.exs. We’ll run this script again with mix run test.exs. Here’s the output that we’ll see:

1 %ToyRobot.Robot{east: 1, facing: :north, north: 1}


2 %ToyRobot.Robot{east: 3, facing: :east, north: 3}

Hooray! It now appears that our robot ends up in the correct position by the end of our test: at (3, 3), and
facing EAST. All the different parts of our robot are working according to our extremely robust test suit.

Reflections

Now it’s time to bust through the 4th wall a little bit.
You might’ve been thinking: “why didn’t we just write the code correctly first?”. Simply put: because that
would be too straight forward and wouldn’t reflect the typical reality of a developer’s life. Developers writing
code perfectly first time is, largely, a myth.
The code could contain bugs, or it might be able to be refactored into a clearer form, one that is more
expressive. We might even attempt a refactoring, only to come to realise that the original way was clearer.
We did such a thing with the turn_right function early on in the chapter, comparing this code that uses
Map.update/2:
Catching bugs 56

lib/toy_robot/robot.ex

1 def turn_right(%Robot{facing: facing} = robot) do


2 new_facing = case facing do
3 :north -> :east
4 :east -> :south
5 :south -> :west
6 :west -> :north
7 end
8
9 robot |> Map.update(facing: new_facing)
10 end

To this code that uses map updating:

lib/toy_robot/robot.ex

1 def turn_right(%Robot{facing: facing} = robot) do


2 new_facing = case facing do
3 :north -> :east
4 :east -> :south
5 :south -> :west
6 :west -> :north
7 end
8
9 %Robot{robot | facing: new_facing}
10 end

It is clearer in the 2nd example that returning a Robot struct is what this method is doing. It’s also a little
shorter, so it gets points there too. My point here is that refactorings are allowed to fail. We do not have to set
out with the goal of achieving, as I said in the introduction “the ultimate 100% perfect and make people weep
with joy at the elegance of the solution”. We’re allowed to experiment and then to have those experiments
fail.
The whole point of this book up to this point has been to demonstrate how we would go about solving the
Toy Robot exercise. I think we’re doing a good job of that so far, but I’m the author so I might be a bit biased.
The “sub-point” of our work in this chapter is to show that even though we thought we had excellent test
coverage – because we tested every single public function – our code was still broken when it was all
connected together. Alongside that point, I wanted to demonstrate that we could very easily write some
regression tests for our broken code, and then set about fixing it.
The great thing about having these regression tests in place is that if the behaviour resets back to a “broken”
state, we will be able to see that brokenness if we run our tests diligently. And I hope that we do run our
tests diligently. They assert that our application works fully, after all.
I hope that what you got out of this chapter was three major things:

• There will be bugs in your code, because nobody’s perfect.


• Regression testing ensures that the bugs that are found can never happen again
• Refactoring is a good practice, and we’re allowed to fail at it
Catching bugs 57

Okay, enough soapboxing from yours truly.


We still have a couple of actions before our robot is complete. We’ve taken care of the MOVE, LEFT and RIGHT
commands for our robot, but we need to take care of the PLACE and REPORT ones too. Let’s take a look at PLACE
in the next chapter.
3. Placing the robot on a table
We’ve written functions for the MOVE, LEFT and RIGHT commands. What’s left to do is the PLACE and REPORT
commands. In this chapter, we’ll look at how we could implement a similar function for the PLACE command.
How might we do this? Well, the best place to start for this command is the same… ahem, place… that
we started at for the other commands: with the description of what the command should do. The PLACE
command’s description says this:

• PLACE will put the toy robot on the table in position X,Y and facing NORTH, SOUTH, EAST
or WEST.

However, there’s another two clues to what this command should do hidden in the description:

Any movement that would result in the robot falling from the table is prevented

And:

The application is a simulation of a toy robot moving on a square tabletop, of dimensions 5 units
x 5 units.

So an invalid PLACE command would be anything that was outside the boundaries of the “5 units by 5 units”
of the table top. For instance, if the robot was placed at (6, 7), or even (4, 5), it would be invalid because
the robot is outside the boundaries of the table. We know that the SOUTH WEST most corner of the table top is
(0, 0), from this part of our problem description:

• The origin (0,0) is the SOUTH WEST most corner.

Therefore if a table is 5 by 5 units, and we start counting from 0 from the south west corner, it would make
sense that the corner opposite – the NORTH EAST corner – would be (4, 4).
To write this place command correctly we’re going to need to know about the table boundaries. Those
boundaries shouldn’t be something that the robot knows about, because the robot is only meant to move
inside those boundaries. Instead, we should have something else that represents a table and its boundaries,
and then we should use that something else to determine if the PLACE command would be acceptable. Later
on, we’ll connect together our table and our robot modules.

The Table module

A good representation of a table would be a struct, just like we have for robots. This struct would keep a
track of the dimensions of our table. I’m imagining something like this:
Placing the robot on a table 59

1 %Table{east_boundary: 4, north_boundary: 4}

Note that we’re setting the boundaries to 4 here and not 5, because in Elixir we start counting from 0.
Remember: the south west corner is (0, 0)!
We could then have a function that took this table, and a NORTH and EAST coordinate, and then determined if
the placement was valid or not:

1 iex> Table.valid_position?(%{north: 0, east: 0})


2 true
3 iex> Table.valid_position?(%{north: 4, east: 5})
4 false

I think this would be a good position to start from. Let’s start writing up some tests for this new Table module,
and then we’ll implement this function. As we did with our Robot module’s implementation, we’ll start with
doctests. This will require some short setup under the test directory first:

test/toy_robot/table_test.exs

1 defmodule ToyRobot.TableTest do
2 use ExUnit.Case
3 doctest ToyRobot.Table
4 end

With that setup in place, we can now start writing our module’s doctests.

lib/toy_robot/table.ex

1 defmodule ToyRobot.Table do
2 defstruct [:north_boundary, :east_boundary]
3
4 alias ToyRobot.Table
5
6 @doc """
7 Determines if a position would be within the table's boundaries.
8
9 ## Examples
10
11 iex> alias ToyRobot.Table
12 ToyRobot.Table
13 iex> table = %Table{north_boundary: 4, east_boundary: 4}
14 %Table{north_boundary: 4, east_boundary: 4}
15 iex> table |> Table.valid_position?(%{north: 4, east: 4})
16 true
17 iex> table |> Table.valid_position?(%{north: 0, east: 0})
18 true
19 iex> table |> Table.valid_position?(%{north: 6, east: 0})
20 false
21 """
22 def valid_position?(
Placing the robot on a table 60

23 %Table{north_boundary: north_boundary, east_boundary: east_boundary},


24 %{north: north, east: east}
25 ) do
26 end
27 end

We’ve now got not one, but four doctests for the valid_position? function. This function takes a Table struct
and map representing a position within that table. If the position lies within the table’s boundaries, then
this function will return true, otherwise false.
These doctests test all four of the boundaries of our table, and should be enough to thoroughly make sure
this function is doing the right job. When we run these new tests with mix test, they will fail because the
function only returns nil:

1 1) doctest ToyRobot.Table.valid_position?/2 (1) (ToyRobot.TableTest)


2 test/toy_robot/table_test.exs:3
3 Doctest failed
4 code: table |> Table.valid_position?(%{north: 4, east: 4}) === true
5 left: nil
6 right: true
7 stacktrace:
8 lib/toy_robot/table.ex:9: ToyRobot.Table (module)

Okay. With these failing doctests, let’s look at how we might implement this function. We want a function
that makes sure that the north value is somewhere between 0 and the table’s north_boundary number. Similarly,
this function should do the same for the east and the east_boundary numbers.
We can check these using greater-than-or-equal-to (>=) and less-than-or-equal-to (<=):

lib/toy_robot/table.ex

1 def valid_position?(
2 %Table{north_boundary: north_boundary, east_boundary: east_boundary},
3 %{north: north, east: east}
4 ) do
5 north >= 0 && north <= north_boundary &&
6 east >= 0 && east <= east_boundary
7 end

This code will make sure that the north value is at least 0, but no more than north_boundary, and it does the
same thing for the east value with east_boundary. If all four of our comparisons in this function return true,
then the function will return true. If any one of them was to fail, then the function would return false.
When we run our tests now with mix test, they will pass:

1 4 doctests, 18 tests, 0 failures


Placing the robot on a table 61

That’s great! With this new Table.valid_position?/2 function, we will be able to tell easily if a PLACE command
is going to place the robot at a valid position on a table.
Let’s keep looking at this PLACE command’s implementation some more. What we have at the moment is
some code over in the Robot module, and some code over in the Table module, but nothing to connect these
two pieces together. We need something that ties them together.

The missing link

We’re now at the point of our codebase where we need something that will connect the Table and Robot
modules together so that we can determine if a robot’s placement is valid. This thing will also be responsible
for keeping track of the current state of the robot, and (eventually) it will receive commands from the
instructions and act on those. This thing would simulate the robot moving around on the table, and it
would make sure that the robot stays within the boundaries of the table. We’ve got to keep in mind this rule
from the original instructions:

Any movement that would result in the robot falling from the table is prevented, however further
valid movement commands are still allowed.

Our robot, given enough calls to the Robot.move/1 function, will continue to move even past the boundaries
of the table. So we will need something now to stop that from happening. Before we get there though, let’s
look at how we could use Table.valid_position/2 to place our robot on the table.

Placing a robot successfully

The missing link here is going to be designed to simulate a robot on a table, and so I think this missing link
should be called a “simulation”. A simulation will know about a table, and it will know how to place a robot
on that table.
This is going to be our first time where an action that we take with our robot could fail; the placement
may be invalid. It may be tempting to try to accommodate both the successful placement and unsuccessful
placement of our robot at the same time, but we’ve been taking a “slow and steady” approach through this
book already, so why change our pace now? We’ll start out with just considering what a successful placement
might look like.
As we’ve done for functions in the past, we’re going to write a doctest for our newest function, one called
ToyRobot.Simulation.place/2. We’ll start by creating the test/toy_robot/simulation_test.exs file:

test/toy_robot/simulation_test.exs
1 defmodule ToyRobot.SimulationTest do
2 use ExUnit.Case
3
4 doctest ToyRobot.Simulation
5 end

Remember: this file inside test will make our doctests for ToyRobot.Simulation run once we write them! These
doctests will live over in the lib directory.
Let’s now write them:
Placing the robot on a table 62

lib/toy_robot/simulation.ex

1 defmodule ToyRobot.Simulation do
2 alias ToyRobot.{Robot, Simulation, Table}
3 defstruct [:table, :robot]
4
5 @doc """
6 Simulates placing a robot on a table.
7
8 ## Examples
9
10 When the robot is placed in a valid position:
11
12 iex> alias ToyRobot.{Robot, Table, Simulation}
13 [ToyRobot.Robot, ToyRobot.Table, ToyRobot.Simulation]
14 iex> table = %Table{north_boundary: 4, east_boundary: 4}
15 %Table{north_boundary: 4, east_boundary: 4}
16 iex> Simulation.place(table, %{north: 0, east: 0, facing: :north})
17 {
18 :ok,
19 %Simulation{
20 table: table,
21 robot: %Robot{north: 0, east: 0, facing: :north}
22 }
23 }
24 """
25 def place(table, placement) do
26 end
27 end

Just so we don’t have to write out the full module names, we’ve aliased both the ToyRobot.Table and
ToyRobot.Simulation modules at the start of this doctest. Then we create a new struct for a table that is
5x5 units. In the final part of our doctest we call the Simulation.place/2 function, passing it a table and
a “placement” – the spot at where the robot will end up on the table, including its facing direction. The
doctest expects this function call to return a two-element tuple, where the first element is an :ok atom,
and the rest is the information about the state of our simulation: what the table is, and where the robot is
currently.
Because this function’s body is blank, our test will fail:

1 1) doctest ToyRobot.Simulation.place/2 (1) (ToyRobot.SimulationTest)


2 test/toy_robot/simulation_test.exs:4
3 Doctest failed
4 code: Simulation.place(table, %{north: 0, east: 0, facing: :north}) ===
5 {:ok, %{table: table, robot: %Robot{north: 0, east: 0, facing: :north}}}
6 left: nil
7 right: {:ok,
8 %Simulation{
9 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 0},
10 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
11 }}
Placing the robot on a table 63

To make this test pass, we need to return the tuple it is looking for. Let’s keep on ignoring the fact that we
have to validate the placement of the robot for the time being. Our test is only concerned with the successful
placement of the robot. Here’s the function that we’ll write:

lib/toy_robot/simulation.ex
1 def place(table, placement) do
2 {
3 :ok,
4 %__MODULE__{
5 table: table,
6 robot: struct(Robot, placement),
7 }
8 }
9 end

Here’s our tuple coming back from our function. The first element is the atom :ok, and then 2nd one here
is a new struct for the Simluation module consisting of table and robot keys. The table key keeps around the
table that was passed in. The robot key though holds a value which uses struct. struct takes the name of a
module and a map, and will build a struct from that module and map combination. So at the end of this call,
we should get a Robot struct assigned to the robot key.
Let’s run our tests now with mix test and see if it’s satisfied with what we’ve done here:

1 5 doctests, 18 tests, 0 failures

Excellent! The tests are happy with our first attempt at placing our robot on a table within our simulation.
Let’s now take a look at how we might change this function to account for an incorrect placement. To start off
with, we’ll add a new doctest to this function, showing what would happen if we did an invalid placement:

lib/toy_robot/simulation.ex
1 @doc """
2 ...
3 When the robot is placed in an invalid position:
4
5 iex> alias ToyRobot.{Table, Simulation}
6 [ToyRobot.Table, ToyRobot.Simulation]
7 iex> table = %Table{north_boundary: 4, east_boundary: 4}
8 %Table{north_boundary: 4, east_boundary: 4}
9 iex> Simulation.place(table, %{north: 6, east: 0, facing: :north})
10 {:error, :invalid_placement}
11 """

In this doctest, we place the robot one unit north of the northern boundary of the table, which would be off
the table. If placed here in the real world, gravity would take over and our toy robot would fall to the floor,
smashing into several pieces. Fortunately for us, we’re working within a simulation where we get to make
the rules, and our rule here is that if the robot is placed outside of a boundary then the function should
return {:error, :invalid_placement}.
When we run this test with mix test, we’ll see it is currently failing:
Placing the robot on a table 64

1 1) doctest ToyRobot.Simulation.place/2 (2) (ToyRobot.SimulationTest)


2 test/toy_robot/simulation_test.exs:4
3 Doctest failed
4 code: Simulation.place(
5 table,
6 %{north: 6, east: 0, facing: :north}
7 ) === {:error, :invalid_placement}
8
9 left:
10 {
11 :ok,
12 %{
13 robot: %ToyRobot.Robot{
14 east: 0, facing: :north, north: 6
15 }, table: %ToyRobot.Table{
16 east_boundary: 4, north_boundary: 4
17 }
18 }
19 }
20 right: {:error, :invalid_placement}
21 stacktrace:
22 lib/toy_robot/simulation.ex:20: ToyRobot.Simulation (module)

Our function will return the {:ok, ...} tuple for all of its invocations. Now that we have a test saying that
the function should no longer do that, let’s change the function. The function should use the Table.valid_-
position?/2 function to determine if the placement is valid or not, and then return the correct tuple. Let’s
change the function to this:

lib/toy_robot/simulation.ex

1 def place(table, placement) do


2 if table |> Table.valid_position?(placement) do
3 {
4 :ok,
5 %Simulation{
6 table: table,
7 robot: struct(Robot, placement),
8 }
9 }
10 else
11 {:error, :invalid_placement}
12 end
13 end

With this new code, we’re checking the outcome of Table.valid_position?/2 before we decide which tuple
to return. If the position of the placement is valid, then we return the {:ok, ...} tuple. If it’s invalid then
we return {:error, :invalid_placement}. The reason behind doing this is that so later on we could use a case
statement to determine what to do in the event of a successful or unsuccessful placement:
Placing the robot on a table 65

1 case Simulation.place(table, placement) do


2 {:ok, simulation} ->
3 # Do something with simulation here
4 {:error, :invalid_placement} ->
5 IO.puts "That placement was invalid."
6 end

Let’s run our tests to see if these changes have worked:

1 6 doctests, 18 tests, 0 failures

Everything is working correctly! Our robot can now be placed upon a table according to the rules of our
simulation. We now have matching functions for the MOVE, LEFT, RIGHT and PLACE commands for our robot.
The only command that is now missing is REPORT. We’ll get around to that very soon.
I think we’ve built enough of the functionality of our toy robot that we could move on to the “reading
commands” part of the instructions. Let’s look at how to do that in the next chapter.
4. Building our simulation
Our Toy Robot instructions contain these words that we have ignored for the entire duration of this book:

The application reads a file using a name passed in the command line …

There is no code in our application yet that reads a file. All we have are some functions that can place, move
and turn a robot. In this chapter, we could look at how we could make our application “read a file using a
name passed in the command line”. However, rather than starting at the command line itself, in this chapter
we’ll build up to that from the existing foundation that we already have. In the next chapter (Chapter 5),
we’ll look at how to process commands. Then in the chapter after that (Chapter 6), we’ll look at how to read
commands from files.
In the last chapter, we just finished building the Simulation.place/2 function. This function will be used by
our code when we process a PLACE X,Y,F instruction. The module itself will be used as a central point for
interacting with our Robot and Table functions.
In this Simulation module, we do not have any functions for handling things outside the placement of a robot.
We do not have any functions that would be used for MOVE, LEFT or RIGHT inputs.
We should be able to write some code that would handle the placement of our robot, and then some parsing
of instructions. I would imagine the code would call these Simulation functions like this:

1 alias ToyRobot.{Table, Simulation}


2
3 table = %Table{north_boundary: 4, east_boundary: 4}
4 placement = Simulation.place(table, %{north: 0, east: 0, facing: :north})
5
6 case placement do
7 {:ok, simulation} ->
8 simulation
9 |> Simulation.move
10 |> Simulation.turn_right
11 |> Simulation.move
12 |> Simulation.move
13 |> Simulation.turn_left
14 |> IO.inspect
15 {:error, :invalid_placement} ->
16 IO.puts "Invalid placement location for robot."
17 end

This code is just spitballing and our final code may not match this implementation.
This code builds our table and attempts to place a robot at (0, 0), facing NORTH. We then check the result of
the placement by using a case statement. In the event of a successful placement, we want to perform our
actions within our simulation, and then see the result with an IO.inspect. We would expect to get this back
as that result:
Building our simulation 67

1 %{
2 robot: %Robot{north: 1, east: 2, facing: :north},
3 table: %Table{north_boundary: 4, east_boundary: 4}
4 }

This output shows that our robot has moved one space NORTH, two spaces EAST, but is still facing NORTH. This
output signifies the current state of our simulation, and we could continue to pipe it to functions within
Simulation to continue to move or turn our robot.

This is all imaginary for the time being. It’s time we made the imaginary real, by adding in some functions
to our Simulation module to move and turn the robot.

Moving a robot, within a simulation

We already have a function to move a robot, called Robot.move/1, which takes a Robot struct and updates
its position. What we need now is a new function called Simulation.move/1 which moves a robot within a
simulation.
You might be thinking: “why do we need another function for moving the robot? We already have one!”. The
reason why we need another one for the Simulation module is because the Simulation module’s functions will
all work with both tables and robots, whereas the Robot module’s functions works just with robots.
Okay, that’s still not clear. Let me try some more explaining: we’re interacting with both the Table and Robot
module within the Simulation module so that we can ensure that a valid placement is made. The thing is
that we will also need to ensure moves are valid, according to this line from our instructions:

Any movement that would result in the robot falling from the table is prevented

The Robot module’s functions do not know about any table, and so they cannot check to see if a robot could
or would fall off a table! We only have that information available in the Simulation functions so far.
So with all that in mind, let’s look first at how we could handle any MOVE command we might get, and then
later on we’ll look at how to validate those MOVE commands to make sure our robot cannot fall off the edges
of the table into the abyss.

Handling a move

To handle the move command, our simulation must know how to move the robot; it must have a function
that moves the robot. Let’s start out here by adding a move function to the Simulation module along with
some doctests:
Building our simulation 68

lib/toy_robot/simulation.ex

1 @doc """
2 Moves the robot forward one space in the direction that it is facing, unless that position is past the bou\
3 ndaries of the table.
4
5 ## Examples
6
7 ### A valid movement
8
9 iex> alias ToyRobot.{Robot, Table, Simulation}
10 [ToyRobot.Robot, ToyRobot.Table, ToyRobot.Simulation]
11 iex> table = %Table{north_boundary: 4, east_boundary: 4}
12 %Table{north_boundary: 4, east_boundary: 4}
13 iex> simulation = %Simulation{
14 ...> table: table,
15 ...> robot: %Robot{north: 0, east: 0, facing: :north}
16 ...> }
17 iex> simulation |> Simulation.move
18 {:ok, %Simulation{
19 table: table,
20 robot: %Robot{north: 1, east: 0, facing: :north}
21 }}
22 """
23 def move(simulation) do
24 {:ok, simulation}
25 end

This doctest checks that when we call this new move function that our robot does indeed move forward a
single space. The end of the doctest uses some pattern matching to make sure of that.
When we run our tests with mix test, they will fail because we’re only returning the simulation that we’re
being passed:

1 1) doctest ToyRobot.Simulation.move/1 (1) (ToyRobot.SimulationTest)


2 test/toy_robot/simulation_test.exs:4
3 Doctest failed
4 code: simulation = %{table: table, robot: %Robot{north: 0, east: 0, facing: :north}}
5 simulation |> Simulation.move() === %Simulation{
6 table: table,
7 robot: %Robot{north: 1, east: 0, facing: :north}
8 }
9 left: {:ok, %ToyRobot.Simulation{
10 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 0},
11 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
12 }
13 right: {:ok, %ToyRobot.Simulation{
14 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 1},
15 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
16 }}
17 stacktrace:
18 lib/toy_robot/simulation.ex:52: ToyRobot.Simulation (module)
Building our simulation 69

To fix this failing test, our move function will need to actually move the robot. Or at least, simulate such a
thing. We can do this quite easily with this code:

lib/toy_robot/simulation.ex

1 def move(%{robot: robot} = simulation) do


2 {:ok, %{simulation | robot: robot |> Robot.move}}
3 end

This code takes our existing simulation that is passed in, and updates its robot key using the singular-pipe
operator (|). The value assigned to that key is the result of piping the existing value of robot to Robot.move/1
and letting that function decide how a robot is to be moved.
With this small change to the move function, our tests will now be passing:

1 7 doctests, 18 tests, 0 failures

Handling the case of moving our robot, no matter its position, was really straight-forward. Let’s now take
a look at how we might handle the case of a robot’s move being invalid – where the robot is facing an edge
and would attempt to fall into the abyss, if not for our code.

Handling an invalid move

According to the instructions for our exercise:

Any movement that would result in the robot falling from the table is prevented

A great place to prevent that movement that “would result in the robot falling from the table” is this new
Simulation.move/1 function that we have just added.

To start off with, we’ll add another doctest block to the move function:

lib/toy_robot/simulation.ex

1 @doc """
2 ...
3
4 ### An invalid movement:
5
6 iex> alias ToyRobot.{Robot, Table, Simulation}
7 [ToyRobot.Robot, ToyRobot.Table, ToyRobot.Simulation]
8 iex> table = %Table{north_boundary: 4, east_boundary: 4}
9 %Table{north_boundary: 4, east_boundary: 4}
10 iex> simulation = %Simulation{
11 ...> table: table,
12 ...> robot: %Robot{north: 4, east: 0, facing: :north}
13 ...> }
14 iex> simulation |> Simulation.move()
15 {:error, :at_table_boundary}
16 """
Building our simulation 70

This doctest block creates a simulation where the robot is on the north-western most corner of the board,
facing north. If the robot was to move once more, it would fall off the end of the table. So the simulation
should return a tuple of {:error, :at_table_boundary} to indicate the movement would fail.
When we run this test, it will fail:

1 1) doctest ToyRobot.Simulation.move/1 (2) (ToyRobot.SimulationTest)


2 test/toy_robot/simulation_test.exs:4
3 Doctest failed
4 code: simulation = %Simulation{
5 table: table,
6 robot: %Robot{north: 4, east: 0, facing: :north}
7 }
8 simulation |> Simulation.move() === {:error, :at_table_boundary}
9 left: {
10 :ok,
11 %ToyRobot.Simulation{
12 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 5}, table: %ToyRobot.Table{east_boundary: 4,\
13 north_boundary: 4}
14 }
15 }
16 right: {:error, :at_table_boundary}
17 stacktrace:
18 lib/toy_robot/simulation.ex:72: ToyRobot.Simulation (module)s

This failure is because we’re not yet accounting for the case where our robot might fall over the edge. To
check that, we’re going to need to make our move function a little more complicated, checking the robot’s
next position by using a combination of the Table.valid_position/2 and Robot.move/1 functions.

lib/toy_robot/simulation.ex
1 def move(%Simulation{robot: robot, table: table} = simulation) do
2 with moved_robot <- robot |> Robot.move,
3 true <- table |> Table.valid_position?(moved_robot)
4 do
5 {:ok, %{simulation | robot: moved_robot}}
6 else
7 false -> {:error, :at_table_boundary}
8 end
9 end

To start off with, we’ve changed the function head to extract both the robot and the table from the simulation.
This is because we will now need both of these values for this function.
Inside the function’s body, we use the with macro from Elixir to first move our robot, and then to check if that
movement was valid. If that Table/valid_position?/1 line returns true, then we return an ok-tuple containing
an updated simulation with the robot’s new position.
If that check returns false however, we report back that we’re at the boundary of the table with an error-
tuple.
That sounds like what our doctest might be expecting. Let’s check if this code is correct with another run of
mix test:
Building our simulation 71

1 8 doctests, 18 tests, 0 failures

Excellent! Everything is working as we expect it to. Our simulation can now have a robot move inside of it.
The major difference between Simulation.move/1 and Robot.move/1 is that Simulation.move/1 takes into account
where the robot is on the table, and will not move the robot past the defined boundaries of the table.
So in our simulation we can now obey the PLACE and MOVE commands. There are two more commands that
we would need to follow in our simulation: LEFT and RIGHT. We do not have a function for either of these two
commands yet, but we will need them if we are to complete the implementation of our simulation.

Turning a robot, within a simulation

Turning a robot within a simulation should be a cinch. After all, we already have the Robot.turn_left/1 and
Robot.turn_right/1 functions, and we do not need to check to see if turning the robot would make it fall off
the edge of the table by calling Table.valid_position?/2.
Let’s go ahead and write some doctests for our Simulation.turn_left/1 function:

lib/toy_robot/simulation.ex

1 @doc """
2 Turns the robot left.
3
4 ## Examples
5
6 iex> alias ToyRobot.{Robot, Table, Simulation}
7 [ToyRobot.Robot, ToyRobot.Table, ToyRobot.Simulation]
8 iex> table = %Table{north_boundary: 4, east_boundary: 4}
9 %Table{north_boundary: 4, east_boundary: 4}
10 iex> simulation = %Simulation{
11 ...> table: table,
12 ...> robot: %Robot{north: 0, east: 0, facing: :north}
13 ...> }
14 iex> simulation |> Simulation.turn_left
15 {:ok, %Simulation{
16 table: table,
17 robot: %Robot{north: 0, east: 0, facing: :west}
18 }}
19 """
20 def turn_left(%Simulation{} = simulation) do
21 simulation
22 end

This doctest takes our simulation and pipes it through to Simulation.turn_left/1, checking that when the
simulation turns the robot left that the robot is then left facing WEST.
When we run this test with mix test, we’ll see it failing:
Building our simulation 72

1 1) doctest ToyRobot.Simulation.turn_left/1 (5) (ToyRobot.SimulationTest)


2 test/toy_robot/simulation_test.exs:4
3 Doctest failed
4 code: simulation = %Simulation{
5 table: table,
6 robot: %Robot{north: 0, east: 0, facing: :north}
7 }
8 simulation |> Simulation.turn_left === {:ok, %Simulation{
9 table: table,
10 robot: %Robot{north: 0, east: 0, facing: :west}
11 }}
12 left: %ToyRobot.Simulation{
13 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 0},
14 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
15 }
16 right: {:ok,
17 %ToyRobot.Simulation{
18 robot: %ToyRobot.Robot{east: 0, facing: :west, north: 0},
19 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
20 }}
21 stacktrace:
22 lib/toy_robot/simulation.ex:100: ToyRobot.Simulation (module)

We’re not turning our robot left within this new function, we’re simply returning the simulation, just as we
did with our initial implementation of the Simulation.move/1 function.
Let’s now change this function to turn the robot left:

lib/toy_robot/simulation.ex

1 def turn_left(%Simulation{robot: robot} = simulation) do


2 {:ok, %{simulation | robot: robot |> Robot.turn_left}}
3 end

This function now extracts the robot from the simulation with some pattern matching. It then takes that
robot and pipes it to Robot.turn_left to turn the robot left of the direction it was facing previously. The
updated robot is then assigned to the robot key in our simulation’s struct.
Let’s run our tests again with mix test and see if they work:

1 9 doctests, 18 tests, 0 failures

Yes, they work now! This is definitely progress. With Simulation.turn_left/1 done, it will be just as easy to
write a function for turning right. Let’s go ahead and quickly do that now. We can even copy and paste the
code from turn_left/1 and tweak a few things:
Building our simulation 73

lib/toy_robot/simulation.ex
1 @doc """
2 Turns the robot right.
3
4 ## Examples
5
6 iex> alias ToyRobot.{Robot, Table, Simulation}
7 [ToyRobot.Robot, ToyRobot.Table, ToyRobot.Simulation]
8 iex> table = %Table{north_boundary: 4, east_boundary: 4}
9 %Table{north_boundary: 4, east_boundary: 4}
10 iex> simulation = %Simulation{
11 ...> table: table,
12 ...> robot: %Robot{north: 0, east: 0, facing: :north}
13 ...> }
14 iex> simulation |> Simulation.turn_right
15 {:ok, %Simulation{
16 table: table,
17 robot: %Robot{north: 0, east: 0, facing: :east}
18 }}
19 """
20 def turn_right(%Simulation{robot: robot} = simulation) do
21 {:ok, %{simulation | robot: robot |> Robot.turn_right}}
22 end

There are only 5 things we need to change here:

1. The first line of the documentation: “Turns the robot right.”


2. Call turn_right instead of turn_left in the doctest
3. The new facing direction of the robot will be :east, not :west
4. The name of the function must be changed from turn_left to turn_right
5. The function body calls Robot.turn_right, not Robot.turn_left.

As long as we’ve copied and pasted from turn_left and then made these 5 changes, then we can be pretty
confident that our tests are going to pass. Let’s look and see:

1 10 doctests, 18 tests, 0 failures

Great! We now have both of our turning functions implemented: turn_left/1 and turn_right/1. We also have
the place/2 and move/1 functions implemented. I would say that is a good amount of code for simulating how
the placement, moving and turning of our robot on a tabletop should happen.
We have one command left to handle with our simulation: report.

Reporting the robot’s position

Our simulation should be able to also report the robot’s current position. This will be helpful for when we
get to handling the REPORT command later on with our robot.
Let’s start out by writing a doctest for this:
Building our simulation 74

lib/toy_robot/simulation.ex

1 @doc """
2 Returns the robot's current position.
3
4 ## Examples
5
6 iex> alias ToyRobot.{Robot, Table, Simulation}
7 [ToyRobot.Robot, ToyRobot.Table, ToyRobot.Simulation]
8 iex> table = %Table{north_boundary: 4, east_boundary: 4}
9 %Table{north_boundary: 4, east_boundary: 4}
10 iex> simulation = %Simulation{
11 ...> table: table,
12 ...> robot: %Robot{north: 0, east: 0, facing: :north}
13 ...> }
14 iex> simulation |> Simulation.report
15 %Robot{north: 0, east: 0, facing: :north}
16 """
17 def report(simulation), do: simulation

This doctest asserts that when we call Simulation.report/1 on a simulation that we get back the current
position of the robot. Sounds easy!
When we run the test, we’ll see that we’re getting back all the information for the simulation:

1 1) doctest ToyRobot.Simulation.report/1 (5) (ToyRobot.SimulationTest)


2 test/toy_robot/simulation_test.exs:4
3 Doctest failed
4 code: simulation = %Simulation{
5 table: table,
6 robot: %Robot{north: 0, east: 0, facing: :north}
7 }
8 simulation |> Simulation.report === %Robot{north: 0, east: 0, facing: :north}
9 left: %ToyRobot.Simulation{
10 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 0},
11 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
12 }
13 right: %ToyRobot.Robot{east: 0, facing: :north, north: 0}
14 stacktrace:
15 lib/toy_robot/simulation.ex:146: ToyRobot.Simulation (module)

Let’s change that definition of the report function just to get out the details of the robot:

lib/toy_robot/simulation.ex

1 def report(%Simulation{robot: robot}), do: robot

Re-running the test now will show it passing:


Building our simulation 75

1 11 doctests, 18 tests, 0 failures

We’ve now connected together the Table and Robot modules with the Simulation module. We can now move
and turn our robot within a simulation, and we can also report on its location. When we issue our robot any
command we will now do this through the simulation itself.
In the next chapter, we’ll build some new parts on top of this Simulation module. We’ll start looking into
how we could read commands in from a file.
5. Reading and handling commands
It is finally time to implement the part of our application that fulfils this line from our instructions:

The application reads a file using a name passed in the command line …

It is finally time to read instructions from a file using a name passed in the command line. Whatever this
code does, it will need to use our new Simulation module and its functions in order to actually move our
robot around the table. This new bit of code needs to take commands like:

1 PLACE 1,2,NORTH
2 MOVE
3 LEFT
4 MOVE
5 RIGHT
6 MOVE
7 REPORT

And turn them into calls to Simulation functions that would perform operations that go something like this:

1 %Table{north_boundary: 4, east_boundary: 4}
2 |> Simulation.place(%{north: 1, east: 2, facing: :north})
3 |> Simulation.move
4 |> Simulation.turn_left
5 |> Simulation.move
6 |> Simulation.turn_right
7 |> Simulation.move
8 |> Simulation.report

Of course, this might be a little difficult as what our functions output and what they accept as input are
slightly different. For instance, the Simulation.place/2 function takes a table and a placement, but outputs a
tuple like: {:ok, %Simulation{...}}. The Simulation.move/1 function then takes a Simulation struct that is not
inside a tuple. These are good things to be aware of now, but they aren’t a serious enough problem to worry
about now. I’m sure we’ll work that problem out when we get to it.
What we need to focus on now is taking that list of input commands and then interpreting these
commands into a format that Elixir could understand. This will be the responsibility of a module called
CommandInterpreter.

The command interpreter will take commands and interpret them; turn them into things that Elixir can easily
pattern match on. Our above input of commands would turn into this list:
Reading and handling commands 77

1 [
2 %{:place, north: 1, east: 2, facing: :north}
3 :move,
4 :turn_left,
5 :move,
6 :turn_right,
7 :move,
8 :report,
9 ]

After we build the CommandInterpreter module, we’ll have a CommandRunner module. This module will be
responsible for taking the list of interpreted commands from CommandInterpreter, and then using the
Simulation functions to place, move and turn our robot. It will also handle situations like what to do in
the case of an invalid MOVE command, or when we ask it to REPORT.
We’ll be keeping the logic for interpreting and running separate for a good reason. These two responsibilities
of interpreting and running the commands are linked, but they are separate concerns. Keeping them
separate like this in two separate modules will lead to code that is easier to work with than if we were
to combine them together into the one file. You’ll see what I mean as we progress through this chapter!
So those are the two modules we’ll be building in this chapter on top of the existing code we have already.
This chapter is the biggest of the book and with good reason. Reading the commands is by far the hardest
part of the Toy Robot exercise!
Let’s start out with the first of these modules, the CommandInterpreter module.

The Command Interpreter

The command interpreter needs to take a file’s contents and to turn it into Elixir values so that we can then
give these to a command handler and have it handle the commands, calling the simulation’s functions to
execute the commands.
Let’s start out by assuming that we have a text file with this list of commands in it:

commands.txt

1 PLACE 1,2,NORTH
2 MOVE
3 LEFT
4 MOVE
5 RIGHT
6 MOVE
7 REPORT

You should save this input as commands.txt in your toy_robot application’s root directory.
We need to read this file with Elixir code to turn its contents into Elixir data types. Because we want to
interpret each line of this file, we should use File.stream!/1 here, as it will give us back a list:
Reading and handling commands 78

1 iex> commands = File.stream!("commands.txt") |> Enum.to_list


2 ["PLACE 1,2,NORTH\n", "MOVE\n", "LEFT\n", "MOVE\n", "RIGHT\n", "MOVE\n", "REPORT\n"]

This list is a good start, but in order to properly interpret the commands, it would be really helpful to not
have those linebreaks which are extraneous. Let’s pipe this once more to strip out those line breaks:

1 iex> commands = File.stream!("commands.txt") |> Enum.map(&String.trim/1) |> Enum.to_list


2 ["PLACE 1,2,NORTH", "MOVE", "LEFT", "MOVE", "RIGHT", "MOVE", "REPORT"]

That’s better. Now we have a more pure version of our list of commands. Each command is represented as
an element in this list. This is the data type that our command interpreter will be built to handle.
Let’s ignore the part about reading from a file to start with, and assume that we already have a list of these
commands. Let’s create a new module called ToyRobot.CommandInterpreter, and in that module write a function
that will handle this list as an input. We’ll write doctests to start off with too, just as we’ve done for every
other function so far:

lib/toy_robot/command_interpreter.ex

1 defmodule ToyRobot.CommandInterpreter do
2 @doc """
3 Interprets commands from a commands list, in preparation for running them.
4
5 ## Examples
6
7 iex> alias ToyRobot.CommandInterpreter
8 ToyRobot.CommandInterpreter
9 iex> commands = ["PLACE 1,2,NORTH", "MOVE", "LEFT", "RIGHT", "REPORT"]
10 ["PLACE 1,2,NORTH", "MOVE", "LEFT", "RIGHT", "REPORT"]
11 iex> commands |> CommandInterpreter.interpret()
12 [
13 {:place, %{north: 2, east: 1, facing: :north}},
14 :move,
15 :turn_left,
16 :turn_right,
17 :report,
18 ]
19 """
20 def interpret(commands) do
21 end
22 end

Because we’ve created yet another new module, we’ll also need to create a new test file for it so that the
doctests will run:
Reading and handling commands 79

test/toy_robot/command_interpreter_test.exs

1 defmodule ToyRobot.CommandInterpreterTest do
2 use ExUnit.Case, async: true
3 doctest ToyRobot.CommandInterpreter
4 end

This doctest asserts that when we call CommandInterpreter.interpret/1, it turns the list of input commands
into a list of things that we could pattern match on. The PLACE command gets converted into a tuple because
it is a useful way of grouping together related things. The MOVE, LEFT and RIGHT do not need this tupleization
because they’re relatively simple. Converting these to atoms is fine enough for our purposes.
Running the test now will show it is currently failing:

1 1) doctest ToyRobot.CommandInterpreter.interpret/1 (1) (ToyRobot.CommandInterpreterTest)


2 test/toy_robot/command_interpreter_test.exs:3
3 Doctest failed
4 code: commands |> CommandInterpreter.interpret() === [
5 {:place, %{north: 1, east: 2, facing: :north}},
6 :move,
7 :turn_left,
8 :turn_right,
9 :report,
10 ]
11 left: nil
12 right: [
13 {:place, %{east: 2, facing: :north, north: 1}},
14 :move,
15 :turn_left,
16 :turn_right,
17 :report,
18 ]
19 stacktrace:
20 lib/toy_robot/command_interpreter.ex:7: ToyRobot.CommandInterpreter (module)

This is a great place to start. We have a list, and we want to perform a particular action to each element in
this list, and return a list. This sounds like a perfect job for Enum.map/2:

lib/toy_robot/command_interpreter.ex

1 def interpret(commands) do
2 commands |> Enum.map(&do_interpret/1)
3 end

We take all these commands and pipe them to Enum.map, where it will call a function called do_interpret for
each of the items in the original list. This do_interpret function will need to act differently depending on the
input it receives, returning different results for each. This is where pattern matching will come in handy.
We can pattern match on the first word of a string like this:
Reading and handling commands 80

lib/toy_robot/command_interpreter.ex
1 defp do_interpret("PLACE " <> rest) do
2 end

This do_interpret function will handle our PLACE command. It does that by matching any command that has
the string "PLACE " at the start. This function needs to return a tuple in the shape of {:place, %{north: 1,
east: 2, facing: :north}}, so let’s start writing some code inside this function:

lib/toy_robot/command_interpreter.ex
1 defp do_interpret("PLACE " <> rest) do
2 [east, north, facing] = rest |> String.split(",")
3 to_int = &String.to_integer/1
4
5 {:place, %{
6 east: to_int.(east),
7 north: to_int.(north),
8 facing: facing |> String.downcase |> String.to_atom
9 }}
10 end

The first line here splits apart our string, picking out the east, north and facing parts. The second line captures
the String.to_integer/1 function so we can use it later with the to_int.() shorthand in the tuple definition.
We build the tuple to be the expected shape.
When we run our tests again, this is what we’ll see:

1 1) doctest ToyRobot.CommandInterpreter.interpret/1 (1) (ToyRobot.CommandInterpreterTest)


2 test/toy_robot/command_interpreter_test.exs:3
3 Doctest failed: got FunctionClauseError with message
4 "no function clause matching in ToyRobot.CommandInterpreter.do_interpret/1"
5 code: alias ToyRobot.{CommandInterpreter}
6 commands = ["PLACE 1,2,NORTH", "MOVE", "LEFT", "RIGHT"]
7 commands |> CommandInterpreter.interpret()
8 stacktrace:
9 (toy_robot) lib/toy_robot/command_interpreter.ex:23: ToyRobot.CommandInterpreter.do_interpret("MOVE")
10 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
11 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
12 (for doctest at) lib/toy_robot/command_interpreter.ex:7: (test)

This test failure is telling us that there is “no function clause matching” for the do_interpret function, but
it doesn’t tell us what it can’t match up the top. Instead, we need to look into the stacktrace to see it:

1 (toy_robot) lib/toy_robot/command_interpreter.ex:23: ToyRobot.CommandInterpreter.do_interpret("MOVE")

That is not particularly helpful.


This is one case where our doctests are going to let us down. When pattern matching failures like this
happen, doctests do not report good errors. What we can do to work around this is to duplicate this test,
putting a new test in the CommandInterpreter test file:
Reading and handling commands 81

test/toy_robot/command_interpreter_test.exs

1 defmodule ToyRobot.CommandInterpreterTest do
2 use ExUnit.Case, async: true
3 doctest ToyRobot.CommandInterpreter
4
5 alias ToyRobot.CommandInterpreter
6
7 test "handles all commands" do
8 commands = ["PLACE 1,2,NORTH", "MOVE", "LEFT", "RIGHT", "REPORT"]
9 commands |> CommandInterpreter.interpret()
10 end
11 end

This test is the same as what we have in our doctest, just transplanted to the ExUnit test file. When we run
mix test, we’ll see two failures now. We should just focus on the one from this newest test:

1 2) test handles all commands (ToyRobot.CommandInterpreterTest)


2 test/toy_robot/command_interpreter_test.exs:7
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandInterpreter.do_interpret/1
4
5 The following arguments were given to ToyRobot.CommandInterpreter.do_interpret/1:
6
7 # 1
8 "MOVE"
9
10 Attempted function clauses (showing 1 out of 1):
11
12 defp do_interpret(<<"PLACE "::binary(), rest::binary()>>)
13
14 code: commands |> CommandInterpreter.interpret()
15 stacktrace:
16 (toy_robot) lib/toy_robot/command_interpreter.ex:23: ToyRobot.CommandInterpreter.do_interpret/1
17 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
18 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
19 test/toy_robot/command_interpreter_test.exs:9: (test)

This output shows that the do_interpret function received a call with one argument, the string "MOVE", and
it wasn’t able to handle that. This is because we haven’t added a version of do_interpret that would handle
"MOVE". Let’s do that now:

lib/toy_robot/command_interpreter.ex

1 defp do_interpret("MOVE"), do: :move

The implementation of this function is very straightforward. We take the string "MOVE" and turn it into the
atom, :move. You might now be anticipating we’ll need the same style of do_interpret functions for the "LEFT",
"RIGHT" and "REPORT" commands. A test run will confirm that:
Reading and handling commands 82

1 1) test handles all commands (ToyRobot.CommandInterpreterTest)


2 test/toy_robot/command_interpreter_test.exs:7
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandInterpreter.do_interpret/1
4
5 The following arguments were given to ToyRobot.CommandInterpreter.do_interpret/1:
6
7 # 1
8 "LEFT"
9
10 Attempted function clauses (showing 2 out of 2):
11
12 defp do_interpret(<<"PLACE "::binary(), rest::binary()>>)
13 defp do_interpret("MOVE")
14
15 code: commands |> CommandInterpreter.interpret()
16 stacktrace:
17 (toy_robot) lib/toy_robot/command_interpreter.ex:23: ToyRobot.CommandInterpreter.do_interpret/1
18 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
19 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
20 test/toy_robot/command_interpreter_test.exs:9: (test)

We’ll add these below our do_interpret function definition for "MOVE":

lib/toy_robot/command_interpreter.ex

1 defp do_interpret("LEFT"), do: :turn_left


2 defp do_interpret("RIGHT"), do: :turn_right
3 defp do_interpret("REPORT"), do: :report

And now when we run our tests, they will all pass:

1 12 doctests, 19 tests, 0 failures

You might be wondering if we should keep around the doctest and if it makes sense to have this duplication
of tests. I think we should. The doctest is not just a test, it’s documentation about how the function should be
used. The test part of doctest didn’t give us helpful error messages, so we had to do a little bit of adaptation.
I think if we were to write any more tests for CommandInterpreter.interpret/1, we would probably do them in
command_interpreter_test.exs just so that we get useful error messages.

Handling invalid commands

Before we carry on, we should probably think about what this CommandInterpreter.interpret/1 function and
its do_interpret/1 friends should do if they encounter an invalid command. An invalid command would be
one that does not match the pattern of any of our current do_interpret functions. For instance, any of the
following commands are invalid:

• SPIN
Reading and handling commands 83

• TWIRL
• EXTERMINATE

If our CommandInterpreter.interpret/1 function was to receive a list containing these, it should not crash but
mark these commands as invalid.
Let’s make sure of that by writing a new test in test/toy_robot/command_interpreter_test.exs. Remember:
We’re going to write a test here in this file because it’ll give us better error messages than a doctest:

test/toy_robot/command_interpreter_test.exs

1 test "marks invalid commands as invalid" do


2 commands = ["SPIN", "TWIRL", "EXTERMINATE"]
3 output = commands |> CommandInterpreter.interpret()
4 assert output == [
5 {:invalid, "SPIN"},
6 {:invalid, "TWIRL"},
7 {:invalid, "EXTERMINATE"}
8 ]
9 end

This test asserts that we get back a tuple that marks the command as invalid. Later on, we can handle this
tuple to show the user an error message if the command is invalid. For now, we’ll just flag it as invalid.
When we run this new test, we’ll see this failure right away:

1 1) test marks invalid commands as invalid (ToyRobot.CommandInterpreterTest)


2 test/toy_robot/command_interpreter_test.exs:12
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandInterpreter.do_interpret/1
4
5 The following arguments were given to ToyRobot.CommandInterpreter.do_interpret/1:
6
7 # 1
8 "SPIN"
9
10 Attempted function clauses (showing 5 out of 5):
11
12 defp do_interpret(<<"PLACE "::binary(), rest::binary()>>)
13 defp do_interpret("MOVE")
14 defp do_interpret("LEFT")
15 defp do_interpret("RIGHT")
16 defp do_interpret("REPORT")
17
18 code: output = commands |> CommandInterpreter.interpret()

This test fails because none of the do_interpret functions match the invalid commands. And with good
reason: they’re all invalid! Let’s add a new do_interpret definition to CommandInterpreter which captures
invalid commands:
Reading and handling commands 84

lib/toy_robot/command_interpreter.ex

1 defp do_interpret("MOVE"), do: :move


2 defp do_interpret("LEFT"), do: :turn_left
3 defp do_interpret("RIGHT"), do: :turn_right
4 defp do_interpret("REPORT"), do: :report
5 defp do_interpret(command), do: {:invalid, command}

This new function will be last in the list so that it is matched last. Our code will attempt to match all the
do_interpret functions above it, and fail. Then this catch-all function at the end will run, take any unknown
command and return that {:invalid, ...} tuple we’re expecting in our most recent tests.
Running our tests again will that this small addition works:

1 12 doctests, 20 tests, 0 failures

We’re now successfully handling all valid and invalid commands!


That now completes the part of our Toy Robot implementation that would read a list of commands and
transmute them into things Elixir could understand. It is only one half of this part of the puzzle. The other
half is taking these Elixir things and actually doing something with them, which is what we’re going to now
build with our command runner.

The Command Runner

The CommandRunner module will be responsible for taking the output from CommandInterpreter and then
executing the commands using the functions from the Simulation module. The way this code will work is
like this:

1 File.stream!("commands.txt")
2 |> Enum.map(&String.trim/1)
3 |> CommandInterpreter.interpret
4 |> CommandRunner.run

This section is where things might get a little tricky. We’re going to use recursive functions in this section.
Recursive functions are ones that call themselves. We’re going to need them here so that we can work
through the list of commands in the best fashion possible.
This is one of those places where an image would certainly come in handy. So here’s an image of a flowchart
that I whipped up to show what we’re about to do:
Reading and handling commands 85

Recursive functions - The big picture

We’re going to start out with a CommandRunner.run/1 function, that will take a list of our commands and then
run them by using ur Simulation functions. This function will first see if the first command in that list is a
:place command. If it is, it will attempt that placement by using Simulation.place/2, which we designed to
handle such a command in the last section.
If we start by focussing on just that part of the diagram first, then we’ll be able to start off simply. So rather
than trying to think about the whole thing, let’s simplify:

Recursive functions - Step 1

This is where we’ll start. We’ll just handle the PLACE command, and ignore anything else. Because of the
complexity of what we’re about to do, we’ll start out by writing some tests over in test/toy_robot/command_-
runner_test.exs. We may choose to write some doctests later, when we have a better understanding of this
module.

Handling a valid placement

We’ll start out by just handling the valid placement of the robot, ignoring the fact (for now), that we’ll need
to handle the invalid placement of that robot later.
Let’s start with a test just for the valid placement:
Reading and handling commands 86

test/toy_robot/command_runner_test.exs

1 defmodule Toyrobot.CommandRunnerTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.{CommandRunner, Simulation}
5
6 test "handles a valid place command" do
7 %Simulation{robot: robot} = CommandRunner.run([{:place, %{east: 1, north: 2, facing: :north}}])
8
9 assert robot.east == 1
10 assert robot.north == 2
11 assert robot.facing == :north
12 end
13 end

In this test, we’re checking that when the CommandRunner.run/1 function receives a valid :place command, that
it is able to place the robot within a simulation. When the CommandRunner.run/1 function runs, we should get
back the new simulation as a value to indicate the simulation has now been started.
Before we can run the test, we should at least create the CommandRunner module:

lib/toy_robot/command_runner.ex

1 defmodule ToyRobot.CommandRunner do
2 end

If we run this test, we’ll see that the CommandRunner.run/1 function is undefined:

1 1) test handles a valid place command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:6
3 ** (UndefinedFunctionError) function ToyRobot.CommandRunner.run/1 is undefined or private
4 code: %{robot: robot} = CommandRunner.run([{:place, %{east: 1, north: 2, facing: :north}}])
5 stacktrace:
6 (toy_robot) ToyRobot.CommandRunner.run([place: %{east: 1, facing: :north, north: 2}])
7 test/toy_robot/command_runner_test.exs:7: (test)

We need to define this function to handle this place command so that this test can pass. Let’s do that now:
Reading and handling commands 87

lib/toy_robot/command_runner.ex

1 defmodule ToyRobot.CommandRunner do
2 alias ToyRobot.{Simulation, Table}
3
4 def run([{:place, placement} | _rest]) do
5 table = %Table{north_boundary: 4, east_boundary: 4}
6 {:ok, simulation} = Simulation.place(table, placement)
7 simulation
8 end
9 end

Why is table here?

It seems a bit weird to set up the table here. This file is supposed to run commands, not setup table. It’s
called CommandRunner, not TableSetupper!

Currently, there is no better place – that I can think of – to put this code. As we build out this application,
there will most likely come a time where there’s a better place to define the table struct. Let’s wait a little
while and see if one comes up.

This run/1 function takes the list of commands, and will only match on a :place command at the start of that
list. This function then calls Simulation.place/2 with a table and the new placement and expects to get back
{:ok, simulation}, which would indicate a successful placement. The function then returns that simulation.

This looks like it would handle the valid placement of our robot. Let’s run the test and find out:

1 18 doctests, 21 tests, 0 failures

Yes! It does handle the valid placement of the robot. That’s a great start. Next up, we’ll handle the invalid
placement of the robot.

Handling an invalid placement

Let’s write up a quick test for how we might handle the invalid placement of the robot:

test/toy_robot/command_runner_test.exs

1 test "handles an invalid place command" do


2 simulation = CommandRunner.run([{:place, %{east: 10, north: 10, facing: :north}}])
3 assert simulation == nil
4 end

In this test, we place our robot far outside the boundaries of our table (4x4). This placement will therefore
be invalid. When we run this test, we’ll see it fails because our CommandRunner.run/1 function doesn’t know
how to handle an invalid placement result:
Reading and handling commands 88

1 1) test handles an invalid place command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:14
3 ** (MatchError) no match of right hand side value: {:error, :invalid_placement}
4 code: simulation = CommandRunner.run([{:place, %{east: 10, north: 10, facing: :north}}])
5 stacktrace:
6 (toy_robot) lib/toy_robot/command_runner.ex:5: ToyRobot.CommandRunner.run/1
7 test/toy_robot/command_runner_test.exs:15: (test)

To determine how we should handle this, we should consult our trusty flowchart:

Recursive functions - Step 1

This flowchart indicates that if the placement was not valid, then we should “call run/1 with remaining
commands”. So let’s change this function to do just that in the case of an invalid placement:

lib/toy_robot/command_runner.ex

1 def run([{:place, placement} | rest]) do


2 table = %Table{north_boundary: 4, east_boundary: 4}
3 case Simulation.place(table, placement) do
4 {:ok, simulation} -> simulation
5 {:error, :invalid_placement} -> run(rest)
6 end
7 end

Here we go, a case statement that handles the {:error, :invalid_placement} return value from Simulation.place/2!
When we get back such a tuple, we’ll recursively call the run function, with the remaining commands that
come from that rest part of the list. We don’t have any remaining (“rest”) commands at the moment, so
nothing much will happen.
Now, remember: we’re only writing the most minimal amount of code possible to make the tests pass. You
might wonder why we’re not recursively calling run again when we get a valid placement match. Well, it’s
because our tests don’t require it… yet!
Let’s focus on making this currently failing test pass. We’ll run it to check-in with how it’s going:
Reading and handling commands 89

1 1) test handles an invalid place command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:14
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandRunner.run/1
4
5 The following arguments were given to ToyRobot.CommandRunner.run/1:
6
7 # 1
8 []
9
10 Attempted function clauses (showing 1 out of 1):
11
12 def run([{:place, _placement} = command | rest])
13
14 code: simulation = CommandRunner.run([{:place, %{east: 10, north: 10, facing: :north}}])
15 stacktrace:
16 (toy_robot) lib/toy_robot/command_runner.ex:4: ToyRobot.CommandRunner.run/1
17 test/toy_robot/command_runner_test.exs:15: (test)

Oops, we forgot to do something! When CommandRunner.run/1 calls itself with the remaining commands, it
may call itself with an empty list. We’re not handling such a thing with CommandRunner.run/1, which is why
this test is still failing.
Let’s add a definition for run/1 that will handle an empty list, after our current run/1 definition:

lib/toy_robot/command_runner.ex
1 def run([]), do: nil

When we run out of commands and we haven’t validly placed the robot yet, we’re going to return nil. This
is because we don’t have a simulation that we know about yet. We would only know of one once a valid
placement has happened. This new function definition essentially says “I’ve tried all the commands that I
know about, and I was unable to place the robot. I am now giving up.”
With that new function, our test should now pass:

1 18 doctests, 22 tests, 0 failures

Excellent! We’re one step closer. And I think we’ve almost finished the first part of our flowchart!

Recursive functions - Step 1


Reading and handling commands 90

We’re handling both the valid and invalid placements of our robot. When we get an invalid placement, we
attempt to run the remaining commands, just in case any one of those might be a valid placement.
This was our first foray into recursive functions within this project, and we handled it well!
The remaining part of our flowchart is what we do when the first command is not a :place command. We
need to account for this line from our instructions:

• All commands are ignored until a valid PLACE is made.

We have only checked with what happens when the :place command is first. But what happens when it’s
not? Let’s find out!

Ignoring commands until a valid placement

Any command that happens before a valid placement is made should be ignored, just as our instructions
tell us:

• All commands are ignored until a valid PLACE is made.

So to make sure we’re handling that case, let’s write another test:

test/toy_robot/command_runner_test.exs
1 test "ignores commands until a valid placement" do
2 %Simulation{robot: robot} = [
3 :move,
4 {:place, %{east: 1, north: 2, facing: :north}},
5 ]
6 |> CommandRunner.run()
7
8 assert robot.east == 1
9 assert robot.north == 2
10 assert robot.facing == :north
11 end

This test asserts that the :move command before the :place command is ignored. This is the part of our
flowchart that goes from “Is the current command a place” to “Call run/1 with remaining commands”:

Recursive functions - Step 1

Running this test will show that the run/1 function is not matching:
Reading and handling commands 91

1 1) test ignores commands until a valid placement (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:16
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandRunner.run/1
4
5 The following arguments were given to ToyRobot.CommandRunner.run/1:
6
7 # 1
8 [:move, {:place, %{east: 1, facing: :north, north: 2}}]
9
10 Attempted function clauses (showing 2 out of 2):
11
12 def run([{:place, _placement} = command | rest])
13 def run([])
14
15 code: |> CommandRunner.run()
16 stacktrace:
17 (toy_robot) lib/toy_robot/command_runner.ex:4: ToyRobot.CommandRunner.run/1
18 test/toy_robot/command_runner_test.exs:21: (test)

We only have two run/1 definitions at the moment:

1. One that matches a list with the {:place, ...} tuple as the first element
2. One that matches an empty list

Let’s add a third one that will match any other command and ignore it. We’ll add this between the two
existing run/1 definitions:

lib/toy_robot/command_runner.ex

1 def run([_command | rest]), do: run(rest)


2 def run([]), do: nil

This new function will ignore any command at this point, and then recursively call run/1 with the rest of the
commands. This should be enough to make our test pass. Let’s find out.

1 18 doctests, 23 tests, 0 failures

Yes, we’re now handling commands that happen before an invalid placement. Now we have finished the
first part of our flowchart.
With that first piece under our belt, let’s look at how we might handle the rest of the flowchart by handling
the remaining commands for :move, :turn_left, :turn_right and :report.

Handling the move command

Now that we’re able to place a robot, let’s try to move one with this CommandRunner module too. Before we
move it though, we’ll need to place it! With that in mind, let’s write a new test:
Reading and handling commands 92

test/toy_robot/command_runner_test.exs
1 test "handles a place + move command" do
2 %Simulation{robot: robot} = [
3 {:place, %{east: 1, north: 2, facing: :north}},
4 :move
5 ]
6 |> CommandRunner.run()
7
8 assert robot.east == 1
9 assert robot.north == 3
10 assert robot.facing == :north
11 end

Previously, we passed a list containing just one command through to CommandRunner.run/1. But the true power
of this function will come with its ability to handle a list of as many commands as we care to pass it. We could
go all-out and pass it all of the commands right now, but we’re preferring the slow-and-steady approach. So
we are just passing it two commands now, a :place and a :move. When this function receives those commands,
the robot should end up at north = 3.
When we run our test, we’ll see that it is not the case:

test/toy_robot/command_runner_test.exs
1 1) test handles a place + move command (Toyrobot.CommandRunnerTest)
2 test/toy_robot/command_runner_test.exs:19
3 Assertion with == failed
4 code: assert robot.north() == 3
5 left: 2
6 right: 3
7 stacktrace:
8 test/toy_robot/command_runner_test.exs:27: (test)

Huh? We’re passing it to two completely valid commands, but it seems like it’s ignoring the last one. Why
might that be? If we look back at CommandRunner.run/1, we’ll see this:

lib/toy_robot/command_runner.ex
1 def run([{:place, placement} | rest]) do
2 table = %Table{north_boundary: 4, east_boundary: 4}
3 case Simulation.place(table, placement) do
4 {:ok, simulation} -> simulation
5 {:error, :invalid_placement} -> run(rest)
6 end
7 end

In the case of a valid placement, we’re returning the simulation and flat-out ignoring any remaining
commands! And so this run/1 function, although it’s taking two commands right now it’s only executing
one command; the place command.
To fix this, we’re going to need to change the clause of our case that matches the {:ok, simulation} so that
it runs more than just one command when a valid placement happens.
Reading and handling commands 93

lib/toy_robot/command_runner.ex

1 defmodule ToyRobot.CommandRunner do
2 alias ToyRobot.{Simulation, Table}
3
4 def run([{:place, placement} | rest]) do
5 table = %Table{north_boundary: 4, east_boundary: 4}
6 case Simulation.place(table, placement) do
7 {:ok, simulation} -> run(rest, simulation)
8 {:error, :invalid_placement} -> run(rest)
9 end
10 end
11
12 def run([_command | rest]), do: run(rest)
13 def run([]), do: nil
14 end

This time rather than returning the simulation, we’re calling a new function: run/2. This function takes the
remaining list of commands, and then it should run the relevant functions in Simulation. This code will fail
to compile because we do not have that run/2 function:

1 == Compilation error in file lib/toy_robot/command_runner.ex ==


2 ** (CompileError) lib/toy_robot/command_runner.ex:7: undefined function run/2
3 (elixir) src/elixir_locals.erl:98: :elixir_locals."-ensure_no_undefined_local/3-lc$^0/1-0-"/2
4 (elixir) src/elixir_locals.erl:99: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3
5 (stdlib) erl_eval.erl:680: :erl_eval.do_apply/6

The point of this function is to handle what happens after a valid placement of our robot. By that stage, we
know of a simulation (through Simulation.place/2) and that means we can call the Simulation functions for
moving or turning the robot. Let’s add this new run/2 function now to handle that move command:

lib/toy_robot/command_runner.ex

1 def run([:move | rest], simulation) do


2 {:ok, simulation} = simulation |> Simulation.move()
3 run(rest, simulation)
4 end

This function operates on that :move command that we will get from CommandInterpreter, and will handle
a successful movement. We’ll work on handling an invalid movement later on. Assuming everything went
alright, this function then calls itself recursively. However, when this happens in our tests, it will fail:
Reading and handling commands 94

1 1) test handles a valid place command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:6
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandRunner.run/2
4
5 The following arguments were given to ToyRobot.CommandRunner.run/2:
6
7 # 1
8 []
9
10 # 2
11 %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 1, facing: :north, north: 2}, table: %ToyRobot.Tab\
12 le{east_boundary: 4, north_boundary: 4}}
13
14 Attempted function clauses (showing 1 out of 1):
15
16 def run([command | rest], simulation)
17
18 code: %Simulation{robot: robot} = CommandRunner.run([{:place, %{east: 1, north: 2, facing: :north}}])
19 stacktrace:
20 (toy_robot) lib/toy_robot/command_runner.ex:13: ToyRobot.CommandRunner.run/2
21 test/toy_robot/command_runner_test.exs:7: (test)

This is because we’re missing a version of run/2 that takes an empty list of commands and a simulation as
arguments! Let’s add one:

lib/toy_robot/command_runner.ex

1 def run([:move | rest], simulation) do


2 {:ok, simulation} = simulation |> Simulation.move()
3 run(rest, simulation)
4 end
5 def run([], simulation), do: simulation

This function returns the simulation, because by this point we’ve run out commands and there’s nothing
left to do! If we run our test once more, we’ll see that with this new addition, our tests are now passing:

1 18 doctests, 24 tests, 0 failures

We’re now successfully handling our :move command! But only when that move is valid. We should also add
another test here to handle what happens when our robot is right up against a table boundary and attempts
to move.

Handling an invalid move command

If the robot is pushing up against a table boundary, it should not move past that point. Let’s make sure it
cannot do that by adding one more test:
Reading and handling commands 95

test/toy_robot/command_runner_test.exs

1 test "handles a place + invalid move command" do


2 %Simulation{robot: robot} = [
3 {:place, %{east: 1, north: 4, facing: :north}},
4 :move
5 ]
6 |> CommandRunner.run()
7
8 assert robot.east == 1
9 assert robot.north == 4
10 assert robot.facing == :north
11 end

With this test, our robot is right up against the northern boundary of our table. If the robot attempts to
move again, that movement should be prevented and the robot should remain right where it is. That’s what
our test is asserting.
When we run this test, we’ll see that our CommandRunner.run/2 code is failing:

1 1) test handles a place + invalid move command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:33
3 ** (MatchError) no match of right hand side value: {:error, :at_table_boundary}
4 code: |> CommandRunner.run()
5 stacktrace:
6 (toy_robot) lib/toy_robot/command_runner.ex:25: ToyRobot.CommandRunner.run/2
7 test/toy_robot/command_runner_test.exs:38: (test)

This is because in this code we are currently only handling the “happy path” of Simulation.move/1:

lib/toy_robot/command_runner.ex

1 def run([:move | rest], simulation) do


2 {:ok, simulation} = simulation |> Simulation.move()
3 run(rest, simulation)
4 end

We need to handle the output of Simulation.move/1 when the robot is at a boundary of the table. We need
to handle that {:error, :at_table_boundary} tuple. Because this error tuple is unique to the :move command,
we’ll write a custom run/2 definition just for that command:
Reading and handling commands 96

lib/toy_robot/command_runner.ex

1 def run([:move | rest], simulation) do


2 new_simulation = simulation
3 |> Simulation.move
4 |> case do
5 {:ok, simulation} -> simulation
6 {:error, :at_table_boundary} -> simulation
7 end
8 run(rest, new_simulation)
9 end
10 def run([], simulation), do: simulation

We need to put this new definition above the existing run/2 definitions so that it is matched first. With this
new function, we will handle both results of CommandHandler.handle(:move, simulation). They are:

• The {:ok, simulation} tuple which indicates the movement was successful
• The {:error, :at_table_boundary} tuple, which indicates the movement was unsuccessful

That seems like it will be enough to satisfy our test. Let’s run it and find out:

1 18 doctests, 25 tests, 0 failures

Yes, it is enough! We’re now able to handle both kinds of movements, valid and invalid. Let’s move on to
handling the :turn_left and :turn_right commands too.

Handling the turning commands

What if I told you at this point, we already had the code in place to handle the turning commands? Would
you be impressed? Or do you not believe me? If you’re the latter, I know a test will prove it to you:

test/toy_robot/command_runner_test.exs

1 test "handles a place + turn_left command" do


2 %Simulation{robot: robot} = [
3 {:place, %{east: 1, north: 2, facing: :north}},
4 :turn_left
5 ]
6 |> CommandRunner.run()
7
8 assert robot.east == 1
9 assert robot.north == 2
10 assert robot.facing == :west
11 end
12
13 test "handles a place + turn_right command" do
14 %Simulation{robot: robot} = [
15 {:place, %{east: 1, north: 2, facing: :north}},
16 :turn_right
Reading and handling commands 97

17 ]
18 |> CommandRunner.run()
19
20 assert robot.east == 1
21 assert robot.north == 2
22 assert robot.facing == :east
23 end

These two new tests assert that when we place our robot and turn it either left or right, that it ends up facing
either west or east. Let’s run these tests and see what happens.

1 1) test handles a place + turn_right command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:67
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandRunner.run/2
4
5 The following arguments were given to ToyRobot.CommandRunner.run/2:
6
7 # 1
8 [:turn_right]
9
10 # 2
11 %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 1, facing: :north, north: 2}, table: %ToyRobot.Ta\
12 ble{east_boundary: 4, north_boundary: 4}}
13
14 Attempted function clauses (showing 2 out of 2):
15
16 def run([:move | rest], simulation)
17 def run([], simulation)
18
19 code: |> CommandRunner.run()
20 stacktrace:
21 (toy_robot) lib/toy_robot/command_runner.ex:15: ToyRobot.CommandRunner.run/2
22 test/toy_robot/command_runner_test.exs:72: (test)
23
24 .
25
26 2) test handles a place + turn_left command (Toyrobot.CommandRunnerTest)
27 test/toy_robot/command_runner_test.exs:55
28 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandRunner.run/2
29
30 The following arguments were given to ToyRobot.CommandRunner.run/2:
31
32 # 1
33 [:turn_left]
34
35 # 2
36 %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 1, facing: :north, north: 2}, table: %ToyRobot.Ta\
37 ble{east_boundary: 4, north_boundary: 4}}
38
39 Attempted function clauses (showing 2 out of 2):
40
41 def run([:move | rest], simulation)
Reading and handling commands 98

42 def run([], simulation)


43
44 code: |> CommandRunner.run()
45 stacktrace:
46 (toy_robot) lib/toy_robot/command_runner.ex:15: ToyRobot.CommandRunner.run/2
47 test/toy_robot/command_runner_test.exs:60: (test)

These two tests fail for identical reasons: the run function that handles the :turn_left or :turn_right
commands is missing. Let’s add those in now:

lib/toy_robot/command_runner.ex

1 def run([:turn_left | rest], simulation) do


2 {:ok, simulation} = simulation |> Simulation.turn_left
3 run(rest, simulation)
4 end
5
6 def run([:turn_right | rest], simulation) do
7 {:ok, simulation} = simulation |> Simulation.turn_right
8 run(rest, simulation)
9 end

These two run/2 functions call the relevant Simulation functions to turn the robot left or right. We don’t need
to handle the case of an “invalid” turn here, because there is no such thing. This should be enough to make
our tests pass. Let’s check!

1 12 doctests, 27 tests, 0 failures

That was easy! If you didn’t catch it, here’s what’s happening:

1. The commands get passed to CommandRunner.run/1


2. The first command is the :place, which is handled by CommandHandler.handle/1
3. This placement is successful, and so CommandRunner.run/2 is called with the rest of the commands
4. The next command is either :turn_left or :turn_right. That command is handled by either Simulation.turn_-
left/1 or Simulation.turn_right/1.
5. The remaining commands – of which there are none – and the simulation get passed to run/2.
6. Because there are no commands, the run/2 function that matches on an empty list returns the
simulation.

See if you can follow that same path on the recursive flowchart too:
Reading and handling commands 99

Recursive flowchart

We have everything we need in place to support both the :turn_left and :turn_right functions. This leaves
us with just one more command to manage in CommandRunner: the :report command.

Handling the report command

The :report command is an interesting case. It’s the only command that does not either create a simulation,
or change the position of the robot. Instead, when we encounter this command we should output the current
position of the robot within the simulation.
Therefore, we’re going to need to change our CommandRunner code to handle this command differently to the
:move, :turn_left and :turn_right commands. We’re also going to need to handle the test differently as we’re
going to be testing for some output, rather than a return value from the function. I’ll show you what I mean
there with this next test example:

test/toy_robot/command_runner_test.exs

1 test "handles a place + report command" do


2 commands = [
3 {:place, %{east: 1, north: 2, facing: :north}},
4 :report
5 ]
6
7 output = capture_io fn ->
8 CommandRunner.run(commands)
9 end
10
11 assert output |> String.trim == "The robot is at (1, 2) and is facing NORTH"
12 end

This test takes two commands: our familiar :place command, and a :report command. Rather than passing
these two commands directly to CommandRunner.run/1, that function call is wrapped in a capture_io function.
This capture_io will capture any output generated by the CommandRunner.run call.
The final line of the test asserts that the output tells us where the robot is, because that’s exactly what we’ve
asked: we asked for a report, and we should get one!
That capture_io function is not automatically available in our tests, so we’ll need to import it at the top of
our test:
Reading and handling commands 100

test/toy_robot/command_runner_test.exs

1 defmodule Toyrobot.CommandRunnerTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.{CommandRunner, Simulation}
5
6 import ExUnit.CaptureIO

By importing the ExUnit.CaptureIO module, we make all of its functions (all called capture_io) available within
each test block within this file. This means we will be able to use capture_io.
When we run this test, we’ll see that it’s going to have difficulty running this :report command:

1 1) test handles a place + report command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:81
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandRunner.run/2
4
5 The following arguments were given to ToyRobot.CommandRunner.run/2:
6
7 # 1
8 [:report]
9
10 # 2
11 %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 1, facing: :north, north: 2}, table: %ToyRobot.Ta\
12 ble{east_boundary: 4, north_boundary: 4}}
13
14 Attempted function clauses (showing 4 out of 4):
15
16 def run([:move | rest], simulation)
17 def run([:turn_left | rest], simulation)
18 def run([:turn_right | rest], simulation)
19 def run([], simulation)
20
21 code: output = capture_io fn ->
22 stacktrace:
23 (toy_robot) ToyRobot.CommandRunner.run/2
24 (ex_unit) lib/ex_unit/capture_io.ex:151: ExUnit.CaptureIO.do_capture_io/2
25 (ex_unit) lib/ex_unit/capture_io.ex:121: ExUnit.CaptureIO.do_capture_io/3
26 test/toy_robot/command_runner_test.exs:87: (test)

Looking at the stacktrace, it looks like that we’re not handling the :report command at all.
To fix this failing test, we can add a version of run/2 that will handle this :report command:
Reading and handling commands 101

lib/toy_robot/command_runner.ex
1 def run(
2 [:report | rest],
3 simulation
4 ) do
5 %{
6 east: east,
7 north: north,
8 facing: facing
9 } = Simulation.report(simulation)
10
11 facing = facing |> Atom.to_string |> String.upcase
12 IO.puts "The robot is at (#{east}, #{north}) and is facing #{facing}"
13
14 run(rest, simulation)
15 end

Here we call out to the Simulation.report/1 function to get the position of the robot. To output that
information, we use IO.puts/1. This will output a line telling us where the robot is.
Let’s try running our tests and see how they go:

1 12 doctests, 28 tests, 0 failures

That’s great! We can handle the :report command here now too. So we have now handled all 4 valid
commands of our robot, let’s finish this section by handling invalid commands.

Handling invalid commands

We’re almost done with CommandRunner. The final piece of this puzzle is what this module should do in the
case of an invalid command. To start of with, as usual, we’ll write a test:

test/toy_robot/command_runner_test.exs
1 test "handles a place + invalid command" do
2 %Simulation{robot: robot} = [
3 {:place, %{east: 1, north: 2, facing: :north}},
4 {:invalid, "EXTERMINATE"}
5 ]
6 |> CommandRunner.run()
7
8 assert robot.east == 1
9 assert robot.north == 2
10 assert robot.facing == :north
11 end

This test is almost the same as our initial :place test, except we now have an invalid command in the mix.
This test says that when we get that invalid command, our CommandRunner.run/1 function should ignore it,
and return the existing simulation.
Running this test will show it failing because we do not handle any invalid commands yet:
Reading and handling commands 102

1 1) test handles a place + invalid command (Toyrobot.CommandRunnerTest)


2 test/toy_robot/command_runner_test.exs:94
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CommandRunner.run/2
4
5 The following arguments were given to ToyRobot.CommandRunner.run/2:
6
7 # 1
8 [invalid: "EXTERMINATE"]
9
10 # 2
11 %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 1, facing: :north, north: 2}, table: %ToyRobot.Ta\
12 ble{east_boundary: 4, north_boundary: 4}}
13
14 Attempted function clauses (showing 5 out of 5):
15
16 def run([:move | rest], simulation)
17 def run([:turn_left | rest], simulation)
18 def run([:turn_right | rest], simulation)
19 def run([:report | rest], simulation)
20 def run([], simulation)
21
22 code: |> CommandRunner.run()
23 stacktrace:
24 (toy_robot) lib/toy_robot/command_runner.ex:15: ToyRobot.CommandRunner.run/2
25 test/toy_robot/command_runner_test.exs:99: (test)

To handle these invalid commands, we can add a new definition of CommandRunner.run/2, above the one for
move:

lib/toy_robot/command_runner.ex
1 def run([{:invalid, _command} | rest], simulation), do: run(rest, simulation)
2 def run([:move | rest], simulation) do

This new function definition matches any {:invalid, ...} tuple and chooses to ignore the command. It
will recursively call run/2 with the remaining commands and the simulation. This should safely ignore the
commands for us. We can check that by running our test again:

1 18 doctests, 29 tests, 0 failures

Everything is green once again! Excellent. Our CommandRunner is now handling all of our commands and
running them successfully. We now have everything in place to run commands for our toy robot. It’s time
we tried this out.

Piecing it all together

It’s time we put all of these pieces together. We’re so close to having this toy robot exercise complete! Let
me show you with this short section.
Let’s create a new file called robot.exs and put this code in it:
Reading and handling commands 103

robot.exs
1 File.stream!("commands.txt")
2 |> Enum.map(&String.trim/1)
3 |> ToyRobot.CommandInterpreter.interpret
4 |> ToyRobot.CommandRunner.run

You might recognise some of this code as the code we used at the start of this chapter! Now that we have
the CommandInterpreter and CommandRunner modules, we can connect those in with piping.
To test this out, we’re going to need a file with a list of commands. Let’s make sure that we have that
commands.txt existing with this content:

commands.txt
1 PLACE 1,2,NORTH
2 MOVE
3 LEFT
4 MOVE
5 RIGHT
6 MOVE
7 REPORT

We can then run that robot.exs file with this command:

1 mix run robot.exs

When we do this, we’ll see just the line from that single REPORT call:

1 The robot is at (0, 4) and is facing NORTH

Our toy robot lives! It can move, turn and report.


This output shows us that the robot ends up at (0, 4) on our board, facing north. We can confirm this if we
draw the movements of our robot:

Movements after commands.txt


Reading and handling commands 104

We know that the left-most side of our board has the EAST coordinate of 0, and we know that the top-most side
of our board has the coordinate of 4. Putting the two together, we get (0, 4), exactly the same coordinates
that our program has come up with too. As you can see from all of this, we have all the pieces that simulate
a robot moving around on a tabletop.
The last piece of this whole thing is to wrap it up in a CLI, which is what the next (and final) chapter will do.
6. Building the CLI
We have one final piece to put into place before we can round out this chapter, which is the same piece we
began last chapter talking about:

The application reads a file using a name passed in the command line …

We currently do not have any part of our application that reads this file. What we have are the parts that
happen after that: the CommandInterpreter, the CommandHandler and the CommandRunner. It’s time we looked into
how we could read this file from the command line.

Reading commands from a file

At the end of the last chapter, we saw that we could run the whole code of our toy robot with these lines:

robot.exs

1 File.stream!("commands.txt")
2 |> Enum.map(&String.trim/1)
3 |> ToyRobot.CommandInterpreter.interpret
4 |> ToyRobot.CommandRunner.run

As long as we had a commands.txt file that looked like this:

commands.txt

1 PLACE 1,2,NORTH
2 MOVE
3 LEFT
4 MOVE
5 RIGHT
6 MOVE
7 REPORT

But this robot.exs script is just that: a hand-rolled script. What we need is a proper command line interface
to our toy robot to complete this exercise. We’re going to call this part of our program the “CLI” which is
short for Command Line Interface. We’re going to write another module which will take the name of the
commands file as an argument, then it will do exactly the same steps as robot.exs. The difference is that it
will be all wrapped up in a single executable file.
Why is that important? Well, right now we’ve hard-coded the commands.txt argument to File.stream! in our
code. What would happen if we chose to have a different name for our file? Sure, we could change the script
itself, but even then we would need to change it every single time we had a different filename.
Building the CLI 106

One other disadvantage of running it the way we’re currently running it is that we’re doing so in a naïve
way. We’re not handling any sort of errors that might come out of this program, like what might happen if
the file is missing.
What we’ll do in this chapter is this:

• We’ll build a CLI for our Toy Robot


• We’ll make it handle different file paths for the file containing the commands
• We’ll make it gracefully handle errors

To get started, we have to lay some foundations first.

Building the CLI template

Let’s start by laying the foundations for our CLI. We’re going to be a little cheeky now and write some code
without writing some tests first! The reason for doing this is because it’s going to be easier to understand
these pieces without having to think about tests.
In order to provide a CLI program for our toy robot application, we can use a built-in thing called an escript.
An escript is an executable that will run on any system that has Erlang installed on it.
To start building one, we need to open mix.exs and add an escript key to our project’s definition:

mix.exs

1 def project do
2 [
3 app: :toy_robot,
4 version: "0.1.0",
5 elixir: "~> 1.8",
6 start_permanent: Mix.env() == :prod,
7 deps: deps(),
8 escript: escript(),
9 ]
10 end

We’ll also need to add a new private method within the ToyRobot.MixProject module within this file:

mix.exs

1 defp escript do
2 [main_module: ToyRobot.CLI]
3 end

This new configuration tells Elixir that when we want to build our executable, it should use a module called
ToyRobot.CLI as the foundation for that executable. This module only exists in our imaginations and Elixir
still to this day is unable to run imaginary code. So to proceed further, we will need to create this module:
Building the CLI 107

lib/toy_robot/cli.ex

1 defmodule ToyRobot.CLI do
2 end

This module looks a bit empty, and it’s not very clear right now what we should put in this module. A way
of finding out what would be required is by building this executable and then attempting to run it. Perhaps
we’ll see some errors. To build the executable, we’ll run this command:

1 mix escript.build

When this command runs we’ll see this output:

1 Generated escript toy_robot with MIX_ENV=dev

This output tells us that there’s a new executable file called toy_robot. Let’s try running it now:

1 ./toy_robot commands.txt

Here’s what we’ll see:

1 ** (UndefinedFunctionError) function ToyRobot.CLI.main/1 is undefined or private


2 (toy_robot) ToyRobot.CLI.main(["commands.txt"])
3 (elixir) lib/kernel/cli.ex:105: anonymous fn/3 in Kernel.CLI.exec_fun/2

An error, but a helpful error. This tell us that the ToyRobot.CLI module needs a function called main that takes
one argument, which is a list. I could’ve told you that earlier because I’m the author and know these things,
but sometimes it’s really helpful to show you how I know these things too.
That main function takes a list of (space separated) arguments that are passed to the executable. For instance,
if we had another argument, the list would look like:

1 ["commands.txt", "second-argument-goes-here"]

Let’s work on the assumption for now that we’re only going to ever receive one argument for this function,
and that it’s going to be the path to the list of commands. We’ll add this ToyRobot.main/1 function now, taking
the same code that we wrote earlier in robot.exs:
Building the CLI 108

lib/toy_robot/cli.ex

1 defmodule ToyRobot.CLI do
2 def main([file_name]) do
3 File.stream!(file_name)
4 |> Enum.map(&String.trim/1)
5 |> ToyRobot.CommandInterpreter.interpret
6 |> ToyRobot.CommandRunner.run
7 end
8 end

This function will now handle the commands for the toy robot coming from any particular file. This code
will take the file that we’ve specified and process it using a few steps that we should be familiar with by now.
Given that this code is almost the exact same code that we had in robot.exs earlier, it should still work. Let’s
try re-building our executable and finding out:

1 mix escript.build
2 ./toy_robot commands.txt
3 The robot is at (0, 4) and is facing NORTH

Excellent! Our CLI is now working! We have now got an executable which can read a file and process it
correctly. We were cheeky and we wrote some code without some tests, but it seems to be working. Now
that we’re at this point, we should try out our CLI and see if we can break it by using it in interesting ways.
Let’s see what happens if we attempt to provide the program with no arguments:

1 ** (FunctionClauseError) no function clause matching in ToyRobot.CLI.main/1


2
3 The following arguments were given to ToyRobot.CLI.main/1:
4
5 # 1
6 []
7
8 (toy_robot) lib/toy_robot/cli.ex:2: ToyRobot.CLI.main/1
9 (elixir) lib/kernel/cli.ex:105: anonymous fn/3 in Kernel.CLI.exec_fun/2

Oh dear. What about two arguments?


Building the CLI 109

1 ** (FunctionClauseError) no function clause matching in ToyRobot.CLI.main/1


2
3 The following arguments were given to ToyRobot.CLI.main/1:
4
5 # 1
6 ["commands.txt", "second"]
7
8 (toy_robot) lib/toy_robot/cli.ex:2: ToyRobot.CLI.main/1
9 (elixir) lib/kernel/cli.ex:105: anonymous fn/3 in Kernel.CLI.exec_fun/2

Uh oh. How about if we point it at a file that doesn’t exist?

1 ** (File.Error) could not stream "no-such-file.txt": no such file or directory


2 (elixir) lib/file/stream.ex:83: anonymous fn/3 in Enumerable.File.Stream.reduce/3
3 (elixir) lib/stream.ex:1377: anonymous fn/5 in Stream.resource/3
4 (elixir) lib/enum.ex:3015: Enum.map/2
5 (toy_robot) lib/toy_robot/cli.ex:4: ToyRobot.CLI.main/1
6 (elixir) lib/kernel/cli.ex:105: anonymous fn/3 in Kernel.CLI.exec_fun/2

Wow, that’s not so great looking. There’s an error message up the top of that output that is useful, but the
rest of it is just noise.
Just when we thought it was all going so well we realise that we haven’t quite finished building our CLI. The
happy path is, well, happy. But the unhappy paths are decidedly un_happy. We knew this at the start of the
chapter but carried on as if nothing was amiss. It’s time to bring back testing of our CLI. We should test the happy
path and the unhappy paths, and make sure that the output of our robot, regardless of the _input, is useful to
anyone using the CLI.

Testing the CLI

It looks like we’re going to need to write tests for our CLI to handle what happens when we:

• Specify no arguments
• Specify more than a single argument
• Ask it to read a file that does not exist

Let’s walk through each of these one-at-a-time.

Handling no CLI arguments

When the CLI receives no arguments then that would mean that someone is trying to use the CLI wrong.
We should tell them the right way of using it:

1 Usage: toy_robot commands.txt

This should give the CLI user enough information of what they did wrong. With that in mind, let’s set about
writing a test for this.
Building the CLI 110

test/toy_robot/cli_test.exs
1 defmodule ToyRobot.CLITest do
2 use ExUnit.Case, async: true
3
4 import ExUnit.CaptureIO
5
6 test "provides usage instructions if no arguments specified" do
7 output = capture_io fn ->
8 ToyRobot.CLI.main([])
9 end
10
11 assert output |> String.trim == "Usage: toy_robot commands.txt"
12 end
13 end

In this new test, we’re doing something we’ve never done before: we’re importing a module called
ExUnit.CaptureIO. When we import a module like this, all of its functions become available as if we defined
them in the current module. In this case, it makes the capture_io/1 function available in our test.
To capture the output of ToyRobot.CLI.main/1, we use that imported function of capture_io, giving it a function
to run. The capture_io function will run that function and capture all of its output, storing it in the output
variable. We can then assert that the output is exactly as we expect on the final line of this test.
When we run this test, we’ll see that it doesn’t even get up to capturing that output. Instead, we’ll see a
FunctionClauseError:

1 1) test provides usage instructions if no arguments specified (ToyRobot.CLITest)


2 test/toy_robot/cli_test.exs:6
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CLI.main/1
4
5 The following arguments were given to ToyRobot.CLI.main/1:
6
7 # 1
8 []
9
10 Attempted function clauses (showing 1 out of 1):
11
12 def main([file_name])
13
14 code: output = capture_io fn ->
15 stacktrace:
16 (toy_robot) lib/toy_robot/cli.ex:2: ToyRobot.CLI.main/1
17 (ex_unit) lib/ex_unit/capture_io.ex:150: ExUnit.CaptureIO.do_capture_io/2
18 (ex_unit) lib/ex_unit/capture_io.ex:120: ExUnit.CaptureIO.do_capture_io/3
19 test/toy_robot/cli_test.exs:7: (test)

This matches up with the error we saw when we initially called our CLI with no arguments. This is great,
because we’ve now been able to reproduce that bad behaviour with a simple test. This test is failing because
there is no ToyRobot.CLI.main/1 function that will handle an empty list. Let’s write one now that will return
our usage instructions:
Building the CLI 111

lib/toy_robot/cli.ex

1 def main([]) do
2 IO.puts "Usage: toy_robot commands.txt"
3 end

What will happen now is that when the main/1 function called with an empty list, it will return our usage
instructions. This seems to be exactly the kind of thing that our test is expecting, so let’s see what happens
if we run it again.

1 18 doctests, 30 tests, 0 failures

Great! Everything is passing. This is the first of our CLI tests complete, in a relatively easy fashion.
Let’s move onto the next test.

Handling too many CLI arguments

Just like Goldilocks, we want our arguments to be “just right” – only one argument. We’ve now tested for
no arguments, but our CLI will still break if we pass it too many arguments. Let’s spend a little bit of time
handling this situation too.
As before, we’ll start off by writing a test:

test/toy_robot/cli_test.exs

1 test "provides usage instructions if too many arguments specified" do


2 output = capture_io fn ->
3 ToyRobot.CLI.main(["commands.txt", "commands2.txt"])
4 end
5
6 assert output |> String.trim == "Usage: toy_robot commands.txt"
7 end

Just like before, we want to return the usage instructions if too many arguments are provided. Running this
test will show another FunctionClauseError:

1 1) test provides usage instructions if too many arguments specified (ToyRobot.CLITest)


2 test/toy_robot/cli_test.exs:14
3 ** (FunctionClauseError) no function clause matching in ToyRobot.CLI.main/1
4
5 The following arguments were given to ToyRobot.CLI.main/1:
6
7 # 1
8 ["commands.txt", "commands2.txt"]
9
10 Attempted function clauses (showing 2 out of 2):
11
12 def main([file_name])
Building the CLI 112

13 def main([])
14
15 code: output = capture_io fn ->
16 stacktrace:
17 (toy_robot) lib/toy_robot/cli.ex:2: ToyRobot.CLI.main/1
18 (ex_unit) lib/ex_unit/capture_io.ex:150: ExUnit.CaptureIO.do_capture_io/2
19 (ex_unit) lib/ex_unit/capture_io.ex:120: ExUnit.CaptureIO.do_capture_io/3
20 test/toy_robot/cli_test.exs:15: (test)

This matches up with what happened earlier when we attempted to run the CLI with too many arguments.
This FunctionClauseError is happening because we do not have a ToyRobot.CLI.main/1 function that handles a
list with two elements in it.
To fix this, we could add another definition to handle this two-element list:

lib/toy_robot/cli.ex

1 def main([_arg1, _arg2]) do


2 IO.puts "Usage: toy_robot commands.txt"
3 end

But then we want whatever changes we make to handle 3, 4, and 5 arguments too. We can’t keep adding
definitions like that! So therefore we’ll be smarter about what code we write. Let’s change that last main
function inside cli.ex to this:

lib/toy_robot/cli.ex

1 def main(_) do
2 IO.puts "Usage: toy_robot commands.txt"
3 end

This underscore will capture anything that is passed as the argument to this function, and will always return
usage instructions. This function will only be invoked if the first main function in our file – the one that
matches the 1-element list – does not match. That sounds like it might fix our test, so let’s try running
them again:

1 18 doctests, 31 tests, 0 failures

Everything is working again! We’ve now handled two out of the three ways that our CLI can fail. Let’s take
a look at the third.

Handling a missing file

The third and final way that our CLI has been known to fail is if we give a file that cannot be found. To write
a test for this, all we need to give the main/1 function is the name of a file that does not exist.
Building the CLI 113

test/toy_robot/cli_test.exs
1 test "shows an error message if the file does not exist" do
2 output = capture_io fn ->
3 ToyRobot.CLI.main(["i-do-not-exist.txt"])
4 end
5
6 assert output |> String.trim == "The file i-do-not-exist.txt does not exist"
7 end

We want to display this error message instead of the usage instructions because our user is using our
program correctly, it’s just that the file could not be found. It makes more sense to show this error message
in this case; to prompt the user to make sure that a file exists before running a robot’s commands.
When we run this test, we’ll see it fail like this:

1 1) test shows an error message if the file does not exist (ToyRobot.CLITest)
2 test/toy_robot/cli_test.exs:22
3 ** (File.Error) could not stream "i-do-not-exist.txt": no such file or directory
4 code: output = capture_io fn ->
5 stacktrace:
6 (elixir) lib/file/stream.ex:83: anonymous fn/3 in Enumerable.File.Stream.reduce/3
7 (elixir) lib/stream.ex:1377: anonymous fn/5 in Stream.resource/3
8 (elixir) lib/enum.ex:3015: Enum.map/2
9 (toy_robot) lib/toy_robot/cli.ex:4: ToyRobot.CLI.main/1
10 (ex_unit) lib/ex_unit/capture_io.ex:150: ExUnit.CaptureIO.do_capture_io/2
11 (ex_unit) lib/ex_unit/capture_io.ex:120: ExUnit.CaptureIO.do_capture_io/3
12 test/toy_robot/cli_test.exs:23: (test)

If we look at the stacktrace, we can see the error is coming from within lib/toy_robot/cli.ex, and it’s pointing
to the line that calls Enum.map/2. This function will trigger the reading of the file from the line above it, the
File.stream!/1 call. The File.stream!/1 function will fail because that file is missing. Therefore, we will need
to change our ToyRobot.CLI.main/1 function to check for the presence of a file before calling File.stream!/1
on it.
A simple File.exists? check here will do:

lib/toy_robot/cli.ex
1 def main([file_name]) do
2 if File.exists?(file_name) do
3 File.stream!(file_name)
4 |> Enum.map(&String.trim/1)
5 |> ToyRobot.CommandInterpreter.interpret
6 |> ToyRobot.CommandRunner.run
7 else
8 IO.puts "The file #{file_name} does not exist"
9 end
10 end

Now before we stream the file we’re checking to see if it exists. If it doesn’t exist, then we’re outputting the
message that our test is expecting. Let’s see if these changes are enough to satisfy our tests:
Building the CLI 114

1 18 doctests, 32 tests, 0 failures

Yes they are! We have now tested the three ways that our CLI can fail. Now we can be sure that a user of
our CLI will get back good error messages when they give the CLI bad input. We can be even surer (if that’s
even possible!) by attempting to use the CLI ourselves. Let’s run the command to rebuild it:

1 mix escript.build

Then, we’ll try out the three ways that our program can fail:

• Specify no arguments
• Specify more than a single argument
• Ask it to read a file that does not exist

Here’s what will now happen if we specify no arguments:

1 $ ./toy_robot
2 Usage: toy_robot commands.txt

Here’s what will happen if we specify too many arguments:

1 $ ./toy_robot commands1.txt commands2.txt


2 Usage: toy_robot commands.txt

And here’s what happens when we specify one argument, but it points at a file that doesn’t exist:

1 $ ./toy_robot i-do-not-exist.txt
2 The file i-do-not-exist.txt does not exist

Great! We’ve handled all the ways that we know that our program can behave badly. We only know of three
of them, but we now have a good foundation of tests for the CLI that we could build on top of if we were to
find more.
There’s one more thing that we need to consider: During this testing, we have neglected to test for the case
when things can go right:

1 $ ./toy_robot commands.txt
2 Robot is at (0, 4) and is facing north

We should most definitely write a test for this too!

Handling what happens when things go right

Let’s write a quick test for making sure that the CLI module behaves itself when we give it a valid filename:
Building the CLI 115

test/toy_robot/cli_test.exs

1 test "handles commands and reports successfully" do


2 commands_path = Path.expand("test/fixtures/commands.txt", File.cwd!)
3
4 output = capture_io fn ->
5 ToyRobot.CLI.main([commands_path])
6 end
7
8 expected_output = """
9 The robot is at (0, 4) and is facing NORTH
10 """
11
12 assert output |> String.trim == expected_output |> String.trim
13 end

This test takes a path of test/fixtures/commands.txt, and passes it to Path.expand which will expand the path
into a full path. On my computer, this is: /Users/ryanbigg/code/toy_robot/test/fixtures/commands.txt. This
path will be different on your machine. When we pass this path to ToyRobot.CLI.main/1, the output should
tell us where the robot is at. This is because our list of commands will contain a REPORT. Let’s create that file
containing the commands before we forget:

test/fixtures/commands.txt

1 PLACE 1,2,NORTH
2 MOVE
3 LEFT
4 MOVE
5 RIGHT
6 MOVE
7 REPORT

Why do we place this file at test/fixtures? “Fixtures” is a term that we use in the programming community
to refer to fixed sets of data that we use in our tests. These fixtures are unlikely to change, and so we can
write some tests based on them. We can also test our CLI’s entire intended purpose this way, complete with
a real file.
Running this test will (for a change) show instant success:

1 18 doctests, 33 tests, 0 failures

It now seems like we’re handling all the happy and unhappy paths of our CLI. Implementing this part of the
program didn’t take very long at all, thanks to the groundwork that we laid in Chapter 5.

Verifying the robot’s behaviour

What else is there left to do?


Building the CLI 116

It isn’t immediately obvious if you think about it. Our program is working as far as we know and we’ve
rigorously tested each part of the program as we have developed it.
All of our tests are green, and so nothing there is indicating what else we could do. That means that it is
now time to review all of our instructions again to make sure we’re meeting the requirements there.
If you flick back to Chapter 1, you can see all of our instructions. We’ll go through them here bit-by-bit and
we can validate things are working as intended.
Let’s start. I’ll quote each sentence from the instructions and talk about it afterwards.

The application is a simulation of a toy robot moving on a square tabletop, of dimensions 5 units
x 5 units.

I want to focus here on the last bit of this sentence: “the tabletop, of dimensions 5 units x 5 units”. We have
one of these! We build a table in ToyRobot.CommandInterpreter:

lib/toy_robot/command_interpreter.ex

1 def handle({:place, placement}) do


2 %Table{
3 north_boundary: 4,
4 east_boundary: 4,
5 } |> Simulation.place(placement)
6 end

But hang on, the instructions say “5 units by 5 units”, but we’ve got the number 4 here instead! That might
seem like a mistake, until you remember we start counting at 0 in this exercise:

Tabletop with diagonal coordinates

We have 5 diagonal squares, and therefore we can see that the table is “5 units x 5 units”. So that’s verified!

There are no other obstructions on the table surface. The robot is free to roam around the surface
of the table.

We didn’t code any obstructions, so this one seems safe.


Building the CLI 117

Any movement that would result in the robot falling from the table is prevented, however further
valid movement commands are still allowed.

This one is easy enough to validate. What we need to see is that if we give our robot enough MOVE commands
that it would not fall off the edge of the table. We can validate this behaviour by updating our commands.txt
file to this:

commands.txt

1 PLACE 0,4,NORTH
2 MOVE
3 REPORT

This PLACE command would put the robot right up against the north-west boundary of our table:

Robot at north west corner, facing north

Therefore if the robot was to be given a MOVE command, the robot should not move; it should remain at (0,
4).

Let’s run this new commands.txt file using our CLI to verify that the robot remains in place:

1 ./toy_robot commands.txt

Here’s the output:

1 The robot is at (0, 4) and is facing NORTH

Our robot did not move! This means that the robot will not move past the northern boundary of our table.
But what we need to consider is that there are three other boundaries as well.
We could update commands.txt to test for those boundaries, but what would be better is to extend our existing
test suite with new test cases. This way, we can ensure in an automated fashion that our program will
continue to function. I think that the tests for CommandRunner are a suitable place for us to extend: they already
test a series of commands. Let’s go ahead and do that now for all four boundaries of our table to make sure
the robot cannot move outside of them:
Building the CLI 118

test/toy_robot/command_runner_test.exs
1 test "robot cannot move past the north boundary" do
2 %Simulation{robot: robot} = [
3 {:place, %{east: 0, north: 4, facing: :north}},
4 :move
5 ]
6 |> CommandRunner.run()
7
8 assert robot.north == 4
9 end
10
11 test "robot cannot move past the east boundary" do
12 %Simulation{robot: robot} = [
13 {:place, %{east: 4, north: 0, facing: :east}},
14 :move
15 ]
16 |> CommandRunner.run()
17
18 assert robot.east == 4
19 end
20
21 test "robot cannot move past the south boundary" do
22 %Simulation{robot: robot} = [
23 {:place, %{east: 0, north: 0, facing: :south}},
24 :move
25 ]
26 |> CommandRunner.run()
27
28 assert robot.north == 0
29 end
30
31 test "robot cannot move past the west boundary" do
32 %Simulation{robot: robot} = [
33 {:place, %{east: 0, north: 0, facing: :west}},
34 :move
35 ]
36 |> CommandRunner.run()
37
38 assert robot.east == 0
39 end

These 4 extra tests will make sure that the robot cannot move past any of the boundaries, and we can run
them along with all the other tests of our application. Now isn’t that better than editing commands.txt and
manually testing it? That was a rhetorical question. Of course it is!
Let’s run these tests now and see what happens:

1 18 doctests, 37 tests, 0 failures

These tests are all passing and that means that our robot cannot go past the boundaries of the table in any
of the 4 directions. We have successfully validated that part of our instructions. Let’s move onto the next
part.
Building the CLI 119

The application reads a file using a name passed in the command line, the following commands
are valid:

1 PLACE X,Y,F
2 MOVE
3 LEFT
4 RIGHT
5 REPORT

• PLACE will put the toy robot on the table in position X,Y and facing NORTH, SOUTH, EAST
or WEST.
• The origin (0,0) is the SOUTH WEST most corner.
• All commands are ignored until a valid PLACE is made.
• MOVE will move the toy robot one unit forward in the direction it is currently facing.
• LEFT and RIGHT rotates the robot 90 degrees in the specified direction without changing
the position of the robot.
• REPORT announces the X,Y and F of the robot.

We definitely have this part of our program working. We have just tested most of its parts by running ./toy_-
robot. I think it’s safe to say that we have this part done.

As for the commands themselves, we’re testing all of these within the tests for CommandRunner, and so we’re
good there too.

The file is assumed to have ASCII encoding.

We don’t mind too much at the moment about the encoding of the file. We take it as it comes.

It is assumed that the PLACE command has only one space, that is PLACE 1, 2, NORTH is an
invalid command.

Ooooh. This is something we haven’t explicitly tested! Let’s see what happens to our program if we try
to handle a PLACE command that has spaces in it. A good place to test this out would be in test/toy_-
robot/command_interpreter_test.exs, since testing this has to do with how the command is processed after
it is read in from the commands file. In fact, we already have a test for invalid commands in this file:

test/toy_robot/command_interpreter_test.exs
1 test "marks invalid commands as invalid" do
2 commands = ["SPIN", "TWIRL", "EXTERMINATE"]
3 output = commands |> CommandInterpreter.interpret()
4 assert output == [
5 {:invalid, "SPIN"},
6 {:invalid, "TWIRL"},
7 {:invalid, "EXTERMINATE"}
8 ]
9 end

This is another case where having existing tests will help us. Let’s add this newfound invalid PLACE command
to the list of commands in this test:
Building the CLI 120

test/toy_robot/command_interpreter_test.exs

1 test "marks invalid commands as invalid" do


2 commands = ["SPIN", "TWIRL", "EXTERMINATE", "PLACE 1, 2, NORTH"]
3 output = commands |> CommandInterpreter.interpret()
4 assert output == [
5 {:invalid, "SPIN"},
6 {:invalid, "TWIRL"},
7 {:invalid, "EXTERMINATE"},
8 {:invalid, "PLACE 1, 2, NORTH"}
9 ]
10 end

Next up, we can run the test to check if that command is correctly marked as invalid or not.

1 1) test marks invalid commands as invalid (ToyRobot.CommandInterpreterTest)


2 test/toy_robot/command_interpreter_test.exs:12
3 ** (ArgumentError) argument error
4 code: output = commands |> CommandInterpreter.interpret()
5 stacktrace:
6 :erlang.binary_to_integer(" 2")
7 (toy_robot) lib/toy_robot/command_interpreter.ex:31: ToyRobot.CommandInterpreter.do_process/1
8 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
9 (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
10 test/toy_robot/command_interpreter_test.exs:14: (test)

It seems like our code in ToyRobot.CommandInterpreter is having trouble handling this PLACE 1, 2, NORTH
command. It’s attempting to take a string of “ 2” (that’s including the space!) and to convert it to an
integer. This won’t do! We’re going to need to be smarter about how we handle the PLACE command in
CommandInterpreter. We’re going to need to use regular expressions!

Pattern matching in Elixir can only get us so far. Currently in the do_process/1 function that handles PLACE,
we’re only matching on the first bit of that command:

lib/toy_robot/command_interpreter.ex

1 defp do_process("PLACE" <> rest) do

The problem that we’re seeing now lies in that rest part. There are spaces there that make this command
invalid. We’re unable to match on whether or not that command has spaces in Elixir, and so we’ll need to
use some regular expressions.
What we need to match is a string in this exact order:

• The start of the line


• The word PLACE
• A space
• A number of any length
• A comma
Building the CLI 121

• Another number of any length


• One of the directions of NORTH, EAST, SOUTH or WEST
• The end of the line

A regular expression that would accomplish this is this one:

1 format = ~r/\APLACE (\d+),(\d+),(NORTH|EAST|SOUTH|WEST)\z/

The \A here matches the start of the line. The word PLACE matches literally the word place. The two (\d+)
here will capture any numbers in our string and remember them. Finally, the (NORTH|EAST|SOUTH|WEST) part
will capture anything at the end of our string which is one of those directions.
We can try this regular expression out in iex:

1 iex> format = ~r/\APLACE (\d+),(\d+),(NORTH|EAST|SOUTH|WEST)\z/


2 iex> Regex.run(format, "PLACE 1,2,NORTH")
3 ["PLACE 1,2,NORTH", "1", "2", "NORTH"]

The list that we get back here shows us that the attempt to run the regular expression over the string was
successful. The first element in the list is the string itself. The parts of the string that we’ve captured are
the remaining elements of the list.
If the regular expression fails to match, like it should in the case of our invalid PLACE command then we will
see this happen:

1 iex> format = ~r/\APLACE (\d+),(\d+),(NORTH|EAST|SOUTH|WEST)\z/


2 iex> Regex.run(format, "PLACE 1, 2, NORTH")
3 nil

We can use this to our advantage in how we handle the PLACE command back in CommandInterpreter by rewriting
that do_process/1 function to this:

lib/toy_robot/command_interpreter.ex

1 defp do_process(("PLACE" <> _rest) = command) do


2 format = ~r/\APLACE (\d+),(\d+),(NORTH|EAST|SOUTH|WEST)\z/
3 case Regex.run(format, command) do
4 [_command, east, north, facing] ->
5 to_integer = &String.to_integer/1
6
7 {:place, %{
8 east: to_integer.(east),
9 north: to_integer.(north),
10 facing: facing |> String.downcase |> String.to_atom
11 }}
12 nil -> {:invalid, command}
13 end
14 end
Building the CLI 122

With this new function, we’re matching the whole of the command against the regular expression. We’re
then checking the result by using a case statement. If that Regex.run/2 call returns a 4-element list, then we
know that the command is valid, and we can handle it as such. However, if nil is returned then we know
that the command is in the wrong format and so we should return a tuple of {:invalid, command}.
This little code change should be enough to make our test pass. Let’s run them and find out!

1 18 doctests, 37 tests, 0 failures

This is great! Our program is no longer going to trip up on invalid PLACE commands. This means that we’re
one step closer to finalising the validation of the toy robot program. Let’s move on to the next (and final!)
part of the validation:

All commands must be in upcase, all lower and mixed case commands will be ignored.

This sounds similar to our PLACE command validation. We can add another couple of lines to our command_-
interpreter_test.exs tests to make sure that this part of our instructions are handled. Let’s update that test:

test/toy_robot/command_interpreter_test.exs
1 test "marks invalid commands as invalid" do
2 commands = [
3 "SPIN",
4 "TWIRL",
5 "EXTERMINATE",
6 "PLACE 1, 2, NORTH",
7 "move",
8 "MoVe",
9 ]
10 output = commands |> CommandInterpreter.interpret()
11 assert output == [
12 {:invalid, "SPIN"},
13 {:invalid, "TWIRL"},
14 {:invalid, "EXTERMINATE"},
15 {:invalid, "PLACE 1, 2, NORTH"},
16 {:invalid, "move"},
17 {:invalid, "MoVe"}
18 ]
19 end

We’ve now added two extra commands to our list here, one called move and one called MoVe. This should be
enough to test that lower and mixed-case commands are considered invalid by our program.
Let’s run our tests to see what’s going to happen.

1 18 doctests, 37 tests, 0 failures

There’s nothing to do here! Our program correctly marks the move and MoVe commands as invalid.
That’s it for the validation of our toy robot’s instructions. Our program is fully compliant!
Building the CLI 123

Conclusion

We’ve now finished building our toy robot application. Go and have a play around with it and kick the tyres.
It’s been a relatively long process, but we’ve reached the end now.
We have a framework for our robot in terms of both code and tests and so if we wanted to add more features
to our robot like:

• A MOVE 2 command which would move the robot 2 spaces instead of 1


• A UTURN command which turns the robot two turns to face the opposite direction.

Outside of the robot itself, you could try making the application read from STDIN (like iex does!) rather than
a file. Provide a prompt for the user to type commands into and then have the robot follow them:

1 > PLACE 0,0,EAST


2 > MOVE
3 > MOVE
4 > REPORT
5 => The robot is at (2, 0) and is facing EAST

Another idea: make it possible to pass a --reverse option to the robot which will make it turn in the opposite
direction. So LEFT will make it turn RIGHT, and vice versa.
The Toy Robot is a versatile exercise – as this book has proven – and now that you’ve completed it you should
feel quite accomplished! You have finished a real Elixir project and have tested it all the way through, just
like the pros would do.
We’re not quite done with this book yet. There’s two bonus chapters included at the end of this book. These
are chapters that you will not find in the Ruby version of this book, because these chapters use concepts
that are unique to Elixir. The chapters are:

• Chapter 7: GenServers (Generic Servers) and process supervision – demonstrating how we could hold
the state of a robot in a process, and then send that process messages to update that state. This chapter
will also cover how to recover from our program crashing.
• Chapter 8: Multi-player Toy Robot – now that we have one robot moving around on a grid, what would
happen if we had more than one? We’ll start up many toy robots and move them around on the grid,
getting them to bump into each other.
I The Toy Robot Game
7. A Single, Supervised Player
In these final two chapters of this book, we’re going to do something outside of the regular Toy Robot
exercise. We’re going to take our existing code, extend it, and end up with a “multi-player” toy robot game.
Where we currently have only one robot moving around on the table top, we’ll have as many robots as can
fit on a table top.
You can think of these two chapters as “extra credit” for the Toy Robot assignment. These chapters will
demonstrate how to use some Elixir features we haven’t seen in this book yet: processes, GenServers,
supervisors and process registries. Elixir’s real strengths come from these things. These are the building
blocks that are used for Elixir’s fault-tolerant concurrency, and you can consider them to be “intermediate”
Elixir features.
In this first chapter of this “bonus” part of the book, we’ll write some code that allows us to start a process
using Elixir’s GenServer module to keep a track of where one particular robot is located. We’ll then make it
possible for the robot to “self-destruct”, which will then make this process crash.
That crashing would be considered a pretty big fault in many other programming languages. But in Elixir, it
isn’t! We’ll see how we can rely on Elixir’s DynamicSupervisor to not only start this process going in the first
place, but also to restart it when the process crashes.
Later on, we’ll be able to give our processes unique names, and be able to look them up through a process
registry. This means that even after a process dies and a new one is started, we can continue to communicate
seamlessly with these processes, almost completely ignoring the fact that a part of our program crashed in
the first place.
To get started, we’ll look at that very first part I mentioned: Elixir’s GenServers.

A single player

What’s a GenServer?

A GenServer is an Elixir process that can be used to remember values within our program (referred to as a
program’s “state”), and to execute code concurrently.

The benefit of using a GenServer rather than building something like it ourselves is that in doing so we rely
on features built-in to the Elixir language. For example: we would not have to store variables to remember
things for our programs and pass them around; instead, we get the GenServer to do the “remembering” for
us.

Lots of people who see a GenServer for the first time do not immediately understand what the real purpose of
using one is. It’s okay if you feel the same way. As you go through this chapter, you will get a much clearer
idea of why we would use a GenServer here.

For more information, I would also recommend reading the GenServer documentation over here:
A Single, Supervised Player 126

https://hexdocs.pm/elixir/GenServer.html

To keep the initial implementation of our game simple, we’ll use the Robot module to build a single player
game in combination with our GenServer. Later on, we’ll use the Simulation module too so that we can detect
if a robot’s movements would push it past the boundary of a table. For now, let’s keep this simple.
Previously, we’ve kept either a robot or a simulation around and had to pass it to each individual function
in order to get an updated robot:

1 %Robot{north: 0, east: 0, facing: :north}


2 |> Robot.move
3 |> Robot.move
4 |> Robot.move
5 |> IO.inspect
6
7 # %Robot{north: 3, east: 0, facing: :north}

What we’ll do here is different. We’ll start up a GenServer process, which will know about a Robot and its
position. We’ll then send that server some commands to move the robot. The GenServer process will then,
for as long as its running, remember where our robot is.
To get started, we will create a new module to house our GenServer code and we’ll call it ToyRobot.Game.Player.
Let’s create this new file now:

lib/toy_robot/game/player.ex

1 defmodule ToyRobot.Game.Player do
2 use GenServer
3
4 def init(robot) do
5 {:ok, robot}
6 end
7
8 def handle_call(:report, _from, robot) do
9 {:reply, robot, robot}
10 end
11 end

The ToyRobot.Game namespace

For these next two chapters, all of the new mdules that we create will have a ToyRobot.Game prefix. This is so
that we can keep our game code separate from the regular exercise code.

We’ll reach over into the “regular” code from time-to-time, but as a rule: we’ll never change that “regular”
code so that it knows about the ToyRobot.Game modules. This is just so that we will keep it possible to run the
Toy Robot exercise as we originally intended to within the first 6 chapters of this book.
A Single, Supervised Player 127

We’re calling this module a Game.Player, as it will track the current state of our solitary player. In the next
chapter we’ll use this new GenServer to start many different players.
This GenServer starts out simple: it just knows about a robot. But later on, we’ll bring in Table and Simulation
too. Just like in our original exercise’s code! This isn’t much code to look at right now, but we’ll build it out
very shortly.
We have the init function here that is responsible for doing whatever is required to setup our Game.Player
server. In this case, it takes the robot, and then returns {:ok, robot}. This indicates that that init/1 function
has successfully initialized our server. The second element of this tuple is the initial state for our GenServer.
The handle_call/3 function below is a GenServer callback function. This function will be invoked when we
make a call to this GenServer via the GenServer.call/2 function, which we’re about to see happen. We do not
call this function directly ourselves, but instead it is called by some internal Elixir code.
Lett’s start a new iex -S mix session, and use this new GenServer we have created:

1 iex> starting_position = %ToyRobot.Robot{north: 0, east: 0, facing: :north}


2 iex> {:ok, player} = GenServer.start(ToyRobot.Game.Player, starting_position)
3 {:ok, #PID<0.190.0>}
4 iex> GenServer.call(player, :report)
5 %{east: 0, facing: :north, north: 0}

Let’s step through this code.


First off, we define the starting position for our robot to be at the SOUTH WEST corner ((0, 0)) of our board,
facing NORTH.
We then start a GenServer with GenServer.start/2, which will start our GenServer process. Elixir’s internal
code will then call ToyRobot.Game.Player.init/1 function to initialize the state of this server. That init call is
successful, and so we get back a tuple containing {:ok, player}, where player is the PID of our new server
process. Your PID number might be different to the one I have shown here. That’s alright; every process in
Elixir is given a unique PID – a Process identifier.
To send messages to this server process, we must use GenServer.call/2, passing it that player PID as the first
argument (the thing that we’re sending a message to), and then :report as the 2nd argument (the message
we’re sending). You’re saying to the GenServer, “hey, can you do this thing for me?” and the GenServer can
decide how it would like to handle that message via the handle_call/3 definitions inside it.
We have a handle_call/3 function that will definitely handle this :report message. It returns a 3-element
tuple of {:reply, robot, robot}. The first element indicates that the GenServer will issue a reply back to the
thing that called GenServer.call. The 2nd element is what we’re going to give back as a return value when
GenServer.call runs. The 3rd element is the state of the GenServer, which can change after a call, but it won’t
this time around.
This GenServer isn’t any more useful than storing a robot in a variable and asking for that variable every time
we wanted to know where the robot is positioned on the board. What would be better is if the GenServer was
to modify the state of our robot, and to remember it too. For instance, when we ask the robot to move, we
will modify the server’s state.
A Single, Supervised Player 128

To move our robot and to keep a track of where it ends up we could use another handle_call/3 function. But
I think a move instruction should be handled differently – as a cast message, not a call message.
A cast message is one that is sent to the GenServer and we would use it in cases where we do not expect a
reply. That’s different to a call message, where we will definitely want a reply. We want to know for instance
the location of our robot when we ask it to “report”!
So let’s add a handle_cast/2 function to our GenServer that will move the robot when we send a message asking
it to move:

lib/toy_robot/game/player.ex

1 def handle_cast(:move, robot) do


2 {:noreply, robot |> Robot.move}
3 end

This new handle_cast/2 function will act on :move messages that are received by this GenServer. When this
server receives this message, it will take the current known robot, pass it to Robot.move/1. Then this handle_-
cast/2 function will update the state of our server to indicate that the robot has moved. We say {:noreply,
...} here because handle_cast messages do not reply.

To use the Robot module here, we’re going to need to add an alias to the top of the ToyRobot.Game.Player
module:

lib/toy_robot/game/player.ex

1 defmodule ToyRobot.Game.Player do
2 use GenServer
3
4 alias ToyRobot.Robot
5 ...
6 end

Rather than restarting the iex prompt to load this code, we can tell IEx to recompile the ToyRobot.Game.Player
module by calling the r/1 function that is built-in to IEx:

1 iex> r ToyRobot.Game.Player
2 warning: redefining module ToyRobot.Game.Player (current version loaded from _build/dev/lib/toy_robot/ebin\
3 /Elixir.ToyRobot.Game.Player.beam)
4 lib/toy_robot/game/player.ex:1
5
6 {:reloaded, ToyRobot.Game.Player, [ToyRobot.Game.Player]}

We can safely ignore the warning here. Now that our module has been reloaded, we can send a :move message
to our player server by using a few calls to GenServer.cast/2:
A Single, Supervised Player 129

1 iex> GenServer.cast(player, :move)


2 :ok
3 iex> GenServer.cast(player, :move)
4 :ok
5 iex> GenServer.cast(player, :move)
6 :ok

Getting back :ok here indicates that the “casting” (or “sending”) of the message to the server worked
correctly. But it’s not really indicating to us that the state has indeed changed. Let’s check that the robot
has actually moved by calling the server with a :report message:

1 iex> GenServer.call(player, :report)


2 %ToyRobot.Robot{east: 0, facing: :north, north: 3}

What’s happening here is that the player server is keeping a track of where the robot and will update the
state each time the GenServer receives a :move call, thanks to the new handle_cast/2 function that we have
added.
This is really the barest sort of GenServer we can have in Elixir. The whole purpose of this is to have
something that will keep track of a robot’s state and remember it for us, rather than us having to care
about how to manage that ourselves. We do not have to manage that state explicitly by passing it around
in variables any more, instead we now have processes.
Hopefully with these two small examples of :report and :move messages, you can have a clear idea of when
we should use GenServer.call and GenServer.cast, and what the behaviour is of each of those. We will see a
few more examples of that throughout this book!
What I’d like to show you now is how we could tidy up the GenServer’s code and how we could test such a
thing too. Once we’re done with these two short sections, we’ll get deeper into processes and supervisors
with Elixir. Then you’ll see some pretty compelling reasons to have a GenServer!

Tidying up the GenServer code

The GenServer code itself at the moment is quite tidy looking. We have three functions: one init/1 function,
one handle_call/3 and one handle_cast/2 function. How on earth could we tidy this up?
Well, the messiness doesn’t lie within the module itself, but in the code that calls the GenServer. What we’ll
do in this section is add more code to the module itself, so that we can tidy up the code that would be used
to talk to the GenServer.
The first place I would like to start at is this line:

1 {:ok, player} = GenServer.start(ToyRobot.Game.Player, starting_position)

This doesn’t seem so bad, but what if we could shorten it to just:


A Single, Supervised Player 130

1 {:ok, player} = Player.start(starting_position)

Well, we can do just this with two tricks:

1. We can use an alias when we refer to this module in any code (including IEx!)
2. We can define a start/1 function on the ToyRobot.Game.Player module to call GenServer.start/2 for us.

Let’s add that start/1 function to the ToyRobot.Game.Player module now:

lib/toy_robot/game/player.ex

1 defmodule ToyRobot.Game.Player do
2 use GenServer
3
4 alias ToyRobot.Robot
5
6 def start(position) do
7 GenServer.start(__MODULE__, position)
8 end
9
10 ...
11 end

And now let’s see if this works for us, and we’ll remember to use alias too:

1 iex> alias ToyRobot.Game.Player


2 iex> r Player
3 iex> starting_position = %ToyRobot.Robot{north: 0, east: 0, facing: :north}
4
5 iex> {:ok, player} = Player.start(starting_position)
6 {:ok, #PID<0.195.0>}

Note here that we’re using r/1 to reload an aliased module. It works in this way too!
Okay, that was a really quick win! This makes our code for starting the player process a little shorter.
Before we had:

1 {:ok, player} = GenServer.start(ToyRobot.Game.Player, starting_position)

And now we have:

1 iex> {:ok, player} = Player.start(starting_position)


2 {:ok, #PID<0.195.0>}

Now let’s do the same for our report and move invocations too.
We’ll change them from this:
A Single, Supervised Player 131

1 GenServer.cast(player, :move)
2 GenServer.call(player, :report)

To this:

1 Player.move(player)
2 Player.report(player)

Why are we going to do this? A god reason why we will add the move and report functions into the
ToyRobot.Game.Player module is that it will provide a clearer interface to this module. For instance, we’ll
see these functions appear in documentation for your library:

Game Player Docs

But where does that documentation come from?

The documentation screenshot shown above, is from a Hex package called ex_doc. You can follow the
installation instructions for setting it up here:

https://hexdocs.pm/ex_doc/readme.html#using-exdoc-with-mix

And it will also appear in autocomplete prompts in the iex prompt:

1 iex> ToyRobot.Game.Player
2 child_spec/1 init/1 move/1 report/1
3 start/1

(You can bring this up by typing ToyRobot.Game.Player and then hitting Tab twice!)
In both of these examples, we can see an additional function: child_spec/1. This is automatically added to
our module when we run use GenServer. It defines how our GenServer should be ran under a supervisor – which
we’ll get to later.
So hopefully by now you can see the reasons for adding the move and report functions to ToyRobot.Game.Player!
We get:

• Documentation in ToyRobot.Game.Player that indicates this function exists


• These functions appearing in tab-complete in iex prompts
• One place where all the functions for working with a player server reside

These are great reasons to add these functions to the module itself! Let’s do that now, adding them directly
under the init function:
A Single, Supervised Player 132

lib/toy_robot/game/player.ex

1 def report(player) do
2 GenServer.call(player, :report)
3 end
4
5 def move(player) do
6 GenServer.cast(player, :move)
7 end

We’re adding these functions directly under init because we these functions will be the public interface for
this module. The handle_call functions are ones that Elixir code will use. We want all our public functions
to be grouped together for code clarity.
With these functions in this module, we’ll be able to use them within our iex prompt:

1 iex> r Player
2 iex> Player.report(player)
3 %ToyRobot.Robot{east: 0, facing: :north, north: 0}
4 iex> Player.move(player)
5 %ToyRobot.Robot{east: 0, facing: :north, north: 1}

This is a great step forward for our code, and will make things a little clearer. What we have now in
ToyRobot.Game.Player is three separate functions: one for starting up a GenServer and two for interacting
with it.
We’ve been working on this module now to get our head around GenServers. It’s a tricky topic to understand
right away in Elixir. Hopefully this section has shed some light on why we would want to use GenServers in
our project.
So far, we’ve been testing this GenServer manually ourselves, just to learn about them. The strong pervading
theme of this book has been to write as many tests as possible for code, but we do not have any tests for
this GenServer code yet. Let’s write some to finish up our work on this first GenServer.

Writing tests for our GenServer

Why would we even want to write tests? We have made sure that the code is working already! And it’s so
simple. What could possibly go wrong?
Well, things change and code is no exception. To help avoid changes to our GenServer breaking our current
behaviour, we should write some tests that check that current behaviour is always maintained. This will
provide us with a stable bedrock that we could build on top of if we wished to add more functionality to this
module down the road.
But how do we test one of these? Here’s a test I’ve prepared for the report/1 function:
A Single, Supervised Player 133

test/toy_robot/game/player_test.exs

1 defmodule ToyRobot.Game.PlayerTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.Game.Player
5 alias ToyRobot.Robot
6
7 describe "report" do
8 setup do
9 starting_position = %Robot{north: 0, east: 0, facing: :north}
10 {:ok, player} = Player.start(starting_position)
11 %{player: player}
12 end
13
14 test "shows the current position of the robot", %{player: player} do
15 assert Player.report(player) == %Robot{
16 north: 0,
17 east: 0,
18 facing: :north
19 }
20 end
21 end
22 end

In this new test file, we have a setup block that starts up our Player GenServer by calling Player.start/1. The
setup block then returns that player. This is very similar to code we saw for robot_test.exs in Chapter 2, and
so we should be familiar with how setup works from that.
The test block receives the player as its 2nd argument, and then calls the Player.report/1 function with that
player as its argument. The test then asserts that the robot is where we think it is: right where we left it!

We can run this test with mix test and see if it works:

1 12 doctests, 38 tests, 0 failures

It does work! Our first test for the GenServer works.


Let’s write another one for the move function:

test/toy_robot/game/player_test.exs

1 describe "move" do
2 setup do
3 starting_position = %Robot{north: 0, east: 0, facing: :north}
4 {:ok, player} = Player.start(starting_position)
5 %{player: player}
6 end
7
8 test "shows the current position of the robot", %{player: player} do
9 :ok = Player.move(player)
10 assert Player.report(player) == %Robot{
A Single, Supervised Player 134

11 north: 1,
12 east: 0,
13 facing: :north
14 }
15 end
16 end

This one is remarkably similar to the report/1 test. The difference is that in the test we call the Player.move/1
function. These two functions both return a Robot struct, but in the case of the move our robot should have
moved forward one spot.
If we run our test, we should see that the assertion that checks that the robot moved forward is satisfied:

1 12 doctests, 39 tests, 0 failures

Looks like it is! As we can see from these two tests, it is relatively painless to start a GenServer and to run
tests for it. In fact, if you compare this with our tests over in robot_test.exs, it’s not too different. In those
tests, we are setting up a Robot struct. In these tests, we’re setting up a Player GenServer.
We’re now in a safe spot with our GenServer. The ToyRobot.Game.Player module has some functions that work
with these GenServer processes, and we have tests for those functions. These functions are relatively simple
and will always work.
But what if we had functions that failed? What would happen if we sent the robot an invalid message? This
is fortunately what we’re going to look at in the very next section!

Let it crash!

One of the mantras in the Elixir community you might hear bandied about is “let it crash!”. The meaning of
this is that when something goes wrong in any process that we start, we should let that failure happen and
then let the system heal itself automatically. We should not spend time too much time worrying about
crashes happening, as they happen within a single process and other processes are isolated from their
effects. In Elixir, we let processes crash.
We’re now going to force a crash of our Player GenServer. We’ll look at what happens when we do this, and
later on we’ll make it so that this server will restart automatically by using another type of process called a
supervisor.

Crashing processes

To crash a process, we need something outside of the ordinary to happen to that process. A super easy way
to make a process crash is to send it a message it doesn’t know how to respond to. We can do that with our
old friend GenServer.call/2. Let’s try it out in an iex -S mix session:
A Single, Supervised Player 135

1 iex> alias ToyRobot.{Game.Player, Robot}


2 iex> starting_position = %Robot{north: 0, east: 0, facing: :north}
3 iex> {:ok, player} = Player.start(starting_position)
4 {:ok, #PID<0.197.0>}
5 iex> GenServer.call(player, :self_destruct)

Take note of the PID here. We’re going to refer to it a lot. Yours might be a different number.
A lot happens when we call our player with a bad message:

1 [timestamp] [error] GenServer #PID<0.197.0> terminating


2 ** (FunctionClauseError) no function clause matching in ToyRobot.Game.Player.handle_call/3
3 (toy_robot) lib/toy_robot/game/player.ex:20: ToyRobot.Game.Player.handle_call(:self_destruct, {#PID<0.\
4 188.0>, #Reference<0.1983368556.1927544836.1906>}, %ToyRobot.Robot{east: 0, facing: :north, north: 0})
5 (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
6 (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
7 (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
8 Last message (from #PID<0.188.0>): :self_destruct
9 State: %ToyRobot.Robot{east: 0, facing: :north, north: 0}
10 Client #PID<0.188.0> is alive
11
12 (stdlib) gen.erl:169: :gen.do_call/4
13 (elixir) lib/gen_server.ex:986: GenServer.call/3
14 (stdlib) erl_eval.erl:677: :erl_eval.do_apply/6
15 (elixir) src/elixir.erl:258: :elixir.eval_forms/4
16 (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
17 (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
18 (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3
19 (iex) lib/iex/evaluator.ex:103: IEx.Evaluator.loop/1
20 ** (exit) exited in: GenServer.call(#PID<0.197.0>, :self_destruct, 5000)
21 ** (EXIT) an exception was raised:
22 ** (FunctionClauseError) no function clause matching in ToyRobot.Game.Player.handle_call/3
23 (toy_robot) lib/toy_robot/game/player.ex:20: ToyRobot.Game.Player.handle_call(:self_destruct, \
24 {#PID<0.188.0>, #Reference<0.1983368556.1927544836.1906>}, %ToyRobot.Robot{east: 0, facing: :north, north:\
25 0})
26 (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
27 (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
28 (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
29 (elixir) lib/gen_server.ex:989: GenServer.call/3

That’s quite a lot to take in. Fortunately, there’s a line break in the middle. Let’s look at everything above
that linebreak first:
A Single, Supervised Player 136

1 [timestamp] [error] GenServer #PID<0.197.0> terminating


2 ** (FunctionClauseError) no function clause matching in ToyRobot.Game.Player.handle_call/3
3 (toy_robot) lib/toy_robot/game/player.ex:20: ToyRobot.Game.Player.handle_call(:self_destruct, {#PID<0.\
4 188.0>, #Reference<0.1983368556.1927544836.1906>}, %ToyRobot.Robot{east: 0, facing: :north, north: 0})
5 (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
6 (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
7 (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
8 Last message (from #PID<0.188.0>): :self_destruct
9 State: %ToyRobot.Robot{east: 0, facing: :north, north: 0}
10 Client #PID<0.188.0> is alive

This first part shows us that the GenServer is terminating, and the reason for that termination is a
FunctionClauseError exception. It shows us that there is no function that matches ToyRobot.Game.Player.handle_-
call/3 with the first argument being :self_destruct.

The “last message” part near the bottom shows us that we received a message from a different process (our
IEx prompt), and what that message was. Other information included here is also the current state of the
Player process, as well as whether or not the calling process is still alive. All of this information can come
in handy if we wanted to debug why this GenServer crashed… but in our case we know perfectly well why it
crashed.
The other part of this stacktrace show us much of the same information, and so we can treat it like a duplicate
version of the first part. The 2nd part of this stacktrace is what we get instead of a “return” value from the
use of GenServer.call. It shows an exit from our process, and provides information about why the process
crashed.
One big issue with our process crashing is that once it has crashed, it is gone for good. We can check if the
process is alive by calling Process.alive?/1:

1 iex> Process.alive?(player)
2 false

Or we could check by trying to send it more messages:

1 GenServer.call(player, :self_destruct)
2 ** (exit) exited in: GenServer.call(#PID<0.197.0>, :self_destruct, 5000)
3 ** (EXIT) no process: the process is not alive or there's no process
4 currently associated with the given name, possibly because its application
5 isn't started
6 (elixir) lib/gen_server.ex:989: GenServer.call/3

He’s dead, Jim. We’ll have to start up a new process if we want to move our robot on a tabletop. This is only
about the external case, of a bad message coming from outside the process. Let’s also take some time now
to look at what happens if the process crashes due to something inside it breaking.
A Single, Supervised Player 137

A robot falls to its doom

In the original version of our toy robot exercise we made it so that if the robot was asked to move past the
boundary of the table that the movement would be prevented. What if, in this game, we were to remove
that restriction? What if a player’s toy robot could move past the boundary of the table, but if it did then
the player process would crash?
We built all the functions we’ll need for this in earlier chapters of the book, and so it should be a simple
matter to use these functions in our Player module. We can use functions such as Simulation.move/1 to
attempt to move a robot and determine if that movement would be valid or invalid.
In order to use that Simulation.move/1 function, we will need a simulation! All that we have at the moment in
our Player module is a robot. For a simulation, we need a robot, which we have, and a table,which we don’t
have. So let’s fix this by changing our Player.init/1 function to this:

lib/toy_robot/game/player.ex

1 def init(robot) do
2 simulation = %Simulation{
3 table: %Table{
4 north_boundary: 4,
5 east_boundary: 4,
6 },
7 robot: robot
8 }
9
10 {:ok, simulation}
11 end

Since we’re now using Simulation and Table here, we will need to change our alias line at the top of the
module:

lib/toy_robot/game/player.ex

1 defmodule ToyRobot.Game.Player do
2 use GenServer
3
4 alias ToyRobot.{Simulation, Table}

Our Player GenServer will now work with a simulation, instead of a robot. This means that we will be able
to use the Simulation.move/1 function in order to determine if a robot’s movements are valid or not.
Before we get to that, we need to check to make sure that we haven’t broken anything. A good way to do
that is to run our tests with mix test. We’ll see that we have indeed broken things:
A Single, Supervised Player 138

1 1) test report shows the current position of the robot (ToyRobot.Game.PlayerTest)


2 test/toy_robot/game/player_test.exs:14
3 Assertion with == failed
4 code: assert Player.report(game) == %Robot{north: 0, east: 0, facing: :north}
5 left: %ToyRobot.Simulation{
6 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 0},
7 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
8 }
9 right: %ToyRobot.Robot{east: 0, facing: :north, north: 0}
10 stacktrace:
11 test/toy_robot/game/player_test.exs:15: (test)
12
13 ...
14 2) test move shows the current position of the robot (ToyRobot.Game.PlayerTest)
15 test/toy_robot/game/player_test.exs:30
16 ** (exit) exited in: GenServer.call(#PID<0.239.0>, :move, 5000)
17 ** (EXIT) an exception was raised:
18 ** (FunctionClauseError) no function clause matching in ToyRobot.Robot.move/1

There are two breakages here: one for report and one for move. Let’s look at the report breakage first. This test
is breaking because we’re getting back a Simulation struct, rather than a Robot struct as the test is expecting.
To fix this test, we can change the handle_call/3 function that handles :report messages to call the
Simulation.report/1 function to get the correct data:

lib/toy_robot/game/player.ex

1 def handle_call(:report, _from, simulation) do


2 {:reply, simulation |> Simulation.report, simulation}
3 end

This function will now reply with the output of Simulation.report/1, and keep the state of the Player server
as-is. That will make our first test happy.
To fix the move test, we’ll need to make similar changes to the handle_cast/2 function that handles :move
messages:

lib/toy_robot/game/player.ex

1 def handle_cast(:move, simulation) do


2 {:ok, new_simulation} = simulation |> Simulation.move()
3 {:noreply, new_simulation}
4 end

We’re moving the robot using Simulation.move/1 here, as that will check the table’s boundaries before it moves
the robot. As a reminder: If the movement is valid, this function will return {:ok, simulation}, and if its
invalid then this function will return {:error, :at_table_boundary}. We’ll handle that latter case soon. For
now, let’s run and look at our tests again:
A Single, Supervised Player 139

1 1) test move moves the robot forward one space (ToyRobot.Game.PlayerTest)


2 test/toy_robot/game/player_test.exs:30
3 Assertion with == failed
4 code: assert Player.move(game) == %Robot{north: 1, east: 0, facing: :north}
5 left: %ToyRobot.Simulation{
6 robot: %ToyRobot.Robot{east: 0, facing: :north, north: 1},
7 table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}
8 }
9 right: %ToyRobot.Robot{east: 0, facing: :north, north: 1}
10 stacktrace:
11 test/toy_robot/game/player_test.exs:31: (test)

The tests are still failing here, because the output does not match what the test expects. The output is now
a little more complex, involving a table and a simulation. Let’s change the test to adjust for this new output:

test/toy_robot/game/player_test.ex

1 describe "move" do
2 setup do
3 starting_position = %Robot{north: 0, east: 0, facing: :north}
4 {:ok, player} = Player.start(starting_position)
5 %{player: player}
6 end
7
8 test "moves the robot forward one space", %{player: player} do
9 %{robot: robot} = Player.move(player)
10
11 assert robot == %Robot{
12 north: 1,
13 east: 0,
14 facing: :north
15 }
16 end
17 end

That should help our tests now. We’re extracting out the robot key’s value from the return value – a Simulation
struct – of Simulation.move/1, and that will enable us to check that the robot has moved forward again.
If we run our tests again, we’ll see that they are all working once more:

1 12 doctests, 39 tests, 0 failures

That’s great! We’re now able to move our robot on a table within a simulation. This will allow us to simulate
the robot falling to its (inevitable) doom and will help us crash our process.
Let’s see one crash right now by experimenting in iex -S mix:
A Single, Supervised Player 140

1 iex> alias ToyRobot.{Game.Player, Robot}


2 iex> starting_position = %Robot{north: 4, east: 0, facing: :north}
3 iex> {:ok, player} = Player.start(starting_position)
4 {:ok, #PID<0.145.0>}

We now have a robot right up against the northern boundary of the table. If this robot was to move another
step forward, it would go over the boundary of the table and fall to its doom. Let’s give it a push:

1 iex> Player.move(player)
2 [timestamp] [error] GenServer #PID<0.145.0> terminating
3 ** (MatchError) no match of right hand side value: {:error, :at_table_boundary}
4 (toy_robot) lib/toy_robot/game/player.ex:39: ToyRobot.Game.Player.handle_call/3
5 (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
6 (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
7 (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
8 Last message (from #PID<0.203.0>): :move
9 State: %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 0, facing: :north, north: 4}, table: %ToyRobot.Ta\
10 ble{east_boundary: 4, north_boundary: 4}}
11 Client #PID<0.203.0> is alive

The robot passes over the boundary of the table and the Player process crashes! We could keep doing this
time and time again: start a robot against the northern boundary and try to move it. But we’re not sadists,
so we won’t.
We’re benevolent programmers, and we would love if the processes that represented our robots were to
restart automatically if the robots were to come to any harm. When that restart happens, the robot should
resume its original position.
It’s time to look at doing this now with a feature called Supervisors.

Watching processes with supervisors

One of Elixir’s super powers is the ability to monitor the processes within an application and to restart those
processes if things go wrong. Like in the case where a process receives a bad message. This is the one of the
really great advantages of using a GenServer within Elixir: we can rely on supervisors to start these processes,
and also to restart them if things go wrong.
What we’ll do now is to look at how we can use a type of supervisor called a DynamicSupervisor to start up a
Player process, and to automatically restart that process if it crashes.

What is a DynamicSupervisor?

A DynamicSupervisor is not your usual type of supervisor in Elixir. Most applications would use a Supervisor
instead.

The difference between DynamicSupervisor and Supervisor is that DynamicSupervisors start, and then, over the
life of a program’s execution, are ordered to manually supervise processes. A Supervisor on the other hand
knows in advance what processes it must start first and that list cannot be changed over the course of the
program’s execution.
A Single, Supervised Player 141

We’re going to be using a DynamicSupervisor in this chapter because it suits our purposes: we want to
dynamically start Player processes, whenever it suits us.

You can read more about supervisors here:

• Supervisor: https://hexdocs.pm/elixir/Supervisor.html
• DynamicSupervisor: https://hexdocs.pm/elixir/DynamicSupervisor.html$

To begin with, we need to start a DynamicSupervisor process:

1 iex> {:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)


2 {:ok, #PID<0.182.0>}

To start this supervisor, we need to use start_link/1 and pass it a strategy. This strategy tells the supervisor
what to do in the case of a process crashing. The one_for_one option means to restart one process when one
crashes. You can see the other options here: https://hexdocs.pm/elixir/Supervisor.html#module-strategies.
As you can see from the output here, supervisors are themselves processes – as indicated by the tuple that
we get back and the 2nd element of that tuple being a PID.
To start a Player process that’s attached to this supervisor, we need to use DynamicSupervisor.start_child/2:

1 iex> alias ToyRobot.{Game.Player, Robot}


2 iex> spec = {Player, %Robot{north: 4, east: 0, facing: :north}}
3 iex> DynamicSupervisor.start_child(sup, spec)

In this code, we’re building up a child specification (or “child spec”) and assigning it to the spec variable. This
tuple tells how we want to start our child process. The first element in the tuple says that we want to use
the Player module. The second element is what will be passed as arguments to the function that starts our
player within the Player module. You might expect this start function to be the ToyRobot.Game.Player.start/1
function and also expect the above code to work, but you’re going to be met with some disappointment
here:

1 {:error,
2 {:undef,
3 [
4 {ToyRobot.Game.Player, :start_link,
5 [%ToyRobot.Robot{east: 0, facing: :north, north: 0}], []},
6 {DynamicSupervisor, :start_child, 3,
7 [file: 'lib/dynamic_supervisor.ex', line: 690]},
8 {DynamicSupervisor, :handle_start_child, 2,
9 [file: 'lib/dynamic_supervisor.ex', line: 676]},
10 {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 661]},
11 {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 690]},
12 {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}
13 ]
14 }
15 }
A Single, Supervised Player 142

This is confusing at first glance. It looks like a bunch of gibberish! What this is an error that is returned
from DynamicSupervisor.start_child/2, and it shows that there’s an undefined function – indicated by {:error,
:undef, ...}. The third element in this tuple is a list, and the first element from that list shows us what that
undefined function is: it’s one called start_link/1 within the ToyRobot.Game.Player module.
You might have expected to see a FunctionClauseError in this case – as we’ve seen plenty of times already
when we have had undefined functions! The DynamicSupervisor module behaves a little differently, and
returns this tuple instead.
So why is this expecting a new function called start_link/1 and not relying start/1? This comes down to how
process supervision works within Elixir. When we’re supervising a process, we must link the supervisor with
its child process. The way we indicate to Elixir that this Game.Player process is linked to our DynamicSupervisor
is by having a start_link function within this module that then uses GenServer.start_link/3. Let’s add this
now and talk about it a bit more:

lib/toy_robot/game/player.ex
1 defmodule ToyRobot.Game.Player do
2 use GenServer
3
4 def start_link(robot) do
5 GenServer.start_link(__MODULE__, robot)
6 end
7
8 ...

This start_link/1 function is the one that our supervisor will call when we call DynamicSupervisor.start_-
child/2. In this function, we use GenServer.start_link/2 and this will indicate to Elixir that we’re starting a
linked process. We’re not exactly telling it what process to link to here, but Elixir is able to figure this out
by using the calling process. What happens is this:

1. iex (Process #1) calls DynamicSupervisor.start_child/2


2. This call sends a message to sup (Process #2) to start a child process
3. sup (Process #2) calls Game.Player.start_link/1 to start the player (Process #3)

Here’s the above list in picture form:

Linked processes

The calling process in this case is Process #2, the sup process. So when Elixir sees GenServer.start_link, the
calling process is the supervisor. This will then result in the supervisor and Game.Player process being linked
together.
What’s the benefit of linking processes like this? The main benefit is that the supervisor will be alerted
when the player process dies. Let’s try starting that player process up again. It should work now that we
have a Player.start_link/1 function:
A Single, Supervised Player 143

1 iex> r Player
2 iex> {:ok, player} = DynamicSupervisor.start_child(sup, spec)
3 {:ok, #PID<0.228.0>}

We can be sure that this new player process is definitely linked to the supervisor by using Supervisor.which_-
children/1:

1 iex> Supervisor.which_children(sup)
2 [
3 {:undefined, #PID<0.228.0>, :worker, [ToyRobot.Game.Player]}
4 ]

This function will list all of the child processes that have started for this supervisor. So far, we have only
started one. In this tuple we have:

• The ID of the process, which is always :undefined in DynamicSupervisor children.


• The PID of the process, which is #PID<0.228.0> (remember, yours might be different!)
• The type of child. A worker indicates a non-supervisor child.
• The module that this child is using for its callback functions when we use GenServer.call/2.

Okay, now that we have a Player process running and we can see that is definitely linked to the supervisor.
That Player process has a robot that is right up against the northern boundary, at (0, 4), and so another
move should make this process crash. Let’s try it out!

1 iex> Player.move(player)
2
3 [timestamp] [error] GenServer #PID<0.158.0> terminating
4 ** (MatchError) no match of right hand side value: {:error, :at_table_boundary}
5 (toy_robot) lib/toy_robot/game/player.ex:39: ToyRobot.Game.Player.handle_call/3
6 (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
7 (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
8 (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
9 Last message (from #PID<0.141.0>): :move
10 State: %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 0, facing: :north, north: 4}, table: %ToyRobot.Ta\
11 ble{east_boundary: 4, north_boundary: 4}}
12 Client #PID<0.141.0> is alive
13
14 (stdlib) gen.erl:167: :gen.do_call/4
15 (elixir) lib/gen_server.ex:1007: GenServer.call/3
16 (stdlib) erl_eval.erl:680: :erl_eval.do_apply/6
17 (elixir) src/elixir.erl:275: :elixir.eval_forms/4
18 (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
19 (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
20 (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3
21 (iex) lib/iex/evaluator.ex:103: IEx.Evaluator.loop/1
22 ** (exit) exited in: GenServer.call(#PID<0.158.0>, :move, 5000)
23 ** (EXIT) an exception was raised:
24 ** (MatchError) no match of right hand side value: {:error, :at_table_boundary}
A Single, Supervised Player 144

25 (toy_robot) lib/toy_robot/game/player.ex:39: ToyRobot.Game.Player.handle_call/3


26 (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
27 (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
28 (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
29 (elixir) lib/gen_server.ex:1010: GenServer.call/3

This looks like what happened before! The process crashes because the robot has moved over the edge of
the table, and has fallen to its doom.
What we tried before is moving that robot again:

1 iex> Player.move(player)
2 ** (exit) exited in: GenServer.call(#PID<0.158.0>, :move, 5000)
3 ** (EXIT) no process: the process is not alive or there's no process currently associated with the given\
4 name, possibly because its application isn't started
5 (elixir) lib/gen_server.ex:989: GenServer.call/3

It’s definitely dead. Nothing seems different this time. But let’s check that supervisor again with Supervisor.which_-
children/1:

1 DynamicSupervisor.which_children(sup)
2 [
3 {:undefined, #PID<0.230.0>, :worker, [ToyRobot.Game.Player]}
4 ]

There’s a new process there! Our supervisor has started up a new Player process!
We can send messages to this new Player process by using the functions we have in the Player module, like
report/1:

1 iex> new_player = pid("0.230.0")


2 iex> Player.report(new_player)
3 %ToyRobot.Robot{east: 0, facing: :north, north: 4}

Everything works as it once did. Our robot has respawned at its original starting location: (0, 4). The
difference is that this time we now have a supervisor that we can start a Player GenServer under, and if that
Player server crashes then it will be restarted automatically by the supervisor.

The position of this robot will go back to the position we provided when we called start_child/2 for the
supervisor. Think of this like in a video game when your character dies, they’re sent back to the start position
of the level. Our player process (and its attached robot state) here has done the same thing.
In this section, we’ve spent some time looking at how to start a supervisor and child processes for that
supervisor. We can see that a supervisor will restart a new Player process when one dies, and this is due to
the DynamicSupervisor’s :one_for_one strategy that we defined when starting the supervisor.
Now that we have got a good idea of how a supervisor works, we should simplify the code that we use to
start the supervisor and to start children for that. The way we can do that is to put this code inside a module.
A Single, Supervised Player 145

Putting our supervisor in a module

What we have currently is the ability to start a DynamicSupervisor by calling this code:

1 iex> {:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)


2 {:ok, #PID<0.182.0>}

And we have the ability to start a player by calling this code:

1 iex> robot = %ToyRobot.Robot{north: 0, east: 0, facing: :north}


2 iex> spec = {Player, robot}
3 iex> DynamicSupervisor.start_child(sup, spec)

This is not unlike the code that we originally had for our ToyRobot.Game.Player module, with the GenServer.start
and GenServer.call function calls. In that earlier section of the chapter, we moved all the functions into one
module to make it clearer about all the actions anyone could take on a player. For the sake of consistency,
we should do the same with our supervisor too.
What we’ll do here is make it possible to start our supervisor by calling this code, using a new function in a
new module:

1 iex> {:ok, sup} = ToyRobot.Game.PlayerSupervisor.start_link([])

And we’ll make it possible to start a new child process with this code:

1 iex> robot = %ToyRobot.Robot{north: 0, east: 0, facing: :north}


2 iex> {:ok, player} = ToyRobot.PlayerSupervisor.start_child(robot)

Best of all, we’ll write some tests for this new supervisor module too! By the end of this section, we’ll have
a ToyRobot.PlayerSupervisor module that we can use to start our supervisor, as well as start child processes.
Let’s start out by writing that test I just mentioned. We’ll create a new file for that test:

test/toy_robot/game/player_supervisor_test.exs

1 defmodule ToyRobot.Game.PlayerSupervisorTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.{Game.PlayerSupervisor, Robot}
5
6 setup do
7 {:ok, sup} = PlayerSupervisor.start_link([])
8 {:ok, %{sup: sup}}
9 end
10
11 test "starts a player child process", %{sup: sup} do
12 robot = %Robot{north: 0, east: 0, facing: :north}
A Single, Supervised Player 146

13 {:ok, _player} = PlayerSupervisor.start_child(sup, robot)


14 %{active: active} = DynamicSupervisor.count_children(sup)
15 assert active == 1
16 end
17 end

This test looks just like the tests we wrote for Player. These two tests look remarkably alike because
supervisors are just processes too. Go ahead and compare this file and player_test.exs and you’ll see that
there aren’t too many differences.
We start the supervisor by using PlayerSupervisor.start_link/1 here. We’re using start_link, because the
function that will be called by this function is DynamicSupervisor.start_link/2, and it helps to make these
two calls consistent to make obvious what would happen when this function is called.
In the main part of the test, we can start a child using PlayerSupervisor.start_child/2. After we start that
child, we make sure that the DynamicSupervisor.count_children/1 data shows that there is one child for that
supervisor.
Let’s go ahead and run this test now. We’ll see it fail quickly:

1 1) test starts a player child process (ToyRobot.Game.PlayerSupervisor)


2 test/toy_robot/game/player_supervisor_test.exs:11
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.start_link/1 is undefined (module T\
4 oyRobot.Game.PlayerSupervisor is not available)
5 stacktrace:
6 ToyRobot.Game.PlayerSupervisor.start_link([])
7 test/toy_robot/game/player_supervisor_test.exs:7: ToyRobot.Game.PlayerSupervisor.__ex_unit_setup_0/1
8 test/toy_robot/game/player_supervisor_test.exs:1: ToyRobot.Game.PlayerSupervisor.__ex_unit__/2

We have not created the module yet! Let’s do that now and add some functions that are required by
DynamicSupervisor:

lib/toy_robot/game/player_supervisor.ex

1 defmodule ToyRobot.Game.PlayerSupervisor do
2 use DynamicSupervisor
3
4 def start_link(args) do
5 DynamicSupervisor.start_link(__MODULE__, args)
6 end
7
8 def init(_args) do
9 DynamicSupervisor.init(strategy: :one_for_one)
10 end
11 end

These are almost the same that we have at the top of the ToyRobot.Game.PlayerSupervisor module for our
GenServer. The difference here is that we start out by using DynamicSupervisor, which indicates that this
module will be used as a DynamicSupervisor. Another thing to notice is that our first function is start_link,
A Single, Supervised Player 147

not start. We’re also starting a DynamicSupervisor here, rather than a GenServer. Supervisors start out being
linked to another process, so that they themselves can be supervised!
The init function can be used here to set up anything additional that we might want to happen when this
supervisor starts. In this case, we don’t have anything special, and so we call DynamicSupervisor.init(strategy:
:one_for_one) to complete the initialization process, configuring the supervisor to restart a child process if
one happens to crash.
Let’s run the test again and see what happens now that we have the module defined.

1 1) test starts a player child process (ToyRobot.Game.PlayerSupervisor)


2 test/toy_robot/game/player_supervisor_test.exs:11
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.start_child/2 is undefined or priva\
4 te
5 code: {:ok, _player} = PlayerSupervisor.start_child(sup, robot)
6 stacktrace:
7 (toy_robot) ToyRobot.Game.PlayerSupervisor.start_child(
8 #PID<0.198.0>,
9 %ToyRobot.Robot{east: 0, facing: :north, north: 0}
10 )
11 test/toy_robot/game/player_supervisor_test.exs:13: (test)

This time our test is failing because we do not have a PlayerSupervisor.start_child/2 function. Let’s add that
one now:

lib/toy_robot/game/player_supervisor.ex

1 defmodule ToyRobot.Game.PlayerSupervisor do
2 use DynamicSupervisor
3
4 def start_link(args) do
5 DynamicSupervisor.start_link(__MODULE__, args)
6 end
7
8 def init(_args) do
9 DynamicSupervisor.init(strategy: :one_for_one)
10 end
11
12 def start_child(sup, robot) do
13 DynamicSupervisor.start_child(sup, {Player, robot})
14 end
15 end

This new function calls DynamicSupervisor.start_child/2 to start a child process of this supervisor. Notice here
that we’re referring to the Player module, which should now be brought in using an alias:
A Single, Supervised Player 148

lib/toy_robot/game/player_supervisor.ex

1 defmodule ToyRobot.Game.PlayerSupervisor do
2 use DynamicSupervisor
3
4 alias ToyRobot.Game.Player
5
6 ...
7 end

Let’s see if that gets our test passing:

1 12 doctests, 39 tests, 0 failures

That’s great! We were able to move our DynamicSupervisor code into the ToyRobot.Game.PlayerSupervisor module,
and now we can start our supervisor with a much simpler call:

1 iex> {:ok, sup} = ToyRobot.Game.PlayerSupervisor.start_link([])

We should remember that we can alias this too:

1 iex> alias ToyRobot.Game.PlayerSupervisor


2 [ToyRobot.Game.PlayerSupervisor]
3 iex> {:ok, sup} = PlayerSupervisor.start_link([])

And we can start player processes simply too:

1 iex> robot = %ToyRobot.Robot{north: 0, east: 0, facing: :north}


2 iex> {:ok, player} = PlayerSupervisor.start_child(sup, robot)

But what if we were able to make it so that we didn’t even have to start the supervisor in the first place?
What if the supervisor was to start up automatically? We can look at that in this next section.

Automatically starting the supervisor

We can automatically start up our ToyRobot.Game.PlayerSupervisor supervisor by changing our application


slightly, adding in a new module called ToyRobot.Application. This module will be used to start our applica-
tion when we run things like mix run and iex -S mix, and can serve as a way to configure things like our
PlayerSupervisor to start automatically.

The first change we need to make is to our mix.exs file. We need to change the application definition to this:
A Single, Supervised Player 149

mix.exs

1 def application do
2 [
3 extra_applications: [:logger],
4 mod: {ToyRobot.Application, []}
5 ]
6 end

This tells Mix (Elixir’s build tool), that we will have a module that will contain logic for how to start our
application. When we run a command like iex -S mix or mix run, this application will be automatically
started for us by Mix.
This ToyRobot.Application module hasn’t been defined yet, and so we will need to do that work ourselves.
We’ll put this module in a new file and define it like this:

lib/toy_robot/application.ex

1 defmodule ToyRobot.Application do
2 use Application
3
4 def start(_type, _args) do
5 children = [
6 ToyRobot.Game.PlayerSupervisor
7 ]
8
9 # See https://hexdocs.pm/elixir/Supervisor.html
10 # for other strategies and supported options
11 opts = [strategy: :one_for_one, name: ToyRobot.Supervisor]
12 Supervisor.start_link(children, opts)
13 end
14 end

This module is where all the logic for our application goes. The start/2 function here is automatically called
by Mix when our Elixir application starts. The code in start/2 will start up a regular, non-dynamic Supervisor
which will monitor all the defined children. We’re using another supervisor here, as we may want to spin up
other sub-processes of our application. That might be things like another supervisor for a different part of
our application. This supervisor provides a central mechanism for registering all the processes we want to
supervise for our application.
What the addition of this Supervisor does to our application is that it makes the process supervision tree look
like this:

Process supervision tree


A Single, Supervised Player 150

The process supervision tree here shows that our Application process has a Supervisor child process, and that
process in turn has our PlayerSupervisor process, and that process has a ToyRobot.Game.Player process. When-
ever the Player process crashes, the PlayerSupervisor process will be notified of that crash and will automat-
ically restart that process. In turn, the ToyRobot.Supervisor process monitors ToyRobot.Game.PlayerSupervisor
and will restart that process if there’s any problems.
The Application process is linked to the Supervisor, and that supervisor is then linked to the ToyRobot.Game.PlayerSupervisor
module’s DynamicSupervisor. By making these changes to our application, the ToyRobot.Game.PlayerSupervisor
supervisor will be started automatically whenever we start the application.
With the supervisor starting up automatically, we won’t have a variable like sup to refer to the supervisor.
We’ve used this previously when we’ve called ToyRobot.Game.PlayerSupervisor.start_child(sup, robot). To
work around this problem, we can give the supervisor a name by changing the PlayerSupervisor.start_link/1
definition to this:

lib/toy_robot/game/player_supervisor.ex

1 def start_link(args) do
2 DynamicSupervisor.start_link(__MODULE__, args, name: __MODULE__)
3 end

When this supervisor’s process starts up, it will now have the same name as the module, meaning we can
refer to the supervisor by this name. The one place where we need that name is as the first argument to
PlayerSupervisor.start_child/2:

lib/toy_robot/game/player_supervisor.ex

1 def start_child(sup, robot) do


2 DynamicSupervisor.start_child(sup, {Player, robot})
3 end

But if we have a predictable name, then we won’t need that argument there at all. Let’s change this start_-
child/2 definition to start_child/1 definition:

lib/toy_robot/game/player_supervisor.ex

1 def start_child(robot) do
2 DynamicSupervisor.start_child(__MODULE__, {Player, robot})
3 end

That will simplify any code that is used to start a new child process of this supervisor!
Let’s see all of this in action – the supervisor automatically starting up and us being able to refer to it by its
module name. Let’s launch a fresh iex -S mix session and run this code:
A Single, Supervised Player 151

1 iex> alias ToyRobot.{Game.PlayerSupervisor, Robot}


2 iex> robot = %ToyRobot.Robot{north: 0, east: 0, facing: :north}
3 iex> {:ok, player} = ToyRobot.Game.PlayerSupervisor.start_child(robot)

Look at all the things we’re not doing! We no longer have to start up the supervisor ourselves. Instead,
we can simply rely on the application starting up the supervisor automatically. This allows us to call
ToyRobot.Game.PlayerSupervisor.start_child/1 and it will start a child process of the supervisor.

We should also remember that we have tests for this code, and since we’ve made changes to that code we
should re-run the tests to make sure they’re still passing. Let’s run mix test quickly to see.

1 1) test starts a player child process (ToyRobot.Game.PlayerSupervisor)


2 test/toy_robot/game/player_supervisor_test.exs:11
3 ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.171.0>}}
4 stacktrace:
5 test/toy_robot/game/player_supervisor_test.exs:7: ToyRobot.Game.PlayerSupervisor.__ex_unit_setup_0/1
6 test/toy_robot/game/player_supervisor_test.exs:1: ToyRobot.Game.PlayerSupervisor.__ex_unit__/2

It looks like we broke our supervisor’s test! This test is breaking because it is attempting to start a
PlayerSupervisor manually – and one that shares the same name (ToyRobot.Game.PlayerSupervisor)! – when
one will already be started up by the application.
So instead of manually starting one here, let’s rely on the one that will be started by the application. The
way we can do that is to take out all the code that sets up the supervisor for this test, reducing the whole
file to just this:

test/toy_robot/game/player_supervisor_test.exs

1 defmodule ToyRobot.Game.PlayerSupervisorTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.{Game.PlayerSupervisor, Robot}
5
6 test "starts a player child process" do
7 robot = %Robot{north: 0, east: 0, facing: :north}
8 {:ok, _player} = PlayerSupervisor.start_child(robot)
9 %{active: active} = DynamicSupervisor.count_children(PlayerSupervisor)
10 assert active == 1
11 end
12 end

There are three changes here:

1. The setup block is no longer required at all.


2. We can call PlayerSupervisor.start_child/1, instead of PlayerSupervisor.start_child/2, since we do not
need to pass the supervisor as an argument any more. We’re using the name of the module within that
function to refer to the supervisor.
3. Because we’re using the module name to refer to the supervisior, we can use that name to count the
children with DynamicSupervisor.count_children/1.
A Single, Supervised Player 152

The changes that we’ve made to the supervisor have dramatically simplified our code across the application.
We no longer have to manually manage the starting of the supervisor, and instead we can focus on starting
up our player processes when we need them.
We’re almost done with implementing a process supervision structure for our Player GenServers. There’s
one more issue that we should spend time addressing. It’s to do with what happens when a player process
crashes.
Let’s see the problem in code, moving another robot off the edge of the table to its inevitable doom:

1 iex> alias ToyRobot.{Game.Player, Game.PlayerSupervisor, Robot}


2 iex> robot = %ToyRobot.Robot{north: 4, east: 0, facing: :north}
3 iex> {:ok, player} = ToyRobot.Game.PlayerSupervisor.start_child(robot)
4 iex> Player.move(player)

Our player process will crash:

1 [timestamp] [error] GenServer #PID<0.154.0> terminating


2 ** (MatchError) no match of right hand side value: {:error, :at_table_boundary}
3 (toy_robot) lib/toy_robot/game/player.ex:39: ToyRobot.Game.Player.handle_call/3
4 (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
5 (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
6 (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
7 Last message (from #PID<0.145.0>): :move
8 State: %ToyRobot.Simulation{robot: %ToyRobot.Robot{east: 0, facing: :north, north: 4}, table: %ToyRobot.Ta\
9 ble{east_boundary: 4, north_boundary: 4}}
10 Client #PID<0.145.0> is alive

When this player process crashes, it’s gone for good. How are we supposed to refer to the new process without
checking DynamicSupervisor.which_children/1 and digging out the new PID from there? What is the benefit of
having a supervisor that will automatically start new processes when old ones die, if we have to manually
find out what their PIDs are?
For instance, we would need to use this code:

1 iex> DynamicSupervisor.which_children(PlayerSupervisor)
2 [{:undefined, #PID<0.204.0>, :worker, [ToyRobot.Player]}]

We fixed this sort of problem once before. To fix this problem of not having a name for the supervisor, we
gave the supervisor a name in its start_link definition:
A Single, Supervised Player 153

lib/toy_robot/game/player_supervisor.ex

1 def start_link(args) do
2 DynamicSupervisor.start_link(__MODULE__, args, name: __MODULE__)
3 end

We could use __MODULE__ for Game.Player’s functions too, but then we could only have one Game.Player process,
and that doesn’t seem like the right decision to make. Ultimately, the direction we’re headed in is being
able to run multiple players within our application – especially in the next chapter. Therefore each player
will need to have a unique name that we can refer to it with. We need to lay the groundwork to make that
possible now.

Giving names to players

Starting a player process is easy. Assigning that process’s PID to a variable is easy. Being able to refer to a
restarted process after the initial process crashes is harder, but still relatively easy… if you know how. After
we’re done here, you will know how!
What we’ll do here is use one more useful feature of Elixir, the Registry module.

What is a Registry?

A Registry is yet another type of process that Elixir gives us.

It allows us to track processes with specific names, and when those processes die and new ones start up, we
can continue to use the same name to refer to that new process.

You can read more about the Registry module here:

https://hexdocs.pm/elixir/Registry.html This is what we’ll end up with at the end of this section:

1 iex> alias ToyRobot.Game.{Player, PlayerSupervisor}


2 iex> alias ToyRobot.Robot
3 # A robot that is near the northern boundary of the table
4 iex> robot = %Robot{north: 3, east: 0, facing: :north}
5 iex> {:ok, player} = PlayerSupervisor.start_child(robot, "Izzy")
6
7 # One move is okay...
8 iex> PlayerSupervisor.move("Izzy")
9
10 # But a second one spells disaster!
11 iex> PlayerSupervisor.move("Izzy")
12
13 # The robot falls to its doom...
14 [long exit stacktrace goes here]
15 # ... but its process is restarted by the supervisor!
16 iex> PlayerSupervisor.report("Izzy")
17 %Robot{north: 3, east: 0, facing: :north}
A Single, Supervised Player 154

What you’ll notice here is that we’ll be starting the robot with PlayerSupervisor.start_child/2, passing in a
name as the 2nd argument. When we move the player, we can do so via the PlayerSupervisor module and
refer to that process by the name we provided to start_child/2.
When the robot moves two times, its process will die, but then a new one will be started instantly with the
same name. We can then continue to use this name to move a robot around the board, even if the original
process we were communicating with has died.
To make sure things are working correctly here, we can extend our current test suite slightly. It’s important
here that we only test code that we write ourselves, and to avoid testing Elixir’s process supervision features
directly. We should trust that they work already and not double up on tests.
So what we’ll test here in this section is:

• We can start a registry


• We can start a process with a particular name
• We can find that process based on that name, within the registry
• We can send commands to that process via its name

Once we’ve done that, we’ll test it all out ourselves in the iex prompt to make sure everything is working as
we intend it to.

Starting a registry

The first thing that we’ll need to handle here is starting the process registry. When a process starts, we’ll
give it a unique name that it will use to register itself in the registry. When we need to send that process a
message, we’ll use that name – instead of the PID – to send that message. We can’t do this right away, we
need a registry to exist first!
Since we’re going to be sending messages to processes via the PlayerSupervisor module, it would make sense
to start the registry as a part of starting the PlayerSupervisor. Let’s add a test for this registry to test/toy_-
robot/game/player_supervisor_test.exs now:

test/toy_robot/game/player_supervisor_test.exs

1 test "starts a registry" do


2 registry = Process.whereis(ToyRobot.Game.PlayerRegistry)
3 assert registry
4 end

This is a relatively simple test. We’re asserting that there is a process that exists with the name ToyRobot.Game.PlayerRegistry.
When we run this test now, it will fail because no such process exists:
A Single, Supervised Player 155

1 1) test starts a registry (ToyRobot.Game.PlayerSupervisor)


2 test/toy_robot/game/player_supervisor_test.exs:13
3 Expected truthy, got nil
4 code: assert registry
5 stacktrace:
6 test/toy_robot/game/player_supervisor_test.exs:15: (test)

This test is expecting a “truthy” value here, but is getting back nothing. To fix this test, we will need to start
this registry. The best place to do that is in the PlayerSupervisor.init/1 function:

lib/toy_robot/game/player_supervisor.ex

1 def init(_args) do
2 Registry.start_link(keys: :unique, name: ToyRobot.Game.PlayerRegistry)
3 DynamicSupervisor.init(strategy: :one_for_one)
4 end

The init/1 function is the right place for this because we want to start this registry up whenever we’re
starting our PlayerSupervisor.
When we run that new test again, we’ll see that it’s now passing:

1 12 doctests, 41 tests, 0 failures

Now that our registry has started up, we can now look at giving the Player server processes names within
that registry.

Starting named processes

Naming processes will allow us to refer to them in a much simpler fashion than by their PIDs. Also, when
a process dies and a new one starts up, we can continue using this name to communicate with that new
process. It will make managing our processes so much easier!
We need to create a test for this! Fortunately, due to our diligent testing strategies, we have a test that
already checks what happens when we start a process, over in player_supervisor_test.exs:

test/toy_robot/game/player_supervisor_test.exs

1 test "starts a game child process" do


2 robot = %Robot{north: 0, east: 0, facing: :north}
3 {:ok, _player} = PlayerSupervisor.start_child(robot)
4 %{active: active} = DynamicSupervisor.count_children(PlayerSupervisor)
5 assert active == 1
6 end

However, this test only checks for starting a process without a name, and without it being registered within
the registry. With a few modifications, we can fix both of those things:
A Single, Supervised Player 156

test/toy_robot/game/player_supervisor_test.exs

1 test "starts a game child process" do


2 robot = %Robot{north: 0, east: 0, facing: :north}
3 {:ok, player} = PlayerSupervisor.start_child(robot, "Izzy")
4
5 [{registered_player, _}] = Registry.lookup(ToyRobot.Game.PlayerRegistry, "Izzy")
6 assert registered_player == player
7 end

In this new version of the test, we call start_child with two arguments: the robot and the name we want to
use for the process. In the bottom-half of this test, we assert that we can look up this process within the
registry and that when we do so, the registered_player PID value is the same as the player value returned
from the start_child/2 call.
There will be a few things that we need to do to make this test pass. Let’s just work through them one at a
time, following the prompts given by the failures of the test. When we run this test for the first time, we’ll
see it fail because there is no start_child/2 function:

1 1) test starts a game child process (ToyRobot.Game.PlayerSupervisorTest)


2 test/game/player_supervisor_test.exs:6
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.start_child/2 is undefined or priva\
4 te
5 code: {:ok, player} = PlayerSupervisor.start_child(robot, "Izzy")
6 stacktrace:
7 (toy_robot) ToyRobot.Game.PlayerSupervisor.start_child(%ToyRobot.Robot{east: 0, facing: :north, north\
8 : 0}, "Izzy")
9 test/game/player_supervisor_test.exs:8: (test)

Let’s fix this up by going into lib/toy_robot/game/player_supervisor.ex and changing that start_child/1
function to take two arguments once again.

lib/toy_robot/game/player_supervisor.ex

1 def start_child(robot, name) do


2 DynamicSupervisor.start_child(
3 __MODULE__,
4 {Player, [robot: robot, name: name]}
5 )
6 end

Not only did we change this function to now take two arguments – a robot and a name – but we’ve also
changed the argument that is passed to our Player module when its start_link/1 function is called. We’re
now passing a keyword list here to pass through the values of both the robot and the name to that function.
Because this argument has changed here, we will need to change Player.start_link/1 to accept this new
format of the argument it receives. Let’s jump over to lib/toy_robot/game/player.ex and do that now:
A Single, Supervised Player 157

lib/toy_robot/game/player.ex

1 def start_link([robot: robot, name: name]) do


2 GenServer.start_link(__MODULE__, robot)
3 end

We’re now taking that keyword list here as the argument, and taking out the robot value, but currently
ignoring the name value. We will need to use that name argument to name our process, but how do we do that?
And how do we tie that name to the registry?
We can do this by adding a new function to the Player module called process_name/1:

lib/toy_robot/game/player.ex

1 def process_name(name) do
2 {:via, Registry, {ToyRobot.Game.PlayerRegistry, name}}
3 end

This function returns a three element tuple which looks a bit strange at first, but let’s break it down into
three parts:

1. The :via says that this process can be found via something
2. That something is a Registry
3. That Registry is called ToyRobot.Game.PlayerRegistry, and the process can be found via that registry
under the key of name.

There, that wasn’t so difficult! To name the process that gets started in Player.start_link/1, we need to use
this process_name/1 function:

lib/toy_robot/game/player.ex

1 def start_link([robot: robot, name: name]) do


2 GenServer.start_link(__MODULE__, robot, name: process_name(name))
3 end

The name option passed to GenServer.start_link/3 here will give our process the “name” of whatever is
returned by process_name. If our Player is called “Izzy”, then that tuple will be:

1 {:via, Registry, {ToyRobot.Game.PlayerRegistry, name}}

Later on, we’ll use this same process_name/1 function to send messages to the process. But for now, we should
stay focussed on our goal of simply starting the processes in the first place. Let’s see what our tests think of
these changes now:
A Single, Supervised Player 158

1 12 doctests, 41 tests, 0 failures

The process is starting correctly, and according to our test we’re able to find that process by looking it up in
the registry. This lookup would only work if the registry was started and the process was registered under
the registry, and this test confirms that both of those things happen. Excellent!

Sending messages to a named process

The last thing we said we would test is that we can send messages to a process using its name. At the start
of this section, we talked about being able to do this:

1 iex> alias ToyRobot.Game.{Player, PlayerSupervisor}


2 iex> alias ToyRobot.Robot
3 # A robot that is near the northern boundary of the table
4 iex> robot = %Robot{north: 3, east: 0, facing: :north}
5 iex> {:ok, player} = PlayerSupervisor.start_child(robot, "Izzy")
6 iex> PlayerSupervisor.move("Izzy")

We have most of this code done already. We can start the process with the name, but we’re not yet able
to send messages to that process based on its name. Let’s write a new test for doing this over in test/toy_-
robot/game/player_supervisor_test.exs:

test/toy_robot/game/player_supervisor_test.exs

1 test "moves a robot forward" do


2 robot = %Robot{north: 0, east: 0, facing: :north}
3 {:ok, _player} = PlayerSupervisor.start_child(robot, "Charlie")
4 %{robot: %{north: north}} = PlayerSupervisor.move("Charlie")
5
6 assert north == 1
7 end

This test asserts that when we start a player process (this time called “Charlie”), with a starting position of
(0, 0) and facing NORTH and we ask it to move then it should end up at (0, 1), or 1 NORTH.

When we run this test, it will fail because this PlayerSupervisor.move/1 function does not exist yet:

1 1) test moves a robot forward (ToyRobot.Game.PlayerSupervisor)


2 test/toy_robot/game/player_supervisor_test.exs:21
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.move/1 is undefined or private
4 code: %{robot: %{north: north}} = PlayerSupervisor.move("Charlie")
5 stacktrace:
6 (toy_robot) ToyRobot.Game.PlayerSupervisor.move("Charlie")
7 test/toy_robot/game/player_supervisor_test.exs:24: (test)

Let’s go over into lib/toy_robot/game/player_supervisor.ex and add this function, after the start_child/2
function:
A Single, Supervised Player 159

lib/toy_robot/game/player_supervisor.ex

1 def move(name) do
2 name |> Player.process_name() |> Player.move()
3 end

This function takes the name and then passes it to Player.process_name/1. This will then return that {:via,
Registry, ...} tuple. We can then pass this tuple directly to Player.move/1, and it will then use that tuple to
communicate with the player process.
When we run our test again, we’ll see that it now passes:

1 12 doctests, 42 tests, 0 failures

We can now use the process’s name to ask it to move, fulfilling the second to last piece of what we set out
at the start of this section. The final piece that we set out was that we could ask for a robot’s position based
simply on its name:

1 iex> PlayerSupervisor.report("Izzy")

We haven’t implemented that PlayerSupervisor.report/1 function yet! It will be really quick to implement,
just like our PlayerSupervisor.move/1 function was.
Let’s add a test for this report/1 function:

test/toy_robot/game/player_supervisor_test.exs

1 test "reports a robot's location" do


2 robot = %Robot{north: 0, east: 0, facing: :north}
3 {:ok, _player} = PlayerSupervisor.start_child(robot, "Davros")
4 %{north: north} = PlayerSupervisor.report("Davros")
5
6 assert north == 0
7 end

We’ll check that the test fails so that we know that we have something to fix:

1 1) test reports a robot's location (ToyRobot.Game.PlayerSupervisor)


2 test/toy_robot/game/player_supervisor_test.exs:29
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.report/1 is undefined or private
4 code: %{robot: %{north: north}} = PlayerSupervisor.report("Davros")
5 stacktrace:
6 (toy_robot) ToyRobot.Game.PlayerSupervisor.report("Davros")
7 test/toy_robot/game/player_supervisor_test.exs:32: (test)

And let’s add this function in to the Players module:


A Single, Supervised Player 160

1 def report(name) do
2 name |> Player.process_name() |> Player.report()
3 end

Running the test again will show it now passing:

1 12 doctests, 43 tests, 0 failures

Alright! Let’s go back now and look at that example from the start of this section once again:

1 iex> alias ToyRobot.Game.{Player, PlayerSupervisor}


2 iex> alias ToyRobot.Robot
3 # A robot that is near the northern boundary of the table
4 iex> robot = %Robot{north: 3, east: 0, facing: :north}
5 iex> {:ok, player} = PlayerSupervisor.start_child(robot, "Izzy")
6
7 # One move is okay...
8 iex> PlayerSupervisor.move("Izzy")
9
10 # But a second one spells disaster!
11 iex> PlayerSupervisor.move("Izzy")
12
13 # The robot falls to its doom...
14 [long exit stacktrace goes here]
15 # ... but its process is restarted by the supervisor!
16 iex> PlayerSupervisor.report("Izzy")
17 %Robot{east: 0, facing: :north, north: 3}

Let’s see if we can do all of this now with the code that we have just written. If you put all that code into iex,
you should see it now completely works! The robot can move once, but twice spells the robot’s doom. But
thankfully due to how we’ve set it up, our robot is automatically restarted by the supervisor.

Conclusion

In this chapter, we’ve seen how we could use three big features of Elixir: GenServer, DynamicSupervisor and
Registry. We use these to:

• Start up processes that will remember where our robots are – GenServers
• Start new processes automatically when our robots run into trouble – DynamicSupervisor
• Find processes easily based on their names – Registry

We have completed the first part of the “extra credit” section of this book. We now have a single player Toy
Robot exercise using some moderately advanced features of Elixir.
In the next chapter, we’ll finish this “extra credit” section by changing up this code so that we can make a
multi-player Toy Robot game, where robots can collide with each other.
8. Multiplayer Toy Robot
In the last chapter, we built a single-player Toy Robot using functions from the GenServer, DynamicSupervisor
and Registry modules within Elixir. In this chapter, we’re going to look at how we can change that code to
support multiple robots moving around on the same table at the same time.
Before we start building this part of our application, we’re going to need to set some ground rules about
how our game will work. To start with: if this was a real table with real robots, you wouldn’t be able to have
two robots occupying exactly the same space. So we should obey that rule in our game too. Based on that
simple rule, I can think of three things that robots should not be allowed to do:

1. A robot cannot be placed at a position that another robot already occupies


2. A robot cannot move onto an occupied square – the movement should be prevented
3. A robot’s process should not restart that robot on an occupied square – it instead starts on a
randomly selected square

In order to enforce these rules, we will need something that keeps a track of the state of our game.
This sounds like we will need another GenServer and it would be called ToyRobot.Game.Server. We’ll call it
ToyRobot.Game.Server to keep it distinct from the other ToyRobot.Game modules. To help with working with the
players themselves, we will also create a ToyRobot.Game.Players module to house functions used to work with
groups of players at the same time.
The ToyRobot.Game.Server module would also be a good place to house the “table building” logic that exists
within ToyPlayer.Game.Player.init/1 at the moment:

lib/toy_robot/game/player.ex
1 def init(position) do
2 simulation = %Simulation{
3 table: %Table{
4 north_boundary: 4,
5 east_boundary: 4,
6 },
7 robot: struct(Robot, position)
8 }
9
10 {:ok, simulation}
11 end

Rather than having our players build their own table, we’ll change this code to pass through a table built
for a game and then use that one.
There’s a lot of ground to cover in this chapter. We’ll start by changing the player and table building logic,
and then transition into making our Game.Server module.
As we progress through this chapter, we’ll look at ways we can clean up our code by splitting it up into
smaller, more specific modules, such as our future Game.Players module.
But let’s start right where we should: at the beginning.
Multiplayer Toy Robot 162

Prelude: a short refactor involving Player and tables

In order for our robots to be able to face each other within a game, we will need a Game.Server module to
exist that can encapsulate this knowledge. One of the things that a game needs to know about is the table.
This table can be used to validate the positions of the robots, ensuring that they stay within the boundaries
of that table.
Currently, each Player process builds up its own representation of a table:

lib/toy_robot/game/player.ex

1 def init(position) do
2 simulation = %Simulation{
3 table: %Table{
4 north_boundary: 4,
5 east_boundary: 4,
6 },
7 robot: struct(Robot, position)
8 }
9
10 {:ok, simulation}
11 end

We did it this way previously becuase there was no better place for this logic to go. Now that we’re about
to create a Game.Server module, it seems like Game.Server would be a better place for that logic to live. This
means then that we could remove most of this logic from Game.Player entirely.
Before we go and build Game.Server, let’s take a quick look at what it might take to get table into this function
that already exists. If we can pass a table argument into this function, then we can remove the need to build
our own table.
Currently, in order to create a new Player process, we go through the PlayerSupervisor module’s function
start_child:

lib/toy_robot/game/player_supervisor.ex

1 def start_child(position, name) do


2 DynamicSupervisor.start_child(
3 __MODULE__,
4 {Player, [position: position, name: name]}
5 )
6 end

To make it so that we don’t need to build the table for each player, we will need to pass a new argument
through to this function: a table. The new code will look like this:
Multiplayer Toy Robot 163

lib/toy_robot/game/player_supervisor.ex
1 def start_child(table, position, name) do
2 DynamicSupervisor.start_child(
3 __MODULE__,
4 {Player, [table: table, position: position, name: name]}
5 )
6 end

Before we go and make those changes for real, we should update the tests that we have so that they build
a table and pass it through. Let’s go over to the PlayerSupervisor tests now and do that by adding in a new
function to build our tables:

test/toy_robot/game/player_supervisor_test.exs
1 def build_table do
2 %Table{
3 north_boundary: 4,
4 east_boundary: 4
5 }
6 end

To use the Table module here, we will need to alias it at the top of this module:

test/toy_robot/game/player_supervisor_test.exs
1 defmodule ToyRobot.Game.PlayerSupervisorTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.Table
5 alias ToyRobot.Game.PlayerSupervisor

Then, we can use this function in our tests. Let’s update the first test to use this function now:

test/toy_robot/game/player_supervisor_test.exs
1 test "starts a game child process" do
2 starting_position = %{north: 0, east: 0, facing: :north}
3 {:ok, player} =
4 PlayerSupervisor.start_child(
5 build_table(),
6 starting_position,
7 "Izzy"
8 )
9
10 [{registered_player, _}] = Registry.lookup(
11 ToyRobot.Game.PlayerRegistry,
12 "Izzy"
13 )
14 assert registered_player == player
15 end

And we’ll update the other two tests that call start_child as well:
Multiplayer Toy Robot 164

test/toy_robot/game/player_supervisor_test.exs

1 test "moves a robot forward" do


2 starting_position = %{north: 0, east: 0, facing: :north}
3 {:ok, _player} = PlayerSupervisor.start_child(
4 build_table(),
5 starting_position,
6 "Charlie"
7 )
8 :ok = PlayerSupervisor.move("Charlie")
9 %{north: north} = PlayerSupervisor.report("Charlie")
10
11 assert north == 1
12 end
13
14 test "reports a robot's location" do
15 starting_position = %{north: 0, east: 0, facing: :north}
16 {:ok, _player} = PlayerSupervisor.start_child(
17 build_table(),
18 starting_position,
19 "Davros"
20 )
21 %{north: north} = PlayerSupervisor.report("Davros")
22
23 assert north == 0
24 end

If we run mix test now, we’ll see that all three of these tests will fail with the same error message:

1 1) test starts a game child process (ToyRobot.Game.PlayerSupervisorTest)


2 test/toy_robot/game/player_supervisor_test.exs:14
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.start_child/3 is undefined or priva\
4 te

This is because the PlayerSupervisor module only has a function called start_child/2, and does not have one
called start_child/3. Let’s change that module now so that it supports a third argument for start_child:

lib/toy_robot/game/player_supervisor.ex

1 def start_child(table, position, name) do


2 DynamicSupervisor.start_child(
3 __MODULE__,
4 {Player, [table: table, position: position, name: name]}
5 )
6 end

We’re now changing the arguments we’re passing when we initialize a Player server here, and so we will
need to change the start_link and init functions over in that module too:
Multiplayer Toy Robot 165

lib/toy_robot/game/player.ex

1 def start_link([table: table, position: position, name: name]) do


2 GenServer.start_link(__MODULE__, [table: table, position: position], name: process_name(name))
3 end
4
5 def init([table: table, position: position]) do
6 simulation = %Simulation{
7 table: table,
8 robot: struct(Robot, position)
9 }
10
11 {:ok, simulation}
12 end

These changes mean that the Player.init/1 function will no longer decide how to build its own table; it is
told about that information from whatever calls that function. This means that our Player module now has
one less responsibility, and so the code is cleaner as a result.
We can remove the Table alias at the top of this file too by changing this line:

lib/toy_robot/game/player.ex

1 alias ToyRobot.{Robot, Simulation, Table}

To this:

lib/toy_robot/game/player.ex

1 alias ToyRobot.{Robot, Simulation}

This seems to be the end of the line for changes to make here, so let’s run our tests with mix test to make
sure there’s nothing left to do. When we run them, we’ll see two of them break with the same error:

1 1) test report shows the current position of the robot (ToyRobot.Game.PlayerTest)


2 test/toy_robot/game/player_test.exs:14
3 ** (MatchError) no match of right hand side value:
4 {:error, {:function_clause, [{ToyRobot.Game.Player, :init, ...

The tests that are now failing are in the player_test.exs file. Let’s jump over to there.
This extremely long error message (redacted above) shows up because we have changed the pattern of
arguments that init takes, but not the pattern provided by Player.start/1. This Player.start/1 function is
used in player_test.exs, and so we will need to make some changes there to build a table as well in order to
make this test pass. Let’s do that now. We’ll add our build_table function to this module:
Multiplayer Toy Robot 166

test/toy_robot/game/player_test.exs

1 def build_table do
2 %Table{
3 north_boundary: 4,
4 east_boundary: 4
5 }
6 end

And we’ll alias Table at the top:

test/toy_robot/game/player_test.exs

1 defmodule ToyRobot.Game.PlayerTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.Game.Player
5 alias ToyRobot.{Robot, Table}

Then we’ll change the two Player.start calls – the ones in the setup blocks – in this file to this:

test/toy_robot/game/player_test.exs

1 {:ok, player} = Player.start(build_table(), starting_position)

This will change the number of arguments that Player.start takes, and so we will need to update that
function’s definition over in Player as well:

lib/toy_robot/game/player.ex

1 def start(table, position) do


2 GenServer.start(__MODULE__, [table: table, position: position])
3 end

This function will now take both of those arguments and the last argument in GenServer.start/2 there will be
the one passed through to Player.init/1. Everything should be in order! Let’s run the tests again to double
check:

1 12 doctests, 43 tests, 0 failures

Yes! That’s a great start.


Players are now able to be informed of what their tables are, rather than build the tables themselves. This
will make a lot of our future code much easier to write. Let’s now move onto building our Game.Server module.
Multiplayer Toy Robot 167

Creating a Game

It’s time to start building our Game.Server module, which will be responsible for handling two main things:
1) knowing about the game’s table 2) sending instructions to the Player processes associated with the game.
In this first section, we’ll look at how we can start a new game and add a player to it.
To start with, we’re going to create a new module called Game.Server. We’re going to call it Game.Server to keep
it distinct from the other Game.* moduels. In this Game.Server module we’ll have a function called place, that
will be invoked like this:

1 :ok = Game.Server.place(game, "Rosie", %{north: 0, east: 0, facing: :north})

An :ok here will indicate a successful placement. We’ll handle invalid placements later on.
For that Game.Server.place/3 code to work, we’re going to need to have a module that has such a function. As
we’ve done all throughout this book, we’ll start out here by writing a test:

test/toy_robot/game/server_test.exs

1 defmodule ToyRobot.Game.ServerTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.Game.Server
5
6 setup do
7 {:ok, game} = Server.start_link([north_boundary: 4, east_boundary: 4])
8 {:ok, %{game: game}}
9 end
10
11 test "can place a player", %{game: game} do
12 :ok = Server.place(game, %{north: 0, east: 0, facing: :north}, "Rosie")
13 assert Server.player_count(game) == 1
14 end
15 end

This test asserts that when we place our first player within a game, everything goes okay and that the game
is tracking a single player. Let’s run this test with mix test to see what happens:

1 1) test can place a robot (ToyRobot.Game.ServerTest)


2 test/toy_robot/game/server_test.exs:11
3 ** (UndefinedFunctionError) function ToyRobot.Game.Server.start_link/1 is
4 undefined (module ToyRobot.Game.Server is not available)
5 stacktrace:
6 ToyRobot.Game.Server.start_link([])
7 test/toy_robot/game/server_test.exs:7:
8 ToyRobot.Game.ServerTest.__ex_unit_setup_0/1
9 test/toy_robot/game/server_test.exs:1:
10 ToyRobot.Game.ServerTest.__ex_unit__/2
Multiplayer Toy Robot 168

The ToyRobot.Game.Server module doesn’t exist, and so the test immediately fails before it can even finish
the setup block. Let’s create this module and in it we’ll make a basic GenServer. We’ll set up a Table in the init
function here:

lib/toy_robot/game/server.ex

1 defmodule ToyRobot.Game.Server do
2 use GenServer
3
4 alias ToyRobot.Table
5
6 def start_link(args) do
7 GenServer.start_link(__MODULE__, args)
8 end
9
10 def init([north_boundary: north_boundary, east_boundary: east_boundary]) do
11 {:ok, %{
12 table: %Table{
13 north_boundary: north_boundary,
14 east_boundary: east_boundary,
15 }
16 }}
17 end
18 end

This new Table we’re building in init is the same table we’ll pass down to PlayerSupervisor.start_child in just
a moment. We’ll also update this code as we go along to track the players that are within this game.
Let’s run our test again to see what the next step is:

1 1) test can place a robot (ToyRobot.Game.ServerTest)


2 test/toy_robot/game/server_test.exs:11
3 ** (UndefinedFunctionError) function ToyRobot.Game.Server.place/3 is undefined or private
4 code: :ok = Server.place(game, %{north: 0, east: 0, facing: :north}, "Rosie")
5 stacktrace:
6 (toy_robot) ToyRobot.Game.Server.place(#PID<0.158.0>, %{east: 0, facing: :north, north: 0}, "Rosie")
7 test/toy_robot/game/server_test.exs:12: (test)

Let’s add this missing place/3 function now:

lib/toy_robot/game/server.ex

1 def place(game, position, name) do


2 GenServer.call(game, {:place, position, name})
3 end

This place/3 function calls a GenServer process, asking it to handle the message of {:place, name, position}.
To handle this, we will need a corresponding handle_call function too. This function should start a Player
process via the PlayerSupervisor module:
Multiplayer Toy Robot 169

lib/toy_robot/game/server.ex

1 def handle_call({:place, position, name}, _from, %{table: table} = state) do


2 {:ok, _player} = PlayerSupervisor.start_child(table, position, name)
3 {:reply, :ok, state}
4 end

This function will now start up the player process using PlayerSupervisor.start_child/2. This will ensure that
the Player process will be monitored by that supervisor, meaning that if the process was to crash a new one
would be started up in its place.
We’re using the short name for the ToyRobot.Game.PlayerSupervisor module in this function, and so we will
need to alias at the top of the module, otherwise it will not be available:

lib/toy_robot/game/server.ex

1 defmodule ToyRobot.Game.Server do
2 use GenServer
3
4 alias ToyRobot.Table
5 alias ToyRobot.Game.PlayerSupervisor

Let’s run our test again and see what happens this time:

1 1) test can place a robot (ToyRobot.Game.ServerTest)


2 test/toy_robot/game/server_test.exs:11
3 ** (UndefinedFunctionError) function ToyRobot.Game.Server.player_count/1 is undefined or private
4 code: assert Server.player_count(game) == 1
5 stacktrace:
6 (toy_robot) ToyRobot.Game.Server.player_count(#PID<0.174.0>)
7 test/toy_robot/game/server_test.exs:13: (test)

Great, it looks like the first line of our test – placement of our robot – is succeeding. What’s now failing is
the 2nd line where we count how many players are on the server. Let’s add the missing function for this:

lib/toy_robot/game/server.ex

1 def player_count(game) do
2 GenServer.call(game, :player_count)
3 end

Because we have another call to our server, we will need another handle_call function too. But how do we
track the number of players within our game? One of the ways that we could track these players is within a
map like this:
Multiplayer Toy Robot 170

1 %{
2 "Rosie" => #PID<0.123.0>
3 }

This way then, we can always refer to a player’s name to get access to the relevant process. One easy way
to build up this map would be to extend the handle_call for the {:place, ...} message to add a player when
they’re placed:

lib/toy_robot/game/server.ex

1 def handle_call(
2 {:place, position, name},
3 _from,
4 %{table: table, players: players} = state
5 ) do
6 {:ok, player} = PlayerSupervisor.start_child(table, position, name)
7 players = players |> Map.put(name, player)
8 {:reply, :ok, %{state | players: players}}
9 end

This will work in the case of an initial placement, but if the player process was to restart – for example, if it
moved off the edge of the table – then our map would be incorrect.
Does this sound familiar to you? Something that lets us register processes with particular names, and it
keeps a track of them when they reboot? That’s right: our PlayerRegistry already does this. We start it up
inside PlayerSupervisor.init/1:

lib/toy_robot/game/player_supervisor.ex

1 def init(_args) do
2 Registry.start_link(keys: :unique, name: ToyRobot.Game.PlayerRegistry)
3 DynamicSupervisor.init(strategy: :one_for_one)
4 end

However, this registry is configured to be globally unique – there is only ever one of these registries at any
given point in time. But now that we’re wanting to know which players are in which games, and to also track
them across crashes of their processes, it’s time that we changed how we construct this registry in the first
place. We will not need to track players within the Game.Server if we do it this way!
We’ll come back to that handle_call definition for counting our game’s players. Let’s first look at how we can
create a registry for each game.

Interlude: Moving the registry

Previously, we were able to rely on ToyRobot.Game.PlayerRegistry to keep a track of our player processes. This
allowed us to refer to those processes by the player’s name, rather than a PID that could keep changing.
In this short interlude, instead of calling the game ToyRobot.Game.PlayerRegistry, we’re going to create a
unique registry per Game.Server. Each registry will have a unique ID, and we’ll get one by using a Hex package
called UUID.
Multiplayer Toy Robot 171

By the end of this, we will be able to count how many players there are in a game by asking a game’s registry
how many players it knows about. The code we’ll end up with will be almost as simple as:

1 Registry.count(registry_id)

Won’t that be nice?


To start off with, we’ll need to stop our current registry from being created inside of PlayerSupervisor. We’ll
change the init function in this module to just this:

lib/toy_robot/game/player_supervisor.ex

1 def init(_args) do
2 DynamicSupervisor.init(strategy: :one_for_one)
3 end

Next up, we’ll then configure our Game.Server.init function to start a registry for each game:

lib/toy_robot/game/server.ex

1 def init(north_boundary: north_boundary, east_boundary: east_boundary) do


2 registry_id = "game-#{UUID.uuid4()}" |> String.to_atom
3 Registry.start_link(keys: :unique, name: registry_id)
4 {:ok,
5 %{
6 registry_id: registry_id,
7 table: %Table{
8 north_boundary: north_boundary,
9 east_boundary: east_boundary
10 },
11 }}
12 end

The first two lines here will generate a unique ID for our game, converting it into an Atom, as that is what
the name option for Registry.start_link needs. Once that registry has started, we then keep a track of its ID
in the state of the Game.Server. We’ll use that ID in just a moment.
Before we do that, we will need to install the uuid package. Let’s go over to the mix.exs file and add it as a
dependency:
Multiplayer Toy Robot 172

mix.exs
1 defp deps do
2 [
3 {:uuid, "~> 1.1.8"},
4 ]
5 end

This will make the package a dependency of our application. We will need to run another command to
actually install it:

1 mix deps.get

With that package installed, the UUID.uuid4() function in Game.Server.init/1 will now work correctly. The next
step here is to use that registry_id inside of the handle_call for {:place, ...} messages, so that we can pass
it down into PlayerSupervisor.start_child:

lib/toy_robot/game/server.ex
1 def handle_call({:place, position, name}, _from, %{registry_id: registry_id, table: table} = state) do
2 {:ok, _player} = PlayerSupervisor.start_child(registry_id, table, position, name)
3 {:reply, :ok, state}
4 end

We’ve changed the whole function here. We’re now extracting registry_id from the third argument (the
state), and we’re passing it through as the first argument to PlayerSupervisor.start_child. We’re passing it
through here so that when our player processes start up, they will be able to register with the relevant
game’s registry.
We’re now passing additional arguments to PlayerSupervisor.start_child again, and just like when we passed
in table earlier, we now need to go and change a few more things: the PlayerSupervisor.start_child function
itself, as well as some functions over in the Player module. Let’s start with start_child:

lib/toy_robot/game/player_supervisor.ex
1 def start_child(registry_id, table, position, name) do
2 DynamicSupervisor.start_child(
3 __MODULE__,
4 {Player,
5 [
6 registry_id: registry_id,
7 table: table,
8 position: position,
9 name: name
10 ]}
11 )
12 end

This function will now take that registry_id argument as its first argument, and will pass it through in the
child specification that is used to start a new Player process. This argument will end up in the list passed to
Player.start_link, so let’s update that function now to take that argument:
Multiplayer Toy Robot 173

lib/toy_robot/game/player.ex

1 def start_link(registry_id: registry_id, table: table, position: position, name: name) do


2 GenServer.start_link(
3 __MODULE__,
4 [
5 table: table,
6 position: position
7 ],
8 name: process_name(registry_id, name)
9 )
10 end

This function has been changed to accept the registry_id keyword, and then the function passes it through
to process_name. The process_name function is used to link the process and its relevant registry. We’ll need to
update this function to take this registry_id argument:

lib/toy_robot/game/player.ex

1 def process_name(registry_id, name) do


2 {:via, Registry, {registry_id, name}}
3 end

The tuple returned here will give the process a name along the lines of:

1 {
2 :via,
3 Registry,
4 {
5 "3b0e5971-d405-4dff-9ed4-d8334d95fecc",
6 "Rosie",
7 }
8 }

Later on when we want to communicate with this process, we’ll have to use this tuple to do so. Fortunately,
if we know both the registry_id and a name, then we can pass both of those to Player.process_name and get
back this tuple.
This should be all the work we need to do in order to create a registry per-game for our multi-player toy
robot. Let’s jump back to our tests.

Back to the tests

Let’s just run this module’s tests to see how things are going. We’ll run this command:

1 mix test test/toy_robot/game/server_test.exs

We’ll see that the corresponding handle_call for player_count is missing:


Multiplayer Toy Robot 174

1 09:23:24.872 [error] GenServer #PID<0.185.0> terminating


2 ** (FunctionClauseError) no function clause matching in ToyRobot.Game.Server.handle_call/3
3 (toy_robot) lib/toy_robot/game/server.ex:28: ToyRobot.Game.Server.handle_call(:player_count,

Let’s now add this handle_call. What this needs to do is to count the number of players within the game.
Thankfully, we have just created a registry to track each of the players in the game and so the code to count
them up is straightforward:

1 def handle_call(:player_count, _from, %{registry_id: registry_id} = state) do


2 {:reply, Registry.count(registry_id), state}
3 end

A simple Registry.count is all we need to count up the number of processes (and therefore players) that the
game knows about.
This should be everything that we need to make this test pass. Let’s run our tests again. We should see that
our test is now passing:

1 1 test, 0 failures

Great! We’re now able to place a new player on the board and have its new Player process register within a
game’s registry. Later on, we’ll use that Player.process_name function to send messages directly to a player
process.
Before we get to that though, we have changed a fair amount in our application and we haven’t yet run all
the tests. Now is a fantastic time to do that. Let’s run mix test and look at the broken things.
There are currently 3 tests broken in PlayerSupervisor’s tests, all because start_child now takes 4 arguments
instead of 3. Here’s one of those tests:

1 1) test starts a game child process (ToyRobot.Game.PlayerSupervisorTest)


2 test/toy_robot/game/player_supervisor_test.exs:14
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.start_child/3 is undefined or priva\
4 te
5 code: PlayerSupervisor.start_child(
6 stacktrace:
7 (toy_robot) ToyRobot.Game.PlayerSupervisor.start_child(%ToyRobot.Table{east_boundary: 4, north_bounda\
8 ry: 4}, %{east: 0, facing: :north, north: 0}, "Izzy")
9 test/toy_robot/game/player_supervisor_test.exs:17: (test)

Another one in that same file fails because PlayerSupervisor processes no longer start up a registry:
Multiplayer Toy Robot 175

1 4) test starts a registry (ToyRobot.Game.PlayerSupervisorTest)


2 test/toy_robot/game/player_supervisor_test.exs:29
3 Expected truthy, got nil
4 code: assert registry
5 stacktrace:
6 test/toy_robot/game/player_supervisor_test.exs:31: (test)

Let’s work through these and fix them up one at a time. Once we’re done, we’ll come back to the Game.Server
module and add the ability to count a game’s players.

Fixing the broken PlayerSupervisor tests

Our first test breaks like this:

1 1) test starts a game child process (ToyRobot.Game.PlayerSupervisorTest)


2 test/toy_robot/game/player_supervisor_test.exs:14
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.start_child/3 is undefined or priva\
4 te
5 code: PlayerSupervisor.start_child(
6 stacktrace:
7 (toy_robot) ToyRobot.Game.PlayerSupervisor.start_child(%ToyRobot.Table{east_boundary: 4, north_bounda\
8 ry: 4}, %{east: 0, facing: :north, north: 0}, "Izzy")
9 test/toy_robot/game/player_supervisor_test.exs:17: (test)

The start_child function has changed now to take a registry_id as its first argument. So the test should be
making a call to this function with a registry_id:

test/toy_robot/game/player_supervisor_test.exs

1 test "starts a game child process" do


2 starting_position = %{north: 0, east: 0, facing: :north}
3 {:ok, player} =
4 PlayerSupervisor.start_child(
5 registry_id,
6 build_table(),
7 starting_position,
8 "Izzy"
9 )
10
11 [{registered_player, _}] = Registry.lookup(
12 registry_id, "Izzy"
13 )
14 assert registered_player == player
15 end

This test does not have a registry_id though. We’ll need to create one! And not just for this test, but for a
majority of the tests in this file. This is a perfect use-case for a new setup block at the top of our test file:
Multiplayer Toy Robot 176

test/toy_robot/game/player_supervisor_test.exs

1 setup do
2 registry_id = "play-sup-test-#{UUID.uuid4()}" |> String.to_atom
3 Registry.start_link(keys: :unique, name: registry_id)
4 {:ok, %{registry_id: registry_id}}
5 end

This new code will start up a new Registry for each test and will make that registry’s ID available in each of
the tests. Let’s change that first test now to take in that ID by changing its first line to this:

test/toy_robot/game/player_supervisor_test.exs

1 test "starts a game child process", %{registry_id: registry_id} do

If we run this file with this command:

1 mix test test/toy_robot/game/player_supervisor_test.exs

We should see now that there is at least one passing test in this file:

1 4 tests, 3 failures

The next test that is failing is the “starts a registry” test:

test/toy_robot/game/player_supervisor_test.exs

1 test "starts a registry" do


2 registry = Process.whereis(ToyRobot.Game.PlayerRegistry)
3 assert registry
4 end

We don’t need this test anymore, because games now start their own registries. Let’s delete it.
For the final two tests in this file, we’ll need to make the same changes that we made to the first test. Namely,
adding registry_id as the first argument to start_child, and also changing the first line of the tests to take
that registry_id argument. We’ll also need to change the calls to move and report to pass in that registry_id
too:
Multiplayer Toy Robot 177

test/toy_robot/game/player_supervisor_test.exs

1 test "moves a robot forward", %{registry_id: registry_id} do


2 starting_position = %{north: 0, east: 0, facing: :north}
3 {:ok, _player} = PlayerSupervisor.start_child(
4 registry_id,
5 build_table(),
6 starting_position,
7 "Charlie"
8 )
9
10 :ok =
11 PlayerSupervisor.move(
12 registry_id,
13 player_name
14 )
15 %{north: north} = PlayerSupervisor.report(registry_id, player_name)
16
17 assert north == 1
18 end
19
20 test "reports a robot's location", %{registry_id: registry_id} do
21 starting_position = %{north: 0, east: 0, facing: :north}
22 {:ok, _player} = PlayerSupervisor.start_child(
23 registry_id,
24 build_table(),
25 starting_position,
26 "Davros"
27 )
28 %{north: north} = PlayerSupervisor.report(registry_id, "Davros")
29
30 assert north == 0
31 end

These test setups are starting to look awfully familar. Let’s refactor them move the start_child call into the
setup block:

test/toy_robot/game/player_supervisor_test.exs

1 setup do
2 registry_id = "play-sup-test-#{UUID.uuid4()}" |> String.to_atom
3 Registry.start_link(keys: :unique, name: registry_id)
4
5 starting_position = %{north: 0, east: 0, facing: :north}
6 player_name = "Izzy"
7
8 {:ok, _player} =
9 PlayerSupervisor.start_child(
10 registry_id,
11 build_table(),
12 starting_position,
13 player_name
14 )
Multiplayer Toy Robot 178

15
16 # Ensure process has been registered
17 [{_registered_player, _}] =
18 Registry.lookup(
19 registry_id,
20 player_name
21 )
22
23 {:ok, %{registry_id: registry_id, player_name: player_name}}
24 end

This change to the setup block will mean that we can delete the top test in this file, the one that uses
Registry.lookup:

test/toy_robot/game/player_supervisor_test.exs

1 test "starts a game child process" do


2 robot = %Robot{north: 0, east: 0, facing: :north}
3 {:ok, player} = PlayerSupervisor.start_child(robot, "Izzy")
4
5 [{registered_player, _}] = Registry.lookup(ToyRobot.Game.PlayerRegistry, "Izzy")
6 assert registered_player == player
7 end

This is now tested by the setup block instead. Let’s update the remaining two tests now to use the information
provided by the setup block:

test/toy_robot/game/player_supervisor_test.exs

1 test "moves a robot forward", %{


2 registry_id: registry_id,
3 player_name: player_name
4 } do
5 :ok =
6 PlayerSupervisor.move(
7 registry_id,
8 player_name
9 )
10 %{north: north} = PlayerSupervisor.report(registry_id, player_name)
11
12 assert north == 1
13 end
14
15 test "reports a robot's location", %{
16 registry_id: registry_id,
17 player_name: player_name
18 } do
19 %{north: north} = PlayerSupervisor.report(registry_id, player_name)
20
21 assert north == 0
22 end
Multiplayer Toy Robot 179

These two tests now only do the bare essentials to test our PlayerSupervisor functions. All the setup has now
been moved back to the setup block.
To make these tests work, we will need to make changes to the calls to move and report as well so that these
functions can correctly locate the players now that their processes are registered within a registry. Let’s
update these two functions in PlayerSupervisor and then we’ll talk more about them:

lib/toy_robot/game/player_supervisor.ex
1 def move(registry_id, name) do
2 registry_id |> Player.process_name(name) |> Player.move()
3 end
4
5 def report(registry_id, name) do
6 registry_id |> Player.process_name(name) |> Player.report()
7 end

These two functions now use the combination of the registry_id and the name and pass that through to
Player.process_name to determine how to send a message to a Player process. At this point, you might consider
PlayerSupervisor a poor home for these two functions now that we have Game.Server… and you would be right.
These two functions do belong over in Game.Server. But for now, we are just trying to get back to green tests.
Later on, we will move these functions over to Game.Server when the time is right.
With these changes in the tests for PlayerSupervisor and in the module itself, everything should once again
work. Let’s run the tests for this file:

1 mix test test/toy_robot/game/player_supervisor_test.exs

And we’ll see that these two tests are passing:

1 2 tests, 0 failures

As I said before: We are going to re-locate the move and report functions from PlayerSupervisor into Game.Server
relatively soon. This is a better place for them to belong, because Game.Server will know about the registry_id
as a part of its state.
For now, let’s stay on the topic of fixing the broken tests. Is everything working again? Let’s check with
another run of mix test:

1 12 doctests, 42 tests, 0 failures

Yes they are!


This means that not only can we place a robot successfully on a table, but we can also correctly count
the number of players within a game. This is the first step to building our multiplayer robot, and it is now
complete. That’s the “happy path” of a robot’s placement covered. We’re good developers, and so we’ll spend
some time looking at the “unhappy path” too.
Next up, let’s look at how we would handle the situation where a robot is placed outside of the boundaries of
a table. Then right after that, we’ll get back to looking at how we can prevent two robots from being placed
on the same position of the board at the same time.
Multiplayer Toy Robot 180

Handling an invalid placement (off the board)

Let’s take a quick look at what happens if we attempt to place a robot outside of the table’s boundaries. We’ll
do this by taking our new code for a spin in a new iex -S mix session:

1 iex> alias ToyRobot.Game.Server


2 ToyRobot.Game.Server
3 iex> {:ok, game} = Server.start_link([north_boundary: 4, east_boundary: 4])
4 {:ok, #PID<0.141.0>}
5 iex> :ok = Server.place(game, %{north: 10, east: 10, facing: :north}, "Rosie")
6 :ok

It seems like we can place a robot wherever we wish! The Game.Server.place function is not validating that
the position of the robot is within the game’s table’s boundaries.
Just as we did back in Chapter 2 when we discovered our first bugs, we’ll now write a regression test for
this one. This test should ensure that we cannot place a robot outside the boundaries of a table. As a
part of writing the fix for this regression test, we’ll move that logic that involves creating a table into
ToyRobot.Game.Server. That way then, all robots can share the same table dimensions.

Let’s start by writing that regression test:

test/toy_robot/game/server_test.exs

1 test "can not place a robot out of bounds", %{game: game} do


2 assert Server.place(
3 game, %{north: 10, east: 10, facing: :north},
4 "Eve"
5 ) == {:error, :out_of_bounds}
6 end

This new test asserts that when we try to place a new robot way outside of the table’s boundaries, it should
return a tuple of {:error, :out_of_bounds}.
When we run this test, we’ll see it fail:

1 1) test can not place a robot out of bounds (ToyRobot.Game.ServerTest)


2 test/toy_robot/game/server_test.exs:16
3 Assertion with == failed
4 code: assert Server.place(game, %{north: 10, east: 10, facing: :north}, "Eve") == {:error, :out_of_bou\
5 nds}
6 left: :ok
7 right: {:error, :out_of_bounds}
8 stacktrace:
9 test/toy_robot/game/server_test.exs:17: (test)

This is a great place to start from. We now have a test that automatically reproduces the bug that we’re
seeing, and that test asserts what the correct behaviour is. We’re now in a safe spot to fix this bug.
Multiplayer Toy Robot 181

There’s two places we could put this code to fix this bug: into the handle_call function that handles the :place
call, or we could put it into the place/3 function itself. I prefer the latter, because then we can separate out
the logic of checking if a placement is valid from the logic of placing the robot. Let’s see this in action. Here’s
what we’ll change our place/3 function to:

lib/toy_robot/game/server.ex

1 def place(game, position, name) do


2 game
3 |> valid_position(position)
4 |> case do
5 :ok -> GenServer.call(game, {:place, position, name})
6 error -> error
7 end
8 end

This valid_position/2 function that we’re calling here will itself do a GenServer.call:

lib/toy_robot/game/server.ex

1 defp valid_position(game, position) do


2 GenServer.call(game, {:valid_position, position})
3 end

Then we will need a handle_call to handle this {:valid_position, position} message:

lib/toy_robot/game/server.ex

1 def handle_call({:valid_position, position}, _from, %{table: table} = state) do


2 reply =
3 if table |> Table.valid_position?(position) do
4 :ok
5 else
6 {:error, :out_of_bounds}
7 end
8
9 {:reply, reply, state}
10 end

This is the way that I would prefer to do it: the logic for checking if a position is valid is in one function and
the logic for doing the actual placement is in another function. Here’s what it would look like if we went the
alternative route:
Multiplayer Toy Robot 182

lib/toy_robot/game/server.ex

1 def handle_call({:place, position, name}, _from, %{players: players, table: table} = state) do
2 if table |> Table.valid_position?(position) do
3 {:ok, player} = PlayerSupervisor.start_child(position, name)
4 players = players |> Map.put(name, player)
5 {:reply, :ok, %{state | players: players}}
6 else
7 {:reply, {:error, :out_of_bounds}, state}
8 end
9 end

This code is a little messy because it’s doing two things at once: it’s checking if the position is valid and it’s
also doing the placement itself. Hopefully this clarifies why I prefer the split route instead!
These changes should be enough to get our test passing. Let’s run it with mix test and find out:

1 12 doctests, 43 tests, 0 failures

All good! Everything is now back to fully operational. We have our Game.Server validating that our robot is
being placed within a table’s boundaries, as we wanted.
Now that we’re correctly handling a single robot’s valid or invalid placement, it’s time to add some more
complexity to our game: let’s handle the case where a placement is invalid because the position on the
board has already been taken by another robot.

Handling an invalid placement (square occupied)

Currently, our game will allow for two robots to be placed on exactly the same position of a game’s board.
This can be demonstrated with this code:

1 iex> alias ToyRobot.Game.Server


2 ToyRobot.Game.Server
3 iex> {:ok, game} = Server.start_link([north_boundary: 4, east_boundary: 4])
4 {:ok, #PID<0.141.0>}
5 iex> :ok = Server.place(game, %{north: 0, east: 0, facing: :north}, "Rosie")
6 :ok
7 iex> :ok = Server.place(game, %{north: 0, east: 0, facing: :north}, "Wall-E")
8 :ok

Our game should follow an essential rule of physics: two objects cannot occupy the same space at the same
time. Let’s look at how we can handle this in our application’s code. Let’s start out by writing a new test:
Multiplayer Toy Robot 183

test/toy_robot/game/server_test.exs

1 test "can not place a robot in the same space as another robot", %{game: game} do
2 starting_position = %{north: 0, east: 0, facing: :north}
3
4 :ok =
5 Server.place(
6 game,
7 starting_position,
8 "Wall-E"
9 )
10
11 assert Server.place(
12 game,
13 starting_position,
14 "Robby"
15 ) == {:error, :occupied}
16 end

This new test asserts that the first robot is placed successfully, but the second one fails with an {:error,
:occupied} tuple. When we run this test, we’ll see that our two robots can currently be placed at the same
place:

1 1) test can not place a robot in the smme space as another robot (ToyRobot.Game.ServerTest)
2 test/toy_robot/game/server_test.exs:23
3 Assertion with == failed
4 code: assert Server.place(game, %{north: 0, east: 0, facing: :north}, "Robby") == {:error, :occupied}
5 left: :ok
6 right: {:error, :occupied}
7 stacktrace:
8 test/toy_robot/game/server_test.exs:29: (test)

Great, now that we have a failing test we can set about fixing it! In the last section, I argued for creating
a valid_position function that would determine if a robot’s position was within the boundaries of a game’s
table. I would argue along almost the same lines for creating a function that checks if a position on the
board is available. Let’s add one of those:

lib/toy_robot/game/server.ex

1 def position_available(game, position) do


2 GenServer.call(game, {:position_available, position})
3 end

We’re taking just the north and east keys here from the position, because we don’t need to take into account
the facing direction of the robot to determine if the position is occupied or not.
Next up, we’ll need to add a handle_call for this new message. In order to check if a position is occupied, we
will first need a way of getting all of the current positions for the players within our game. To do that, we’re
going to need to be able to ask all of the players where they are on the board. Luckily for us, we are tracking
those players in a registry.
Multiplayer Toy Robot 184

Before we add this handle_call, let’s first add a function that will give us a list of players that we are available
in that registry, and their relevant process names. Rather than cramming it into this Server module, let’s
split it out to a new module called Players:

lib/toy_robot/game/players.ex
1 defmodule ToyRobot.Game.Players do
2 alias ToyRobot.Game.Player
3
4 def all(registry_id) do
5 registry_id
6 |> Registry.select([{{:"$1", :_, :_}, [], [:"$1"]}])
7 |> Enum.map(fn (player_name) ->
8 Player.process_name(registry_id, player_name)
9 end)
10 end
11 end

We’re splitting this out to a separate module because we’re going to be working with a collection of players
like this a lot throughout our code. If we kept this logic within Game.Server, that module will get quite bloated.
It’s better, in this case, to separate it out early. We’ll add more functions to Game.Players as we need them.
The Registry.select syntax here looks like a bit of arcane black magic. Let’s just step through it so that we
can understand it. The first element inside that list is a match pattern. When we register a process within a
registry, it stores that information in a tuple in the shape of:

1 {
2 key,
3 pid,
4 value
5 }

Where key here is the player’s name, and the pid is the process that is currently registered to that key. The
value here will be nil for our purposes. So this first element in the list is equivalent to saying “get me
everything in the registry, but just the keys”. The “$1” here is a placeholder that we can use to refer to
that key later on in the third argument.
The second element in the list is a list of guards – checks that we could do to filter the keys within the
registry. We do not do any of these, so let’s ignore them.
The third element is the form that we want to output our keys from the registry. We use $1 here to pull out
that key. All of this confusing arcane black magic code will give us a simple list of player names:

1 [
2 "Wall-E",
3 ...
4 ]

We can’t quite send messages to players based on their name though. There’s one more thing we need to do:
we need to convert this list of player names into a list of process via addresses, which we do by piping this
list through to Enum.map and Player.process_name. This converts our list of names into a list of process name
tuples:
Multiplayer Toy Robot 185

1 [
2 {:via, Registry, {:"game-ade9c963-b26c-4d7f-bbd2-dfaabdf0e5c5", "Wall-E"}},
3 ...
4 ]

With this list of process addresses, we will be able to send our player processes messages asking where they
are!
Let’s now go about adding this handle_call:

lib/toy_robot/game/server.ex

1 def handle_call({:position_available, position}, _from, %{registry_id: registry_id} = state) do


2 positions =
3 registry_id
4 |> Players.all
5 |> Enum.map(&ToyRobot.Game.Player.report/1)
6
7 {:reply, false, state}
8 end

The code inside this handle_call looks like it might be a good candidate for the Game.Players module: it’s
working with a group of players and asking them to report their positions. Let’s move that code over now:

lib/toy_robot/game/players.ex

1 def positions(players) do
2 players |> Enum.map(&(&1 |> Player.report |> coordinates))
3 end
4
5 defp coordinates(position) do
6 position |> Map.take([:north, :east])
7 end

This new positions method will take a list of players, and then use Enum.map to go through that list. For each
element in that list, it pipes that process’s name through to Player.report, which will give us a player’s
current location in the form of a map like this:

1 %{north: 0, east: 0, facing: :north}

Then we pipe that map through to coordinates, as we only care about the north and east part of this map.
This will give us:

1 %{north: 0, east: 0}

By moving this code over to Players, we can reduce the complexity in that handle_call function back in
Game.Server:
Multiplayer Toy Robot 186

lib/toy_robot/game/server.ex

1 def handle_call({:position_available, position}, _from, %{registry_id: registry_id} = state) do


2 positions = registry_id |> Players.all |> Players.positions
3
4 {:reply, false, state}
5 end

This makes our code really elegant! It almost reads as easy as: “find all the players within a particular
registry, and then give me those players coordinates”. Let’s not forget to add an alias for this new module
to the top of the Game.Server module:

lib/toy_robot/game/server.ex

1 alias ToyRobot.Game.{PlayerSupervisor, Players}

We could even go further and add a function here to determine if the position is available:

lib/toy_robot/game/server.ex

1 def handle_call({:position_available, position}, _from, %{registry_id: registry_id} = state) do


2 position_available =
3 registry_id
4 |> Players.all
5 |> Players.positions
6 |> Players.position_available?(position)
7 {:reply, false, state}
8 end

And then the relevant function in Players:

lib/toy_robot/game/players.ex

1 def position_available?(occupied_positions, position) do


2 (position |> coordinates()) not in occupied_positions
3 end

The first argument we get in this function is the result of Players.positions. The second arugment passed to
this function, position, is the position of the player’s robot. Just to make sure we’ve got just the north and
east keys in that map, we pass it to coordinates which will give us just that. Then we’re able to check if that
coordinate map is contained within the list of known coordinate positions. If that position is not in th e list
of occupied positions, then we can consider this position to be avialable.
Now that we have all the positions of all the robots that are currently placed on the board, we can compare
this with the position that is passed in at the top of our function by changing our code to this:
Multiplayer Toy Robot 187

lib/toy_robot/game/server.ex

1 def handle_call({:position_available, position}, _from, %{registry_id: registry_id} = state) do


2 position_available =
3 registry_id
4 |> Players.all
5 |> Players.positions
6 |> Players.position_available?(position)
7
8 reply = if position_available, do: :ok, else: {:error, :occupied}
9 {:reply, reply, state}
10 end

We’ve added a new line to check if that position is available. If it is,


We now have spent a lot of time on this position_available/2 function, but we’re not actually using it
anywhere! It’s time to fix that by changing our place/3 function to use this new function. Here’s one way to
do it with nested case statements:

lib/toy_robot/game/server.ex

1 def place(game, position, name) do


2 game
3 |> valid_position(position)
4 |> case do
5 :ok ->
6 game
7 |> position_available(position)
8 case do
9 :ok ->
10 GenServer.call(game, {:place, position, name})
11 error -> error
12 end
13 error -> error
14 end
15 end

That code is very noisy and hard to read. We have outgrown the usefulness of a case statement. A better
approach would be to use a with statement instead:

lib/toy_robot/game/server.ex

1 def place(game, position, name) do


2 with :ok <- game |> valid_position(position),
3 :ok <- game |> position_available(position) do
4 GenServer.call(game, {:place, position, name})
5 else
6 error -> error
7 end
8 end
Multiplayer Toy Robot 188

In this with statement, as long as both valid_position and position_available return :ok then the placement
of the robot on our game’s table will happen. If either of them returns something that is not :ok, then the
else clause of the with catches that, and will return the specified error.

These changes should mean that our robots can no longer occupy the same space at the same time, and that
robots still cannot be placed outside the boundaries of the table. If we were to add any further checks here,
we can simply add another line to the with statement.
Let’s check our work by running the tests:

1 12 doctests, 44 tests, 0 failures

All good! Two robots now cannot be placed at the same position on the same table. We’ve now completed
the first rule for our robot… and we’ve set up some great groundwork that we can use for the other two rules
too.
Before we get to those rules though, I want to spend a bit of time on refactoring this Server module so that
it’s code is a little easier to work with. This will only take a few short minutes, and then we can get back to
the two remaining rules.

A refactoring interlude: Breaking Server apart

I want to take some time now to look at how we might make our Server module’s code a little cleaner to
work with. We’ve tackled something like this by creating our Players module, but I think we could ptentially
go another step further.
I think at this point, we have two pretty clearly defined levels of responsibilities in the Game.Server module:

1. Call a Server process with a particular message


2. Handle that message

When we combine responsibilities like this in the same place, then the mental fatigue can increase – “am
I working on the calling or the handling part of this code?”, is a question you might ask yourself. So what
we’ll do here is we’ll tease apart the code into two distinct modules. One called Game that sends messages to
Server, and then we’ll leave all the messaging handling stuff in Server. This will make it less mentally taxing
to work in this code.
The first thing that we’ll do is focus on moving out these calling functions. To make sure that things are still
working as they should, we’ll need to alter our tests slightly. Let’s rename the test/toy_robot/game/server_-
test.exs file to test/toy_robot/game_test.exs, as these tests will now be calling functions on a Game module,
not a Server module. Let’s change those tests now:
Multiplayer Toy Robot 189

test/toy_robot/game/game_test.exs

1 defmodule ToyRobot.GameTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.Game
5
6 setup do
7 {:ok, game} = Game.start(north_boundary: 4, east_boundary: 4)
8 {:ok, %{game: game}}
9 end
10
11 test "can place a robot", %{game: game} do
12 :ok = Game.place(game, %{north: 0, east: 0, facing: :north}, "Rosie")
13 assert Game.player_count(game) == 1
14 end
15
16 test "can not place a robot out of bounds", %{game: game} do
17 assert Game.place(
18 game, %{north: 10, east: 10, facing: :north},
19 "Eve"
20 ) == {:error, :out_of_bounds}
21 end
22
23 test "can not place a robot in the same space as another robot", %{game: game} do
24 :ok = Game.place(
25 game,
26 %{north: 0, east: 0, facing: :north},
27 "Wall-E"
28 )
29 assert Game.place(
30 game,
31 %{north: 0, east: 0, facing: :north},
32 "Robby"
33 ) == {:error, :occupied}
34 end
35 end

Rather than calling these functions on the Server module, we’re now calling them on the Game module. One
important difference to note is right at the top, in the setup block. We’re now calling Game.start instead of
Server.start_link([]). The reason for doing this is so that we can simplify the process of starting up a game.

If we run these tests now, we will see them fail because we’re attempting to call functions that are missing:
Multiplayer Toy Robot 190

1 1) test can place a robot (ToyRobot.GameTest)


2 test/toy_robot/game/server_test.exs:11
3 ** (UndefinedFunctionError) function ToyRobot.Game.start/1 is undefined or private
4 stacktrace:
5 (toy_robot) ToyRobot.Game.start()
6 test/toy_robot/game/server_test.exs:7: ToyRobot.GameTest.__ex_unit_setup_0/1
7 test/toy_robot/game/server_test.exs:1: ToyRobot.GameTest.__ex_unit__/2

Let’s start by adding that start function to a new module called ToyRobot.Game:

lib/toy_robot/game/game.ex
1 defmodule ToyRobot.Game do
2 alias ToyRobot.Game.Server
3
4 def start([north_boundary: north_boundary, east_boundary: east_boundary]) do
5 Server.start_link([
6 north_boundary: north_boundary,
7 east_boundary: east_boundary
8 ])
9 end
10 end

This function will now be the one responsible for calling Server.start_link/1 and this will start our Server
process up.
The next function that we will need in this module is place/3, which we can move over from ToyRobot.Game.Server
into ToyRobot.Game:

lib/toy_robot/game/game.ex
1 defmodule ToyRobot.Game do
2 alias ToyRobot.Game.Server
3
4 def start([north_boundary: north_boundary, east_boundary: east_boundary]) do
5 Server.start_link([
6 north_boundary: north_boundary,
7 east_boundary: east_boundary
8 ])
9 end
10
11 def place(game, position, name) do
12 with :ok <- game |> valid_position(position),
13 :ok <- game |> position_available(position) do
14 GenServer.call(game, {:place, position, name})
15 else
16 error -> error
17 end
18 end
19 end

This function needs valid_position/2 and position_available/2 to operate, and so we should move those over
too, putting them underneath the place function, as they’re private functions:
Multiplayer Toy Robot 191

lib/toy_robot/game/game.ex
1 def valid_position(game, position) do
2 GenServer.call(game, {:valid_position, position})
3 end
4
5 def position_available(game, position) do
6 GenServer.call(game, {:position_available, position})
7 end

While we’re in this file, let’s take that final “call” function player_count/1 back into Game:

lib/toy_robot/game/game.ex
1 def player_count(game) do
2 GenServer.call(game, :player_count)
3 end

Our Game module should now be this:

lib/toy_robot/game/game.ex
1 defmodule ToyRobot.Game do
2 alias ToyRobot.Game.Server
3
4 def start([north_boundary: north_boundary, east_boundary: east_boundary]) do
5 Server.start_link([
6 north_boundary: north_boundary,
7 east_boundary: east_boundary
8 ])
9 end
10
11 def place(game, position, name) do
12 with :ok <- game |> valid_position(position),
13 :ok <- game |> position_available(position) do
14 GenServer.call(game, {:place, position, name})
15 else
16 error -> error
17 end
18 end
19
20 def player_count(game) do
21 GenServer.call(game, :player_count)
22 end
23
24 defp valid_position(game, position) do
25 GenServer.call(game, {:valid_position, position})
26 end
27
28 defp position_available(game, position) do
29 GenServer.call(game, {:position_available, position})
30 end
31 end
Multiplayer Toy Robot 192

And our server.ex code should now be this:

lib/toy_robot/game/server.ex

1 defmodule ToyRobot.Game.Server do
2 use GenServer
3
4 alias ToyRobot.Game.{Players, Player, PlayerSupervisor}
5 alias ToyRobot.{Game, Table}
6
7 def start_link(args) do
8 GenServer.start_link(__MODULE__, args)
9 end
10
11 def init(north_boundary: north_boundary, east_boundary: east_boundary) do
12 registry_id = "game-#{UUID.uuid4()}" |> String.to_atom()
13 Registry.start_link(keys: :unique, name: registry_id)
14
15 {:ok,
16 %{
17 registry_id: registry_id,
18 table: %Table{
19 north_boundary: north_boundary,
20 east_boundary: east_boundary
21 }
22 }}
23 end
24
25 def handle_call(
26 {:place, position, name},
27 _from,
28 %{registry_id: registry_id, table: table} = state
29 ) do
30 {:ok, _player} = PlayerSupervisor.start_child(registry_id, table, position, name)
31 {:reply, :ok, state}
32 end
33
34 def handle_call(:player_count, _from, %{registry_id: registry_id} = state) do
35 {:reply, Registry.count(registry_id), state}
36 end
37
38 def handle_call({:valid_position, position}, _from, %{table: table} = state) do
39 reply =
40 if table |> Table.valid_position?(position) do
41 :ok
42 else
43 {:error, :out_of_bounds}
44 end
45
46 {:reply, reply, state}
47 end
48
49 def handle_call({:position_available, position}, _from, %{registry_id: registry_id} = state) do
50 position_available =
Multiplayer Toy Robot 193

51 registry_id
52 |> Players.all
53 |> Players.positions
54 |> Players.position_available?(position)
55
56 reply = if position_available, do: :ok, else: {:error, :occupied}
57 {:reply, reply, state}
58 end
59 end

This approach makes more sense to me (and hopefully you too!) because all the code responsible for sending
messages to our server lives in ToyRobot.Game, and all the code responsible for receiving messages for that
server lives in ToyRobot.Game.Server. And we also have all the code that handles players and their relevant
processes over in ToyRobot.Game.Players.
You might wonder why we didn’t do this kind of thing for ToyRobot.Game.Player back in Chapter 7. The answer
that I have for that is that I think that the ToyRobot.Game.Player module is not quite complex enough yet. I
think if we built it out a little bit further then we would probably make the same changes there, splitting
into into two modules: Player.Server and Player. I will leave that as an exercise to the reader, however.
If we run mix test again, we’ll see everything is still working:

1 12 doctests, 44 tests, 0 failures

Our code is now better organised, and we will have an easier time implementing the other two rules.
Speaking of, let’s look at the next rule right now.

Preventing robots from colliding

The second (of three) rules that we’re implementing in our toy robot game is this:

1. A robot cannot move onto an occupied square – the movement should be prevented

In the last few sections, we’ve just made it so that we can check if a square is occupied while a robot is being
placed onto an occupied square, but this second rule concerns itself with what happens when a robot moves
onto an occupied square.
If you think back to the previous chapter, the way that we implemented moving a player was through the
PlayerSupervisor module, like this:
Multiplayer Toy Robot 194

lib/toy_robot/game/player_supervisor.ex
1 def move(name) do
2 name |> Player.process_name() |> Player.move()
3 end

Remember how I said earlier on that we were going to remove the move and report functions from
PlayerSupervisor? This is that section!

I reckon the application will not currently check if robots are colliding if they move. Call it intuition, call it
creative book editing. But I have a suspicion.
Let’s check this out with some code in the form of a test. While we’re here we’ll also add another test for
the “happy path” of a movement being allowed:

test/toy_robot/game/game_test.exs
1 describe "move" do
2 test "cannot move a robot onto another robot's square", %{game: game} do
3 :ok = Game.place(
4 game,
5 %{north: 0, east: 0, facing: :north},
6 "Marvin"
7 )
8
9 :ok = Game.place(
10 game,
11 %{north: 1, east: 0, facing: :south},
12 "Chappie"
13 )
14
15 assert Game.move(game, "Chappie") == {:error, :occupied}
16 end
17
18 test "can move onto an unoccupied square", %{game: game} do
19 :ok = Game.place(
20 game,
21 %{north: 0, east: 0, facing: :east},
22 "Mr. Roboto"
23 )
24
25 :ok = Game.place(
26 game,
27 %{north: 1, east: 0, facing: :south},
28 "Kit"
29 )
30
31 assert Game.move(game, "Mr. Roboto") == :ok
32 end
33 end

The movement on the final lines of these two tests are slightly different to our iex experiment – we’re using
the Game module to do the movement. This is because our Game.Server will be responsible for determining if
the movement is possible or not.
Multiplayer Toy Robot 195

When we run these newest tests, they will both fail:

1 1) test cannot move a robot onto another robot's square (ToyRobot.GameTest)


2 test/toy_robot/game/game_test.exs:36
3 ** (UndefinedFunctionError) function ToyRobot.Game.move/2 is undefined or private
4 code: assert Game.move(game, "Chappie") == {:error, :occupied}
5 stacktrace:
6 (toy_robot) ToyRobot.Game.move(#PID<0.206.0>, "Chappie")
7 test/toy_robot/game/game_test.exs:49: (test)
8
9 2) test can move onto an unoccupied square (ToyRobot.GameTest)
10 test/toy_robot/game/game_test.exs:52
11 ** (UndefinedFunctionError) function ToyRobot.Game.move/2 is undefined or private
12 code: assert Game.move(game, "Mr. Roboto") == :ok
13 stacktrace:
14 (toy_robot) ToyRobot.Game.move(#PID<0.224.0>, "Mr. Roboto")
15 test/toy_robot/game/game_test.exs:65: (test)

The Game.move/2 function is missing. This function will need to first figure out where the player’s next move
will be. We’ll need to a bit of work here to just to get this information. Let’s start by adding this move/2
function and make it figure out the player’s next position:

lib/toy_robot/game/game.ex

1 def move(game, name) do


2 next_position = next_position(game, name)
3 end

We’ll add a private function for next_position/2 to the bottom of this module:

lib/toy_robot/game/game.ex

1 defp next_position(game, name) do


2 GenServer.call(game, {:next_position, name})
3 end

This function will send a {:next_position, name} message to the Game.Server process, and so we’ll need to
handle that by creating a handle_call in that module:

lib/toy_robot/game/server.ex

1 def handle_call({:next_position, name}, _from, %{registry_id: registry_id} = state) do


2 {:reply, registry_id |> Players.next_position(name), state}
3 end

The next_position function doesn’t yet exist within the Players module. Let’s add it in over there now:
Multiplayer Toy Robot 196

lib/toy_robot/game/players.ex
1 def next_position(registry_id, name) do
2 registry_id
3 |> Player.process_name(player_name)
4 |> Player.next_position
5 end

This function uses the existing Player.process_name to figure out the address used to communicate with the
player. Then it uses the Player.next_position function to get that player’s nex tposition. The Player.next_-
position function doesn’t yet exist, so we will need to create this:

lib/toy_robot/game/player.ex
1 def next_position(player) do
2 GenServer.call(player, :next_position)
3 end

With every new GenServer.call, we need a corresponding handle_call. The other handle_call functions in this
module delegate responsibility to functions from the Simulation module, and our newest handle_call will not
be any different:

lib/toy_robot/game/player.ex
1 def handle_call(:next_position, _from, simulation) do
2 next_position = simulation |> Simulation.next_position()
3 {:reply, next_position, simulation}
4 end

Finally, we need a way of reporting the next position from within the simulation. Let’s add that Simulation.next_-
position/1 function, following the conventions of using doctests that this module already follows:

lib/toy_robot/simulation.ex
1 @doc """
2 Shows where the robot would move next.
3
4 ## Examples
5
6 iex> alias ToyRobot.{Robot, Table, Simulation}
7 [ToyRobot.Robot, ToyRobot.Table, ToyRobot.Simulation]
8 iex> table = %Table{north_boundary: 5, east_boundary: 5}
9 %Table{north_boundary: 5, east_boundary: 5}
10 iex> simulation = %Simulation{
11 ...> table: table,
12 ...> robot: %Robot{north: 0, east: 0, facing: :north}
13 ...> }
14 iex> simulation |> Simulation.next_position
15 %Robot{north: 1, east: 0, facing: :north}
16 """
17 def next_position(%{robot: robot} = _simulation) do
18 robot |> Robot.move
19 end
Multiplayer Toy Robot 197

We’re now at the end of this particular journey! That was quite a few steps. Here they are in condensed form:

1. Added a Game.move/2 function, that calls Game.next_position/2


2. Added next_position/2, that calls {:next_position, name} on the Game.Server process
3. Added a handle_call to Game.Server to handle this message, delegating to Player.next_position/1
4. Added Players.next_position/1 that sends a :next_position message to the relevant Player process.
5. Added a handle_call to Player to handle that message, delegating to Simulation.next_position/1
6. Added Simulation.next_position/1, which finally returns the next position of our robot.

All of this code will give us the ability to see what the next position of the robot would be, even if that’s off
the table. We don’t need it to be on the table, because where we’re about to use this data is in a function
that checks if the table position is currently occupied or not. Fortunately, that part is a little shorter to
write! Let’s update the move/2 function now to use our new “next move” function, in conjunction with the
position_available/2 function we wrote earlier:

lib/toy_robot/game/game.ex

1 def move(game, name) do


2 next_position = game |> next_position(name)
3
4 game
5 |> position_available(next_position)
6 |> case do
7 :ok -> GenServer.call(game, {:move, name})
8 error -> error
9 end
10 end

This move function has now been upgraded to determine if the next_position of the robot is occupied. If that
position_available/2 function returns :ok, then that next position is not occupied, and we can carry on with
moving the robot. If it returns anything else, we return that value and do not move the robot.
The GenServer.call here that sends {:move, name} to our game’s server doesn’t have a corresponding handle_-
call yet. Let’s add one back over in Server:

lib/toy_robot/game/server.ex

1 def handle_call({:move, name}, _from, %{registry_id: registry_id} = state) do


2 Players.move(registry_id, name)
3
4 {:reply, :ok, state}
5 end

This function moves a player with Players.move and replies with :ok if the movement is successful. By the
time this function has been called, we have determined if the space is occupied or not, and so we don’t need
to check it again in this function.
We need to add that Players.move function now:
Multiplayer Toy Robot 198

lib/toy_robot/game/players.ex

1 def move(registry_id, name) do


2 registry_id
3 |> Player.process_name(player_name)
4 |> Player.move
5 end

The first two lines of this function are identical to the ones in next_position, which would be a good sign
that we could move these lines out to another function. Let’s do that, and then update both functions:

lib/toy_robot/game/players.ex

1 def next_position(registry_id, name) do


2 registry_id |> find(name) |> Player.next_position
3 end
4
5 def move(registry_id, name) do
6 registry_id |> find(name) |> Player.move
7 end
8
9 defp find(registry_id, name) do
10 registry_id |> Player.process_name(name)
11 end

All of this code should be enough to make our tests pass. Let’s run them again and find out:

1 13 doctests, 46 tests, 0 failures

Excellent! Everything is working. We can now move our robot within the context of the game, while also
preventing it from “colliding” with other robots.
Before we move on to the third and final rule, there’s just a little bit of tidying up I would like us to do first.
We already had a way to move a robot and that was by calling this function in PlayerSupervisor:

lib/toy_robot/game/player_supervisor.ex

1 def move(name) do
2 name |> Player.process_name() |> Player.move()
3 end

This function worked well for our purposes in the last chapter, but now we have added a new group of
functions that will handle not just moving, but also checking if a movement is valid. Therefore, we will no
longer need this function. Let’s delete it from PlayerSupervisor now. There’s also a report function here that
we don’t need as well:
Multiplayer Toy Robot 199

lib/toy_robot/game/player_supervisor.ex

1 def report(registry_id, name) do


2 registry_id |> Player.process_name(name) |> Player.report()
3 end

Let’s remove this one too.


To see where these functions were used previously, let’s run mix test again. It will report that these functions
were only used in one place, a test:

1 1) test reports a robot's location (ToyRobot.Game.PlayerSupervisorTest)


2 test/toy_robot/game/player_supervisor_test.exs:52
3 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.report/2 is undefined or private
4 code: %{north: north} = PlayerSupervisor.report(registry_id, player_name)
5 stacktrace:
6 (toy_robot) ToyRobot.Game.PlayerSupervisor.report(:"play-sup-test-59eed71a-ac64-461f-b71c-2c3c7306ef6\
7 5", "Izzy")
8 test/toy_robot/game/player_supervisor_test.exs:56: (test)
9
10
11 2) test moves a robot forward (ToyRobot.Game.PlayerSupervisorTest)
12 test/toy_robot/game/player_supervisor_test.exs:39
13 ** (UndefinedFunctionError) function ToyRobot.Game.PlayerSupervisor.move/2 is undefined or private
14 code: PlayerSupervisor.move(
15 stacktrace:
16 (toy_robot) ToyRobot.Game.PlayerSupervisor.move(:"play-sup-test-bd1dd06b-8a6f-4825-8ae4-27b876cd8bf3"\
17 , "Izzy")
18 test/toy_robot/game/player_supervisor_test.exs:44: (test)

We don’t need any of the tests in this file any more. Let’s delete the whole file.
And now if we run mix test again, we’ll see that our tests are now passing:

1 13 doctests, 44 tests, 0 failures

What we have just finished is implementing the second rule of our game:

1. A robot cannot move onto an occupied square – the movement should be prevented

Movement is indeed prevented, as proven by our tests over in game_test.exs.


It’s now time to move on to the third and final rule for our game.
Multiplayer Toy Robot 200

Preventing robots from respawning in occupied spaces

The third and final rule for our multi-player robot game is:

1. A robot’s process should not restart that robot on an occupied square – it instead starts
on a randomly selected square

So far, we’ve looked at what happens when we either start a new robot process, or when we attempt to move
an existing one. This third rule concerns itself with what happens when a robot respawns on its original
square, but another robot has since moved onto that square.
Just like we did for the first two rules, let’s look at some code that would replicate this bad behaviour in the
form of a new test:

test/toy_robot/game/game_test.exs

1 describe "respawning" do
2 test "davros does not respawn on (1, 1)", %{game: game} do
3 izzy_origin = %{east: 1, north: 0, facing: :north}
4 :ok = Game.place(game, izzy_origin, "Izzy")
5
6 davros_origin = %{east: 1, north: 1, facing: :west}
7 :ok = Game.place(game, davros_origin, "Davros")
8 :ok = Game.move(game, "Davros")
9 :ok = Game.move(game, "Izzy")
10 :ok = Game.move(game, "Davros")
11 :timer.sleep(100)
12
13 refute match?(%{north: 1, east: 1}, Game.report(game, "Davros"))
14 end
15 end

And here’s an image to help explain what’s happening here:

A robot respawns on an occupied spot


Multiplayer Toy Robot 201

Davros moves off his original square. In the mean time, Izzy moves onto that same square. Then Davros
wheels himself off the board and that will crash his process.
Then we sleep (computer speak for “wait”) for “100” a hundred milliseconds (a tenth of a second). This is
the time it will take for a new “Davros” process to respawn. Once that process has spawned, we can ask
Game.report where Davros is.

The refute here checks if the two sides of the match are equal. If they are, then the test will break. If Davros
is still at (1, 1), then this test will fail. If Davros is at literally any other position other than (1, 1), then the
test will pass.
When we run this test, we’ll see it fail:

1 1) test respawning davros does not respawn on (1, 1) (ToyRobot.GameTest)


2 test/toy_robot/game/game_test.exs:73
3 Expected false or nil, got %ToyRobot.Robot{east: 1, facing: :west, north: 1}
4 code: refute %{north: 1, east: 1} = Game.report(game, "Davros")
5 stacktrace:
6 test/toy_robot/game/game_test.exs:84: (test)

This is because Davros spawns in his original location, which is a direct violation of Rule 3. He should spawn
on a square that is unoccupied instead.
Now’s the time to remember that when a process crashes and a new one starts up, the init function in that
process’s module is always called. So it makes sense to put whatever check that we’re going to need to make
into that function. That particular function will be Player.init/1, which is currently:

lib/toy_robot/game/player.ex
1 def init([table: table, position: position]) do
2 simulation = %Simulation{
3 table: table,
4 robot: struct(Robot, position)
5 }
6
7 {:ok, simulation}
8 end

Whatever changes we make for rule 3, we’ll definitely need to involve this function in those changes.
Before we get there though, we’re going to need to be able to vary the position if the specified one is already
taken. The best place for that sort of logic to go would be in ToyRobot.Game.Players, because we will need to
use all the positions of the current players to determine where not to place our robots.
There’s some work we need to accomplish before then. First, we need to find a way to get all the valid
positions of a table. That means every single place a robot could be placed on the board. Once we have that,
then we can combine that data with the occupied positions of the board to get the available positions of the
board; places where a robot could be placed.
We will use all of this data to check if the position of the robot is occupied, and if it is then we can choose
from one of the available places instead.
So to start with, let’s get to choosing the valid positions for the table.
Multiplayer Toy Robot 202

Determining valid positions

To start solving this final rule, we’re going to add a new function to ToyRobot.Table that will give us back a
list of all valid positions that robots could be placed at.
Just like old times, we’re going to start with a doctest:

lib/toy_robot/table.ex

1 @doc """
2 Returns all valid positions that are within a table's boundaries.
3
4 ## Examples
5
6 iex> alias ToyRobot.Table
7 ToyRobot.Table
8 iex> table = %Table{north_boundary: 1, east_boundary: 1}
9 %Table{north_boundary: 1, east_boundary: 1}
10 iex> table |> Table.valid_positions
11 [
12 %{north: 0, east: 0},
13 %{north: 0, east: 1},
14 %{north: 1, east: 0},
15 %{north: 1, east: 1}
16 ]
17 """
18 def valid_positions(
19 %Table{north_boundary: north_boundary, east_boundary: east_boundary}
20 ) do
21 []
22 end

In these doctests, we’re using a smaller-than-usual table just to keep the test relatively short. This table is
a 2x2 grid – because we start counting at 0, remember? – meaning that there will be 4 total valid positions,
as shown by the list at the end of the doctest.
When we run this test, it will currently fail:

1 1) doctest ToyRobot.Table.valid_positions/1 (2) (ToyRobot.TableTest)


2 test/toy_robot/table_test.exs:3
3 Doctest failed
4 doctest:
5 iex> table |> Table.valid_positions
6 [
7 %{north: 0, east: 0},
8 %{north: 0, east: 1},
9 %{north: 1, east: 0},
10 %{north: 1, east: 1}
11 ]
12 code: table |> Table.valid_positions() === [
13 %{north: 0, east: 0},
14 %{north: 0, east: 1},
Multiplayer Toy Robot 203

15 %{north: 1, east: 0},


16 %{north: 1, east: 1}
17 ]
18 left: []
19 right: [%{east: 0, north: 0}, %{east: 1, north: 0}, %{east: 0, north: 1}, %{east: 1, north: 1}]
20 stacktrace:
21 lib/toy_robot/table.ex:37: ToyRobot.Table (module)

This is because our function is just returning an empty list, rather than what it really should return: a list of
all the valid positions within the table’s boundaries. Let’s update that function now to do the right thing:

lib/toy_robot/table.ex

1 def valid_positions(
2 %Table{north_boundary: north_boundary, east_boundary: east_boundary}
3 ) do
4 for north <- 0..north_boundary, east <- 0..east_boundary do
5 %{north: north, east: east}
6 end
7 end

This code here is a list comprehension. This code will go through all the numbers in the range of 0..north_-
boundary, and then all the numbers in the range of 0..east_boundary and generate a list of maps for us with
the combination of those numbers. It will give us the list of maps that our test needs. Or, at least I think it
will. Let’s run the tests for Table and find out. Here’s the command we will run:

1 mix test test/toy_robot/table_test.exs

And here’s what we’ll see:

1 2 doctests, 0 failures

Yes! Everything is all green. We’ve now got a function that will give us all the valid positions within the
boundaries of a table. We can now use this function combined with the occupied positions to determine the
available positions on a table.

Determining available positions

To determine available positions, we will need to compare the list of valid positions that we can now get
from Table.valid_positions to a list of occupied positions. This code will be relatively straightforward, thanks
to the foundations that we have created previously.
Let’s start here by writing a test for our upcoming function:
Multiplayer Toy Robot 204

test/toy_robot/game/players_test.exs

1 defmodule ToyRobot.Game.PlayersTest do
2 use ExUnit.Case, async: true
3
4 alias ToyRobot.Table
5 alias ToyRobot.Game.Players
6
7 describe "available positions" do
8 setup do
9 table = %Table{
10 north_boundary: 1,
11 east_boundary: 1,
12 }
13
14 {:ok, table: table}
15 end
16
17 test "does not include the occupied positions", %{table: table} do
18 occupied_positions = [%{north: 0, east: 0}]
19
20 available_positions = Players.available_positions(
21 occupied_positions,
22 table
23 )
24
25 assert occupied_positions not in available_positions
26 end
27 end
28 end

This newest test starts out by creating another 2x2 table that we can use to get the valid positions a robot
can be placed in. In the body of the test, we specify a single position that is occupied. We then assert that
when we call Players.available_positions with the occupied_positions list and the table, that we do not see
the occupied position in the list of available positions returned from this function.
If we run this test now, we’ll see it fail because the available_positions function is missing:

1 1) test available positions does not include the occupied positions (ToyRobot.Game.PlayersTest)
2 test/toy_robot/game/players_test.exs:17
3 ** (UndefinedFunctionError) function ToyRobot.Game.Players.available_positions/2 is undefined or private
4 code: available_positions = Players.available_positions(
5 stacktrace:
6 (toy_robot) ToyRobot.Game.Players.available_positions([%{east: 0, north: 0}], %ToyRobot.Table{east_bo\
7 undary: 1, north_boundary: 1})
8 test/toy_robot/game/players_test.exs:20: (test)

This function will need to be added to Players, and it will need to use the new Table.valid_positions function
to get that list of valid positions, and then to remove from that list any occupied positions. The code here
is a simple one-liner:
Multiplayer Toy Robot 205

lib/toy_robot/game/players.ex

1 def available_positions(occupied_positions, table) do


2 Table.valid_positions(table) -- occupied_positions
3 end

This code says to take the list of valid_positions, and subtract (--) the list of occupied positions. For this
code to work, we’ll need to add the alias for Table to the top of this module:

lib/toy_robot/game/players.ex

1 defmodule ToyRobot.Game.Players do
2 alias ToyRobot.Game.Player
3 alias ToyRobot.Table

When we run the test for this module with this command:

1 mix test test/toy_robot/game/players_test.exs

We’ll see that this test passes:

1 1 test, 0 failures

With a list of available positions now easily attainable, the last part will be much easier. What this last part
needs to do is to take a position and determine if that position is in the list of available positions. If that
position is in the list, then everything’s fine, we’ll use that position for the robot. However, if the position
is taken then we will need to pick a random position from the list of available positions.

Picking a random available position

Now we get to the meat of what we wanted to implement: if a player’s chosen position is taken, pick a
different one. Let’s write a couple of tests for this function now:

lib/toy_robot/game/players.ex

1 describe "change_position_if_occupied" do
2 setup do
3 table = %Table{
4 north_boundary: 1,
5 east_boundary: 1,
6 }
7
8 {:ok, table: table}
9 end
10
11 test "changes position if it is occupied", %{table: table} do
12 occupied_positions = [%{north: 0, east: 0}]
Multiplayer Toy Robot 206

13 original_position = %{north: 0, east: 0, facing: :north}


14
15 new_position = Players.change_position_if_occupied(
16 occupied_positions,
17 table,
18 original_position
19 )
20
21 assert new_position != original_position
22 assert new_position.facing == original_position.facing
23 end
24
25 test "does not change position if it is not occupied", %{table: table} do
26 occupied_positions = []
27 original_position = %{north: 0, east: 0, facing: :north}
28
29 assert Players.change_position_if_occupied(
30 occupied_positions,
31 table,
32 original_position
33 ) == original_position
34 end
35 end

The first test here checks that if the position is occupied, then a random position is chosen. As this function
is likely to receive a position with a facing key in it, we should make sure that we keep that facing value so
that the robot’s position remains in a valid form.
The second test checks that if the position is not occupied, then we return the original position with no
changes.
Running this test will show that the change_position_if_occupied function is missing:

1 1) test change_position_if_occupied changes position if it is occupied (ToyRobot.Game.PlayersTest)


2 test/toy_robot/game/players_test.exs:39
3 ** (UndefinedFunctionError) function ToyRobot.Game.Players.change_position_if_occupied/3 is undefined or\
4 private
5 code: new_position = Players.change_position_if_occupied(
6 stacktrace:
7 (toy_robot) ToyRobot.Game.Players.change_position_if_occupied([%{east: 0, north: 0}], %ToyRobot.Table{\
8 east_boundary: 1, north_boundary: 1}, %{east: 0, facing: :north, north: 0})
9 test/toy_robot/game/players_test.exs:43: (test)

Let’s add this new function to the Players module:


Multiplayer Toy Robot 207

lib/toy_robot/game/players.ex

1 def change_position_if_occupied(occupied_positions, table, position) do


2 if occupied_positions |> position_available?(position) do
3 position
4 else
5 new_position =
6 occupied_positions
7 |> available_positions(table)
8 |> Enum.random
9
10 new_position |> Map.put(:facing, position.facing)
11 end
12 end

This function receives a list of occupied_positions, and can pass that to position_available? along with the
position argument to determine if the position is available. If it is, then we can return that position.

If the position is _not vailable, then we pass that list of occupied_positions through to available_positions
along with the table argument. This will give us a list of available positions on the table. From that list,
we can pick a random position with Enum.random. Finally, we take that random position and add a facing
key to it, so that we can maintain any facing‘ value that may have been specified with the original position.

This should be enough to make our test pass. Let’s check by re-running this file’s tests:

1 mix test test/toy_robot/game/players_test.exs

And we will see that they are all passing:

1 3 tests, 0 failures

All good!
We now have all the parts in place that we can use to check if a player’s position is taken on the board. Let’s
get back to the Player.init/1 function now, and solve this final rule.

Getting back to the init function

We’re now at the point of our code where we can use the change_position_if_occupied function in the
Player.init function. This function will take the specified position in Player.init, and if that position is
already taken then it will use a random position from the same table.
To make sure this function behaves correctly, we’ll write a new test for it. Even though it’s a function that
is called by code within Elixir, it’s still a function we wrote that is now going to have more complex logic
inside this function and so we should be testing it.
To start with, we’ll write a test to ensure that this init function works as it stands currently.
Multiplayer Toy Robot 208

test/toy_robot/game/player_test.exs

1 describe "init" do
2 test "maintains the original position" do
3 position = %{north: 0, east: 0, facing: :north}
4
5 {:ok, %{robot: robot}} =
6 Player.init([
7 table: build_table(),
8 position: position
9 ])
10
11 assert robot.north == 0
12 assert robot.east == 0
13 assert robot.facing == :north
14 end
15 end

This new test uses the current implementation of Player.init, where it takes a table and a position argument.
It checks that this function returns a tuple in the shape of , and ensures that the robot's north, east and
facing values are the same values that we pass in to init‘.
Let’s run this command to see if these tests work:

1 mix test test/toy_robot/game/player_test.exs

We’ll see that all the tests in this file are currently passing:

1 3 tests, 0 failures

Okay, now that we’ve validated the current behaviour works correctly, let’s add another test for this new
behaviour of picking a random location:

test/toy_robot/game/player_test.exs

1 describe "init, with another player registered" do


2 setup do
3 registry_id = :player_init_test
4 Registry.start_link(keys: :unique, name: registry_id)
5
6 table = build_table()
7
8 Player.start_link(
9 registry_id: registry_id,
10 table: table,
11 position: %{north: 0, east: 0, facing: :west},
12 name: "Joanna"
13 )
14
15 {:ok, registry_id: registry_id, table: table}
Multiplayer Toy Robot 209

16 end
17
18 test "picks a random position on the board", %{registry_id: registry_id, table: table} do
19 position = %{north: 0, east: 0, facing: :north}
20
21 {:ok, %{robot: robot}} =
22 Player.init([
23 registry_id: registry_id,
24 table: table,
25 position: position
26 ])
27
28 refute match?(%{north: 0, east: 0}, robot)
29 assert robot.facing == :north
30 end
31 end

The setup block here starts up a registry, builds a table and starts a new Player process at the (0, 0) position
of the board. When we get to the test itself, if we call init with the same coordinates then the robot returned
from that function should not be at (0, 0), but some other random location. The final line of the test asserts
that the facing value is maintained.
One special thing to note in this test is that the init function receives the registry_id as a keyword argument.
This is passed to Player.init so that we will be able to use the Players functions to change the position if it
is occupied.
Let’s change that Player.init function now to handle that new argument, and to use the functions from
Players:

lib/toy_robot/game/player.ex

1 def init([registry_id: registry_id, table: table, position: position]) do


2 position =
3 registry_id
4 |> Players.all
5 |> Players.positions
6 |> Players.change_position_if_occupied(table, position)
7
8 simulation = %Simulation{
9 table: table,
10 robot: struct(Robot, position)
11 }
12
13 {:ok, simulation}
14 end

This function now uses a few functions from Players to determine if it should change the position. To use
these functions, we’ll need to add an alias for Players to the top of this module:
Multiplayer Toy Robot 210

lib/toy_robot/game/player.ex

1 defmodule ToyRobot.Game.Player do
2 use GenServer
3
4 alias ToyRobot.{Robot, Simulation}
5 alias ToyRobot.Game.Players

Because we’ve changed the init function’s arguments, we’ll also need to change start_link as well, so that
it passes through the registry_id:

lib/toy_robot/game/player.ex

1 def start_link(registry_id: registry_id, table: table, position: position, name: name) do


2 GenServer.start_link(
3 __MODULE__,
4 [
5 registry_id: registry_id,
6 table: table,
7 position: position
8 ],
9 name: process_name(registry_id, name)
10 )
11 end

Let’s try running just this new test to ensure that things are working correctly. We can do that by targetting
this line:

1 describe "init, with another player registered" do

This line is line 30 in my player_test.exs, and it might be in yours too. Just double check that before running
this next command:

1 mix test test/toy_robot/game/player_test.exs:30

When this test runs, we’ll see it crash like this:

1 1) test init, with another player registered picks a random position on the board (ToyRobot.Game.PlayerTes\
2 t)
3 test/toy_robot/game/player_test.exs:47
4 ** (EXIT from #PID<0.184.0>) exited in: GenServer.call({:via, Registry, {:player_init_test, "Joanna"}},\
5 :report, 5000)
6 ** (EXIT) process attempted to call itself

This might seem a bit weird, but let’s step through what’s happening here:
Multiplayer Toy Robot 211

1. In the setup for this test, we call Player.start_link for “Joanna”


2. That starts up a Player process for “Joanna”, which registers itself with the specified registry
3. When that Player process starts up, it calls init, which then asks the registry for all players’ positions
4. This “all players” includes “Joanna”
5. The “Joanna” process attempts to call itself to find its position, resulting in this error

So it seems like we’re missing one final piece here in the init/1 function. When a player initializes, it should
check all the positions of all of the players except themselves. So we will need to update this function to
exclude the current player.
We can do this by adding another keyword argument to the list, the player’s process’s name:

lib/toy_robot/game/player.ex

1 def init([registry_id: registry_id, table: table, position: position, name: name]) do


2 position =
3 registry_id
4 |> Players.all
5 |> Players.except(name)
6 |> Players.positions
7 |> Players.change_position_if_occupied(table, position)
8
9 simulation = %Simulation{
10 table: table,
11 robot: struct(Robot, position)
12 }
13
14 {:ok, simulation}
15 end

This name argument will need to come from start_link, so let’s pass it down from there:

lib/toy_robot/game/player.ex

1 def start_link(registry_id: registry_id, table: table, position: position, name: name) do


2 name = process_name(registry_id, name)
3 GenServer.start_link(
4 __MODULE__,
5 [
6 registry_id: registry_id,
7 table: table,
8 position: position,
9 name: name
10 ],
11 name: name
12 )
13 end

We will also need to update our test to pass this name in as well:
Multiplayer Toy Robot 212

test/toy_robot/game/player_test.exs

1 test "picks a random position on the board", %{registry_id: registry_id, table: table} do
2 position = %{north: 0, east: 0, facing: :north}
3
4 {:ok, %{robot: robot}} =
5 Player.init([
6 registry_id: registry_id,
7 table: table,
8 position: position,
9 name: Player.process_name(registry_id, "Bobbie")
10 ])
11
12 refute match?(%{north: 0, east: 0}, robot)
13 assert robot.facing == :north
14 end

The Players.except/2 function will need to be added too:

lib/toy_robot/game/players.ex

1 def except(players, name) do


2 players |> Enum.reject(&(&1 == name))
3 end

This function will take a list of players and reject any of them that match the name passed in.
Let’s re-run this one particular test again to see if it passes:

1 mix test test/toy_robot/game/player_test.exs:30

We’ll see:

1 4 tests, 0 failures, 3 excluded

This means that this particular test is now passing! Players will spawn in a random location if their specified
position is already taken.
Let’s not forget about the first test we wrote for init, which is not looking so good right now. We’ll run this
with:

1 mix test test/toy_robot/game/player_test.exs:14

And we’ll see:


Multiplayer Toy Robot 213

1 2) test init maintains the original position (ToyRobot.Game.PlayerTest)


2 test/toy_robot/game/player_test.exs:15
3 ** (FunctionClauseError) no function clause matching in ToyRobot.Game.Player.init/1
4
5 The following arguments were given to ToyRobot.Game.Player.init/1:
6
7 # 1
8 [table: %ToyRobot.Table{east_boundary: 4, north_boundary: 4}, position: %{east: 0, facing: :north, n\
9 orth: 0}]
10
11 Attempted function clauses (showing 1 out of 1):
12
13 def init([registry_id: registry_id, table: table, position: position, name: name])
14
15 code: Player.init([
16 stacktrace:
17 (toy_robot) lib/toy_robot/game/player.ex:25: ToyRobot.Game.Player.init/1
18 test/toy_robot/game/player_test.exs:19: (test)

The init function here is not being passed either a registry_id or a name. Let’s change this test now to pass
some proper arguments in:

lib/toy_robot/game/player.ex

1 describe "init" do
2 setup do
3 registry_id = :player_init_test
4 Registry.start_link(keys: :unique, name: registry_id)
5
6 {:ok, registry_id: registry_id}
7 end
8
9 test "maintains the original position", %{registry_id: registry_id} do
10 position = %{north: 0, east: 0, facing: :north}
11
12 {:ok, %{robot: robot}} =
13 Player.init([
14 registry_id: registry_id,
15 table: build_table(),
16 position: position,
17 name: Player.process_name(registry_id, "Joanne")
18 ])
19
20 assert robot.north == 0
21 assert robot.east == 0
22 assert robot.facing == :north
23 end
24 end

If we re-run this test, we’ll see that it’s now passing:


Multiplayer Toy Robot 214

1 4 tests, 0 failures, 3 excluded

Let’s run all the tests for this file now:

1 mix test test/toy_robot/game/player_test.exs

We’ll now see that the move and report tests are broken in this file:

1 1) test move moves the robot forward one space (ToyRobot.Game.PlayerTest)


2 test/toy_robot/game/player_test.exs:96
3 ** (MatchError) no match of right hand side value: {:error, {:function_clause, [{ToyRobot.Game.Player, :\
4 init,

This is because both of these tests do not pass in a registry_id. We could fix this up by passing down a
registry_id here, but a quicker way would be to have an init callback that doesn’t need a registry_id or a
name:

lib/toy_robot/game/player.ex
1 def init([table: table, position: position]) do
2 simulation = %Simulation{
3 table: table,
4 robot: struct(Robot, position)
5 }
6
7 {:ok, simulation}
8 end

This function will allow us to test the move and report functions within Player, without having to setup a
registry or to give the processes a name. All thanks to Elixir’s wonderful pattern matching capabilties!
Let’s run all the tests in this file now:

1 mix test test/toy_robot/game/player_test.exs

We’ll see that they’re now all passing:

1 4 tests, 0 failures

Excellent! We have now finished making our init function check for occupied positions. This function
will now use the Players.change_position_if_occupied function to, well, change the specified position if it
is already occupied by another player.
We’re allllmost done here! We started this big section by first writing a test over in game_test.exs, and so now
we should loop back there and make sure things are working.

Getting back to the Game test

As a reminder, this is our test that we wrote at the start of this section:
Multiplayer Toy Robot 215

test/toy_robot/game/game_test.exs

1 describe "respawning" do
2 test "davros does not respawn on (1, 1)", %{game: game} do
3 izzy_origin = %{east: 1, north: 0, facing: :north}
4 :ok = Game.place(game, izzy_origin, "Izzy")
5
6 davros_origin = %{east: 1, north: 1, facing: :west}
7 :ok = Game.place(game, davros_origin, "Davros")
8 :ok = Game.move(game, "Davros")
9 :ok = Game.move(game, "Izzy")
10 :ok = Game.move(game, "Davros")
11 :timer.sleep(100)
12
13 refute match?(%{north: 1, east: 1}, Game.report(game, "Davros"))
14 end
15 end

It checks that when Davros respawns after going past the boundary of the table that his new process does
not restart in the same location that the Izzy process occupies. He can start anywhere else, just as long as
it’s not that space.
Hopefully, due to our work with the Player and Players modules, things should be working correctly.
Let’s run this particular test and check the status:

1 1) test respawning davros does not respawn on (1, 1) (ToyRobot.GameTest)


2 test/toy_robot/game/game_test.exs:71
3 ** (UndefinedFunctionError) function ToyRobot.Game.report/2 is undefined or private
4 code: refute match?(%{north: 1, east: 1}, Game.report(game, "Davros"))
5 stacktrace:
6 (toy_robot) ToyRobot.Game.report(#PID<0.185.0>, "Davros")
7 test/toy_robot/game/game_test.exs:82: (test)

It appears that we’re missing a Game.report function! Let’s add one:

lib/toy_robot/game/game.ex

1 def report(game, name) do


2 GenServer.call(game, {:report, name})
3 end

And we’ll add a matching handle_call over on Game.Server too:


Multiplayer Toy Robot 216

lib/toy_robot/game/server.ex

1 def handle_call({:report, name}, _from, %{registry_id: registry_id} = state) do


2 {:reply, Players.report(registry_id, name), state}
3 end

And finally, Players.report:

lib/toy_robot/game/players.ex

1 def report(registry_id, name) do


2 registry_id |> find(name) |> Player.report
3 end

That’s everything that I can see that we need. Let’s try re-running this particular test once more:

1 6 tests, 0 failures, 5 excluded

It now passes! We have finished the third and final rule of our game! Robots can no longer respawn on a
square that is occupied by another robot. This is a great acheivement.
Let’s run all the tests to ensure things are working:

1 14 doctests, 50 tests, 0 failures

Yes! Everything in our application is working correctly.


We now have a mostly-fully-built multi-player Toy Robot game. We can place robots with Game.place and
move them with Game.move. Then we can report on their locations with Game.report.
We started this chapter with three rules:

1. A robot cannot be placed at a position that another robot already occupies


2. A robot cannot move onto an occupied square – the movement should be prevented
3. A robot’s process should not restart that robot on an occupied square – it instead starts on a
randomly selected square

We can be sure that these three rules are catered for, thanks to our extensive tests.

The End

This marks the end of this Toy Robot book, all 60,000 words of it. I hope that you’ve gleaned something
from this book. Maybe it was the early chapters around how to get started with the problem. Or it was the
marvellous doctests. Or it was these last two chapters where we explored GenServer and friends.
Whatever it was, I hope you’ve enjoyed reading it as much as I enjoyed writing it. Thank you for reading!
Multiplayer Toy Robot 217

Homework

If you’d like more things to do, consider these as additional exercises you could do. Remember to write tests
for them as you go!

1. Implement Game.turn_left and Game.turn_right


2. Add a function that lets a robot move two spaces instead of one
3. Add a function called Game.u_turn that makes the robot rotate from facing NORTH to facing SOUTH

You might also like