iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Modern Web

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

14 實作出牌倒數 誒這是什麼放置遊戲

實作出牌倒數計時

ok, 我們有兩個地方需要開始計時
第一個是遊戲開始的時候,
第二個是 turn 結束的時候。

  def init(game) do
    Process.send_after(self(), {:times_up, :host, 1, 1}, 3000) # 遊戲開始的時候,開始倒數
    Process.send_after(self(), {:times_up, :guest, 1, 1}, 3000) # 一人一個
    {:ok, game}
  end

Process.send_after/3 的第一個變數是收 pid,
我們可以用 self() 來得到目前執行這個地方本身的 pid。
為了等一下好測試,我們先用 3 秒,而不是原本要的 30

再來是時間到了之後要執行的 handle_info :times_up

執行的時候要做的事情有

  1. 檢查目前的遊戲狀態還是不是同個回合(turn):
    因為就算玩家出牌,這個超時自動出牌的要求也不會自動取消
    因此要在這邊判斷,如果回合已經換了,就是玩家有正常出牌,就不需要執行自動出牌
  2. 從玩家手牌中隨機抽一張牌
  3. 代替玩家出牌,並執行正常的出牌程序
  4. 出牌後,為新回合執行出牌倒數

我直接按照列表直接做,變超長的,我把項目標示在下方註解裡

  def handle_info(
        {:times_up, player, round, turn},
        %{round: current_round, turn: current_turn} = game
      )
      when round == current_round and turn == current_turn do
      # 這個 guard 用來實現 1.檢查目前的遊戲狀態還是不是同個回合

      # 2.隨機抽牌
    card =
      game
      |> Map.get(player)
      |> Map.get(:hand)
      |> Enum.random()

    # 我們暫時在這邊用 IO.puts 來印出訊息,待會在 iex 跑的時候比較知道在跑啥
    IO.puts("time's up, play #{card} for #{player}")

    # 3.代替玩家出牌
    # 這個是直接從原本的出牌方法 handle_cast :play_card 複製過來的
    game =
      game
      |> play_card_for(player, card)
      |> end_turn()
      |> add_wins()
      |> end_round()
      |> end_game()

    # 4. 出牌後,為新回合執行出牌倒數
    # 這邊判斷如果 遊戲還在進行,而且這次出牌有導致 換到新的 turn
    if game.status == :start && current_turn != game.turn do
      Process.send_after(self(), {:times_up, :host, game.round, game.turn}, 3000)
      Process.send_after(self(), {:times_up, :guest, game.round, game.turn}, 3000)
    end

    {:noreply, game}
  end

  # 如果玩家有出過牌了,就忽略
  def handle_info({:times_up, _player, _round, _turn}, game), do: {:noreply, game}

完成了,使用 GenServer 真的讓這件事情簡單很多

印出一些回饋來看遊戲情況

除了上面在幫玩家出牌的時候有印出 幫誰出什麼牌之外
在 add_wins 方法,我也暫時用 IO.puts 顯示誰贏一局,與目前比數

  defp add_wins(%{turn: turn, round: round, host: host, guest: guest} = game) when turn > 3 do
    range_start = (round - 1) * 3
    range = range_start..(range_start + 2)
    host_desk = Enum.slice(host.desk, range)
    guest_desk = Enum.slice(guest.desk, range)

    if apply_reverse(
         get_score(host_desk) > get_score(guest_desk),
         reverse?(host_desk ++ guest_desk)
       ) do
      IO.puts("? Host win this round, host: #{host.wins + 1}, guest: #{guest.wins}") # 顯示該局結果
      assign_to_player(game, :host, :wins, game.host.wins + 1)
    else
      IO.puts("? Guest win this round, host: #{host.wins}, guest: #{guest.wins + 1}") # 顯示該局結果
      assign_to_player(game, :guest, :wins, game.guest.wins + 1)
    end
  end

還有結束遊戲的地方也加上通知

  defp end_game(%{guest: %{wins: 2}} = game) do
    IO.puts "? winner is Guest"
    Map.replace(game, :status, :guest_win)
  end
  defp end_game(%{host: %{wins: 2}} = game) do
    IO.puts "? winner is Host"
    Map.replace(game, :status, :host_win)
  end
  defp end_game(game), do: game

來跑跑看吧,這次只要打一行,因為我們暫時把出牌時間設定成 3 秒,就好像遊戲自動玩一樣

iex(1)> Game.start
{:ok, #PID<0.112.0>}
time's up, play 3 for host
time's up, play reverse for guest
time's up, play 6 for host
time's up, play 3 for guest
time's up, play reverse for host
time's up, play 3 for guest
? Host win this round, host: 1, guest: 0
time's up, play reverse for host
time's up, play 1 for guest
time's up, play 5 for host
time's up, play reverse for guest
time's up, play 1 for host
time's up, play 5 for guest
? Host win this round, host: 2, guest: 0
? winner is Host

執行動圖
https://ppt.cc/fY35lx@.gif


上一篇
13 出牌倒數要怎麼做才好?
下一篇
15 修bug兼整理
系列文
連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)32

尚未有邦友留言

立即登入留言