现在我们已经做好了牌面大小的比较,游戏的流程,但还没有做玩家登陆,人数限制,甚至没有将奖金发送给赢家。接下来,让我们来完成它们。
玩家需要兑换游戏中的筹码才能开始游戏,在当不在游戏过程时,可以兑换筹码。
我们引入了两个新的进程。
银行
首先我们将建立一个银行,玩家可以在这里进行现金和筹码的相互转换。
银行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并发的威力,完全避免了竞态条件,因为是在同一个进程里执行的,所以所有操作会以队列来执行。
开桌
我们需要同时进行许多局独立的游戏。在玩家入座之后,可以兑换筹码或现金:
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:
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。
来源:oschina
链接:https://my.oschina.net/u/2864245/blog/754488