(整理)用Elixir作一個多人撲克遊戲 4

sockets 和 channels 是Phoenix中用來實現實時效果的兩大工具。git

Sockets

socket是用來鏈接客戶端與服務器的,它使用endpoint來聲明:github

defmodule GenPoker.Endpoint do
  use Phoenix.Endpoint, otp_app: :gen_poker

  socket "/socket", GenPoker.PlayerSocket
end

Channels

客戶端只有加入了channel以後才能發送消息。web

defmodule GenPoker.PlayerSocket do
  use Phoenix.Socket

  channel "tables:*", GenPoker.TableChannel
end

建立socket

defmodule GenPoker.PlayerSocket do
  use Phoenix.Socket

  transport :websocket, Phoenix.Transports.WebSocket

  def connect(%{"playerId" => player_id}, socket) do
    {:ok, assign(socket, :player_id, player_id)}
  end

  def id(socket) do 
    "players_socket:#{socket.assigns.player_id}"
  end
end

註冊進程

defmodule Poker.Table do
  use GenServer

  def start_link(table_name, sup, storage, num_seats) do
    GenServer.start_link(
      __MODULE__, 
      [table_name, sup, storage, num_seats], 
      name: via_tuple(table_name)
    )
  end

  defp via_tuple(table) do 
    {:via, :gproc, {:n, :l, {:table, table}}}
  end

  def whereis(table) do
    :gproc.whereis_name({:n, :l, {:table, table}})
  end
end

咱們使用了gproc庫來註冊進程,這樣就能夠使用一個term而不單單是atom做爲名字。讓咱們來定義Channel:服務器

module GenPoker.TableChannel do
  use GenPoker.Web, :channel
  alias Poker.Table

  def join("tables:" <> table, _payload, socket) do
    {:ok, assign(socket, :table, table)}
  end

  def handle_in(command, payload, socket) 
    when command in ~w(sit leave buy_in cash_out deal) 
  do
    table = Table.whereis(socket.assigns.table)
    arguments = [table, socket.assigns.player_id] ++ payload
    result = apply(Table, String.to_atom(command), arguments)
    if result == :ok do
      broadcast! socket, "update", Table.get_state(table)
    end
    {:reply, result, socket}
  end
end

對於客戶端的join請求,咱們有不一樣的回覆。在JavaScript中能夠這樣寫:websocket

channel.push("message", arguments)
  .receive("ok", (msg) => console.log("Got OK!"))
  .receive("error", (msg) => console.log("Oops!"))

發送初始的state

def join("tables:" <> table, _payload, socket) do
  state = table |> Table.whereis |> Table.get_state
  push socket, "update", state
  {:ok, assign(socket, :table, table)}
end

使用handle_infoapp

def join("tables:" <> table, _payload, socket) do
  send self, :after_join
  {:ok, assign(socket, :table, table)}
end

def handle_info(:after_join, socket) do
  state = socket.assigns.table |> Table.whereis |> Table.get_state
  push socket, "update", state
  {:noreply, socket}
end

def handle_info(_, socket) do
  {:noreply, socket}
end

攔截消息socket

def handle_out("update", state, socket) do
  push socket, "update", hide_other_hands(state, socket)
  {:noreply, socket}
end

defp hide_other_hands(state, socket) do
  player_id = socket.assigns.player_id
  hide_hand_if_current_player = fn
    %{id: ^player_id} = player -> player
    player -> Map.delete(player, :hand)
  end

  update_in(state.players, fn players ->
    Enum.map(players, hide_hand_if_current_player)
  end)
end

完整代碼ide

相關文章
相關標籤/搜索