文档章节

(整理)用Elixir做一个多人扑克游戏 2

ljzn
 ljzn
发布于 2016/10/04 22:07
字数 953
阅读 177
收藏 0

#程序员薪资揭榜#你做程序员几年了?月薪多少?发量还在么?>>>

原文

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

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

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

银行

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

银行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。

© 著作权归作者所有

ljzn
粉丝 30
博文 69
码字总数 96245
作品 0
南平
程序员
私信 提问
加载中

评论(0)

(整理)用Elixir做一个多人扑克游戏 1

原文 学习一门新的语言或框架,最好的方法就是做一些小项目。Elixir和Phoenix很适合用来做扑克应用。 洗牌 我们要做的是德州扑克,首先,需要牌组: 我们定义了一个能够给出一套洗好了的52张...

ljzn
2016/10/03
629
2
(整理)用Elixir做一个多人扑克游戏 3

今天我们将为德州扑克游戏添加故障恢复能力。 OTP为我们准备好了构建容错程序所需要的工具。我们只需要定义正确的behavior 行为。 Supervisor 有了Supervisor,我们就只需要关心当进程崩溃时...

ljzn
2016/10/05
79
0
(整理)用Elixir做一个多人扑克游戏 4

sockets 和 channels 是Phoenix中用来实现实时效果的两大工具。 Sockets socket是用来连接客户端与服务器的,它使用endpoint来声明: Channels 客户端只有加入了channel之后才能发送消息。 ...

ljzn
2016/10/06
131
0
AI攻陷多人德扑再登Science,训练成本150美元,每小时赢1000刀

在无限制德州扑克六人对决的比赛中,德扑 AI Pluribus 成功战胜了五名专家级人类玩家。Pluribus 由 Facebook 与卡耐基梅隆大学(CMU)共同开发,实现了前辈 Libratus(冷扑大师)未能完成的任...

Cashcow
2019/07/17
0
0
独家首发 | NIPS 最佳论文视频解读!德州扑克背后的不完全信息博弈

美国时间, 2017 年 12 月 4 日 8:00。 全球机器学习顶级会议 NIPS 在美国长滩开幕了。 本年度 NIPS 将持续一周,你现在才想参加肯定来不及,因为票早就卖光了。 为了让你隔着太平洋都能跟上...

雷锋字幕组
2017/12/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何用数据结构解释计算机系统 常用数据结构

详细:https://www.cnblogs.com/morui/p/10726864.html 数据结构(计算机存储、组织数据方式) 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元...

osc_rnx2cje5
53分钟前
23
0
黑客你咋这么牛掰,不屑用鼠标?

如需快速跳转,请戳以下蓝色字条 01 前情提要 02 有苦说得出 1、黑客开发绝大多数工具是没有图形化界面的 2、命令行更有助于批量操作 3、图形化界面消耗系统资源量大,增加计算机性能负担 4、...

osc_m6gaz63w
54分钟前
24
0
计算机网络TCP/IP模型复习笔记(随时补充)

看到一篇大佬的博客,刚好前段时间也有简单了解了一点计算机网络的TCP/IP,就顺便总结一下。 大佬文章链接: https://blog.csdn.net/ThinkWon/article/details/104903925 计算机网络的自己理...

osc_boqyoaed
56分钟前
17
0
IDEA使用技巧-->查看类的继承关系图

IDEA使用技巧-->查看类的继承关系图 简单实用(很实用) 转自 ☞https://www.cnblogs.com/deng-cc/p/6927447.html 最近忙,有用的直接拿来给大家分享,但凡分享的都是我亲测有效的!...

宇宝
56分钟前
21
0
浏览器同源政策及其规避方法

自己以思维导图的形式梳理了一遍 浏览器同源政策及其规避方法

酒窝yun过去了
57分钟前
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部