games.riesd.com hosts online games that have an open API so you can easily write a game client and have it play against an opponent. The the site is written in elixir and uses phoenix and channels.
Opponents can be:
- A human using the web interface
- A hosted AI
- Some other program using a websocket
Tonight we are going to write an elixir program that opens a websocket and plays a game of tic-tac-toe.
We'll start the project by running mix new toelixir
.
This will create a skeleton mix project which we will use.
cd
into the project directory and run mix test
to make sure that your project is working correctly.
You should see one test pass. 🎉⚡💥
We will use an erlang library to handle our websockets for us.
websocket_client is an excellent erlang project for making websocket connections.
It hasn't been published to hex.pm yet so we will use it directly from github.
Update your mix.exs
file to have use this dependency as seen below.
def deps do
[
{:websocket_client, git: "https://github.com/sanmiguel/websocket_client.git", branch: "master"},
]
end
Now run mix deps.get
and mix compile
to download the erlang library and compile it in your project.
The example from websocket_client's README file is in erlang.
So to start our project we will copy and paste the example code into our lib/toelixir.ex
file and start translating it into Elixir.
This example connects to a public websocket server that just echoes back whatever you send to it.
If you get behind during this part of the workshop you can grab a copy of the finished translation from this gist. See the 02.toelixir.ex
file below.
Now startup an iex session with the command iex -S mix
and you can run this code like this:
iex(1)> {:ok, pid} = Toelixir.start_link
{:ok, #PID<0.127.0>}
connected! time to send the first message
Received text! "message 1" (2)
Received text! "hello, this is message 2" (3)
Received text! "hello, this is message 3" (4)
Received text! "hello, this is message 4" (5)
iex(2)>
Now that we have a proof of concept under our belts we can see what sorts of things we can do with this websocket_client
library.
Let's use this knowledge to write up a super basic websocket client to connect to the game server and allow us to send erlang messages that in turn get forwarded to the game server.
This way we can play a game of tic tac toe from iex.
- If you get behind during this part of the workshop you can grab a copy of the completed code from this gist. See the
03.toelixir.ex
file below.*
Now we can talk to our websocket from iex. This is a good time to review the tic-tac-toe tutorial. Below you can see an example of a full game played out over iex.
iex(1)> {:ok, pid} = Toelixir.start_link
{:ok, #PID<0.127.0>}
connected! time to send the first message
iex(2)> join = %{topic: "tictactoe:1", event: "phx_join", ref: 1, payload: %{token: "me", name: "me"}}
%{event: "phx_join", payload: %{name: "me", token: "me"}, ref: 1,
topic: "tictactoe:1"}
disconnected because {:remote, :closed}
connected! time to send the first message
iex(3)> send pid, {:send, join}
sending %{event: "phx_join", payload: %{name: "me", token: "me"}, ref: 1, topic: "tictactoe:1"}
{:send,
%{event: "phx_join", payload: %{name: "me", token: "me"}, ref: 1,
topic: "tictactoe:1"}}
Received text! "{\"topic\":\"tictactoe:1\",\"ref\":1,\"payload\":{\"status\":\"ok\",\"response\":{\"role\":\"O\"}},\"event\":\"phx_reply\"}" (1)
Received text! "{\"topic\":\"tictactoe:1\",\"ref\":null,\"payload\":{\"winner\":null,\"whose_turn\":\"X\",\"board\":[null,null,null,null,null,null,null,null,null]},\"event\":\"state\"}" (1)
Received text! "{\"topic\":\"tictactoe:1\",\"ref\":null,\"payload\":{\"winner\":null,\"whose_turn\":\"O\",\"board\":[null,null,null,null,null,null,null,null,\"X\"]},\"event\":\"state\"}" (1)
iex(4)> send pid, {:send, %{topic: "tictactoe:1", event: "move", ref: 2, payload: %{token: "me", square: 0}}}
sending %{event: "move", payload: %{square: 0, token: "me"}, ref: 2, topic: "tictactoe:1"}
{:send,
%{event: "move", payload: %{square: 0, token: "me"}, ref: 2,
topic: "tictactoe:1"}}
Received text! "{\"topic\":\"tictactoe:1\",\"ref\":null,\"payload\":{\"winner\":null,\"whose_turn\":\"X\",\"board\":[\"O\",null,null,null,null,null,null,null,\"X\"]},\"event\":\"state\"}" (1)
Received text! "{\"topic\":\"tictactoe:1\",\"ref\":null,\"payload\":{\"winner\":null,\"whose_turn\":\"O\",\"board\":[\"O\",null,\"X\",null,null,null,null,null,\"X\"]},\"event\":\"state\"}" (1)
iex(5)> send pid, {:send, %{topic: "tictactoe:1", event: "move", ref: 2, payload: %{token: "me", square: 6}}}
sending %{event: "move", payload: %{square: 6, token: "me"}, ref: 2, topic: "tictactoe:1"}
{:send,
%{event: "move", payload: %{square: 6, token: "me"}, ref: 2,
topic: "tictactoe:1"}}
Received text! "{\"topic\":\"tictactoe:1\",\"ref\":null,\"payload\":{\"winner\":null,\"whose_turn\":\"X\",\"board\":[\"O\",null,\"X\",null,null,null,\"O\",null,\"X\"]},\"event\":\"state\"}" (1)
Received text! "{\"topic\":\"tictactoe:1\",\"ref\":null,\"payload\":{\"winner\":\"X\",\"whose_turn\":null,\"board\":[\"O\",null,\"X\",null,null,\"X\",\"O\",null,\"X\"]},\"event\":\"game_over\"}" (1)
iex(6)>
Next step is to automate the sending of the messages. We'll leave the hardcoded token, topic and name for now. And we'll just focus on triggering the different behaviors at the right time.
- If you get behind during this part of the workshop you can grab a copy of the completed code from this gist. See the
04.toelixir.ex
file below.*
From here you should be able to improve the game client on your own. Here are a few suggestions:
- Pass in
topic
,token
,name
andai
as arguments to thestart_link
function - Improve the strategy for picking which square to play
- Write a mix task so we can kick off the game directly from the console
I'll leave a few browser windows open on the projector watching games utex1
, utex2
...
Feel free to join those games and do battle against the other attendees.