iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0
Modern Web

連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)系列 第 24

24 讓畫面跟遊戲聯動

用 PubSub 更新遊戲狀態

現在我們要讓玩家訂閱遊戲的狀態
並讓遊戲在狀態更新的時候,廣播到雙方的畫面

先來弄訂閱,我們在邀請頁面已經用過 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

完成了,遊戲開始的時候
會自動照著遊戲的狀態反應

https://ppt.cc/fvu2Ex@.gif


上一篇
23 搞半天終於在網頁上啟動遊戲了
下一篇
25 把卡片擺一擺
系列文
連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言