原文并发
如今咱们已经作好了牌面大小的比较,游戏的流程,但尚未作玩家登录,人数限制,甚至没有将奖金发送给赢家。接下来,让咱们来完成它们。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。