iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
Modern Web

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

9 結束這回合

今天我們可能可以來做一下回合,畢竟要算分數還是幹嘛都是建立在回和上面。

目前我們的 game struct 長這樣,已經有預留 round 跟 turn

%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
}

這次要做的事情有

  1. 雙方都出牌後,turn + 1
  2. 第 3 turn 結束後 turn 變回 1, round + 1

感覺這件事情是要放在出牌的方法裡面
雙方都出牌這個條件,應該是出完牌檢查兩個人的 desk 長度是不是一樣

  defp maybe_end_turn(%{guest: guest, host: host} = game) do
    if length(guest.desk) == length(host.desk) do
      Map.merge(game, %{turn: game.turn + 1})
    else
      game
    end
  end

elixir 在 module 定義 private 方法,就是只有在這個 module 裡面才可以呼叫的方法,是用 defp

我們在這個 maybe_end_turn 方法的變數收 game struct
方法的結果也是回傳 game struct
所以我們在 handle :play_card 方法裡面可以這樣子接

  def handle_cast({:play_card, :host, card}, %{host: host} = game) do
    game =
      game
      |> Map.replace(:host, play_card_helper(host, card))
      |> maybe_end_turn()

    {:noreply, game}
  end

這裡有一個新符號 |> 這個像三角形的東西叫 pipe operator ,可能要直接用範例比較好解釋

defmodule MyMath do
  def add_one(number) do
    number + 1
  end
end

今天我們有一個 add_one/1 方法,他收一個變數,並回傳變數加一為結果
假如我要用 3 次

MyMath.add_one(MyMath.add_one(MyMath.add_one(1)))

或是

first_add = MyMath.add_one(1)
second_add = MyMath.add_one(first_add)
result = MyMath.add_one(second_add)

這兩個寫法都讓人很煩躁,於是我們用 pipe operator 看看

1
|> MyMath.add_one()
|> MyMath.add_one()
|> MyMath.add_one()

|> 之後的方法的第一個變數,會自動填入 |> 左邊的結果
所以如果原本有兩個變數,串在 |> 裡面的時候,只需要填第二個。

因為這樣子寫的時候配合適當的方法名稱,讀起來會像句子

水餃皮
|> 加餡(水餃餡)
|> 包起來

懂 pipe 之後我們再回來看

  def handle_cast({:play_card, :host, card}, %{host: host} = game) do
    game =
      game
      |> Map.replace(:host, play_card_helper(host, card))
      |> maybe_end_turn()

    {:noreply, game}
  end
新的game =
  舊的game
  |> 出卡方法
  |> 回合方法

感覺出卡方法應該可以在變得更清楚一些,像是

  def handle_cast({:play_card, player, card}, game) do
    game =
      game
      |> play_card_for(player, card)
      |> maybe_end_turn()

    {:noreply, game}
  end

  defp play_card_for(game, player, card) do
    data =
      game
      |> Map.get(player)
      |> then(&Map.merge(&1, %{hand: &1.hand -- [card], desk: &1.desk ++ [card]}))

    Map.replace(game, player, data)
  end

  defp maybe_end_turn(%{guest: guest, host: host} = game) do
    if length(guest.desk) == length(host.desk) do
      Map.merge(game, %{turn: game.turn + 1})
    else
      game
    end
  end

整理完之後,在 handle_cast 裡面只留下比較好讀的方法名稱,
細部的做法都丟進 private 方法中。
也順便把兩個 handle_cast :play_card 合成一個。

最後來 iex 試試看
我們這次進 iex 之後先 import Game 這樣我們就可以直接呼叫 Game module 裡面的方法

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

成功了,雙方都出牌之後,turn 就 + 1 了

round 留到明天好了哈哈,掰掰


上一篇
8 稍微重構一下下,一點就好
下一篇
10 有局數就可以打分數
系列文
連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)32

尚未有邦友留言

立即登入留言