文档章节

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

ljzn
 ljzn
发布于 2016/10/04 22:07
字数 953
阅读 33
收藏 0
点赞 0
评论 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
粉丝 29
博文 69
码字总数 96245
作品 0
南平
程序员
(整理)用Elixir做一个多人扑克游戏 1

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

ljzn ⋅ 2016/10/03 ⋅ 2

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

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

ljzn ⋅ 2016/10/05 ⋅ 0

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

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

ljzn ⋅ 2016/10/06 ⋅ 0

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

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

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

(译)循序渐进学习Elixir

——Chris Bell Learning Elixir at Made by Many 在今年奥兰多的Elixir大会上演讲之后,就经常有人问我:你们团队是如何学习Elixir和OTP的? 和学习其他语言一样,学习Elixir也需要付出时间...

ljzn ⋅ 2016/09/29 ⋅ 0

17年的人工智能承包了我们所有的游戏?!

  【IT168 资讯】在20世纪的大部分时间里,国际象棋博弈都是人工智能研究人员的基准。约翰・麦卡锡(John McCarthy)在20世纪50年代早期创造了“人工智能”一词,曾经把国际象棋称为“人工智...

it168网站 ⋅ 01/05 ⋅ 0

Pokerth

PokerTH是一个用C++/QT4写的单用户的扑克游戏。你能和最多六个电脑对手玩流行的"Texas Hold'em"扑克。这个扑克引擎是开源和在Linux, Windows和MacOSX上可用的。 开始网络游戏 网络游戏多达七...

匿名 ⋅ 2008/11/14 ⋅ 1

(二)深入了解超文本

1.给网页增加超链接需要使用元素,格式类似: 其中,通过href属性指定的“AAAA”为指向的目标文件,"BBBB"为可点击的链接文本。 2.利用属性可以指定元素的附加信息,其写法都是一样的,即属性...

好好先生_1028 ⋅ 2016/06/12 ⋅ 0

德扑AI之父解答Libratus的13个疑问:没有用到任何深度学习,DL远非AI的全部

雷锋网 AI 科技评论按:昨天晚上,卡耐基梅隆大学计算机系在读博士生 Noam Brown 和计算机系教授 Tuomas Sandholm 来到 reddit 的机器学习分版,和网友们一起来了一场「你问我答」(ask me ...

杨晓凡 ⋅ 2017/12/20 ⋅ 0

德扑 AI 之父解答 Libratus 的13个疑问:没有用到任何深度学习,DL 远非 AI 的全部

width="auto" class=" " src="http://ss.csdn.net/p?http://mmbiz.qpic.cn/mmbizjpg/vJe7ErxcLmg1KLkLdggoTAVAB3Rs2TJsJwxAiaaQ2bsfLe4hrW5qIktwXDTr1bnBGQBWoIG4E154vHqYMyBdCicQ/?wxfrom=5......

w9JLwB ⋅ 2017/12/20 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

RabbitMQ学习以及与Spring的集成(三)

本文介绍RabbitMQ与Spring的简单集成以及消息的发送和接收。 在RabbitMQ的Spring配置文件中,首先需要增加命名空间。 xmlns:rabbit="http://www.springframework.org/schema/rabbit" 其次是模...

onedotdot ⋅ 26分钟前 ⋅ 0

JAVA实现仿微信红包分配规则

最近过年发红包拜年成为一种新的潮流,作为程序猿对算法的好奇远远要大于对红包的好奇,这里介绍一种自己想到的一种随机红包分配策略,还请大家多多指教。 算法介绍 一、红包金额限制 对于微...

小致dad ⋅ 38分钟前 ⋅ 0

Python 数电表格格式化 xlutils xlwt xlrd的使用

需要安装 xlutils xlwt xlrd 格式化前 格式化后 代码 先copy读取的表格,然后按照一定的规则修改,将昵称中的学号提取出来替换昵称即可 from xlrd import open_workbookfrom xlutils.copy ...

阿豪boy ⋅ 今天 ⋅ 0

面试题:使用rand5()生成rand7()

前言 读研究生这3 年,思维与本科相比变化挺大的,这几年除了看论文、设计方案,更重要的是学会注重先思考、再实现,感觉更加成熟吧,不再像个小P孩,人年轻时总会心高气傲。有1 道面试题:给...

初雪之音 ⋅ 今天 ⋅ 0

Docker Toolbox Looks like something went wrong

Docker Toolbox 重新安装后提示错误:Looks like something went wrong in step ´Checking if machine default exists´ 控制面板-->程序与应用-->启用或关闭windows功能:找到Hyper-V,如果处......

随你疯 ⋅ 今天 ⋅ 0

Guacamole 远程桌面

本文将Apache的guacamole服务的部署和应用,http://guacamole.apache.org/doc/gug/ 该链接下有全部相关知识的英文文档,如果水平ok,可以去这里仔细查看。 一、简介 Apache Guacamole 是无客...

千里明月 ⋅ 今天 ⋅ 0

nagios 安装

Nagios简介:监控网络并排除网络故障的工具:nagios,Ntop,OpenVAS,OCS,OSSIM等开源监控工具。 可以实现对网络上的服务器进行全面的监控,包括服务(apache、mysql、ntp、ftp、disk、qmail和h...

寰宇01 ⋅ 今天 ⋅ 0

AngularDart注意事项

默认情况下创建Dart项目应出现以下列表: 有时会因为不知明的原因导致列表项缺失: 此时可以通过以下步骤解决: 1.创建项目涉及到的包:stagehand 2.执行pub global activate stagehand或pub...

scooplol ⋅ 今天 ⋅ 0

Java Web如何操作Cookie的添加修改和删除

创建Cookie对象 Cookie cookie = new Cookie("id", "1"); 修改Cookie值 cookie.setValue("2"); 设置Cookie有效期和删除Cookie cookie.setMaxAge(24*60*60); // Cookie有效时间 co......

二营长意大利炮 ⋅ 今天 ⋅ 0

【每天一个JQuery特效】淡入淡出显示或隐藏窗口

我是JQuery新手爱好者,有时间就练练代码,防止手生,争取每天一个JQuery练习,在这个博客记录下学习的笔记。 本特效主要采用fadeIn()和fadeOut()方法显示淡入淡出的显示效果显示或隐藏元...

Rhymo-Wu ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部