現在我們要讓玩家訂閱遊戲的狀態
並讓遊戲在狀態更新的時候,廣播到雙方的畫面
先來弄訂閱,我們在邀請頁面已經用過 id 來當作訂閱的頻道名稱了,
我們來改一下好了,幫 id 前面加個 invite_
,id 留給遊戲用
# lib/card/room.ex 檔案
def subscribe(id) do
Phoenix.PubSub.subscribe(Card.PubSub, "invite" <> id)
end
def broadcast(id, room) do
Phoenix.PubSub.broadcast(Card.PubSub, "invite" <> id, room)
end
幫他們前面加invite
接著在 lib/card/game.ex 加一下類似的方法
def subscribe(id) do
Phoenix.PubSub.subscribe(Card.PubSub, id)
end
def broadcast(id, game) do
Phoenix.PubSub.broadcast(Card.PubSub, id, game)
end
在 game 的 mount 方法訂閱,就像我們在邀請頁面做的一樣
def mount(%{"id" => id} = _params, _session, socket) do
# 訂閱
if connected?(socket), do: Card.Game.subscribe(id)
pid = Card.Dealer.find_or_create_game(id)
game = Game.status(pid)
{:ok, assign(socket, %{pid: pid, id: id, game: game})}
end
與相對應的 handle_info
方法
def handle_info(game, socket) do
{:noreply, assign(socket, :game, game)}
end
接著我們要回到原本的遊戲程式 lib/card/game.ex
在有更新遊戲狀態的時候 也廣播狀態到目前id
首先 game 在執行的時候要先知道他的 id 是什麼
我們也加在 game struct 好了
defstruct host: %{desk: [], hand: initial_hand, wins: 0},
guest: %{desk: [], hand: initial_hand, wins: 0},
turn: 1,
round: 1,
status: :start,
id: nil
並在開始遊戲的時候幫他加上
def start(id) do
GenServer.start_link(__MODULE__, %__MODULE__{id: id})
end
接著是 發牌員 建立遊戲的時候要把 id 帶給他
defmodule Card.Dealer do
use GenServer
def init(_) do
{:ok, []}
end
def handle_call(id, _from, games) do
case :ets.lookup(:games, id) do
[{^id, pid}] -> {:reply, pid, games}
[] ->
{:ok, pid} = Card.Game.start(id)
:ets.insert(:games, {id, pid})
{:reply, pid, [id | games]}
end
end
最後在遊戲裡面,有改變 game 的時候,像是成功出牌
就要廣播給大家知道,
這邊我偷吃步直接加在出牌的事件串
def play_card_with_checks(%{turn: current_turn} = game, player, card) do
game
|> play_card_for(player, card)
|> end_turn()
|> add_wins()
|> end_round()
|> end_game()
|> start_turn_timer(current_turn)
|> maybe_broadcast(game)
end
defp maybe_broadcast(game, old_game) when game == old_game, do: game
defp maybe_broadcast(game, _old_game) do
broadcast(game.id, game)
game
end
完成了,遊戲開始的時候
會自動照著遊戲的狀態反應