7 重做 Game struct 与 出牌方法

昨天的进度

defmodule Game do
  defstruct rounds: [], host_hand: [], guest_hand: [], round: 1, winner: nil

  use GenServer

  def init(status) do
    {:ok, status}
  end

  def handle_call(:get_status, _from, status) do
    {:reply, status, status}
  end
end

补上 client 方法

我们今天补上 client 这边的方法
来取代 GenServer.start_link(Game, %Game{})
GenServer.call(game_pid, :get_status)
我们一样放在 Game module 里面

defmodule Game do
# ...上略
  def handle_call(:get_status, _from, status) do
    {:reply, status, status}
  end

  # Client 方法

  def start do
    GenServer.start_link(__MODULE__, %Game{})
  end

  def status(game_pid) do
    GenServer.call(game_pid, :get_status)
  end
end

在 start 方法里面我们使用了 __MODULE__,这个是指方法所在的 module 在这边即是 Game
我们再来 iex 试试看吧

$ iex game.ex
iex(1)> {:ok, pid} = Game.start
{:ok, #PID<0.115.0>}
iex(2)> Game.status(pid)
%Game{guest_hand: [], host_hand: [], round: 1, rounds: [], winner: nil}

看起来都正常

马上後悔前天的决定

在我试做出牌方法的时候,我发现有些东西不太妥当,尤其是 Game struct,原本的结构超烂,
变得很难用非常直觉的方式去更改场上的卡片

我们来更新一下,在我们还没使用到任何逻辑的情况,这次是无痛升级

defmodule Game do
  initial_hand = [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn]
  defstruct host: %{desk: [], hand: initial_hand, wins: 0},
            guest: %{desk: [], hand: initial_hand, wins: 0},
            turn: 1, round: 1
  # 以下省略

这个作法我们也不需要另一个 Round struct,可以把它整个移除

接着是 在各个 GenServer 方法内,我想把 status 这个变数改成 game
毕竟那个变数永远都会是 Game struct 就直接用游戏本身比较贴切
於是变成这样:

  # 上略
  def init(game) do
    {:ok, game}
  end

  def handle_call(:get_status, _from, game) do
    {:reply, game, game}
  end

  def start do
    GenServer.start_link(__MODULE__, %Game{})
  end

  def status(pid) do
    GenServer.call(pid, :get_status)
  end
end

出牌!

我们先来看看出一张牌需要改什麽
原本的 game 是这样

%Game{
  guest: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  host: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  round: 1,
  turn: 1
}

假如 host 出一张 1 ,那我们要做的事情有

  1. 从 hand 里面拿掉一张 1
  2. 把 1 加在 host 的 desk 里面
  3. 组成新的 game 来替换旧的

这次我们用 handle_cast 来做,
并且按步骤一步一步做做看

  def play_card(pid, :host, card) do
    GenServer.cast(pid, {:play_card, :host, card})
  end

  def handle_cast({:play_card, :host, card}, game) do
    %{host: host} = game

    # 从 hand 里面拿掉一张 1 
    new_hand = host.hand -- [card]

    # 把 1 加在 host 的 desk 里面
    new_desk = host.desk ++ [card]

    # 组成新的 host 来替换旧的
    new_host = Map.merge(host, %{hand: new_hand, desk: new_desk})

    # 组成新的 game 来替换旧的
    new_game = Map.replace(game, :host, new_host)

    # 让 process 使用新的 game
    {:noreply, new_game}
  end

我们来用 iex 验证看看

$ iex game.ex

iex(1)> {:ok, pid} = Game.start
{:ok, #PID<0.112.0>}
iex(2)> Game.status pid
%Game{
  guest: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  host: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  round: 1,
  turn: 1
}
iex(3)> Game.play_card pid, :host, 1                 
:ok
iex(4)> Game.status pid             
%Game{
  guest: %{desk: [], hand: [1, 1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  host: %{desk: [1], hand: [1, 2, 2, 3, 3, 4, 5, 6, :turn, :turn], wins: 0},
  round: 1,
  turn: 1
}

在我们执行完出牌後,host 的桌面多了 1 ,手牌少了一张 1 , 正是我们要的,
但是我们还没有考虑到 guest 的情况,也写得比较不像 elixir 的做法,
下一篇就来稍微重构一下出牌方法。


<<:  [Day06]没有稽核经验咋怎

>>:  Day21 光晕文字

Day 16 : 案例分享(5.2) CRM与ERP整合 - 商机与客户往来记录(会议与行事历)

案例说明及适用场景 客户往来记录,属於商机管理的一环,一个客户不见得袛有一次商机,而一个商机的完成必...

Ubuntu巡航记(2) -- 在 Ubuntu 作业系统内安装 TensorFlow

前言 前一篇搞定 Ubuntu 作业系统的安装,接下来我们继续安装『机器学习』的相关软件及工具,包括...

D26: 工程师太师了: 第13.5话

工程师太师了: 第13.5话 杂记: 蓝画面指的是Windows崩溃停止执行时出现的蓝底白字画面。 ...

我老爸教我赌钱的时候,赢要冲,输要缩 - 顺势交易

区间突破的策略,是顺势交易中重要的一环 ORB策略(Open Range Breakout syst...

Day 23 Object oriented programming

物件导向程序设计是程序设计中极为重要的一环,其基本概念为物件及类别。 类别定义事物的特点,物件为事件...