From 7e20cac9c1a3994f95013f6c108971b03b0f1d3d Mon Sep 17 00:00:00 2001 From: Liru Date: Sat, 21 Apr 2018 00:48:17 -0400 Subject: [PATCH] Improved infrastructure for step five --- lib/zombie_survivor/game.ex | 106 +++++++++++++++++++++++------- test/game_test.exs | 124 +++++++++++++++--------------------- 2 files changed, 134 insertions(+), 96 deletions(-) diff --git a/lib/zombie_survivor/game.ex b/lib/zombie_survivor/game.ex index aa34739..91c94b1 100644 --- a/lib/zombie_survivor/game.ex +++ b/lib/zombie_survivor/game.ex @@ -1,38 +1,98 @@ defmodule ZombieSurvivor.Game do alias ZombieSurvivor.{Game, Survivor} - @type t :: %__MODULE__{survivors: %{String.t() => Survivor.t()}} + defmodule State do + alias __MODULE__, as: Game - defstruct survivors: %{} + @type t :: %__MODULE__{ + survivors: %{String.t() => Survivor.t()}, + history: [String.t()] + } + @type history_type :: + :start + | :new_survivor + | :new_equipment + | :wounded + | :death + | :levelup + | :game_levelup + | :end + @type history :: {history_type, any} - @spec new() :: Game.t() - def new(), do: %Game{} + defstruct survivors: %{}, history: [] - @spec add_survivor(Game.t(), Survivor.t()) :: Game.t() - def add_survivor(game, survivor) do - name = survivor.name + @spec new() :: Game.t() + def new(), do: %Game{} - if Map.has_key?(game, name) do - game - else - %{game | survivors: Map.put(game.survivors, name, survivor)} + @spec add_survivor(Game.t(), Survivor.t()) :: Game.t() + def add_survivor(game, survivor) do + name = survivor.name + + if Map.has_key?(game, name) do + game + else + %{game | survivors: Map.put(game.survivors, name, survivor)} + end + end + + @spec ended?(Game.t()) :: boolean + def ended?(%Game{survivors: survivors}) when map_size(survivors) == 0, do: false + + def ended?(game) do + Enum.all?(game.survivors, fn {_, survivor} -> + Survivor.dead?(survivor) + end) + end + + @spec level(Game.t()) :: ZombieSurvivor.level() + def level(game) do + game.survivors + |> Enum.reject(fn {_, s} -> Survivor.dead?(s) end) + |> Enum.reduce(0, fn {_, s}, acc -> max(s.experience, acc) end) + |> ZombieSurvivor.level() end end - @spec ended?(Game.t()) :: boolean - def ended?(%Game{survivors: survivors}) when map_size(survivors) == 0, do: false + use GenServer - def ended?(game) do - Enum.all?(game.survivors, fn {_, survivor} -> - Survivor.dead?(survivor) - end) + def new, do: start_link() + + def add_survivor(pid, survivor), do: GenServer.cast(pid, {:add_survivor, survivor}) + def ended?(pid), do: GenServer.call(pid, :ended?) + def history(pid), do: GenServer.call(pid, :history) + def level(pid), do: GenServer.call(pid, :level) + def survivors(pid), do: GenServer.call(pid, :survivors) + + ## Server callbacks + + def start_link(opts \\ []) do + GenServer.start_link(__MODULE__, :ok, opts) end - @spec level(Game.t()) :: ZombieSurvivor.level() - def level(game) do - game.survivors - |> Enum.reject(fn {_, s} -> Survivor.dead?(s) end) - |> Enum.reduce(0, fn {_, s}, acc -> max(s.experience, acc) end) - |> ZombieSurvivor.level() + @impl GenServer + def init(:ok) do + {:ok, State.new()} + end + + @impl GenServer + def handle_call(:ended?, _from, state) do + {:reply, State.ended?(state), state} + end + + def handle_call(:history, _from, state) do + {:reply, state.history, state} + end + + def handle_call(:level, _from, state) do + {:reply, State.level(state), state} + end + + def handle_call(:survivors, _from, state) do + {:reply, state.survivors, state} + end + + @impl GenServer + def handle_cast({:add_survivor, survivor}, state) do + {:noreply, State.add_survivor(state, survivor)} end end diff --git a/test/game_test.exs b/test/game_test.exs index 82e2d44..1d3896e 100644 --- a/test/game_test.exs +++ b/test/game_test.exs @@ -7,124 +7,102 @@ defmodule GameTest do @new_survivor Survivor.new(name: "Zombait") @dead_survivor Survivor.new(name: "Deadman", wounds: 2) + setup do + game = start_supervised!(Game) + %{game: game} + end + describe "new/0 starts a game" do - test "that has 0 survivors" do - assert Map.size(Game.new().survivors) == 0 + test "that has 0 survivors", %{game: game} do + assert Map.size(Game.survivors(game)) == 0 end - test "that's at level blue" do - assert Game.level(Game.new()) == :blue + test "that's at level blue", %{game: game} do + assert Game.level(game) == :blue end end describe "add_survivor/1" do - test "adds a survivor to a game" do - g = - Game.new() - |> Game.add_survivor(@new_survivor) + test "adds a survivor to a game", %{game: game} do + Game.add_survivor(game, @new_survivor) - assert Map.size(g.survivors) == 1 + assert Map.size(Game.survivors(game)) == 1 - g = - g - |> Game.add_survivor(%{@new_survivor | name: "Larry"}) + Game.add_survivor(game, %{@new_survivor | name: "Larry"}) - assert Map.size(g.survivors) == 2 + assert Map.size(Game.survivors(game)) == 2 end - test "ensures that two survivors with the same name can't exist" do - g = - Game.new() - |> Game.add_survivor(@new_survivor) - |> Game.add_survivor(@new_survivor) + test "ensures that two survivors with the same name can't exist", %{game: game} do + game + |> Game.add_survivor(@new_survivor) + |> Game.add_survivor(@new_survivor) - assert Map.size(g.survivors) == 1 + assert Map.size(Game.survivors(game)) == 1 end end describe "ended?/1" do - test "returns true if all its survivors are dead" do + test "returns true if all its survivors are dead", %{game: game} do # TODO: Property test, add many dead survivors - g = - Game.new() - |> Game.add_survivor(@dead_survivor) + game + |> Game.add_survivor(@dead_survivor) - assert Game.ended?(g) + assert Game.ended?(game) - g = - g - |> Game.add_survivor(%{@dead_survivor | name: "Zambee"}) + game + |> Game.add_survivor(%{@dead_survivor | name: "Zambee"}) - assert Game.ended?(g) + assert Game.ended?(game) end - test "returns false if at least one survivor is alive" do - g = - Game.new() - |> Game.add_survivor(@new_survivor) - |> Game.add_survivor(@dead_survivor) + test "returns false if at least one survivor is alive", %{game: game} do + game + |> Game.add_survivor(@new_survivor) + |> Game.add_survivor(@dead_survivor) - refute Game.ended?(g) + refute Game.ended?(game) end - test "returns false if no survivors joined" do - g = Game.new() - - refute Game.ended?(g) + test "returns false if no survivors joined", %{game: game} do + refute Game.ended?(game) end end describe "level/1" do - test "returns the level of the highest levelled survivor" do - g = - Game.new() - |> Game.add_survivor(@new_survivor) + test "returns the level of the highest levelled survivor", %{game: game} do + Game.add_survivor(game, @new_survivor) - assert Game.level(g) == :blue + assert Game.level(game) == :blue - g = - g - |> Game.add_survivor(Survivor.new(name: "Eric", experience: 10)) + Game.add_survivor(game, Survivor.new(name: "Eric", experience: 10)) - assert Game.level(g) == :yellow + assert Game.level(game) == :yellow - g = - g - |> Game.add_survivor(Survivor.new(name: "Jack", experience: 20)) + Game.add_survivor(game, Survivor.new(name: "Jack", experience: 20)) - assert Game.level(g) == :orange + assert Game.level(game) == :orange + Game.add_survivor(game, Survivor.new(name: "Liru", experience: 1_000_000)) - g = - g - |> Game.add_survivor(Survivor.new(name: "Liru", experience: 1_000_000)) - - assert Game.level(g) == :red + assert Game.level(game) == :red end - test "returns the level of the highest levelled living survivor" do - g = - Game.new() - |> Game.add_survivor(@new_survivor) + test "returns the level of the highest levelled living survivor", %{game: game} do + Game.add_survivor(game, @new_survivor) - assert Game.level(g) == :blue + assert Game.level(game) == :blue - g = - g - |> Game.add_survivor(Survivor.new(name: "Eric", experience: 10)) + Game.add_survivor(game, Survivor.new(name: "Eric", experience: 10)) - assert Game.level(g) == :yellow + assert Game.level(game) == :yellow - g = - g - |> Game.add_survivor(Survivor.new(name: "Jack", experience: 20, wounds: 2)) + Game.add_survivor(game, %{@dead_survivor | name: "Jack", experience: 20}) - assert Game.level(g) == :yellow + assert Game.level(game) == :yellow - g = - g - |> Game.add_survivor(Survivor.new(name: "Liru", experience: 1_000_000, wounds: 2)) + Game.add_survivor(game, %{@dead_survivor | name: "Fake Liru", experience: 1_000_000}) - assert Game.level(g) == :yellow + assert Game.level(game) == :yellow end end end