By Developers, For Developers

Historical errata for Functional Web Development with Elixir, OTP, and Phoenix

PDF PgPaper PgTypeDescriptionFixed onComments
21TYPO

“Those clients can all broadcast messages to each other, coordinated though the server.”

s/though/through/

2017-04-05Thanks!
40TYPO

“…and add public functions to it to define it’s behavior.”

s/it’s/its/

2017-04-05Thanks!
31ERROR

In the `to_string` function, the return value has an error:

```
def to_string(coordinate) do
“(in_island:#{island(coordinate)}, guessed:#{guessed?(coordinate)})”
end
```

should read

def to_string(coordinate) do
“(in_island:#{in_island(coordinate)}, guessed:#{guessed?(coordinate)})”
end

as `island` is not a function

2017-04-06Thanks, but we do have an "island/1" function in the Coordinate module.
35TYPO

Third paragraph, last sentence: “While we’e at it” should be “While we’re at it”.

2017-04-05Thanks!
20TYPO

“It’s also leads to Elixir’s great fault tolerance.”

s/It’s/It/

2017-04-05Thanks!
21SUGGEST

There’s a minor inconsistency here. We used “mix new islands_engine —sup” to create the project so coordinate.ex is located in islands_engine/lib. The code headings all say agent/lib.

Should I report this sort of thing?

2017-04-05The agent/lib headings are for the code bundle that comes with the book, not the Elixir project on your machine. Thanks!
23SUGGEST

In listing:
agent/lib/coordinate.ex
def in_island?(coordinate) do
case Agent.get(coordinate, fn state -> state.in_island end) do
:none -> false
_ -> true
end
end

def island(coordinate) has already been defined to do this:
Agent.get(coordinate, fn state -> state.in_island end)

This block of code should reuse that function.

2017-04-05Thanks!
15SUGGEST

It may be worth mentioning when `mix new` is run that you’re calling the project “islands_engine” instead of “islands” because it’s the engine for the game.

2017-04-07Thanks!
18SUGGEST

I would use either “coord” or “coordinate” variable name in all examples, but only one. It looks more consistent.

2017-04-07Thanks!
23SUGGEST

I would use the previously defined function “island” in “in_island” function to avoid code duplication.

def in_island?(coordinate) do
Coordinate.island(coordinate) !== :none
end

or, if you prefer to use pattern matching:

def in_island?(coordinate) do
case Coordinate.island(coordinate) do
:none -> false
_ -> true
end

2017-04-05Thanks!
27ERROR

Missing “island diagram” image. Instead, I see

2017-04-05Thanks!
28SUGGEST

Wouldn’t it be “IslandsEngine.Island.replace_coordinates/2” instead of “IslandsEngine.replace_coordinates/2”?

2017-04-05Thanks!
28TYPO

Instead of:
“While we’e at it, let’s add a guard”

would be:
While we’re at it, let’s add a guard"

2017-04-05Thanks!
38ERROR

You must bind the IslandSet struct to “island_set” variable, so you can access the field “atoll” later.

Instead of:
Agent.get(island_set, &(&1))

It should be:
island_set = Agent.get(island_set, &(&1))

2017-04-06Thanks!
33SUGGEST

In order to avoid confusion, you could use “state” as the argument name in the anonymous function. It will also be consistent as you’re using “state” in other code snippets.

def get_coordinate(board, key) when is_atom key do Agent.get(board, fn state -> state[key] end)
end

2017-04-06Thanks, I try to use the name of the module/entity wherever I can, unless it is the same as a parameter passed into the function.
77ERROR

keys() function is introduced into Enum.reduce line in island_set.ex and you are asked to go into iex to test it before showing the definition of keys().

Pull keys() function definition two pages earlier.

2017-04-07Thanks!
59ERROR

iex session when dealign with IslandSet doesn’t work..

{:ok,​​ ​​island_set}​​ ​​=​​ ​​IslandSet.start_link

island_set is a pid at this point.

so next line works…

Agent.get(island_set,​​ ​​&(&1))​

but very next iex line does not….

Agent.get(island_set.atoll,​​ ​​&(&1))​

I think what you want is for previous line to reset island_set so you have a reference to struct to call .atoll on.

So change it to:
island_set = ​​Agent.get(island_set,​​ ​​&(&1))​

2017-04-06Thanks!
68TYPO

“The general form of init/1 is to patten match on an argument” misspells “pattern” as “patten”

2017-04-06Thanks!
68SUGGEST

A quick explanation of what MODULE refers to would be nice here.

2017-04-07Thanks!
72TYPO

Missing/broken image:

2017-04-05Thanks!
73SUGGEST

“In the unlikely event that the coordinate already is a PID” — it would be nice to describe how this could happen.

2017-04-07Thanks!
28ERROR

At the bottom of this page: you’re referring to “Enum.all/2” in the text but the function shown in the example is “Enum.all?/2”

2017-04-05Thanks!
29SUGGEST

Why not use the short-hand function syntax in Island.forested?:

Agent.get(island, &(&1)) |> Enum.all?(&Coordinate.hit?/1)

2017-04-07Thanks!
30ERROR

The image at the bottom of this page does not match the description above.

2017-04-06Thanks!
38ERROR

iex> {:ok, island_set} = IslandSet.start_link {:ok, #PID<0.112.0>}

iex> Agent.get(island_set, &(&1))
%IslandsEngine.IslandSet{atoll: #PID<0.113.0>, dot: #PID<0.114.0>,
l_shape: #PID<0.115.0>, s_shape: #PID<0.116.0>, square: #PID<0.117.0>}

iex> Agent.get(island_set.atoll, &(&1)) []

The 2nd function needs to rebind island_set, otherwise you get an argument error when you call island_set.atoll on the PID.

2017-04-06Thanks!
39TYPO

In string_body/1 there is a semi-colon at the end of the line:

island = Agent.get(island_set, &(Map.fetch!(&1, key)));

2017-04-06Thanks!
40SUGGEST

You might consider combining the first two aliases to:
alias IslandsEngine.{Coordinate, Island}

It’s done this way earlier in the book

2017-04-06Thanks!
57SUGGEST

The syntax of “{:noreply, state}” hasn’t been explained at this point, but is being used. I think there should be an explanation of why this function needs to return this value.

2017-04-07Thanks!
29SUGGEST

I suggest using the following code:

def state(island) do
Agent.get(island, &(&1))
end

def forested?(island) do
island
|> state
|> Enum.all?(&Coordinate.hit?/1)
end

def to_string(island) do
island_string =
island
|> state
|> Enum.map_join(" “, &Coordinate.to_string/1)
”[" <> island_string <> “]”
end

2017-04-07Thanks!
32SUGGEST

I suggest using the following code:

letters ?a..?j numbers 1..10

defp keys do
for letter <- letters, number <- numbers do
String.to_atom(“#{List.to_string([letter])}#{number}”)
end
end

defp initialized_board do
keyword_list = for k <- keys() do
{:ok, coord} = Coordinate.start_link
{k, coord}
end
Map.new(keyword_list)
end

2017-04-06Thanks, but in the context of the book, I like the explicitness of setting the keys and values for the board map.
35SUGGEST

I suggest using the following code:

def to_string(board) do
board_string =
board
|> Agent.get(&(&1))
|> Enum.map_join(“,\
”, fn {key, coord} ->
“#{key} => #{Coordinate.to_string(coord)}”
end)
“%{” <> board_string <> “}”
end

2017-04-07Thanks, but I like the elements to come out ordered.
15SUGGEST

With elixir 1.4.2 there is also a file created called:
lib/islands_engine/application.ex

and also the next page (16) has the application.ex inside the lib/islands_engine

2017-04-07Thanks!
27ERROR

Missing image for island diagram, showing:

2017-04-05Thanks!
28ERROR

You say that “The anonymous function [passed as the second argument to ```Enum.all?/2] must return a boolean or nil”, but in fact it can return anything, with the result being true as long as all the collected results are all truthy (neither false nor nil).

You’ve also missed the “?” from the function name in the text, though it’s correct in the code.

2017-04-07Thanks!
26SUGGEST

In the IEx demonstration of Coordinate.to_string (and also later Island.to_string on p30) you show the result of both calling the function and also passing the result of calling it to IO.puts. I wonder whether using both in the same example might add a tiny bit of incidental complexity and potentially confuse some readers for a moment.

2017-04-06Thanks, I'll keep watching to see if anybody reports any confusion over this.
30SUGGEST

It seems odd that when you call Island.to_string, the constituent coordinates print themselves out with “island: :none”. Presumably this missing reverse connection will be addressed later – if so, it might be worth mentioning at this point.

2017-04-06Thanks!
38SUGGEST

I was confused by the statement “You may have noticed that we wrote our own function to get the struct keys …”. I’d noticed the call to “keys()”, and thought it must be some magic function auto-imported by defstruct. When I read “we wrote …” I went back and couldn’t find it – it turns out it’s actually defined further down the page. Maybe “we called” would be clearer.

2017-04-07Thanks!
46TYPO

There’s an extra “the” in the second paragraph of “Wrapping up” (“instead of double lives in the both the application and the database”).

2017-04-06Thanks!
59SUGGEST

It might help to explain the “%{state | test: ”new value“}}” syntax used here at the end of the page, in the handle_cast example.

2017-04-07Thanks!
62SUGGEST

An explanation of what “name” is being received in init would be helpful, I think.

2017-04-06Thanks! I think we explain that in the preceding paragraphs.
64TYPO

“IslandsEngine.Game” should be monospaced at the bottom of this page.

2017-04-06Thanks!
65ERROR

Example for player2 here shows:

%Player{:name => none,

But it should be:

%Player{:name => :none,

(Missing a colon before none)

2017-04-07Thanks!
65ERROR

Player names should be wrapped in quotes:

iex> IO.puts IslandsEngine.Player.to_string(state.player1) %Player{:name => Frank,

iex> IO.puts IslandsEngine.Player.to_string(state.player2) %Player{:name => Dweezil,

2017-04-07Thanks!
65ERROR

Player names should be wrapped in quotes:

iex> IO.puts IslandsEngine.Player.to_string(state.player1) %Player{:name => Frank,

iex> IO.puts IslandsEngine.Player.to_string(state.player2) %Player{:name => Dweezil,

2017-04-07
67SUGGEST

Agent.get(player, fn state -> state.board end)

Could be written shorter:

Agent.get(player, &(&1.board))

2017-04-06Thanks, but here my preference is for the more verbose form. To me, it's clearer.
71SUGGEST

While I understand that _player matches on anything else, I think it would be good to have :player2 in the second “defp opponent” call, just for consistency.

2017-04-06Thanks!
71ERROR

I think this code:

case Board.coordinate_hit?(opposite_board, coordinate) do
true -> :hit
false -> :miss
end

Could be better written using an “if”:

coordinate_hit =
if Board.coordinate_hit?(opposite_board, coordinate) do
:hit
else
:miss
end

My rationale for this is that you’re using a “case” statement as if it were an “if” statement. The language has an “if” statement and so why not use it here?

2017-04-06Thanks, but I prefer the case statement here.
79SUGGEST

I think this would be a good time to show Elixir 1.4’s Registry in action. Looks like you’re using the “old” way of doing it.

2017-04-06Thanks, but it's my understanding that global registry isn't going away, and that it will even be improving in future versions of OTP.
36SUGGEST

“We know that an island set will have one each of five different island
shapes—an atoll, a dot, an “L” shape, an “S” shape, and a square."

We would know this if we had played the game before. Up until this point though, this hasn’t been explained at all.

Perhaps a link to somewhere which explains the rules of Islands would be handy?

2017-04-06Thanks!
41ERROR

I think start_link function needs to pass name to %Player{}, otherwise pg 68 function doesn’t initialize with name “Frank”

Agent.start_link(fn -> %Player{board: board, island_set: island_set, name: name} end)

2017-04-06Thanks!
60TYPO

iex session example alternates between pid & game variables

also, might consider using IslandsEngine.Game.call_demo instead of GenServer.call

2017-04-06Thanks!
25SUGGEST

The “to_string” functions are helpful for seeing what is going on, but I didn’t really get it until I used observer to navigate through the processes to see how they are linked and look at the state. You might mention it as an option for better understanding.

2017-04-06Thanks, I think this is a little early in the book for :observer. It may make an appearance later on, though. :)
65SUGGEST

The second call to Game.call_demo/1 to rebind the result to the variable state is unnecessary because the PIDs don’t change. This concept is discussed in the callout “Players are Agents” on the bottom of the previous page.

Maybe deleting will cause confusion, but I think it might reinforce the concept if it is deleted. It says it’s not necessary to rebind, but then that’s what the example does.

2017-04-06Thanks!
25TYPO

The implementation of Coordinate.to_string/1 calls island/1, which doesn’t exist; this should probably be in_island, which does. (Great book so far!)

2017-04-05Glad you're enjoying the book!
25TYPO

(doh! Ignore that last report about island/1 - Just noticed I misnamed the function when I typed it in. Sorry!)

2017-04-05No problem. :)
38ERROR

At the point when we’re told to “Open up a console session and alias IslandsEngine.IslandSet.”, we haven’t created IslandSet.keys/0, so iex doesn’t start: “undefined function keys/0”.

2017-04-07Thanks!
39TYPO

When we “check the state of the atoll island”, it says to use “Agent.get(island_set.atoll, &(&1))” — I think this maybe oughta be “Agent.get(island_set, &(&1.atoll))” since island_set is just a PID.

2017-04-06Thanks!
39TYPO

Oops (sorry, wish I could edit errata after submission!), when checking the state of the atoll island, calling Agent.get(island_set, &(&1.atoll)) returns the PID of the atoll island, so getting its (empty) coordinates list requires another Agent.get: “Agent.get(island_set, &(&1.atoll)) |> Agent.get(&(&1))” produces the empty list we expect.

2017-04-06Thanks!
41SUGGEST

If we’re not going to set the Player’s name in its start_link function, we probably don’t need the unused “name” parameter at all.

2017-04-06Thanks!
29SUGGEST

This function
def forested?(island) do
Agent.get(island, fn state -> state end)
|> Enum.all?(fn coord -> Coordinate.hit?(coord) == true end)
end

violates two guides from elixir style guide. “Use bare variables in the first part of a function chain” and “Avoid using the pipe operator just once”.

2017-04-06Thanks!
29SUGGEST

def to_string(island) do
island_string =
Agent.get(island, fn state -> state end)
|> Enum.reduce(“”, fn coord, acc -> “#{acc}, #{Coordinate.to_string(coord)}” end )
“[” <> String.replace_leading(island_string, “, ”, “”) <> “]”
end

Starts a pipeline with a function return and pipes to just on function. This is my suggestion

def to_string(island) do
islands = Agent.get(island, fn state -> state end)
island_string = Enum.reduce(islands, “”, fn coord, acc ->
“#{acc}, #{Coordinate.to_string(coord)}”
end)

“[” <> String.replace_leading(island_string, “, ”, “”) <> “]”
end

2017-04-06Thanks!
19TYPO

Our job for this section is to take the IslandsEngine.Coordinate module into an
agent

take -> turn

2017-04-05Thanks!
22ERROR

You might have noticed that we named the function Agent.get/3

should be

You might have noticed that we called the Agent.get/3 function

Nothing was named: a standard function was called.

2017-04-05Thanks!
28, 3ERROR

In page 39 the line
Agent.get(island_set.atoll, &(&1))

doesn’t work because island_set is a PID.

My suggestion: In page 38 bind the struct to a variable

island_struct = Agent.get(island_set, &(&1))

and then in page 39

Agent.get(island_struct.atoll, &(&1))

2017-04-06Thanks!
39ERROR

In page 39 the call
IslandSet.to_string(island_set)
shows that
dot => [(in_island:none, guessed:false)]\

but there are no coordinates assigned before to any island.

Subsequent call to
IO.puts IslandSet.to_string(island_set)
in the same page shows correct output with all the islands set to []

2017-04-06Thanks!
63ERROR

In page 63 this line
IO.puts IslandsEngine.Player.to_string(state.player1)
does not yield this line
%Player{:name => Frank,

because IslandsEngine.Player.start_link does not set the name.

relevant errata #81284

2017-04-06Thanks!
66ERROR

In the middle of the page missing chart image for “the path we’ll follow”. Instead it shows the line

2017-04-05Thanks!
66SUGGEST

The function

def handle_call({:set_island_coordinates, player, island, coordinates}, _from, state) do
Map.get(state, player)
|> Player.set_island_coordinates(island, coordinates)
{:reply, :ok, state}
end

Should be

def handle_call({:set_island_coordinates, player, island, coordinates}, _from, state) do
state
|> Map.get(player)
|> Player.set_island_coordinates(island, coorsinates)

{:reply, :ok, state}
end

So it would both confirm the path is state -> player and and comply with the “Elixir Style Guide”

2017-04-07Thanks!
68ERROR

The function definition for set_island_coordinates/3 uses the Coordinate module but that module is not aliased in the file. The same for the linked code file.

2017-04-07Thanks!
15SUGGEST

In the Preface, it would be good to add a section indicating the versions of Elixir/Phoenix used for the examples. Right now, 6 days after release of B1.0, I am already scratching my head at an inconsistency between what you say and (fresh install) what I see.

2017-04-07Thanks, this will happen as I flesh out the preface.
70TYPO

The line
response = Player.guess_coordinate(opponent_board), coordinate)
has an extra parenthesis in the middle.

Should be:
response = Player.guess_coordinate(opponent_board, coordinate)

2017-04-06Thanks!
70SUGGEST

The definition of the function

def handle_call({:guess, player, coordinate}, _from, state) do

end

looks like a good use case for the pipe operator

2017-04-06Thanks!
71SUGGEST

the line
alias IslandsEngine.{Board, Coordinate}

is not needed in player.ex because there is already the line
alias IslandsEngine.{Board, Coordinate, IslandSet, Player}
from page 41

2017-04-06Thanks!
71SUGGEST

The variable name opposite_board in the line
def guess_coordinate(opposite_board, coordinate) do

maybe should be opponent_boad because the board is opposite to what?
Even better maybe it should be just board since the function doesn’t discriminate against caller players or opponents board.

2017-04-06Thanks, I went with "opponent_board" because I think it's still important to say that any guess goes toward the player's opponent's board.
71ERROR

the line
IO.puts Player.to_string(player1)
rises

(UndefinedFunctionError) function Player.to_string/1 is undefined (module Player is not available)

because IslandsEngine.Player is not aliased in this iex session.

Possible fix: the line
alias IslandsEngine.Game
in page 71 shoould be
alias IslandsEngine.{Game, Player}

2017-04-07Thanks!
72TYPO

The line
response = Player.guess_coordinate(opponent_board), coordinate)
should be
response = Player.guess_coordinate(opponent_board, coordinate)

2017-04-06Thanks!
29SUGGEST

I like Igor’s suggestion to use Enum.map_join in to_string/1, it’s a lot cleaner to read. Small typo in his suggestion though, first argument for Enum.map_join should be “, ”

2017-04-07Thanks!
63ERROR

It looks as though IslandsEngine.Player.start_link/1 should have been updated at some point to include the starting player’s name since the name argument isn’t used, causing the player1 name to remain as :none.

I’m guessing line 5 needs “name: name” added to the %Player{} struct like this:

1> def start_link(name \\\\ :none) do
2> {:ok, board} = Board.start_link()
3> {:ok, island_set} = IslandSet.start_link()
4> Agent.start_link(fn ->
5> %Player{board: board, island_set: island_set, name: name}
6> end)
7> end

2017-04-06Thanks!
75SUGGEST

The function Game.forest_check/3 returns either {:miss, :none} or {:hit, key} but not {:hit, :none}. So maybe the function

defp win_check({hit_or_miss, :none}, _opponent, state) do
{:reply, {hit_or_miss, :none, :no_win}, state}
end

could be
defp win_check({:miss, :none}, _opponent, state) do
{:reply, {:miss, :none, :no_win}, state}
end

2017-04-07Thanks, but it {:hit, :none} is valid if the guess resulted in a hit but no islands were forested.
97SUGGEST

In the IslandsEngine project, that’s /lib/islands_engine.ex.

In my installation with elixir 1.4.2 in linux that file is lib/islands_engine/application.ex

also the module name is IslandsEngine.Application

2017-04-07Thanks!
97TYPO

The line:
This contents of this file is what the BEAM needs in order to properly handle
mabe should be:
The contents of this file is what the BEAM needs in order to properly handle

2017-04-06Thanks!
97ERROR

the line
/_build/dev/lib/islands_engine/islands_engine.app.
maybe should be:
/_build/dev/lib/islands_engine/ebin/islands_engine.app.

That what is said in the next paragraph. Also that is where I found it in my system.

2017-04-06Thanks!
70TYPO

Guard clauses should both be is_atom

I think the second guard should also use is_atom (see below), not is_tuple.

def guess_coordinate(pid, player, coordinate)
when is_atom player and is_atom coordinate do
GenServer.call(pid, {:guess, player, coordinate})
end

2017-04-06Thanks!
80SUGGEST

When stopping the GenServer, why not implement by calling GenServer.stop(pid) instead of manually wiring up a cast?

2017-04-06Thanks, it's just for practice, showing the form again.
75ERROR

The return value for both win_check variations should not include the :reply and state as that is already returned by the :guest handle_call function :)

2017-04-07Thanks, but in the final version of those function clauses, I do believe we need them.
130SUGGEST

might consider reminding reader to recompile after adding handle_in function for “new_game”

2017-04-07Thanks!
133SUGGEST

consider
{:reply, {:error, %{reason: inspect(reason)}}, socket}

otherwise, the Poison.EncodeError mentioned on pg 131 would happen for any errors

2017-04-07Thanks!
129SUGGEST

Might consider consistently handling the response object in the javascript functions in pages 129-133

Sometimes we console.log the entire response object, other times console.log the string within the object, e.g. response.message

2017-04-07Thanks, I'll consider this. We usually use response.message if I want it to read a certain way in the console. The plain response is there so we can click on the object in the console and inspect it.
133TYPO

should be ‘game_channel.on’ not ‘game.on’

otherwise you get an error in javascript

2017-04-06Thanks!
133SUGGEST

recommend

IO.puts IslandsEngine.Player.to_string(game.player1)

to take advantage of formatting

same for player2

2017-04-07Thanks!
134SUGGEST

Consider removing

iex> game = IslandsEngine.Game.call_demo({:global, “game:moon”})

Not necessary to rebind “game” variable

2017-04-07Thanks!
134SUGGEST

Would it be safer to use String.to_existing_atom/1 instead of String.to_atom/1?

Player, Island, and Coordinate atoms should already exist.

2017-04-07Thanks, but that's not necessary in this case. We're re-using the same atoms, not creating lots of new ones.
118TYPO

File name above code box is ‘channel/lib/game_channel.ex’, but should be ‘/web/channels/game_channel.ex’. this is consistent throughout this chapter.

2017-04-06Thanks, the pathname in the code box represents the path in the code bundled with the book, not code in the project on your filesystem.
123TYPO

> join(game)
should be
> join(game_channel)
because new_channel was saved to variable ‘game_channel’

2017-04-06Thanks!
126TYPO

Payload map needs to have key “message” instead of “reason” to work with say_hello function definition on pg 125

pg 125
.receive(“error”, response => {
console.log(“Unable to say hello to the channel.”, response.message) })

pg 126 should be:

def handle_in(“hello”, payload, socket) do
payload = %{message: “We forced this error.”}
{:reply, {:error, payload}, socket}
end

2017-04-07Thanks!
121SUGGEST

Consider using double quotes for consistency

> var phoenix = require(‘phoenix’)

The rest of the javascript code uses double quotes.

Same suggestion on pg 128

2017-04-06Thanks!
129TYPO

should be ‘response.message’ not ```’response’ otherwise you get the entire payload object. The console code afterwards shows only the string returned.

> game_channel.on(“said_hello”, response => {
console.log(“Returned Greeting”, response.message)
})

> say_hello(game_channel, “World!”)
undefined
Returned Greeting: World!

2017-04-07Thanks!
125SUGGEST

If not sure how to make it more clear and maybe it was just me, but I confused game_channel.push() on pg 125 with push/3 introduced on page 124. I kept trying to reread pg 124 to figure it out and then finally figured out my error when I got to push/3 at the top of page 127.

It’s clear now when I reread it, but I got thrown off because I was looking for a push function and game_channel.push() came before push/3.

2017-04-07Thanks!
135TYPO

As we have with all these new actions so far, we’ll need a new function to wrap the chanel.push call and show us what the result is.

Should be channel.push

2017-04-06Thanks!
135SUGGEST

Suggest explaining how to not execute the expression on the first line. If the reader presses enter, the line will be evaluated and they will not be able to use the pipe operator on the 2nd line.

iex> IslandsEngine.Player.get_island_set(game.player1)
…> |> IslandsEngine.IslandSet.to_string()
…> |> IO.puts

2017-04-07Thanks!
135SUGGEST

Suggest reminding the reader to recompile game_channel after adding handle_in function for “set_island_coordinates”.

2017-04-07Thanks, I'll consider this, but I want to avoid sounding like a broken record. :)
135TYPO

Should be game_channel not game, see update below

> set_island_coordinates(game_channel, “player1”, “atoll”, [“a1”])
undefined
New coordinates set! Object {}

2017-04-06Thanks!
139SUGGEST

Consider reminding reader to recompile game_channel after adding handle_in function for “guess_coordinate”

2017-04-07Thanks, same as above.
139TYPO

Function guess_coordinate has an extra coordinate that should be deleted, see below. “coordinate” is already in the params.

params = {“player”: player, “coordinate”: coordinate}
channel.push(“guess_coordinate”, params, coordinate)

2017-04-06Thanks!
139TYPO

console.log(“Unable to guess a coordinates: ” + player, response)

should be “coordinate”, not “coordinates” or remove “a”

2017-04-06Thanks!
140ERROR

Coordinates can only keep track of the last island they’ve been set in. When we set all island coordinates to “a1” on pg 136, it creates a weird result in the IslandSet where the A1 coordinate is in the list for each island, but it only thinks it is in square:

iex> IslandsEngine.Player.get_island_set(game.player1) |> …> IslandsEngine.IslandSet.to_string() |>
…> IO.puts
%IslandSet{atoll => [(in_island:square, guessed:false)] dot => [(in_island:square, guessed:false)]
l_shape => [(in_island:square, guessed:false)]
s_shape => [(in_island:square, guessed:false)]
square => [(in_island:square, guessed:false)]
}
:ok

The response object on pg 141 only returns the “square” island (you would expect it to return a list of the hit islands, but as mentioned above, the coordinate only keeps track of 1 island):

> guess_coordinate(game_channel, “player2”, “a1”) undefined
Player Guessed Coordinate: player2
Object {win: “win”, island: “square”, hit: true}

This seems like a minor bug. It may not need to be corrected in the code, but you may not want to highlight this as an example.

2017-04-07Thanks, and yes, this is a highly unusual situation that's set up so that we can see a win without slogging through a number of guesses for each player.
143ERROR

Need to ‘alias IslandsInterface.Presence’ to use Presence functions in GameChannel

2017-04-07Thanks!
143TYPO

Formatting issue with GameChannel.join/3 update. No syntax coloring and missing filename header.

2017-04-07Thanks! Syntax highlighting is back. You won't see the bar above the code snippet until the we get to the final version of the function, which will be the version in the code bundle for the book.
144SUGGEST

Just curious, why add params key on pg 144? It might be beneficial to explain the difference.

pg 121
> var socket = new phoenix.Socket(“/socket”, {})

pg 144
> var socket = new phoenix.Socket(“/socket”, {params: {}})

2017-04-07Thanks!
144SUGGEST

For the new_channel function, I think “subtopic” from pg 121 is more appropriate than “player” pgs 128 & 144 since we are going to use “moon” for both player1 and player2.

pg 121
> function new_channel(subtopic, screen_name) {
return socket.channel(“game:” + subtopic, {screen_name: screen_name});
}

pg 128 & 144
> function new_channel(player, screen_name) {
return socket.channel(“game:” + player, {screen_name: screen_name});
}

2017-04-07Thanks, it does represent the player that created the game, so I'm inclined to keep it as is.
57ERROR

Using Elixir 1.4.2 sending the message :second after defining a handle_info/2 for :first doesn’t act like sending :first did before handling it — it causes an error:
$ iex -S mix
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Compiling 1 file (.ex)
Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {:ok, game} = GenServer.start_link(IslandsEngine.Game, %{}, [])
{:ok, #PID<0.122.0>}
iex(2)> send(game, :first)
This message has been handled by handle_info/2, matching on :first
:first
iex(3)> send(game, :second)
:second

(EXIT from #PID<0.120.0>) an exception was raised:
(FunctionClauseError) no function clause matching in IslandsEngine.Game.handle_info/2
(islands_engine) lib/game.ex:4: IslandsEngine.Game.handle_info(:second, %{})
(stdlib) gen_server.erl:601: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:667: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
17:56:11.541 [error] GenServer #PID<0.122.0> terminating

(FunctionClauseError) no function clause matching in IslandsEngine.Game.handle_info/2
(islands_engine) lib/game.ex:4: IslandsEngine.Game.handle_info(:second, %{})
(stdlib) gen_server.erl:601: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:667: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: :second
State: %{}

nil
iex(2)>

2017-04-07Thanks!
49TYPO

which would call the following service to see who
the give user follows, and then create the timeline based on that list.

should be

which would call the following service to see who
the given user follows, and then create the timeline based on that list.

2017-04-06Thanks!
50TYPO

If we use mocks, keeping them in sync with the
services the represent will be a continuous chore.

should be

If we use mocks, keeping them in sync with the
services they represent will be a continuous chore.

2017-04-05Thanks!
52ERROR

“Here’s the /lib/islands_engine.ex file that Elixir generated for us.
defmodule IslandsEngine do
use Application
. . .
end

In Elixir 1.4.1 the “use Application” directive is located in /lib/islands_engine/application.ex

"
defmodule IslandsEngine.Application do
# See elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
@moduledoc false

use Application

def start(_type, _args) do
. . .
end
end
"

2017-04-07Thanks!
27ERROR

does not appear:

2017-04-05Thanks!
14TYPO

Elixir State is Separate From Behavior -> Elixir State is Separated From Behavior

2017-04-06Thanks, I think the original is correct.
73TYPO

The handle_call should be placed in “gen_server/lib/game.ex” but there is not a colored bar indicating where the code should go.

def handle_call({:guess, player, coordinate}, _from, state) do
opponent = opponent(state, player)
opponent_board = Player.get_board(opponent)
response = Player.guess_coordinate(opponent_board), coordinate)
|> forest_check(opponent, coordinate)
end

2017-04-06Thanks, those colored bars are for the code that is bundled with the book, not the code in the project on your filesystem. I only put the final code for each chapter in the files bundled with the book, so you won't find the intermediate forms of the code there.
43TYPO

Just a typo. Repeat of of. “We’ve built up a tree structure of of agents…”

2017-04-07Thanks!
21ERROR

I’m reading in iBooks. I tried copying the line
​​ {:ok,​​ ​​coordinate}​​ ​​=​​ ​​Coordinate.start_link
from iBooks to paste into iTerm2/iex. iex replied with this error:
(SyntaxError) iex:18: unexpected token: “” (column 6, codepoint U+200B)
This was not a problem when copying from Acrobat Reader.
The point of this report is that the process for formatting your code as you “typeset” the epub version of the book is apparently introducing some non-standard space characters, leaving the reader open to the seeing the error above.

2017-04-07Thanks!
23SUGGEST

In the definition of Coordinate.hit?/1, you don’t need to specify the Coordinate module - just

def hit?(coordinate) do
in_island?(coordinate) && guessed?(coordinate)
end

is fine.

2017-04-07Thanks!
37ERROR

“media.pragprog.com/titles/lhelph/code/agent/lib/island_set.ex”

IslandSet isn’t aliased here (it is in the book)

2017-04-08Thanks!
29SUGGEST

Suggest removing the “== true” part of the Enum.all?/2 anonymous function, in the Island.forested?/1 definition, given Coordinate.hit?/1 returns true or false anyway.

2017-04-08Thanks!
16SUGGEST

The “hit” in “whether the coordinate has been hit.” seems to be out of step with the “planting trees on islands” model. Maybe it should be “planted”?

2017-04-09Thanks for this suggestion/question. Here's a little window into how my brain works, how I see this in my mind. I imagine a sort of catapult launching coconuts at coordinates. If one hits an island when it lands, a coconut tree will grow there. \n \nAs an aside, seeing how a coconut sprouts is kinda cool. I'm talking about full coconuts with the green sheath still attached, not the brown ones you might see in a grocery store. At any rate, they just sit on the surface of the sand on the beach. If the coconut senses that the conditions are right (moisture, sunlight, etc.), it sends out roots, and splits open to let a new tree shoot come out. The hull of the coconut will still be there for a time, and appears to act as protection for the young shoot. \n \nProbably TMI, but there you go. :) \n \nP.S. I think I'm going to leave it as "hit." :)
35SUGGEST

In the definition of Board.string_body/1, you can reuse the existing Board.get_coordinate/2 method instead of reimplementing it (with coord = Agent.get,….)

2017-04-09Thanks!
44TYPO

Missing the word “is” between “this” and “keeping”.

“The challenge with a structure like this keeping coordinate data in synch on both branches of the tree.”

2017-04-10Thanks!
23TYPO

The case statement in the definition for in_island? contains an extra close paren.

2017-04-12Thanks!
23SUGGEST

Half-way down the page, the code fragment at the wrapped end of the line is not formatted like the earlier code in the same statement.

in_island: :my_island} end)

2017-04-12Thanks!
65TYPO

At the bottom of page 65 it probably meant to have an image. It has this line instead

2017-04-12Thanks!
77SUGGEST

Is the empty space from the middle of page 77 intended?

2017-04-12Thanks, that was not intentional. I'll check into how that happened.
43ERROR

contains extra close paren in line:
coord = get_coordinate(board, key))

2017-04-12Thanks!
93ERROR

On page 93, when trying to run:

iex> {:ok, pid} = Rules.start_link()

I’m receiving the following…

14:06:03.181 [error] State machine #PID<0.115.0> terminating

When server state = :undefined

Reason for termination = :error::function_clause

Callback mode = :undefined

Stacktrace =

[{IslandsEngine.Rules, :init, [:initialized], [file: ‘lib/rules.ex’, line: 12]},
{:gen_statem, :init_it, 6, [file: ‘gen_statem.erl’, line: 626]},
{:proc_lib, :init_p_do_apply, 3, [file: ‘proc_lib.erl’, line: 247]}]

I’ve copied the code directly from the book, the linked version, and the source download. I’m new to Elixir so I’m not exactly sure as to the why.

Additionally, the full rules file is listed across pages 91 / 92 but I’m assuming we aren’t supposed to that at this point?

2017-04-13Apologies! There's a typo in the code file for Rules.ex. If you change the start_link function to look like the following, you should be good to go: \n \ndef start_link do \n :gen_statem.start_link(__MODULE__, :ok, []) \nend
84TYPO

I think “We’re at the beginning of a sea change in web development.” was meant to be “We’re at the beginning of a sea of change in web development.”

Love the book!! Great job!!! - Martin

2017-04-14Thanks so much for you kind words about the book! Very glad you're enjoying it. <3 <3 <3 \n \nThanks for this report as well. I do think the original is correct, though.
115ERROR

Hi Lance,

First of all, thanks for taking the time to write this book. It’s been a great read so far!

I think that some code went missing in the example on page 115 of the pdf.

def handle_call({:guess, player, coordinate}, _from, state) do
opponent = opponent(state, player)
Rules.guess_coordinate(state.fsm, player)
|> guess_reply(opponent.board, coordinate)
|> forest_check(opponent, coordinate)
|> win_check(opponent, state)
end

Here opponent.board is directly accessed but it’s a pid. Shouldn’t it be:

def handle_call({:guess, player, coordinate}, _from, state) do
opponent = opponent(state, player)
opponent_board = Player.get_board(opponent)

Rules.guess_coordinate(state.fsm, player)
|> guess_reply(opponent_board, coordinate)
|> forest_check(opponent, coordinate)
|> win_check(opponent, state)
end

Regards, Bas

2017-04-16Thanks so much for the kind words. So glad you're enjoying the book! \n \nThanks for this report as well. :)
116ERROR

Also on page 116

defp guess_reply({:error, :action_out_of_sequence}, _opponent_board, _coordinate) do
{:error, :action_out_of_sequence}
end

matches on {:error, :action_out_of_sequence} but it should be just the atom :error.

or the error is earlier in the book on page 105 where the catchall for

def player1_turn({:call, from}, _, _state_data) do
{:keep_state_and_data, {:reply, from, :error}}
end

is inconsistent with the catchall for

def player2_turn(_event, _caller_pid, state) do
{:reply, {:error, :action_out_of_sequence}, :player2_turn, state}
end

Regards, Bas

2017-04-16Thanks again!
177ERROR

The wrong file name is given when adding IslandsInterface.Presence to the supervised children.

Correct file name:
Phoenix 1.2: /lib/islands_interface.ex
(or Phoenix 1.3: /lib/islands_interface/application.ex)

The PDF incorrectly says the following:
Open up /lib/islands_engine.ex
and add the presence module in the list of children.

children = [
supervisor(IslandsInterface.Endpoint, []),
supervisor(IslandsInterface.Presence, []),
]

2017-04-16Thanks!
35TYPO

coord = get_coordinate(board, key))

exceeding closing parentheses

2017-04-15Thanks!
148144ERROR

Hello,

I’m reading the part for calling IslandsEngine from the Phoenix controller :

def test(conn, %{“name” => name}) do
{:ok, _pid} = IslandsEngine.Game.start_link(name)

That part mismatch with the code juste below in the next paragraph :
{:ok, _pid} = Supervisor.start_child(:game_supervisor, [name])

I think it will be fixed in the next release but I used the code for the chapter on supervisor from the code archive, and I needed to adapt this code to get a working solution. In the next examples in iex, we need the IslandsEngine.GameSupervisor code (in the code archive), setup correctly IslandEngine for calling the GameSupervice, and the code in phoenix should be :

def test(conn, %{“name” => name}) do
{:ok, _pid} = Supervisor.start_child(:game_supervisor, [name])
conn
|> put_flash(:info, “You entered the name: ” <> name)
|> render(“index.html”)
end

Cheers

2017-04-16Thanks for reporting this. Yes, I got a little out of sync with the beta releases. This will be the correct code when the supervisor chapter is released.
4234SUGGEST

three methods are presented:

def coordinate_hit?(board, key) do
get_coordinate(board, key)
|> Coordinate.hit?
end

def set_coordinate_in_island(board, key, island) do
get_coordinate(board, key)
|> Coordinate.set_in_island(island)
end

def coordinate_island(board, key) do
get_coordinate(board, key)
|> Coordinate.island
end

These are fine, but many elixir styleguides and linters emphasize starting pipechains with a raw value if possible.

Perhaps instead:

def coordinate_hit?(board, key) do
board
|> get_coordinate(board, key)
|> Coordinate.hit?
end

def set_coordinate_in_island(board, key, island) do
board
|> get_coordinate(key)
|> Coordinate.set_in_island(island)
end

def coordinate_island(board, key) do
board
|> get_coordinate(key)
|> Coordinate.island
end

this suggestion is applicable in other places where a pipe chain is used.

2017-04-16Thanks!
12TYPO

Multiple problems with agreement in the following sentence: “Processes all have mailboxes where they receives messages in a queue, then processes them in order.”

2017-05-19Thanks!
12SUGGEST

You begin a paragraph with these words: “We can think about state on two levels. On the application level, . . . ” but you never (as far as I can see) tell us what the other level is.

2017-05-19Thanks!
19SUGGEST

Beginning with the sentence “There are four functions we could use to do this” it seems clear that you’re talking about the documentation for Elixir’s OTP support library. However, you never actually move the reader to go look at those docs. An extra phrase or sentence could point to the docs, together with a footnote leading directly to the Agent start* functions.

2017-05-19Thanks!
23TYPO

The third paragraph starts with “In in_island/1, we use”, but perhaps it should say “in_island?/1”.

2017-05-19Thanks!
23SUGGEST

In the first paragraph, the purpose is different from the last code sample in the previous page, but the difference is rather subtle. Perhaps it would help to add a phrase like “as a Boolean” in the last sentence, like this: “a function that tells us as a Boolean value whether a coordinate”

2017-05-19Thanks!
21SUGGEST

A footnote pointing to these functions could help to move the reader to actually read the docs.

Context, this sentence: “The Agent module includes functions for both, Agent.get and Agent.update.”

Links:
hexdocs.pm/elixir/Agent.html#get/3
hexdocs.pm/elixir/Agent.html#update/3

2017-05-19Thanks!
23SUGGEST

The description in the third paragraph seems to confuse the layers of responsibility in the implementation. Here’s the wording: “we use the Coordinate.island/1 function to get the agent’s state, which is a coordinate struct. Then we use the value of the :in_island key in a case statement to determine the return value.”

There are a couple of ways this could be clarified while keeping the implementation. Here’s one example: “we use the Coordinate.island/1 function to request the atom indicating which island a coordinate belongs to. Then we use the value of that atom in a case statement to determine the return value.”

Another alternative works by looking into the island/1 function: “we ask the Coordinate.island/1 function to get the agent’s state, which is a coordinate struct, and return the value of the :in_island key. We use that value in a case statement to determine whether the coordinate is in an island or not.”

2017-05-19Thanks!
26SUGGEST

The first paragraph begins “At the moment, the coordinate reports that it isn’t in an island at all.”

This sentence seems unclear, because we have just seen that we have the ability to update a coordinate’s island, although you didn’t demonstrate the to_string/1 function with an island value other than :none.

Maybe what you mean is closer to “At the moment we don’t have any islands to test our coordinate with.” Or maybe this paragraph has become extraneous.

2017-05-19Thanks!
28SUGGEST

The first paragraph is “Let’s just check to make sure that works.”

This might be an interesting place to talk about how to get the new code into iex. Do you recommend using recompile/0? or restarting iex? or . . . ?

2017-05-19Thanks!
28SUGGEST

Last paragraph: “To make sure that the guard clause works, let’s try calling IslandsEngine.
Island.replace_coordinates/2 with a single coordinate.”

Maybe add (for clarity) “with a single coordinate not wrapped in a list.”

2017-05-19Thanks!
30SUGGEST

This section closes with “Now that we can model one-to-many relationships, let’s move on to something just a little more complex.”

This feels a little awkward because you have only demonstrated our island with a single coordinate. Perhaps the iex sample above could be slightly expanded to demonstrate two coordinates in one island.

2017-05-19Thanks!
31TYPO

Disagreement in number: “and a coordinate belong to”

2017-05-19Thanks!
32SUGGEST

Is it possible to use a Range in the definition of numbers? What about when defining letters?

2017-05-19Thanks!
32SUGGEST

“function to enumerate over” . maybe should be “function to iterate over”

2017-05-19Thanks!
34SUGGEST

I’m still sensing a little resistance to telling a coordinate which island it belongs to. I wonder if this was a premature optimization leading to duplication (and possibly corruption) of data. Since we anticipate a small number of islands, isn’t it enough to ask the collection of islands whether a given coordinate belongs to any of them? This thought is springboarding from the last phrase on the page, “setting a coordinate in an island”

Maybe the reverse approach would be better, where an Island doesn’t care what its coordinates are, because Coordinates track that. One way or the other, it seems like there is duplicate knowledge: either an island should know its coordinates, or a coordinate should know which island it is in, but probably not both.

2017-05-19Thanks!
37SUGGEST

Consider expanding the first sentence of the last paragraph to read “We use an empty IslandSet struct as the initial accumulator.”

2017-05-19Thanks!
37SUGGEST

Consider clarifying the second sentence of the last paragraph as follows: “This will be passed into each iteration as the set[typeset the word ‘set’ as code] argument, the anonymous function will use Map.put/3 to update it with a new Island agent, and then Enum.reduce/3 will return it to act as an argument for the next iteration.”

Also, I’m noticing several places where the word enumeration appears, but the word iteration seems to be intended. It is probably worth doing a search through the document to validate each use of this term.

2017-05-19Thanks!
37SUGGEST

Consider adding “This won’t compile yet because we haven’t written the keys/0 function yet.” at the end of the paragraph just above the definition of initialized_set/0.

2017-05-19Thanks!
38TYPO

Examples from iex in the middle of the page do not have color syntax highlighting, in contrast to the examples near the bottom of the page.

2017-05-19Thanks!
38TYPO

Change “Our new keys/1 function fixes” to “Our new keys/0 function fixes”

2017-05-19Thanks!
39SUGGEST

It would be nice if the output of the last iex command on the previous page could be kept together with the command, widows/orphans

2017-05-19Thanks!
39SUGGEST

In the call-out “A New Construct”, would it be accurate and useful to add a final sentence as follows? “Similarly, &(&1.in_island) is equivalent to the fn state -> state.in_island end”

2017-05-19Thanks!
40SUGGEST

By the start of the section on page 40 I’m feeling the lack of any code to enforce valid shapes on the coordinates added to an Island. You’re probably saving this for later in the book, but it might be nice to throw in a brief mention that we’re not checking now, but we’ll worry about it later.

In a related note, it might be nice to add two coordinates (rather than a single coordinate) in the iex examples on page 40, just to make the concept of a List snap-in for the reader.

2017-05-19Thanks!
41SUGGEST

There’s a disconnect between the last paragraph on p40 and the diagram at the top of p41. You’ve just listed three keys for the struct—:name, :board, and :island_set. But the diagram (which is showing hierarchy, not keys) doesn’t mention name. Would it make more sense if the diagram came between the 1st and 2nd paragraphs of this section?

2017-05-19Thanks!
41SUGGEST

Throughout the chapter, whenever you create a to_string function and it displays the :none atom, I find myself wishing there could be an atom like :none that was more visibly/visually distinct from the atoms we want to be seeing. Maybe :n or something that looks really different from :atoll.

2017-05-19Thanks!
42SUGGEST

The word “initialize” in this sentence, “Once we have that, we can initialize the struct and see what we get”, seems to be at odds with the following code example. The struct actually gets initialized before being bound to the variable, right? Maybe just replace “initialize” with “display”?

2017-05-19Thanks!
46SUGGEST

For those of us who are trying to imagine reimplementing our business systems using the approaches and techniques in this book, the biggest question we have at this point is about persistence of long-lived things, like customers and payments and other things that we want to see months and years in the future. One or two sentences somewhere in this chapter could give us assurance that you’re planning to show us how to manage persistence later in the book.

2017-05-19Thanks!
44SUGGEST

In the diagrams on page 44, would be accurate and/or useful to show only a single circle for Coord, with both Board and Island pointing to the same circle?

2017-05-19Thanks!
45SUGGEST

In the code examples, the use of a1 might be unfortunate because in some typefaces, the 1 and the closing square brace look too similar. Instead of “Island.replace_coordinates(island, [a1])”, it looks like “Island.replace_coordinates(island, [all)”. For teaching purposes, a2 might be a more visible choice.

2017-05-19Thanks!
117ERROR

The included source /gen_statem/lib/game.ex for this chapter does not include

def call_demo(game) do
GenServer.call(game, :demo)
end

def handle_call(:demo, _from, state) do
{:reply, state, state}
end

2017-05-30Thanks! This is going to change a lot in future betas, but I'm going to leave it as is for now.
117ERROR

For the command: “iex> Game.guess_coordinate(game, :player1, :a1)” I was receiving the error:

(EXIT from #PID<0.112.0>) an exception was raised:
(FunctionClauseError) no function clause matching in IslandsEngine.Game.guess_reply/3
(islands_engine) lib/game.ex:94: IslandsEngine.Game.guess_reply(:error, #PID<0.224.0>, :a1)
(islands_engine) lib/game.ex:60: IslandsEngine.Game.handle_call/3
(stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:647: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

I modified the “defp guess_reply({:error, :action_out_of_sequence}, _opponent_board, _coordinate) do” signature in the game.ex file to “defp guess_reply(:error, _opponent_board, _coordinate) do” and all is working now.

Love the book! Excellent coverage and methodology!!

Best,
Martin

2017-05-30Thanks!
118ERROR

Using Erlang/OTP 19, Elixir 1.4.2:

Towards the bottom of the page, when player1 guesses out of turn, I get a “bad return from state function” error (command = “Game.guess_coordinate(game, :player1, :a1)”). My guess is that this is due to not having a function which matches the pattern returned by IslandsEngine.Rules. Here’s the console error dump:

(EXIT from #PID<0.135.0>) {:bad_return_from_state_function, {:reply, {:error, :action_out_of_sequence}, :player2_turn, %IslandsEngine.Rules{player1: :islands_set, player2: :islands_set}}}

Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
18:42:05.340 [error] State machine #PID<0.596.0> terminating

Last event = {{:call, {#PID<0.379.0>, #Reference<0.0.2.1718>}},
{:guess_coordinate, :player1}}

When server state = {:player2_turn,
%IslandsEngine.Rules{player1: :islands_set, player2: :islands_set}}

Reason for termination = :error:{:bad_return_from_state_function,
{:reply, {:error, :action_out_of_sequence}, :player2_turn,
%IslandsEngine.Rules{player1: :islands_set, player2: :islands_set}}}

Callback mode = :state_functions

Stacktrace =

[{:gen_statem, :parse_event_result, 8, [file: ‘gen_statem.erl’, line: 1301]},
{:gen_statem, :loop_event, 6, [file: ‘gen_statem.erl’, line: 998]},
{:proc_lib, :init_p_do_apply, 3, [file: ‘proc_lib.erl’, line: 247]}]

Best,
Martin

2017-05-30Thanks! This is going to change a lot in future betas, but I'm going to leave it as is for now.
118ERROR

I figured out how to solve my state machine woes. I am not entirely sure of all the possible valid returns from :gen_statem, so I had to experiment quite a bit (Looks like I need to learn more Erlang). I did a couple of things to solve the issues I was experiencing. 1) I changed the return type for the default “catch all” event handler for player turns (i.e. player1_turn) to be “{:keep_state_and_data, {:reply, from, :error}}”. The routine wasn’t being called when a player guessed out of turn because the first clause wasn’t matching “{:call, from}”, but I was experiencing a timeout previously and I wasn’t sure how to match for the from term without the whole tuple, so I did… 2) I created a matching callback in the rules.ex file which matches on the invalid player guessing, which returns an error.

Here’s the example code from player1_turn (player2_turn mirrors this example) (with the commented our attempts along the way):

def player1_turn({:call, from}, {:guess_coordinate, :player2}, _state_data) do
IO.puts “player1_turn - invalid guess from player2”
{:keep_state_and_data, {:reply, from, :error}}
end

  1. original signiture def player1_turn(_event, _caller_pid, state) do
  2. def player1_turn({:call, from}, _caller_pid, _state) do
    def player1_turn({:call, from}, _caller_pid, _state) do
    IO.puts “player1_turn - catch all called”
    {:keep_state_and_data, {:reply, from, :error}}
    # {:keep_state, {:reply, from, :error}}
    # original {:reply, {:error, :action_out_of_sequence}, :player2_turn, state}
    end

It would be nice to have a more thorough explanation of the :gen_statem module for the final release of this book. Specifically, the types of function signatures which are allowed and the potential return values and their effects.

Best,
Martin

2017-05-30Thanks! This is going to change a lot in future betas, but I'm going to leave it as is for now.
35ERROR

in this bit of code;
defp string_body(board) do
Enum.reduce(keys(), “”, fn key, acc ->
coord = get_coordinate(board, key))
acc <> “#{key} => #{Coordinate.to_string(coord)},\

end)
end

There is an unpaired right periphrasis.
“coord = get_coordinate(board, key))” should be ->
“coord = get_coordinate(board, key)”

2017-05-19Thanks!
32ERROR

On page 31/32 you start the Board module. However, you omit the ‘end’ of the module. This is on contrast to the IslandSet module that you start on page 37, where you include the ‘end’ line.

2017-05-19Thanks!
46SUGGEST

Throughout this chapter, the dimensions of the board require the reader to scroll back after displaying the current board. You also avoid displaying the entire board in your output examples included in the book. I would be OK with an extra paragraph in which you comment out parts of the numbers and letters lists to constrain the board to 2x2 or 2x3. That would help the reader by abbreviating their output and avoiding the need to scroll back. It would also allow you to include complete output in your listings without wasting too much space.

2017-05-19Thanks!
23TYPO

def in_island?(coordinate) do
case island(coordinate)) do <- must be island(coordinate)
:none -> false
_
-> true
end
end

I love your book

2017-05-19Thanks!
21TYPO

“(…) that wraps the bare Agent.start_link/2 call.” should be Agent.start_link/1 (based on the code below the text):

def start_link() do
Agent.start_link(fn -> %Coordinate{} end)
end

2017-05-19Thanks!
65ERROR

Right below the code for

gen_server/lib/game.ex
def set_island_coordinate(pid, player, island, coordinates)
[..]
end"

it reads:

“Since we’re using the Coordinate module, let’s add that to the aliases we’re already using.
alias IslandsEngine.{Coordinate, Island, IslandSet}”

But the actual aliases in game.ex are: alias IslandsEngine.{Game, Player}
and I can’t see where the Coordinate module is used in the Game module.

Could it be a misplaced paragraph?

2017-05-19Thanks!
56SUGGEST

Formatting: the error message about “unexpected message” will naturally be wrapped by iex at the right margin. There’s no reason to avoid wrapping it in the book.

2017-05-19Thanks!
57TYPO

It would be good to manage widows and orphans so that you don’t get a single line of code appearing on the page previous or subsequent to the other parts of the code sample. In this case, a lonely ‘end’ statement appears by itself at the top of page 57.

2017-05-19That will happen at a later stage in the process. Thanks!
62TYPO

Formatting missing from last code sample on the page (IO.puts IslandsEngine…)

2017-05-19Thanks!
65ERROR

On page 65 you talk about adding Coordinate to the aliases we’re already using. However, the next line does not match any alias statement already in game.ex. And if I add Coordinate to the existing statement, it only generates warnings, at least for the next couple of pages. If this alias is needed later or in a different file, the paragraph and statement should be relocated at the point in the book where it will make a difference.

Also, the line is not formatted/syntax highlighted.

2017-05-19Thanks!
69TYPO

Formatting missing from first line of second code sample, right after the words “Ok, let’s see what player1’s state looks like now.”

2017-05-19Thanks!
123TYPO

join(game)

should be

join(game_channel)

as the game object is not defined at this point.

2017-05-30Thanks!
133TYPO

game.on(“player_added”, response => {
console.log(“Player Added”, response)
})

should be

game_channel.on(“player_added”, response => {
console.log(“Player Added”, response)
})

the game object does not exist

2017-05-30Thanks!
41ERROR

def start_link(name \\\\ :none) do
{:ok, board} = Board.start_link
{:ok, island_set} = IslandSet.start_link
Agent.start_link(fn -> %Player{board: board, island_set: island_set} end)
end

Does not set the player’s name even if it is provided. As a result starting a game and supplying player1’s name does not set player1’s name. One would either have to explicitly set player1’s name OR adjust the start_link call to:

Agent.start_link(fn -> %Player{board: board, island_set: island_set, name: name} end)

Then if a name is not supplied the default parameter of “:none” is used but if a name is supplied it is also set in the Agent struct.

2017-05-19Thanks!
21TYPO

On page 17 we define the ````’defmodule IslandsEngine.Coordinate’ in the file ‘lib/coordinate.ex’. On page 21 we add a ‘start_link’ function to that same defmodule and the green header above the code states its location as ‘agent/lib/coordinate.ex’. I believe this should simply be ‘lib/coordinate.ex’.

2017-05-19Thanks!
127TYPO

In the second paragraph after the first definition of the Bicycle class, first sentence, you have the word “out” when you wanted “our” before the word “Bicycle”. It should say, “Rails tends to push us toward putting domain models, like our Bicycle class, …”.

2017-05-30Thanks!
30SUGGEST

There may be missed “functional” opportunities with regard to the capture operator here. The ‘&(&1)’ usage is discussed in a sidebar on p.39(47) even though it could have been introduced as early as p.22(30) as a short form for ‘fn state -> state end’.
However here on pp.29..30(37..38) there is an opportunity to use (and briefly discuss) both capture forms:

def forested?(island) do
island
|> Agent.get(&(&1))
|> Enum.all?(&Coordinate.hit?/1)
end

defp coordinate_strings(island) do
island
|> Agent.get(&(&1))
|> Enum.map(&Coordinate.to_string/1)
|> Enum.join(“, ”)
end

Then again, maybe calling out ‘&fn/#’ here first, may be a good lead up to ‘`&(&1)’ later.
— Peer Reynders

2017-05-19Thanks!
32SUGGEST

“This uses two “for” comprehensions."

Wouldn’t it be more accurate(?) to state:

“This comprehension uses two generators.”

elixir-lang.org/getting-started/comprehensions.html#generators-and-filters

2017-05-19Thanks!
38TYPO

The “nil” atoms should be “:none” in :

iex> island_set = %IslandSet{}
%IslandsEngine.IslandSet{atoll: nil, dot: nil, l_shape: nil, s_shape: nil,
square: nil}

2017-05-19Thanks!
41TYPO

“Notice that we did not set a value for the :name key in the start_link/0 function.”
(Probably left over from a previous edit?)

1. The arity is ‘start_link/1’ - default values for parameters do not affect arity.
2. Suggestion: Could possibly rephrase this in terms of “accepting the default parameter value” or “not overriding the default parameter value” which may be more descriptive than “not setting the value”.

2017-05-19Thanks!
49TYPO

“given a list other users an individual follows”

should probably be

“given a list of other users an individual follows”

2017-05-19
52TYPO

“We’ll see them in much more detail when we talk about Supervisors in the (as yet) unwritten chapter.design_for_recovery, and”

Presumably this can now be replaced with a link to “Chapter 5. Process Supervision for recovery”.

2017-05-19Thanks!
56SUGGEST

“iex> send(game, :first)”

I understand why ‘send’ is used in this context. That being said I was under the impression that using raw concurrency primitives in connection with OTP code is generally a no-no. If that is the case, a quick “don’t do this in your code” reminder in a footnote may be useful. ‘handle_info’ is useful for all sorts of other reasons.

2017-05-19Thanks!
58SUGGEST

“_from is the PID of the calling process,”
1. Actually ‘from() :: {pid, tag :: term}’ - Tuple describing the client of a call request.

hexdocs.pm/elixir/GenServer.html#t:from/0

2. I’m not sure why the documentation is so cavalier about exposing this type.

“Designing for Scalability with Erlang/OTP (2016)”; Chapter 4. Generic Servers - Message Passing - Synchronizing Clients, p.89:
“Always use From as an opaque data type; don’t assume it is a tuple, as its representation might change in future releases.”

Essentially the ‘from’ parameter is intended for use with OTP - if the server logic requires a “naked” PID then that PID should be part of the payload, the ‘request’ parameter (i.e. and not be extracted from the ‘from’ parameter).

2017-05-30Thanks!
64TYPO

“iex> IO.puts IslandsEngine.Player.to_string(state.player2)
%Player{:name => none,”

the current version of ‘name_to_string’ actually prints “:none” instead of “none”, i.e.
“%Player{:name => :none,”

2017-05-19Thanks!
65SUGGEST

“Since we’re using the Coordinate module, let’s add that to the aliases we’re already using.
alias IslandsEngine.{Coordinate, Island, IslandSet}”

We’ve just come off discussing ‘Game.set_island_coordinates’ (game.ex) - however the above text switches the context to the IslandSet (island_set.ex) module. This “switch” to ‘IslandSet’ needs to be made much clearer and explicit.

2017-05-19Thanks!
70TYPO

“Let’s go back to Game.get_opponent/2.”

In the current implementation it’s simply ‘Game.opponent/2’, not ‘Game.get_opponent/2’.

2017-05-19Thanks!
70SUGGEST

The multi-clause nature of ‘opponent/2’ could potentially be more strikingly emphasized by using the short form function definition syntax, i.e.

defp opponent(state, :player1), do: state.player2
defp opponent(state, _player2), do: state.player1

2017-05-30Thanks! This will change significantly in future betas, so I'll leave it as is for now.
72SUGGEST

The pipeline in ‘handle_call({:guess, player, coordinate}, _from, state)’ could be made more “conformant” with the style guide
github.com/christopheradams/elixir_style_guide#bare-variables

e.g.
def handle_call({:guess, player, coordinate}, _from, state) do
opponent = opponent(state,player)
response =
opponent
|> Player.get_board()
|> Player.guess_coordinate(coordinate)
|> forest_check(opponent, coordinate)
|> win_check(opponent)

{:reply, response, state}
end

2017-05-30Thanks! This is going to change a lot in future betas, so I'm going to leave it as is for now.
75SUGGEST

“def win?(opponent) do” in (player.ex)

Naming the argument supplied to ‘Player.win?/1’ ‘opponent’ in ‘Game.win_check’ makes sense; however the name of the parameter in the ‘Player.win?/1’ definition should simply be ‘player’ given that the function simply checks whether the specified player has won.

Further exploring player.ex it is noticeable that agents seem to get parameter names like ‘player’, ‘island’, ‘island_set’, while the (game.ex) Game GenServer simply gets ‘pid’ (instead of simply ‘game’).

Still in (player.ex) there are functions that don’t involve ‘player’ at all. It seems that

‘convert_coordinates(board, coordinates)’
‘convert_coordinate(board, coordinate)’
‘guess_coordinate(board, coordinate)’

might be more at home in the ‘Board’ module (board.ex).

2017-05-30Thanks! This is going to change a lot in future betas, so I'm going to leave it as is for now.
76SUGGEST

“iex> GenServer.start_link(Game, {:ok, ”Frank“}, name: :islands_game)”

It could beneficial to briefly mention in a footnote that there is some “keyword list syntactic sugar” magic at work here, i.e. that plain old ‘name: :islands_game’ is actually equivalent to ‘[{:name, :islands_game}]’

2017-05-30Thanks!
79SUGGEST

Why use/prefer

def stop(pid) do
GenServer.cast(pid, :stop)
end

def handle_cast(:stop, state) do
{:stop, :normal, state}
end

### over

def stop(pid) do
GenServer.stop(pid)
end

def terminate(_reason, _state) do
:ok
end

???

In any case mentioning the alternatives may be a good idea.

2017-05-30Thanks!
94TYPO

extra parenthesis

“agent/lib/board.ex
​ \t​def​ to_string(board) ​do​
​ \t ​”​​%{“​ <> string_body(board) <> ​”​​}“​
​ \t​end​
​ \t
​ \t​defp​ string_body(board) ​do​
​ \t Enum.reduce(keys(), ​”​​“​, ​fn​ key, acc ->
​ \t coord = get_coordinate(board, key))
#^ extra )
​ \t acc <> ​”​​#{​key​}​​ => ​​#{​Coordinate.to_string(coord)​}​​,\
“​
​ \t ​end​)
​ \t​end​”

Excerpt From: Lance Halvorsen. “Functional Web Development with Elixir, OTP, and Phoenix (for Bill Tihen).” iBooks.

should be:

“agent/lib/board.ex
​ \t​def​ to_string(board) ​do​
​ \t ​”​​%{“​ <> string_body(board) <> ​”​​}“​
​ \t​end​
​ \t
​ \t​defp​ string_body(board) ​do​
​ \t Enum.reduce(keys(), ​”​​“​, ​fn​ key, acc ->
​ \t coord = get_coordinate(board, key)
​ \t acc <> ​”​​#{​key​}​​ => ​​#{​Coordinate.to_string(coord)​}​​,\
“​
​ \t ​end​)
​ \t​end​”

Excerpt From: Lance Halvorsen. “Functional Web Development with Elixir, OTP, and Phoenix (for Bill Tihen).” iBooks.

2017-05-30Thanks!
133TYPO

Presumably it should be

defmodule IslandsEngine.Application do

rather than

defmodule IslandsEngine do

i.e. the code belongs in

islands_engine/lib/islands_engine/application.ex

rather than

islands_engine/lib/islands_engine.ex

2017-05-30Thanks!
135SUGGEST

Both

iex> Application.start(:islands_engine)

and

iex> :application.start(:islands_engine)`

are used and both work - but it does leave me wondering whether this was actually intentional (especially as

Application

doesn’t seem to expose everything that

:application

does, namely

which_applications/0

).

2017-05-30Thanks!
137SUGGEST

At the point of

$ mix phoenix.new islands_interface —no-ecto

It wasn’t clear to me where in relation to the

islands_engine

directory we were supposed to be (i.e.

islands_interface

should be created as a sibling directory to

islands_engine

- so we are executing the command from

islands_engine’s

parent directory).

2017-05-30Thanks!
157TYPO

iex> JOIN game:moon to IslandsInterface.GameChannel
Transport: Phoenix.Transports.WebSocket
Parameters: %{“screen_name” => “moon”}
[info] Replied game:diva :ok

the last line is actually

[info] Replied game:moon :ok

2017-05-30Thanks!
161TYPO

Returned Greeting: World!}

the closing brace is extraneous, should simply be:

Returned Greeting: World!

2017-05-30Thanks!
167TYPO

{:reply, {:error, %{reason: inspect(reasonn}}, socket}

typo “reasonn}}” should be

{:reply, {:error, %{reason: inspect(reason)}}, socket}

2017-05-30Thanks!
171TYPO

iex> fsm = Game.call_demo(game).fsm

I had to use this instead

iex> fsm = IslandsEngine.Game.call_demo({:global,“game:moon”}).fsm

- there is no previous “alias InterfaceEngine.Game”
- “game” was previously set to the game state, not the game PID

so

iex> fsm = game.fsm

would have sufficed but the “longer” version doesn’t rely on “game” being set.

2017-05-30Thanks! This is going to change a lot in future betas, but I'm going to leave it as is for now.
180SUGGEST

The “if” in “authorized?” is redundant

defp authorized?(socket, screen_name) do
number_of_players(socket) < 2 && !existing_player?(socket.screen_name)
end

should be sufficient.
Regarding “join”; isn’t “cond” more idiomatic than “if” in Elixir?

2017-05-30:) Thanks! And actually, I think "if" is quite acceptable in Elixir.
169ERROR

For the JavaScript function set_island_coordinates, the params variable needs to be declared (“var”).

Here’s the corrected function in its entirety:

function set_island_coordinates(channel, player, island, coordinates) {
var params = {“player”: player, “island”: island, “coordinates”: coordinates}
channel.push(“set_island_coordinates”, params)
.receive(“ok”, response => {
console.log(“New coordinates set!”, response)
})
.receive(“error”, response => {
console.log(“Unable to set new coordinates”, response)
})
}

Love the book!! Great job!!
Best,
Martin

2017-05-30Thanks so much!
35TYPO

Extra closing parenthesis in:

coord = get_coordinate(board, key))

2017-05-19Thanks!
91SUGGEST

After “The first one we need to tackle is callback_mode/0”, the code shown for “gen_statem/lib/rules.ex” shows a whole lot of new functions that have not yet been explained.

2017-05-20Thanks!
117ERROR

At this point there was some bug I could not easily trace so I downloaded the source code following the links from the beginning of the book. But it doesn’t work either, now with:

iex(2)> {:ok, game} = Game.start_link(“Betty”)

(EXIT from #PID<0.120.0>) :function_clause

14:33:31.620 [error] State machine #PID<0.340.0> terminating

When server state = :undefined

Reason for termination = :error::function_clause

Callback mode = :undefined

Stacktrace =

[{IslandsEngine.Rules, :init, [:initialized],
[file: ‘lib/islands_engine/rules.ex’, line: 40]},
{:gen_statem, :init_it, 6, [file: ‘gen_statem.erl’, line: 626]},
{:proc_lib, :init_p_do_apply, 3, [file: ‘proc_lib.erl’, line: 247]}]

2017-05-30
82ERROR

The handle_call/3 previously included

opponent = opponent(state, player)
opponent_board = Player.get_board(opponent)
Player.guess_coordinate(opponent_board, coordinate)

But is later switched to
opponent = opponent(state, player)
Player.guess_coordinate(opponent.board, coordinate)

Running the win checks in iex produces the following error
[error] GenServer #PID<0.1032.0> terminating

(ArgumentError) argument error
:erlang.apply(#PID<0.1140.0>, :board, [])
(islands) lib/game.ex:25: Islands.Game.handle_call/3
(stdlib) gen_server.erl:629: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:661: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

Which I fixed by restoring the opponent_board call

2017-05-30Thanks! This is going to change a lot in future betas, but I'm going to leave it as is for now.
2619ERROR

There is a missing ] on the end of the @enforce_keys line. It is in the code download but it is missing in the book.

2017-06-04Thanks!
3124SUGGEST

In the code for the add_coordinate function, I would rewrite the case expression as follows:

case Coordinate.new(row + row_offset, col + col_offset) do
{:ok, coordinate} ->
{:cont, MapSet.put(coordinates, coordinate)}
error ->
{:halt, error}
end

Doing it this way, anything that doesn’t match {:ok, coordinate} is considered an error and gets returned in the :halt tuple. By explicitly matching {:error, :invalid_coordinate}, if any other type of error was returned, it would not match one of the case patterns and cause a runtime error which would be more confusing to diagnose, potentially.

Also, matching on error means that you don’t have to type {:error, :invalid_coordinate} twice and I am very lazy.

2017-07-03Thanks!
13SUGGEST

In Game introduction (page 5) The
grid is labeled with the letters A through J going down the left side of the
board and the numbers 1 through 10 across the top. We name individual
coordinates with a letter-number combination—A1, J5, D10, and so on.

But the graphic in page 12 did no reflect the nomenclature.

2017-06-04Thanks!
3124SUGGEST

In the last paragraph, you should mention that you are redefining new/0 to new/2 and that you are not keeping the new/0 function around anymore.

2017-06-04Thanks!
3427SUGGEST

Prior to introducing the code for the add/3 function, you might want to mention that the alias directive at the top of the source file should be changed from:

alias MODULE

to

alias IslandsEngine.{Coordinate, Guesses}

2017-06-04Thanks!
4033ERROR

You reference island_types in the types/0 function but you don't mention defining island_types in the preceding discussion. It does exist in the code download but it should probably be mentioned in the book.

2017-07-03Thanks!
4033SUGGEST

Not sure how complete you want the code in the book to be. When you introduce the position_island/3 function, it relies on an alias for IslandsEngine.Island which has not been mentioned in the preceding discussion.

2017-06-04Thanks!
4639TYPO

In the second paragraph after the first iex session, first sentence, the word “and” before the word “island” should be “an”. It should say, “Now let’s try for a guess that doesn’t forest an island or win.”.

2017-06-04Thanks!
39TYPO

“Perfect, that’ exactly what we expected.” -> “Perfect, that’s exactly what we expected.”

2017-06-04Thanks!
2114TYPO

In the last paragraph.

“We can define a struct with row and col keys. Since we’ve aliased the Coordinate module, we can now refer to coordinate structs as %Coordinate{} instead of %IslandsEngine.Coordinate.”

… instead of %IslandsEngine.Coordinate{} . ( missing curly braces )

2017-06-04Thanks!
19TYPO

Missing closing square bracket on “@enforce_keys [:coordinates, :hit_coordinates”
for module IslandsEngine.Island

2017-06-18Thanks!
39ERROR

The code used to test if the guess is a miss is technically correct, but for the wrong reason. Coordinate.new/2 returns a tuple instead of just the coordinate. You have the correct code for this in the example below testing when the guess returns that a hit occurred.

2017-07-03Thanks!
60TYPO

missing a do at the end of def

def check(%Rules{state: :player2_turn} = rules, {:win_check, win_or_not}) case win_or_not do
:no_win -> {:ok, rules}
:win -> {:ok, %Rules{rules | state: :game_over}} end
end

2017-07-21Thanks!
59,TYPO

Pages 59, 60, 63

When running the code to test the :win and :no_win: clauses I think you are calling it incorrectly. Believe it should be like:

{:ok, rules} = Rules.check(rules, {:win_check, :no_win})

and

{:ok, rules} = Rules.check(rules, {:win_check, :win})

2017-07-21Thanks!
60ERROR

“we’ll need the same three clauses” -> “we’ll need the same two clauses”

2017-07-21Thanks!
62TYPO

There is a typo in the word ‘should’ in the sentence:

When one player sets their islands, they should no longer be able to position them, but the other player still shuld be able to.

2017-07-21Thanks!
79TYPO

2nd sentence under “Initializing GenServer State” heading:

“.. the idea that we’re starting a new game process get’s buried in the arguments.”

Change “get’s” to “gets”, or consider changing wording to something like, “… the idea that we’re starting a new game process becomes buried in the arguments”

2017-07-29Thanks!
79SUGGEST

In the phrase, “To do this we’ll follow the GenServer pattern”, add a comma, e.g.:

“To do this, we’ll follow the GenServer pattern”

2017-07-29Thanks!
117TYPO

“it created a new Application for us with it’s own identity, above Phoenix itself”

it’s -> its

2017-08-02Thanks!
83ERROR

:no_win won’t match with any funciton defined, needs to be the tuple { :win_check, :no_win }

iex> ​​ ​​ {:ok, ​​ ​​ rules} ​​ ​​ = ​​ ​​ Rules.check(rules, ​​ ​​ :no_win)
​ ​ {:ok, ​ %IslandsEngine.Rules{player1: :islands_set, player2: :islands_set, ​ state: :player1_turn}}
Correction
Iex> {:ok, rules} = Rules.check(rules, {:win_check, :no_win})

2017-08-02Thanks!
88ERROR

In order to get {:error, :invalid_coordinate} on page 88 from the code
Game.position_island(game, :player1, :l_shape, 10, 10) the new function on page 25 should match to this:

def new(type, %Coordinate{} = upper_left) do
with | = offsets <- offsets(type),
%MapSet{} = coordinates <- add_coordinates(offsets, upper_left)
do
{:ok, %Island{coordinates: coordinates, hit_coordinates: MapSet.new()}}
else
error -> {:error, error}
end
end

The error case should return tuple {:error, error}

2017-08-02Thanks, but the error case will already be a tuple.
107ERROR

Position Islands on GenServer

def ​ position_island(game, player, key, row, col) ​ when ​ player ​ in ​ @players, ​ do ​: ​ GenServer.call(game, {​ :position_island ​, player, key, row, col})

@players is not defined.

2017-10-03Thanks!
89SUGGEST

I think the method
``def set_islands(game, player) when player in @players, do: GenServer.call(game, {:set_islands, player})

should be defined before happy path examples on page 87. It seems it should be defined right after you display whole function on page 86:

def handle_call({:position_island, player, key, row, col}, _from, state_data)

2017-10-03Thanks!
109ERROR

on the game.ex on page 109 we are using: ​ ​ board = player_board(state_data, player), then on page 110 we are using iex to test, but player_board isn’t defined until page 112.

2017-10-03Thanks!
112ERROR

@players isn’t defined yet. This is related with reported error #81916

​ def ​ set_islands(game, player) ​ when ​ player ​ in ​ @players, ​ do ​: ​ GenServer.call(game, {​ :set_islands ​, player})

2017-10-03Thanks!
124ERROR

On the text refers:

Notice that we’re pattern matching for a successful start of the game server
{:ok, _pid} = Supervisor.start_child(:game_supervisor, [name]). If the game server fails to
start, we’ll get an error page.

but in the sample code:
{:ok, _pid} = IslandsEngine.Game.start_link(name)

In order to make it work, we will need to add the supervisor on the IslandEngine, and start as a child below the Register.

On the latest errors that I reported I used the page of the readers app ( #81916 #81918 #81923), not the pdf (sorry for the confusion)

Ruben.
Also the GameSupervisor hasn’t been implemeted yet.(probably on the chapter that is going to be released)

2017-10-03Thanks!
63TYPO

“iex> {:ok, rules} = Rules.check(rules, :no_win)” should be "iex> {:ok, rules} = Rules.check(rules, {:win_check, :no_win})

2017-10-02Thanks!
84TYPO

iex> Game.demo_call(game) instead of Game.call_demo(game) at the third line from the bottom of the page.

2017-10-02Thanks!
26SUGGEST

Additional rationale for using a Map to represent the Board: the guarantee of key uniqueness prevents 2 islands of the same type from being added to the Board.

2017-10-04Thanks!
13SUGGEST

Maybe a colon would be better to introduce a list in a sentence containing 5 commas.

“Just by describing that picture, we’ve identified four main entities, boards, islands, guesses, and coordinates.”

could be

“Just by describing that picture, we’ve identified four main entities: boards, islands, guesses, and coordinates.”

2017-10-02Thanks!
18TYPO

Apostrophe “confusion” in a couple of places at the beginning of the page:

“Now lets’ add coordinate2 to the hits set as well.” → “Now let’s add coordinate2 to the hits set as well.”

“Now lets try adding coordinate1 to the hits set again.” → “Now let’s try adding coordinate1 to the hits set again.”

2017-10-02Thanks!
29TYPO

There are a couple of places on this page where the “?” is missing from a function name:

“There’s a great function to test for this, MapSet.disjoint/2.” → “There’s a great function to test for this, MapSet.disjoint?/2.”

“We’ll use this overlaps/2 function later from a board to compare one island to all the existing ones.” → “We’ll use this overlaps?/2 function later from a board to compare one island to all the existing ones.”

2017-10-02Thanks!
32ERROR

iex> :miss = Island.guess(dot, Coordinate.new(2, 2))
:miss

This is a false positive. It’s not giving a :miss because the coordinate is wrong. It’s giving a :miss because it’s trying to match {:ok, coordinate}.

Using the actual coordinate for the dot island in the example would also result in a :miss.

:miss = Island.guess(dot, Coordinate.new(2,1))

2017-10-03Thanks!
57SUGGEST

iex> Rules.check(rules, {:set_islands, :player2})
:error

This call is also done on page 56 just a few lines above. It reads kind-of weird to have it in there twice.

2017-10-03Thanks!
32ERROR

:miss = Island.guess(dot, Coordinate.new(2, 2))

even Island.guess(dot, Coordinate.new(4, 4)) will still return “false” even though this is dot island coordinate. Coordinate.new(2, 2) return a tuple instead of a coordinate.

It will be nice to have
{:ok, new_coordinate1} = Coordinate.new(2, 2)
and
{:ok, new_coordinate2} = Coordinate.new(4, 4)
to prevent confusion.

2017-10-03Thanks!
7063TYPO

This is what’s on the first line of the page in question:
iex> {:ok, rules} = Rules.check(rules, :no_win)

This is what it should be:
iex> {:ok, rules} = Rules.check(rules, {:win_check, :no_win})

2017-10-02Thanks!
84TYPO

In the “handle_cast” function, change this return value:
{:noreply, Map.put(state, :test, new_value}}
to this return value:
{:noreply, Map.put(state, :test, new_value)}

2017-10-03Thanks!
10TYPO

5th paragraph, first sentence reads:

But building an application with a framework from the very beginning makes it’s nearly impossible to maintain that separation.

perhaps it should read:

But building an application with a framework from the very beginning makes it nearly impossible to maintain that separation.

2017-10-02Thanks!
10TYPO

6th paragraph, last sentence reads:

If frees us to focus on the pure domain of our application.

it should read:

It frees us to focus on the pure domain of our application.

2017-10-02Thanks!
77SUGGEST

Lack of consistency in last page snippet: first line uses simply Game and the second one IslandsEngine.Game

2017-11-09Thanks!
9487SUGGEST

in iex snippet “alias IslandsEngine.Game” should be enough (other modules are not used). Similar fix can be applied into iex snipped on p. 90 (97 pdf).

2017-11-09Thanks!
uTYPO

In the initial content of lib/islands_engine/island.ex, missing a closing square brace at the end of the line that begins, “@enforce_keys”

2017-11-09Thanks!
kindlERROR

In the session exploring strategy for positioning islands, I got:

iex(6)> offsets = [{0,0},{0,1},{1,0},{1,1}]
[{0, 0}, {0, 1}, {1, 0}, {1, 1}]
iex(7)> Enum.map(offsets, fn {row_offset, col_offset} ->
…(7)> Coordinate.new(row + row_offset, col + col_offset)
…(7)> end)
[ok: %IslandsEngine.Coordinate{col: 1, row: 1},
ok: %IslandsEngine.Coordinate{col: 2, row: 1},
ok: %IslandsEngine.Coordinate{col: 1, row: 2},
ok: %IslandsEngine.Coordinate{col: 2, row: 2}]
iex(8)>

The output shown in the book lacks the “ok:” before each item.

2017-11-09Thanks!
126120ERROR

Currently “timeout = 15000", should be "timeout 15000”

2017-11-09Thanks!
127121ERROR

Presented snippet does not terminate game after 15s. Instead “16:38:16.874 [error] IslandsEngine.Game #PID<0.117.0> received unexpected message in handle_info/2: :timeout” error is shown and the Game process is still alive (elixir v1.5.0, Erlang/OTP 19).

2017-11-15Thanks!
127121ERROR

Currently: “timeout = 60 * 60 * 24 * 1000", should be "timeout 60 * 60 * 24 * 1000”

2017-11-09Thanks!
140135TYPO

You are referring to “part one” where game engine was build but it was done in part I and II. At the beginning of last page paragraph there is “Our task in part two” - but we are in part III.

2017-11-09Thanks!
191186ERROR

Currently: “Open up /lib/islands_engine/application.ex”, should be “Open up /lib/islands_interface/application.ex”

2017-11-09Thanks!
155160SUGGEST

executing Supervisor.which_children with :game_supervisor produced the following for me:
iex(2)> Supervisor.which_children(:game_supervisor)

(exit) exited in: GenServer.call(:game_supervisor, :which_children, :infinity)
(EXIT) no process: the process is not alive or there’s no process currently associated with the given name, possibly because its application isn’t started
(elixir) lib/gen_server.ex:766: GenServer.call/3

However, the following worked for me:
iex(2)> Supervisor.which_children(IslandsEngine.GameSupervisor)
[{:undefined, #PID<0.361.0>, :worker, [IslandsEngine.Game]}]

2017-11-15Thanks!
154159SUGGEST

in the test function you use the following to start the game server:
{:ok, _pid} = IslandsEngine.Game.start_link(name)
then, a little later you refer to: {:ok, _pid} = Supervisor.start_child(:game_supervisor, [name]). Also, I found that executing the code in the test function started an independent game pid not associated with any proc supervisor. For me, using the following line in the test function worked ok:
{:ok, pid} = IslandsEngine.GameSupervisor.start_game(name)

2017-11-15Thanks!
60ERROR

Section “Player Two’s Turn”
Last check clause has a missing “do” in the first line.
It’s also missing in the linked source code on line 49.

2017-11-16Thanks!
158147SUGGEST

It is probably better to be consistent which mix task to use to generate phoenix project. You mentioned both phoenix.new and phx.new mix tasks.

Regards,
Ruslan.

2018-01-02Thanks!
79TYPO

`GenServer.cast(pid, {:demo, new_value})` should be `GenServer.cast(pid, {:demo_cast, new_value})`

2018-01-02Thanks!
111TYPO

In the section “One For All”, the text below immediately refers to “all-for-one”. I think this is a mistake and should also be one-for-all.

2018-01-02Thanks!
27SUGGEST

$ cd islands_engine
in mobi file there are visible some strange dots around spaces

2018-01-02Thanks!
79TYPO

Third paragraph from the bottom. “GenServer.start_link/3 three triggers” should be “GenServer.start_link/3 triggers”.

2018-01-02Thanks!
92TYPO

Half way down the page there’s a list describing what the with needs to check. In the third item of that list “whether it forested and island” should be “whether it forested an island”

2018-01-02Thanks!
110TYPO

Sixth paragraph down, “when starting child process” should either be “when starting a child process” or “when starting child processes”.

2018-01-02Thanks!
117ERROR

In the code for the start/2 function you have the line import Supervisor.Spec, warn: false. This is not in my version of this function. For reference, I’m on Elixir 1.5.2.

2018-01-02Thanks!
140ERROR

In the 5th paragraph it says that the callback module is in “/lib/islands_engine.ex”. However it is in “/lib/islands_engine/application.ex”.

2018-01-02Thanks!
194ERROR

In the Phoenix installation notes it says to use “mix archive.install” with a URL ending in “phoenix_new.ez” in order to have access to the mix phx.new command. However the URL end in “phx_new.ez” instead.

2018-01-02Thanks!
12TYPO

Context: second-to-last full paragraph on page, last sentence.

Problem: “i” should be capitalized.

“He also spoke to the community on behalf of the book at times when i could
not.”

2018-01-02Thanks!
150ERROR

This is related to bug #82296, but if you install phoenix using the instructions in the book then IslandsEngine will not start properly without adding it to the applications list, because that wasn’t fixed until phoenix 1.3, which is not the version installed by the instructions in the book.

2018-01-02Thanks!
5035TYPO

at the top of the page
“Because of that, each guess could match at most one coordinate.”
seems that it should be
“Because of that, each guess could match at most one island.”

2018-01-02Thanks!
xviiSUGGEST

On pages xvii and xviii, use of commas after chapter titles is inconsistent. For example:

“In Chapter 2, Model Data and Behavior, on page 9, we’ll …”
“In Chapter 4, Wrap It Up in a GenServer, on page 65 we’ll …”

1TYPO

There are only two clauses in “We’re about to go exploring, and it’s going to be a blast.”,
so I don’t think the comma is needed.

5SUGGEST

In “The players can move the islands around as much as they like until they say that they are set.”, the multiple uses of “they” are distracting and arguably ambiguous. I’d change this to something such as “The players can move their islands around as much as they like until they are satisfied.”

10SUGGEST

I’d change “It frees us to focus …” to “This frees us to focus …”

11SUGGEST

The reuse of the word “end” seems awkward here: “In the end, we end up …”.

12SUGGEST

I’d explain the use of the term “engine”.

13SUGGEST

The board images have poor contrast between blue and green (at least on my printer’s output). Be sure that the published book does not have this problem. Also, in the electronic version, I’d
also present the boards as textual tables, to make them accessible to the blind.

13TYPO

In “… three groups of coordinates, guessed coordinates …”, the comma should be a colon:
“… three groups of coordinates: guessed coordinates …”.

2018-01-22
14SUGGEST

I’d change “We should be careful …” to “However, we should be careful …”.

15SUGGEST

I’d rewrite “Make sure that you define enforce_keys before defstruct—otherwise it won’t have any effect, and you’ll get a warning saying that enforce_keys was defined but never used.” as two sentences: “Make sure that you define enforce_keys before defstruct. Otherwise, it won’t have any effect and you’ll get a warning saying that enforce_keys was defined but never used.”.

16SUGGEST

Page 14 says “The first thing we’ll need is a Coordinate module that aliases itself.” Also, the code for the Coordinates and Guesses modules each include the line “alias MODULE”.

However, the following iex sessions explicitly alias each of these modules. Why?

18SUGGEST

I’d change “Once that rebinding happens, the original value will fall out of scope and be garbage collected.” to “Once that rebinding happens, the original value will fall out of scope and be garbage collected (unless another part of the process is still using it).”.

18SUGGEST

I’d change “Now let’s add coordinate2 to the :hits set as well:” to “Now let’s add coordinate2 to the :hits set, as well:”.

18SUGGEST

I’d change “It kept the set unique.” to “MapSet kept the set unique.”.

19SUGGEST

I’d change “Let’s create new Island module at …” to “Let’s create a new Island module at …”.

101ERROR

GenServer callback for :position_island does not handle {:error, :overlapping_island} in the with clause.

As the reply is simply an echo of the error, the error handle could be simplified as follows:

with …..
do
….
else
error -> {:reply, error, state}
end

94SUGGEST

Rather than creating a new instance of Rules in IslandsEngine.Game.init/1 with %Rules{}:
{:ok, %{player1: player1, player2: player2, rules: %Rules{}}}
use the IslandsEngine.Game.new/0 function instead:
{:ok, %{player1: player1, player2: player2, rules: Rules.new()}}

15ERROR

test

2019-02-01
15TYPO

test

2019-02-01
92ERROR

when calling :sys.replace_state, the parameter for the anonymous function (data) does not correspond to anything in the function body

suggest changing the data parameter to state_data to read as:

iex> state_data = :sys.replace_state(game, fn state_data ->
…> %{state_data | rules: %Rules{state: :player1_turn}}
…> end)

174ERROR

with Elixir 1.6, the call to String.to_existing_atom(island) where island = “atoll” fails as:

[debug] INCOMING “position_island” on “game:moon” to IslandsInterfaceWeb.GameChannel
Transport: Phoenix.Transports.WebSocket
Parameters: %{“col” => 1, “island” => “atoll”, “player” => “player2”, “row” => 1}
[error] GenServer #PID<0.453.0> terminating

(ArgumentError) argument error
:erlang.binary_to_existing_atom(“atoll”, :utf8)

in GameChannel, as a hack workaround, I alias IslandsEngine.Island and call Island.types() in the :ok branch of handle_in(“new_game”)

note: the earlier call to String.to_existing_atom(player) succeeds

14ERROR

Thank you for the book. I noticed that you are using the final placement of coordinate.ex here as a reference that includes model_data in the file path.

Since that directory hasn’t been mentioned, it was a little confusing at first. It might be worth a mention or to just use the lib root that was just created.

160ERROR

Template file path is wrong.

/islands_interface/web/templates/page/index.html.eex

maybe this should

/islands_interface/lib/islands_interface_web/templates/page/index.html.eex

171ERROR

Typo : JS command does not work
> new_game(game)

Ought to be :
> new_game(game_channel)

19ERROR

The first code snippet does not compile. It’s missing an “alias MODULE”. Either that expression should be added or “%IslandsEngine.Island{…}” should be used in the “new” function.

146ERROR

Adding a New Dependency…. “Only two steps are involved”

The first is to add :islands_engine to the compile-time deps function. That is shown correctly.

The second step, not described, is to add :islands_engine to the applications: list as follows:

def application do
[mod: {IslandsInterface, []},
applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext, :islands_engine]]
end

11SUGGEST

The output when running the command to create the new project is missing the file .formatter.exs that is created in latest versions of elixir.

16SUGGEST

The listing for the guesses.ex (when clicking on the link) has the following for alias:
alias IslandsEngine.{Coordinate, Guesses}
while the listing in the books has the following:
alias MODULE

114ERROR

This is a really good book. The topics you covered are exactly what I was seeking. Very well done separation of concerns - a good example of how to structure things right.

An errata(?):
In Starting and Stopping Child Processes the function in the book differs from the function in the online source. Maybe someone could double check this for me. Thanks!
———————-
Book:
def stop_game(name) do
Supervisor.terminate_child(MODULE, pid_from_name(name))
end
——————————-
Online Source:
def stop_game(name) do
:ets.delete(:game_state, name)
Supervisor.terminate_child(MODULE, pid_from_name(name))
end

After following the precluding instructions in the book neither is behaving as specified in the book, which is to result in the process being stopped with a return of :ok. The supervised child process is shown as still active using: Supervisor.count_children(GameSupervisor) and/or Supervisor.which_children(GameSupervisor).

Book results:
Using:
def stop_game(name) do
Supervisor.terminate_child(MODULE, pid_from_name(name))
end

Running mix compile.app (inside IslandsEngine.Mixfile)
Interactive Elixir (1.6.3) - press Ctrl+C to exit (type h() ENTER for help
)
iex(1)> alias IslandsEngine.{Game, GameSupervisor}
[IslandsEngine.Game, IslandsEngine.GameSupervisor]
iex(2)> {:ok, game} = GameSupervisor.start_game(“Cassatt”)
{:ok, #PID<0.118.0>}
iex(3)> via = Game.via_tuple(“Cassatt”)
{:via, Registry, {Registry.Game, “Cassatt”}}
iex(4)> Supervisor.count_children(GameSupervisor)
%{active: 1, specs: 1, supervisors: 0, workers: 1}
iex(5)> Supervisor.which_children(GameSupervisor)
[{:undefined, #PID<0.118.0>, :worker, [IslandsEngine.Game]}]
iex(6)> GameSupervisor.stop_game(“Cassatt”)
{:error, :simple_one_for_one}
iex(7)> Process.alive?(game)
true
iex(8)> GenServer.whereis(via)
nil
iex(9)>

Online Source results:
Using:
def stop_game(name) do
:ets.delete(:game_state, name)
Supervisor.terminate_child(MODULE, pid_from_name(name))
end

Running mix compile.app (inside IslandsEngine.Mixfile)
Interactive Elixir (1.6.3) - press Ctrl+C to exit (type h() ENTER for help
)
iex(1)> alias IslandsEngine.{Game, GameSupervisor}
[IslandsEngine.Game, IslandsEngine.GameSupervisor]
iex(2)> {:ok, game} = GameSupervisor.start_game(“Cassatt”)
{:ok, #PID<0.118.0>}
iex(3)> via = Game.via_tuple(“Cassatt”)
{:via, Registry, {Registry.Game, “Cassatt”}}
iex(4)> Supervisor.count_children(GameSupervisor)
%{active: 1, specs: 1, supervisors: 0, workers: 1}
iex(5)> Supervisor.which_children(GameSupervisor)
[{:undefined, #PID<0.118.0>, :worker, [IslandsEngine.Game]}]
iex(6)> GameSupervisor.stop_game(“Cassatt”)

(ArgumentError) argument error
(stdlib) :ets.delete(:game_state, “Cassatt”)
(islands_engine) lib/islands_engine/game_supervisor.ex:21: IslandsEngi
ne.GameSupervisor.stop_game/1
iex(6)> Process.alive?(game)
true
iex(7)> GenServer.whereis(via)
nil
iex(8)>

160ERROR

Really learning a lot from this book - I highly recommend it.

I don’t know that this is an actual error or that my browser is not setup quite right.
I am in the section “Establish a Client Connection” attempting to step through the client connection tests
shown in the book. I can’t seem to get past the first step. The Islands server is compiling and running fine.
When attempting to execute the javascript statement
var phoenix = require(“phoenix”) inside Chrome’s javascript console I get the following error:
———-
var phoenix = require(“phoenix”)
VM57:1 Uncaught ReferenceError: require is not defined
at :1:15
————
I am more of a server side developer, so I am having trouble trying to determine what the problem is here. It seems
that Chrome’s javascript implementation does not recognize the “require” function.

I would appreciate it if somebody could be kind enough to help me with this issue I am having.
Thanks in advance!

This is my environment:
Google Chrome is up to date
Version 65.0.3325.181 (Official Build) (64-bit)
OS X El Capitan Version 10.11.6
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Elixir 1.6.4 (compiled with OTP 20)
| => mix phoenix.new —version

Running mix loadconfig (inside IslandsInterface.Mixfile)

Running mix phoenix.new —version (inside IslandsInterface.Mixfile)
Phoenix v1.3.2

87ERROR

Top of page, in the with statement, missing line (board is not defined)

board <- player.board,

full statement should be

with {:ok, rules} <- Rules.check(state.rules, {:set_islands, player}),
board <- player.board,
true <- Board.all_islands_positionned?(board)

87ERROR

There is bug in my last bug report…

Please do not consider my last bug report.

72SUGGEST

After sending the message send(game, :first)

iex reports this as an [error] not [warn] and also the word message is missing from ‘received unexpected in handle_info/2: :first’ text.

47TYPO

“fine state machine” should probably be “finite state machine”

80SUGGEST

On the book the with keyword is in a different line than in the actual game.ex file. Similar to do and else clauses with the {:ok, rules} .. in a different line.

87SUGGEST

Last sentence of the page reads:
We’ll need to alias the Game and Rules modules …

Does the Rule module need to be aliased, as the following pages and examples work without aliasing it?

103SUGGEST

(EXIT from #PID<0.122.0>) evaluator process exited with reason: :kaboom

should be:

(EXIT from #PID<0.122.0>) shell process exited with reason: :kaboom

using elixir 1.6.4.

108SUGGEST

The output in iex after running Game.child_spec(“Kusama”)
(in Elixir 1.6.4) is as follows (no restart, shutdown, type information)

%{id: IslandsEngine.Game, start: {IslandsEngine.Game, :start_link, [“Kusama”]}}

139SUGGEST

The line:

IslandsEngine.Worker.start_link(arg)

is missing from the book, but it is generated in the code (Elixir 1.6.4).

160ERROR

scope “/”, IslandsInterface do

should be

scope “/”, IslandsInterfaceWeb do

162ERROR

[debug] Processing by IslandsInterface.PageController.test/2

should be

[debug] Processing by IslandsInterfaceWeb.PageController.test/2

169ERROR

located at lib/islands_interface_web/islands_interface_web.ex

should be

lib/islands_interface_web.ex

169ERROR

socket “/socket”, IslandsInterface.UserSocket

should be

socket “/socket”, IslandsInterfaceWeb.UserSocket

170ERROR

web/channels/user_socket.ex

should be

lib/islands_interface_web/channels/user_socket.ex

159ERROR

defmodule IslandsInterface.UserSocket do

should be

defmodule IslandsInterfaceWeb.UserSocket do

160ERROR

iex> c “lib/islands_interface_web/channels/game_channel.ex”

should be

iex> c “lib/islands_interface_web/game_channel.ex”

149TYPO

path `islands_interface/web/` should be `islands_interface_web/` in the file paths for templates, router.ex & controllers

146ERROR

[Location 5855 in Kindle version; under “Adding a New Dependency”]

I added ‘{​: islands_engine​, ​ path: ​ ​“​../islands_engine”​}’ to ‘deps/0’. But when I run ‘iex -S mix phx.server’ in ‘/lib/islands_interface’, I get the error message:

Could not compile :islands_engine, no “mix.exs”, “rebar.config” or “Makefile” (pass
:compile as an option to customize compilation, set it to “false” to do nothing)
Unchecked dependencies for environment dev:

  • islands_engine (../islands_engine)
    could not find an app file at “_build/dev/lib/islands_engine/ebin/islands_engine.
    app”. This may happen if the dependency was not yet compiled, or you specified the
    wrong application name in your deps, or the dependency indeed has no app file (then
    you can pass app: false as option)
    • (Mix) Can’t continue due to errors on dependencies

This file does exist, but in the ‘/_build’ for :islands_engine at the root level. ‘/islands_engine’ within the :islands_interface build is empty. See repo: on GitHub, English3000/Elixir/tree/master/functional_design

Commenting out this line, the islands_interface is able to startup. (I also tried doing the other errata for this page too, but that didn’t work either.)

246SUGGEST

Figured it out.

I created the islands_interface inside islands_engine’s lib. I thought it was an umbrella project.

I’d suggest tweaking the language to make crystal clear to navigate OUTSIDE the ENTIRE islands_engine project.

Current language:

Let’s change out of the the (sic) islands_engine into its parent directory. That way, when we run the phx.new task, we’ll create the interface directory parallel to the engine directory.

Lance Halvorsen. Functional Web Development with Elixir, OTP, and Phoenix (Kindle Locations 5708-5710). The Pragmatic Bookshelf, LLC.

Suggested:

Let’s navigate outside of our islands_engine project. “That way, when we run the phx.new task, we’ll create the interface directory parallel to the engine directory.”

The mixup is that there are 2 islands_engine folders in the project (one inside lib, one as the root folder). And readers may be in the one inside the lib, hence my mixup.

185ERROR

The call to String.to_existing_atom here fails. For some reason, the atoms for the island types are not initialized when the app starts.

187ERROR

With Phoenix 1.4, It’s not possible to return the board in the reply. Jason.Encoder throws an error. The only way to get it to work was sending the board inspected, but it looks really ugly on the browser client.

175SUGGEST

The in-browser javascript console commands in this section have caused some confusion.

Rather than:
var phoenix = require(“phoenix”)

Which doesn’t work in Chrome. Maybe just start out using:

var socket = new Phoenix.Socket(“/socket”, {})

Which does and the reader can then follow on from there.

176ERROR

Just a clarification on my previous suggestion Hopefully this is of use to someone in future.

Suggested commands for the “Establish a Client Connection” section:

var socket = new Phoenix.Socket(“/socket”, {params: {token: window.userToken}})

socket.connect()

function new_channel(subtopic, screen_name) {
return socket.channel(“game:” + subtopic, {screen_name: screen_name});
}

var game_channel = new_channel(“moon”, “moon”)

function join(channel) {
channel.join()
.receive(“ok”, response => {
console.log(“joined sucessfully!”, response)
})
.receive(“error”, response => {
console.log(“unable to join”, response)
})
}

join(game_channel)

185ERROR

To get around the problem described above, with the islands’ atom names not being initialized, you can update the islands_engine\\lib\\islands_engine\\islands.ex file like so…

Add the following module param:
@island_types [:atoll, :dot, :l_shape, :s_shape, :square]

Then, update the types() function to the following:
def types(), do: @island_types

This worked for me… hope it helps someone else.

185SUGGEST

Nevermind my previous workaround… Gary Fleshman’s suggestion is probably a better way to hack this.

177SUGGEST

Phoenix 1.4 comes with a new JSON parser - “Jason”. You’ll get an error if you will try to return a board in response:

{:reply, {:ok, %{board: board}}, socket}

I found two ways to fix it. The first one is sending the inspected board as we did it with tuples before: “inspect(board)”. But it won’t return a JSON object, just a string. The second one is much better. It allows us to get a standard JSON object.

You need to add a code bellow to solve the issue (I’ve put it in “lib/islands_interface_web/channels/protocol.ex”, but it’s up to you):

#==
require Protocol

alias IslandsEngine.{Coordinate, Island}

Protocol.derive(Jason.Encoder, Coordinate)
Protocol.derive(Jason.Encoder, Island)

defimpl Jason.Encoder, for: [MapSet] do
def encode(struct, opts) do
Jason.Encode.list(Enum.to_list(struct), opts)
end
end
#==

190ERROR

I am reading the book at learning.oreilly.com, so don’t know exact page number. The issue is at “Connect the Channel to the Game” of Chapter 7.

When I input “set_islands(game_channel, ”player2“)” in the second browser(player2), iex returns quite long error saying.
’’’
[error] GenServer #PID<0.655.0> terminating

(Protocol.UndefinedError) protocol Jason.Encoder not implemented for %IslandsEngine.Island{coordinates: #MapSet<[%IslandsEngine.Coordinate{col: 1, row: 1}, %IslandsEngine.Coordinate{col: 1, row: 3}, %IslandsEngine.Coordinate{col: 2, row: 1}, %IslandsEngine.Coordinate{col: 2, row: 2}, %IslandsEngine.Coordinate{col: 2, row: 3}]>, hit_coordinates: #MapSet<[]>} of type IslandsEngine.Island (a struct), Jason.Encoder protocol must always be explicitly implemented.

If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:

@derive {Jason.Encoder, only: [….]}
defstruct …
’’’
I need your opinion to solve this issue.

PS> there is another issue. “player = String.to_existing_atom(player)” in “handle_in(”set_islands“, player, socket)” function of “game_channel.ex” needs an existing atom. So, I hard coded “String.to_atom(”player1“)” before it. Anyway, solved.

0TYPO

on oreilly.com so no page number
chapter 4, Naming GenServer Processes

Go ahead and add {Registry, keys: :unique, name: Registry.Game)} to that list:

there is a stray closing parenthesis in that instruction.

0ERROR

on oreilly books

Chapter 7
Phoenix Presence

application.ex

where does the supervisor function come from?

children = [
​ \t supervisor(IslandsInterfaceWeb.Endpoint, []),
​ \t supervisor(IslandsInterfaceWeb.Presence, []),
​ \t]

compiling this results in an exception.

160SUGGEST

Browser does not support “require(”phoenix") since Require is Node-only. There is a workaround: put it on the window directly.

Example: in your app.js put the following:

— example start
var phoenix = require(“phoenix”)
var socket = new phoenix.Socket(“/socket”, {})

window.kconnect = () => socket.connect()

window.kprint = (str) => console.log(str)

window.new_channel = (subtopic, screen_name) => {
return socket.channel(“game:” + subtopic, {screen_name: screen_name})
}

window.join = (channel) => {
channel.join()
.receive(“ok”, resp => {
console.log(“Joined successfully!”, resp)
})
.receive(“error”, err => {
console.log(“Unable to join”, err)
})
}

window.leave = function(channel) {
channel.leave()
.receive(“ok”, resp => console.log(“left successfully”, resp))
}

— example end

Then you call kconnect, make a new channel and store it in a variable, and call join with the channel object as an argument.

This isn’t obvious with the code in the book. Maybe webpack stopped supporting “require” calls or something. Maybe it never did support that.

Categories: