iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

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

10 有局數就可以打分數

昨天做到雙方都出卡後,增加 turn 數
現在在做 每到 3 turn 換一局 round + 1

昨天做到這邊

  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

再來一局

也許這個可以不要跟 turn 同一個方法
來寫一個 maybe_end_round 好了

  defp maybe_end_round(%{turn: turn} = game) when turn > 3 do
      Map.merge(game, %{turn: 1, round: game.round + 1})
  end
  defp maybe_end_round(game), do: game

這是什麼巫術,怎麼有 do: 然後沒有 end
先從為什麼要寫兩個 maybe_end_round 開始
還不習慣這個的時候可以會寫出

  defp maybe_end_round(%{turn: turn} = game) do
    if turn > 3 do
      Map.merge(game, %{turn: 1, round: game.round + 1})
    else
      game
    end
  end

我們先利用 when 把簡單的判斷拉到 pattern matching 方法層面的 guards
這樣子這個方法除了變數 match 之外,when 的條件也要符合,但 when 只能使用基本的方法 詳見 Guards 文件

拉出來之後方法可以變成兩個

  defp maybe_end_round(%{turn: turn} = game) when turn > 3 do
    Map.merge(game, %{turn: 1, round: game.round + 1})
  end

  defp maybe_end_round(game) do
    game
  end

再把只回傳 game 沒有做別的事的小方法改寫成一行,用 do:
至於為什麼可以這樣寫,牽扯到 macro
這個比較進階,我也還在摸索,但在這次應該是不會用到,
我們先記得有這個可以簡化單行方法就好

把他加在 handle_cast :play_card 後面

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

    {:noreply, game}
  end

在 iex 試試看

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)> play_card pid, :host, 1 
:ok
iex(6)> play_card pid, :guest, 2
:ok
iex(7)> play_card pid, :host, 1 
:ok
iex(8)> play_card pid, :guest, 5
:ok
iex(9)> status pid
%Game{
  guest: %{desk: [4, 2, 5], hand: [1, 1, 2, 3, 3, 6, :turn, :turn], wins: 0},
  host: %{desk: [3, 1, 1], hand: [2, 2, 3, 4, 5, 6, :turn, :turn], wins: 0},
  round: 2,
  turn: 1
}

成功

不可以偷出牌

另外還有就是,我們可能要限制一下,同一個人不能連續出牌,要等雙方都出完,完成一回合才能再往下,
這點也許後面會改,但是我們先把它做好來。

  def handle_cast({:play_card, :host, _card}, %{host: host, guest: guest} = game)
      when length(host.desk) > length(guest.desk),
      do: {:noreply, game}

  def handle_cast({:play_card, :guest, _card}, %{host: host, guest: guest} = game)
      when length(host.desk) < length(guest.desk),
      do: {:noreply, game}

這次我寫在原本的 handle_cast :play_card 前面來攔截,
假如 host 出牌,可是他牌桌上的牌數已經比對方多了,這代表他是連續出,所以就忽略這次動作,反之亦然
雖然我覺得沒有比起前面收成方法疊再一起,但先這樣吧

iex 試試看

iex(1)> import Game
Game
iex(2)> {:ok, pid} = start
{:ok, #PID<0.113.0>}
iex(3)> play_card pid, :host, 1
:ok
iex(4)> play_card pid, :host, 1
:ok
iex(5)> 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
}

完成拉

算個輸贏

我們有出卡,有回合,好像可以來算個輸贏,
今天先撇開特殊卡片,假如只出數字卡的情況。
在換 round 的時候,來算一下上一 round 的結果,
誰贏就在誰的 wins 裡面 + 1

  defp maybe_end_round(%{turn: turn} = game) when turn > 3 do
    game
    |> Map.merge(%{turn: 1, round: game.round + 1})
    |> compare_score(score(:host, game), score(:guest, game))
  end

這裡加一個 compare_score/3 收 game, host分數, guest分數
分數的部分也另外寫一個 socre 方法算

  # 這兩個方法用 when 裡面來判斷誰的點數大
  defp compare_score(game, host_score, guest_score) when host_score > guest_score,
    do: add_wins(game, :host)

  defp compare_score(game, host_score, guest_score) when host_score < guest_score,
    do: add_wins(game, :guest)

  defp add_wins(game, player) do
  # 這個方法幫獲勝者加一勝
    player_data =
      game
      |> Map.get(player)
      |> then(&Map.merge(&1, %{wins: &1.wins + 1}))

    Map.replace(game, player, player_data)
  end

  defp score(player, %{round: round} = game) do
    range_start = (round - 1) * 3
    range = range_start..(range_start + 2)
    
    # range 是依照回合要從桌上拿的卡片範圍
    # 1 局 就是從 desk 裡面利用 slice 方法拿 0..2 位置的卡片
    # 2 局 就是 3..5 位置的卡片,依此類推

    game
    |> Map.get(player)
    |> Map.get(:desk)
    |> Enum.slice(range)
    |> Enum.sum() #最後用 sum 計算總和,回傳分數
  end

好像離可以玩的狀態,好拉在 iex 可以玩的狀態不遠了。
再撐一下,這個核心的地方完成之後,我們做介面會比較放心。


上一篇
9 結束這回合
下一篇
11 發動迴轉卡!
系列文
連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言