iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Modern Web

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

7 重做 Game struct 與 出牌方法

昨天的進度

defmodule Game do
  defstruct rounds: [], host_hand: [], guest_hand: [], round: 1, winner: nil

  use GenServer

  def init(status) do
    {:ok, status}
  end

  def handle_call(:get_status, _from, status) do
    {:reply, status, status}
  end
end

補上 client 方法

我們今天補上 client 這邊的方法
來取代 GenServer.start_link(Game, %Game{})
GenServer.call(game_pid, :get_status)
我們一樣放在 Game module 裡面

defmodule Game do
# ...上略
  def handle_call(:get_status, _from, status) do
    {:reply, status, status}
  end

  # Client 方法

  def start do
    GenServer.start_link(__MODULE__, %Game{})
  end

  def status(game_pid) do
    GenServer.call(game_pid, :get_status)
  end
end

在 start 方法裡面我們使用了 __MODULE__,這個是指方法所在的 module 在這邊即是 Game
我們再來 iex 試試看吧

$ iex game.ex
iex(1)> {:ok, pid} = Game.start
{:ok, #PID<0.115.0>}
iex(2)> Game.status(pid)
%Game{guest_hand: [], host_hand: [], round: 1, rounds: [], winner: nil}

看起來都正常

馬上後悔前天的決定

在我試做出牌方法的時候,我發現有些東西不太妥當,尤其是 Game struct,原本的結構超爛,
變得很難用非常直覺的方式去更改場上的卡片

我們來更新一下,在我們還沒使用到任何邏輯的情況,這次是無痛升級

defmodule Game do
  initial_hand = [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn]
  defstruct host: %{desk: [], hand: initial_hand, wins: 0},
            guest: %{desk: [], hand: initial_hand, wins: 0},
            turn: 1, round: 1
  # 以下省略

這個作法我們也不需要另一個 Round struct,可以把它整個移除

接著是 在各個 GenServer 方法內,我想把 status 這個變數改成 game
畢竟那個變數永遠都會是 Game struct 就直接用遊戲本身比較貼切
於是變成這樣:

  # 上略
  def init(game) do
    {:ok, game}
  end

  def handle_call(:get_status, _from, game) do
    {:reply, game, game}
  end

  def start do
    GenServer.start_link(__MODULE__, %Game{})
  end

  def status(pid) do
    GenServer.call(pid, :get_status)
  end
end

出牌!

我們先來看看出一張牌需要改什麼
原本的 game 是這樣

%Game{
  guest: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  host: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  round: 1,
  turn: 1
}

假如 host 出一張 1 ,那我們要做的事情有

  1. 從 hand 裡面拿掉一張 1
  2. 把 1 加在 host 的 desk 裡面
  3. 組成新的 game 來替換舊的

這次我們用 handle_cast 來做,
並且按步驟一步一步做做看

  def play_card(pid, :host, card) do
    GenServer.cast(pid, {:play_card, :host, card})
  end

  def handle_cast({:play_card, :host, card}, game) do
    %{host: host} = game

    # 從 hand 裡面拿掉一張 1 
    new_hand = host.hand -- [card]

    # 把 1 加在 host 的 desk 裡面
    new_desk = host.desk ++ [card]

    # 組成新的 host 來替換舊的
    new_host = Map.merge(host, %{hand: new_hand, desk: new_desk})

    # 組成新的 game 來替換舊的
    new_game = Map.replace(game, :host, new_host)

    # 讓 process 使用新的 game
    {:noreply, new_game}
  end

我們來用 iex 驗證看看

$ iex game.ex

iex(1)> {:ok, pid} = Game.start
{:ok, #PID<0.112.0>}
iex(2)> Game.status pid
%Game{
  guest: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  host: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  round: 1,
  turn: 1
}
iex(3)> Game.play_card pid, :host, 1                 
:ok
iex(4)> Game.status pid             
%Game{
  guest: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  host: %{desk: [1], hand: [1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  round: 1,
  turn: 1
}

在我們執行完出牌後,host 的桌面多了 1 ,手牌少了一張 1 , 正是我們要的,
但是我們還沒有考慮到 guest 的情況,也寫得比較不像 elixir 的做法,
下一篇就來稍微重構一下出牌方法。


上一篇
6 用 GenServer 做 server?
下一篇
8 稍微重構一下下,一點就好
系列文
連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)32

尚未有邦友留言

立即登入留言