# lib/tic_tac_toe/game_supervisor.ex defmodule TicTacToe.GameSupervisor do use Supervisor def start_link do Supervisor.start_link(__MODULE__, nil, name: __MODULE__) end def start_child(name) do Supervisor.start_child(__MODULE__, [name]) end def init(_) do children = [ worker(TicTacToe.Game, [], restart: :temporary) ] supervise(children, strategy: :simple_one_for_one) end end
# lib/tic_tac_toe/registry.ex defmodule TicTacToe.Registry do use GenServer def start_link do GenServer.start_link(__MODULE__, nil, name: __MODULE__) end def init(_) do {:ok, nil} end end
# lib/tic_tac_toe/registry.ex def game_process(name) do case TicTacToe.Game.whereis(name) do :undefined -> GenServer.call(__MODULE__, {:game_process, name}) pid -> pid end end def handle_call({:game_process, name}, _from, state) do game_pid = case TicTacToe.Game.whereis(name) do :undefined -> {:ok, pid} = TicTacToe.GameSupervisor.start_child(name) pid pid -> pid end API (client process) Callback (server process)
Start Game # lib/tic_tac_toe/game.ex defmodule TicTacToe.Game do use GenServer def start_link(name) do GenServer.start_link(__MODULE__, nil, name: via_tuple(name)) end end
Leave Game # lib/tic_tac_toe/game.ex def handle_call({:leave, symbol}, _from, state) do new_state = state |> remove_player(symbol) |> reset_score() |> reset_board() if empty?(new_state) do {:stop, :normal, new_state} else {:reply, new_state, new_state} end end
# lib/tic_tac_toe/board.ex def put(board, symbol, pos) when symbol in @symbols do case Enum.at(board.data, pos) do nil -> data = List.replace_at(board.data, pos, symbol) {:ok, %TicTacToe.Board{board | data: data}} _ -> :error end end def put(_board, _symbol, _pos) do :error end Put Symbol
# web/controllers/player_controller.ex defmodule Demo.PlayerController do use Demo.Web, :controller plug :scrub_params, "player" when action in [:create] def new(conn, _params) do render conn, "new.html" end def create(conn, %{"player" => player_params}) do player = Map.get(player_params, "name", "Anonymous") conn |> Demo.Auth.login(player) |> redirect(to: game_path(conn, :new)) end def delete(conn, _params) do conn |> Demo.Auth.logout() |> redirect(to: player_path(conn, :new)) end end
# web/channels/user_socket.ex defmodule Demo.UserSocket do use Phoenix.Socket channel "game:*", Demo.GameChannel def connect(%{"player" => player}, socket) do {:ok, assign(socket, :player, player)} end end
# web/static/app.js import {Socket} from "phoenix" import Game from "./game" let socket = new Socket( "/socket", {params: {player: window.currentPlayer}} ) let element = document.getElementById("game") if (element) { Game.init(socket) }