Skip to content

Commit

Permalink
Add a GenServer test
Browse files Browse the repository at this point in the history
  • Loading branch information
denvaar committed Nov 29, 2020
1 parent ea41ded commit 48a73a8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 14 deletions.
77 changes: 65 additions & 12 deletions lib/sternhalma.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,30 @@ defmodule Sternhalma do
game_id: binary(),
board: Board.t(),
turn: nil | char(),
last_move: list(Cell.t()),
status: game_status(),
players: list(char())
}

def start_link(game_id) do
GenServer.start_link(__MODULE__, game_id, name: game_id)
name = via_tuple(game_id)
GenServer.start_link(__MODULE__, game_id, name: name)
end

def add_player(game_id, player_name) do
GenServer.call(game_id, {:add_player, player_name})
GenServer.call(via_tuple(game_id), {:add_player, player_name})
end

def play_game(game_id) do
GenServer.call(game_id, {:set_status, :playing})
GenServer.call(via_tuple(game_id), {:set_status, :playing})
end

def end_game(game_id) do
GenServer.call(game_id, {:set_status, :over})
GenServer.call(via_tuple(game_id), {:set_status, :over})
end

def move_marble(game_id, start_position, end_position) do
GenServer.call(game_id, {:find_path, start_position, end_position})
GenServer.call(via_tuple(game_id), {:find_path, start_position, end_position})
end

#
Expand All @@ -47,6 +49,7 @@ defmodule Sternhalma do
board: Board.empty(),
status: :setup,
turn: nil,
last_move: [],
players: []
}

Expand All @@ -72,13 +75,42 @@ defmodule Sternhalma do
end

def handle_call({:find_path, start_position, end_position}, _from, state) do
with {:ok, start_cell} <- Board.get_board_cell(start_position),
{:ok, end_cell} <- Board.get_board_cell(end_position) do
{result, path} = find_path(state.board, start_position, end_position)
# TODO: update position
{:reply, {result, path}, state}
%{turn: turn} = state

# TODO refactor this function
with(
{:ok, %Cell{marble: ^turn} = start_cell} <-
Board.get_board_cell(state.board, start_position),
{:ok, end_cell} <- Board.get_board_cell(state.board, end_position)
) do
{result, path} = find_path(state.board, start_cell, end_cell)

board =
state.board
|> Enum.map(fn board_cell ->
cond do
board_cell.position == start_cell.position ->
Cell.set_marble(board_cell, nil)

board_cell.position == end_cell.position ->
Cell.set_marble(board_cell, state.turn)

true ->
board_cell
end
end)

new_state = %{
state
| board: board,
last_move: path,
turn: next_turn(state.players, state.turn)
}

{:reply, {result, new_state}, new_state}
else
{:reply, {:error, []}, state}
_ ->
{:reply, {:error, "invalid start or end position"}, state}
end
end

Expand All @@ -91,9 +123,11 @@ defmodule Sternhalma do
{:reply, {result, new_state}, new_state}
end

# TODO consider moving these private functions to some other module

@spec change_game_status(game_state(), game_status()) :: {:ok | :error, game_state()}
defp change_game_status(game_state, :playing)
when game_state.players > 1 and game_state.status == :setup,
when length(game_state.players) > 1 and game_state.status == :setup,
do: {:ok, %{game_state | status: :playing}}

defp change_game_status(game_state, :over) when game_state.status == :playing,
Expand All @@ -107,6 +141,7 @@ defmodule Sternhalma do
{:ok, %{game_state | turn: List.first(game_state.players)}}
end

defp perform_side_effects({:ok, game_state}, _), do: {:ok, game_state}
defp perform_side_effects({:error, game_state}, _), do: {:error, game_state}

@spec position_opponent(0..5) :: Board.home_triangle()
Expand All @@ -126,4 +161,22 @@ defmodule Sternhalma do
@spec pathfinding_status(list(Cell.t())) :: :ok | :error
defp pathfinding_status([]), do: :error
defp pathfinding_status(_path), do: :ok

@spec next_turn(list(char()), char()) :: char()
defp next_turn(players, turn) do
next_player_index =
case Enum.find_index(players, &(&1 == turn)) do
nil ->
0

current_player_index ->
rem(current_player_index + 1, length(players))
end

Enum.at(players, next_player_index)
end

defp via_tuple(game_id) do
{:via, Registry, {:sternhalma_registry, game_id}}
end
end
1 change: 1 addition & 0 deletions lib/sternhalma/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Sternhalma.Application do
children = [
# Starts a worker by calling: Sternhalma.Worker.start_link(arg)
# {Sternhalma.Worker, arg}
{Registry, keys: :unique, name: :sternhalma_registry}
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
2 changes: 1 addition & 1 deletion lib/sternhalma/board.ex
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ defmodule Sternhalma.Board do
"""
@spec get_board_cell(t(), {number(), number()}) :: Cell.t() | nil
@spec get_board_cell(t(), {number(), number()}) :: {:ok | :error, Cell.t() | nil}
def get_board_cell(board, pixel_coord) do
case Enum.find(board, fn cell ->
cell.position == Hex.from_pixel(pixel_coord)
Expand Down
72 changes: 71 additions & 1 deletion test/sternhalma_test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,74 @@
defmodule SternhalmaTest do
use ExUnit.Case
use ExUnit.Case, async: true
doctest Sternhalma

@game_id "testing"

setup do
game_pid = start_supervised!({Sternhalma, @game_id})
%{game_pid: game_pid}
end

test "add a player to a game in with :setup status", %{game_pid: game_pid} do
assert :sys.get_state(game_pid).status == :setup
assert {:ok, _game} = Sternhalma.add_player(@game_id, 'a')
assert {:ok, game} = Sternhalma.add_player(@game_id, 'b')
assert length(Enum.filter(game.board, &(&1.marble == 'a'))) == 10
assert length(Enum.filter(game.board, &(&1.marble == 'b'))) == 10
end

test "cannot set status to :playing with less than two players", %{game_pid: game_pid} do
assert :sys.get_state(game_pid).status == :setup
assert {:ok, _game} = Sternhalma.add_player(@game_id, 'a')
assert {:error, game} = Sternhalma.play_game(@game_id)
assert game.status == :setup
end

test "set status to :playing then to :over", %{game_pid: game_pid} do
assert :sys.get_state(game_pid).status == :setup
assert {:ok, _game} = Sternhalma.add_player(@game_id, 'a')
assert {:ok, _game} = Sternhalma.add_player(@game_id, 'b')

assert {:error, _game} = Sternhalma.end_game(@game_id)

assert {:ok, game} = Sternhalma.play_game(@game_id)
assert game.status == :playing
refute game.turn == nil

assert {:ok, game} = Sternhalma.end_game(@game_id)
assert game.status == :over
end

describe "gameplay" do
setup %{game_pid: game_pid} do
{:ok, _game} = Sternhalma.add_player(@game_id, 'a')
{:ok, _game} = Sternhalma.add_player(@game_id, 'b')
{:ok, _game} = Sternhalma.play_game(@game_id)

%{game_pid: game_pid}
end

test "players cannot move when it's not their turn", %{game_pid: game_pid} do
assert %{turn: 'b'} = :sys.get_state(game_pid)

assert {:error, "invalid start or end position"} =
Sternhalma.move_marble(@game_id, {12.598, 20.5}, {11.732, 19})
end

test "players alternate turns moving marbles", %{game_pid: game_pid} do
assert %{turn: 'b'} = :sys.get_state(game_pid)

assert {:ok, game} = Sternhalma.move_marble(@game_id, {9.134, 5.5}, {10, 7})
assert %{turn: 'a', last_move: last_move} = game
assert length(last_move) > 0

assert {:ok, game} = Sternhalma.move_marble(@game_id, {12.598, 20.5}, {11.732, 19})
assert %{turn: 'b', last_move: last_move} = game
assert length(last_move) > 0

assert {:ok, game} = Sternhalma.move_marble(@game_id, {10.866, 5.5}, {9.134, 8.5})
assert %{turn: 'a', last_move: last_move} = game
assert length(last_move) > 0
end
end
end

0 comments on commit 48a73a8

Please sign in to comment.