原文併發
如今咱們已經作好了牌面大小的比較,遊戲的流程,但尚未作玩家登錄,人數限制,甚至沒有將獎金髮送給贏家。接下來,讓咱們來完成它們。fetch
玩家須要兌換遊戲中的籌碼才能開始遊戲,在當不在遊戲過程時,能夠兌換籌碼。code
咱們引入了兩個新的進程。blog
首先咱們將創建一個銀行,玩家能夠在這裏進行現金和籌碼的相互轉換。隊列
銀行GenServer會有兩個API, deposit/2 和withdraw/2:遊戲
defmodule Poker.Bank do use GenServer def start_link do GenServer.start_link(__MODULE__, [], name: __MODULE__) end def deposit(player, amount) do GenServer.cast(__MODULE__, {:deposit, player, amount}) end def withdraw(player, amount) do GenServer.call(__MODULE__, {:withdraw, player, amount}) end end
咱們用模塊名註冊了這個進程,這樣咱們就不須要知道它的pid,就能進行訪問了:進程
def init(_) do {:ok, %{}} end def handle_cast({:deposit, player, amount}, state) when amount >= 0 do { :noreply, Map.update(state, player, amount, fn current -> current + amount end) } end def handle_call({:withdraw, player, amount}, _from, state) when amount >= 0 do case Map.fetch(state, player) do {:ok, current} when current >= amount -> {:reply, :ok, Map.put(state, player, current - amount)} _ -> {:reply, {:error, :insufficient_funds}, state} end end
這段代碼就顯示了Elixir併發的威力,徹底避免了競態條件,由於是在同一個進程裏執行的,因此全部操做會以隊列來執行。ci
咱們須要同時進行許多局獨立的遊戲。在玩家入座以後,能夠兌換籌碼或現金:get
defmodule Poker.Table do use GenServer def start_link(num_seats) do GenServer.start_link(__MODULE__, num_seats) end def sit(table, seat) do GenServer.call(table, {:sit, seat}) end def leave(table) do GenServer.call(table, :leave) end def buy_in(table, amount) do GenServer.call(table, {:buy_in, amount}) end def cash_out(table) do GenServer.call(table, :cash_out) end end
下面來實現GenServer的init/1:it
def init(num_seats) do players = :ets.new(:players, [:protected]) {:ok, %{hand: nil, players: players, num_seats: num_seats}} end
咱們用ETS來保存玩家的信息。對於sit 消息,咱們是這樣處理的:
def handle_call( {:sit, seat}, _from, state = %{num_seats: last_seat} ) when seat < 1 or seat > last_seat do {:reply, {:error, :seat_unavailable}, state} end def handle_call({:sit, seat}, {pid, _ref}) when is_integer(seat) do {:reply, seat_player(state, pid, seat), state} end defp seat_player(%{players: players}, player, seat) do case :ets.match_object(players, {:_, seat, :_}) do [] -> :ets.insert(players, {player, seat, 0}) :ok _ -> {:error, :seat_taken} end end
leave 操做和 sit 正相反:
def handle_call(:leave, {pid, _ref}, state = %{hand: nil}) do case get_player(state, pid) do {:ok, %{balance: 0}} -> unseat_player(state, pid) {:reply, :ok, state} {:ok, %{balance: balance}} when balance > 0 -> {:reply, {:error, :player_has_balance}, state} error -> {:reply, error, state} end end defp get_player(state, player) do case :ets.lookup(state.players, player) do [] -> {:error, :not_at_table} [tuple] -> {:ok, player_to_map(tuple)} end end defp unseat_player(state, player) do :ets.delete(state.players, player) end defp player_to_map({id, seat, balance}), do: %{id: id, seat: seat, balance: balance}
在ETS中,全部數據都是元組形式,元組的第一個元素表明key。
咱們是這樣實現 buy_in 的:
def handle_call( {:buy_in, amount}, {pid, _ref}, state = %{hand: nil} ) when amount > 0 do case state |> get_player(pid) |> withdraw_funds(amount) do :ok -> modify_balance(state, pid, amount) {:reply, :ok, state} error -> {:reply, error, state} end end defp withdraw_funds({:ok, %{id: pid}}, amount), do: Poker.Bank.withdraw(pid, amount) defp withdraw_funds(error, _amount), do: error defp modify_balance(state, player, delta) do :ets.update_counter(state.players, player, {3, delta}) end
當牌局結束時,咱們須要從hand狀態切換出來,並把獎金給贏家。
首先咱們須要實現deal命令, 用於開始新的一局:
def deal(table) do GenServer.call(table, :deal) end def handle_call(:deal, _from, state = %{hand: nil}) do players = get_players(state) |> Enum.map(&(&1.id)) case Poker.Hand.start(self, players) do {:ok, hand} -> Process.monitor(hand) {:reply, {:ok, hand}, %{state | hand: hand}} error -> {:reply, error, state} end end def handle_call(:deal, _from, state) do {:reply, {:error, :hand_in_progress}, state} end
在一局結束時咱們會收到一個信息:
def handle_info( {:DOWN, _ref, _type, hand, _reason}, state = %{hand: hand} ) do {:noreply, %{state | hand: nil}} end
經過向牌桌發送一個消息來更新玩家的錢包:
def update_balance(table, player, delta) do GenServer.call(table, {:update_balance, player, delta}) end def handle_call( {:update_balance, player, delta}, {hand, _}, state = %{hand: hand} ) when delta < 0 do case get_player(state, player) do {:ok, %{balance: balance}} when balance + delta >= 0 -> modify_balance(state, player, delta) {:reply, :ok, state} {:ok, _} -> {:reply, {:error, :insufficient_funds}, state} error -> {:reply, error, state} end end def handle_call({:update_balance, _, _}, _, state) do {:reply, {:error, :invalid_hand}, state} end
在下一章中,咱們將應用Phoenix與Supervisor。