昨天的進度
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 這邊的方法
來取代 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 ,那我們要做的事情有
這次我們用 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 的做法,
下一篇就來稍微重構一下出牌方法。