博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(整理)用Elixir做一个多人扑克游戏 2
阅读量:6615 次
发布时间:2019-06-25

本文共 4726 字,大约阅读时间需要 15 分钟。

  hot3.png

现在我们已经做好了牌面大小的比较,游戏的流程,但还没有做玩家登陆,人数限制,甚至没有将奖金发送给赢家。接下来,让我们来完成它们。

玩家需要兑换游戏中的筹码才能开始游戏,在当不在游戏过程时,可以兑换筹码。

我们引入了两个新的进程。

银行

首先我们将建立一个银行,玩家可以在这里进行现金和筹码的相互转换。

银行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})  endend

我们用模块名注册了这个进程,这样我们就不需要知道它的pid,就能进行访问了:

def init(_) do  {:ok, %{}}enddef handle_cast({:deposit, player, amount}, state)   when amount >= 0 do  {    :noreply,    Map.update(state, player, amount, fn current ->      current + amount     end)  }enddef 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}  endend

这段代码就显示了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)  endend

下面来实现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}enddef handle_call({:sit, seat}, {pid, _ref}) when is_integer(seat) do  {:reply, seat_player(state, pid, seat), state}enddefp seat_player(%{players: players}, player, seat) do  case :ets.match_object(players, {:_, seat, :_}) do    [] ->      :ets.insert(players, {player, seat, 0})      :ok    _ -> {:error, :seat_taken}  endend

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}  endenddefp get_player(state, player) do  case :ets.lookup(state.players, player) do    [] -> {:error, :not_at_table}    [tuple] -> {:ok, player_to_map(tuple)}  endenddefp unseat_player(state, player) do  :ets.delete(state.players, player)enddefp 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}  endenddefp withdraw_funds({:ok, %{id: pid}}, amount), do:  Poker.Bank.withdraw(pid, amount)defp withdraw_funds(error, _amount), do: errordefp 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)enddef 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}  endenddef 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})enddef 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}  endenddef handle_call({:update_balance, _, _}, _, state) do  {:reply, {:error, :invalid_hand}, state}end

在下一章中,我们将应用Phoenix与Supervisor。

转载于:https://my.oschina.net/ljzn/blog/754488

你可能感兴趣的文章
第 8 章 Spring Data
查看>>
[裴礼文数学分析中的典型问题与方法习题参考解答]5.1.24
查看>>
8.5. profile
查看>>
C语言 编程练习22题
查看>>
Log4Net 生成多个文件、文件名累加解决方法
查看>>
oracle 包,函数,过程,块的创建和执行及在java中执行(转)
查看>>
CloudDBA现场助力双十一
查看>>
虚拟现实技术或会产生副作用
查看>>
【云图】如何设置微信里的全国实体店地图?
查看>>
db file async I/O submit 等待事件优化
查看>>
前端需要了解的 SSO 与 CAS 知识
查看>>
李开复谈未来工作:虽然会被AI取代,但谁说人类非得工作不可?
查看>>
PostgreSQL 空间切割(st_split)功能扩展 - 空间对象网格化
查看>>
Intercom的持续部署实践:一天部署100次,1次10分钟
查看>>
SpringBoot权限控制
查看>>
阿里云中间件技术 促进互联网高速发展
查看>>
智能时代悄然到来 物联网称王将引爆传感器产业
查看>>
物理隔离计算机被USB蜜蜂刺破 数据通过无线信号泄露
查看>>
利用一点机器学习来加速你的网站
查看>>
中国域名现状:应用水平较低,安全仍存隐患
查看>>