Git repo: https://github.com/NatTuck/hangman-2019-01/
This section goes from branch "master" to "01-31-channel-hangman"
Remove the TODO header from lib/hangman_web/templates/page
Move todo.jsx to hangman.jsx
Fix app.js
Work in hangman.jsx
The game:
The game state:
Traditional HTTP provides request-response semantics.
One problems:
We could build Hangman with traditional HTTP requests. But if we wanted to add multiplayer, then there would be no way to show actions by other players without polling.
Solution: Websockets
Idea: A persistent stream layer (basically TCP) on top of HTTP.
Since a websocket connection is basically just a TCP connection, you need to have a protocol on top of it. Phoenix gives us one: Phoenix Channels.
$ mix phx.gen.channel games
Add to .../channels/user_socket.ex:
channel "games:*", HangmanWeb.GamesChannel
Open .../js/socket.js - scan through.
Add to head in .../layout/app.html.eex
<script>
window.userToken = "TODO";
</script>
Add a route, to .../hangman_web/router.ex
get "/game/:name", PageController, :game
Add a function to page_controller.ex
def game(conn, %{"name" => name}) do
render conn, "game.html", name: name
end
Copy the index.html.eex page to game.html.eex
Edit index.html.eex to this:
<div class="row">
<div class="column">
<h1>Roll a Die</h1>
<p><button id="roll-button">Roll</button></p>
<p id="roll-output"></p>
</div>
</div>
Edit app.js to add:
function roll_init() {
$('#roll-button').click(() => {
channel.push("roll", {}).receive("roll", msg => {
console.log("roll", msg);
$('#roll-output').text(msg.roll);
});
});
}
$(() => {
roll_init();
Edit games_channel.ex:
Edit games_channel.ex
def join("games:" <> name, payload, socket) do
if authorized?(payload) do
{:ok, %{"join" => name}, socket}
else
{:error, %{reason: "unauthorized"}}
end
end
def handle_in("roll", payload, socket) do
resp = %{ "roll" => :rand.uniform(6) }
{:reply, {:roll, resp}, socket}
end
end
Update index.html.eex:
<div class="row">
<div class="column">
<h1 id="index-page">Join a Game</h1>
<p><a href="/game/demo">Join "demo"</a></p>
</div>
</div>
page_controller.ex:
game.html.eex:
<script>
window.gameName = "<%= @name %>";
</script>
<div class="row">
<div class="column">
<h1>Hangman Game: <%= @name %></h1>
<div id="root">
<p>React app not loaded...</p>
</div>
</div>
</div>
app.js:
import socket from "./socket";
import game_init from "./hangman";
function start() {
let root = document.getElementById('root');
if (root) {
let channel = socket.channel("games:" + window.gameName, {});
// We want to join in the react component.
game_init(root, channel);
}
}
$(start);
Write attached code:
games_channel.ex:
alias Hangman.Game
def join("games:" <> name, payload, socket) do
if authorized?(payload) do
game = Game.new()
socket = socket
|> assign(:game, game)
|> assign(:name, name)
{:ok, %{"join" => name, "game" => Game.client_view(game)}, socket}
else
{:error, %{reason: "unauthorized"}}
end
end
def handle_in("guess", %{"letter" => ll}, socket) do
game = Game.guess(socket.assigns[:game], ll)
socket = assign(socket, :game, game)
{:reply, {:ok, %{ "game" => Game.client_view(game)}}, socket}
end
Then update the JSX code: