Skip to content

Commit

Permalink
Start working on GenServer
Browse files Browse the repository at this point in the history
  • Loading branch information
denvaar committed Nov 29, 2020
1 parent 3c58f82 commit ea41ded
Show file tree
Hide file tree
Showing 2 changed files with 277 additions and 39 deletions.
129 changes: 120 additions & 9 deletions lib/sternhalma.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,129 @@
defmodule Sternhalma do
@moduledoc """
Documentation for `Sternhalma`.
"""

@doc """
Hello world.
use GenServer

## Examples
alias Sternhalma.{Board, Cell, Pathfinding}

iex> Sternhalma.hello()
:world
@type game_status :: :setup | :playing | :over

"""
def hello do
:world
@type game_state :: %{
game_id: binary(),
board: Board.t(),
turn: nil | char(),
status: game_status(),
players: list(char())
}

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

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

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

def end_game(game_id) do
GenServer.call(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})
end

#
# | |
# \/ Server API \/

@impl true
def init(game_id) do
initial_state = %{
game_id: game_id,
board: Board.empty(),
status: :setup,
turn: nil,
players: []
}

{:ok, initial_state}
end

@impl true
def handle_call({:add_player, player_name}, _from, state) do
number_of_existing_players = length(state.players)

new_state = %{
state
| players: [player_name | state.players],
board:
Board.setup_triangle(
state.board,
position_opponent(number_of_existing_players),
player_name
)
}

{:reply, {:ok, new_state}, new_state}
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}
else
{:reply, {:error, []}, state}
end
end

def handle_call({:set_status, status}, _from, state) do
{result, new_state} =
state
|> change_game_status(status)
|> perform_side_effects(status)

{:reply, {result, new_state}, new_state}
end

@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,
do: {:ok, %{game_state | status: :playing}}

defp change_game_status(game_state, :over) when game_state.status == :playing,
do: {:ok, %{game_state | status: :over}}

defp change_game_status(game_state, _), do: {:error, game_state}

@spec perform_side_effects({:ok | :error, game_state()}, game_status()) ::
{:ok | :error, game_state()}
defp perform_side_effects({:ok, game_state}, :playing) do
{:ok, %{game_state | turn: List.first(game_state.players)}}
end

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

@spec position_opponent(0..5) :: Board.home_triangle()
defp position_opponent(0), do: :top
defp position_opponent(1), do: :bottom
defp position_opponent(2), do: :top_left
defp position_opponent(3), do: :bottom_right
defp position_opponent(4), do: :top_right
defp position_opponent(5), do: :bottom_left

@spec find_path(Board.t(), Cell.t(), Cell.t()) :: {:ok | :error, list(Cell.t())}
defp find_path(board, start_cell, end_cell) do
result_path = Pathfinding.path(board, start_cell, end_cell)
{pathfinding_status(result_path), result_path}
end

@spec pathfinding_status(list(Cell.t())) :: :ok | :error
defp pathfinding_status([]), do: :error
defp pathfinding_status(_path), do: :ok
end
187 changes: 157 additions & 30 deletions lib/sternhalma/board.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,45 +28,172 @@ defmodule Sternhalma.Board do
Fill in a home triangle with marbles.
"""
@spec setup_triangle(t(), home_triangle(), char()) :: t()
def setup_triangle(board, :bottom_left, _marble) do
# x = [
# make_row({3, -6, 3}, 1),
# make_row({2, -5, 3}, 2),
# make_row({1, -4, 3}, 3),
# make_row({0, -3, 3}, 4),
# ]
# |> List.flatten()
#
# Enum.map(board, fn cell ->
# Enum.
# %Cell{marble: marble, position: position}
# end)
def setup_triangle(board, :bottom, marble) do
positions = [
%Hex{x: 3, y: 3, z: -6},
%Hex{x: 2, y: 3, z: -5},
%Hex{x: 3, y: 2, z: -5},
%Hex{x: 1, y: 3, z: -4},
%Hex{x: 2, y: 2, z: -4},
%Hex{x: 3, y: 1, z: -4},
%Hex{x: 0, y: 3, z: -3},
%Hex{x: 1, y: 2, z: -3},
%Hex{x: 2, y: 1, z: -3},
%Hex{x: 3, y: 0, z: -3}
]

setup_triangle_helper(board, positions, marble)
end

def setup_triangle(board, :bottom_left, marble) do
positions = [
%Hex{x: -5, y: 7, z: -2},
%Hex{x: -5, y: 6, z: -1},
%Hex{x: -4, y: 6, z: -2},
%Hex{x: -5, y: 5, z: 0},
%Hex{x: -4, y: 5, z: -1},
%Hex{x: -3, y: 5, z: -2},
%Hex{x: -5, y: 4, z: 1},
%Hex{x: -4, y: 4, z: 0},
%Hex{x: -3, y: 4, z: -1},
%Hex{x: -2, y: 4, z: -2}
]

setup_triangle_helper(board, positions, marble)
end

def setup_triangle(board, :top_left, marble) do
positions = [
%Hex{x: -9, y: 3, z: 6},
%Hex{x: -8, y: 2, z: 6},
%Hex{x: -8, y: 3, z: 5},
%Hex{x: -7, y: 1, z: 6},
%Hex{x: -7, y: 2, z: 5},
%Hex{x: -7, y: 3, z: 4},
%Hex{x: -6, y: 0, z: 6},
%Hex{x: -6, y: 1, z: 5},
%Hex{x: -6, y: 2, z: 4},
%Hex{x: -6, y: 3, z: 3}
]

setup_triangle_helper(board, positions, marble)
end

def setup_triangle(board, :top, marble) do
positions = [
%Hex{x: -5, y: -5, z: 10},
%Hex{x: -5, y: -4, z: 9},
%Hex{x: -4, y: -5, z: 9},
%Hex{x: -5, y: -3, z: 8},
%Hex{x: -4, y: -4, z: 8},
%Hex{x: -3, y: -5, z: 8},
%Hex{x: -5, y: -2, z: 7},
%Hex{x: -4, y: -3, z: 7},
%Hex{x: -3, y: -4, z: 7},
%Hex{x: -2, y: -5, z: 7}
]

setup_triangle_helper(board, positions, marble)
end

def setup_triangle(board, :top_right, marble) do
positions = [
%Hex{x: 3, y: -9, z: 6},
%Hex{x: 2, y: -8, z: 6},
%Hex{x: 3, y: -8, z: 5},
%Hex{x: 1, y: -7, z: 6},
%Hex{x: 2, y: -7, z: 5},
%Hex{x: 3, y: -7, z: 4},
%Hex{x: 0, y: -6, z: 6},
%Hex{x: 1, y: -6, z: 5},
%Hex{x: 2, y: -6, z: 4},
%Hex{x: 3, y: -6, z: 3}
]

setup_triangle_helper(board, positions, marble)
end

def setup_triangle(board, :bottom_right, marble) do
positions = [
%Hex{x: 7, y: -5, z: -2},
%Hex{x: 6, y: -5, z: -1},
%Hex{x: 6, y: -4, z: -2},
%Hex{x: 5, y: -5, z: 0},
%Hex{x: 5, y: -4, z: -1},
%Hex{x: 5, y: -3, z: -2},
%Hex{x: 4, y: -5, z: 1},
%Hex{x: 4, y: -4, z: 0},
%Hex{x: 4, y: -3, z: -1},
%Hex{x: 4, y: -2, z: -2}
]

setup_triangle_helper(board, positions, marble)
end

@doc """
Return a cell from the game board based on pixel coordinates, x and y.
Return nil if the cell does not exist.
## Examples
iex> get_board_cell(empty(), {17.794, 14.5})
{:ok, %Sternhalma.Cell{marble: nil, position: %Sternhalma.Hex{x: 3, y: -6, z: 3}}}
iex> get_board_cell(empty(), {172.794, -104.5})
{:error, nil}
"""
@spec get_board_cell(t(), {number(), number()}) :: Cell.t() | nil
def get_board_cell(board, pixel_coord) do
case Enum.find(board, fn cell ->
cell.position == Hex.from_pixel(pixel_coord)
end) do
nil -> {:error, nil}
board_cell -> {:ok, board_cell}
end
end

@spec setup_triangle_helper(t(), list(Hex.t()), char()) :: t()
defp setup_triangle_helper(board, target_positions, marble) do
board
|> Enum.map(fn cell ->
if Enum.any?(target_positions, fn position ->
position == cell.position
end) do
%Cell{marble: marble, position: cell.position}
else
cell
end
end)
end

@spec six_point_star() :: list(Hex.t())
defp six_point_star() do
# left -> right, bottom -> top
[
make_row({3, -6, 3}, 1),
make_row({2, -5, 3}, 2),
make_row({1, -4, 3}, 3),
make_row({0, -3, 3}, 4),
make_row({-5, -2, 7}, 13),
make_row({-5, -1, 6}, 12),
make_row({-5, 0, 5}, 11),
make_row({-5, 1, 4}, 10),
make_row({-5, 2, 3}, 9),
make_row({-6, 3, 3}, 10),
make_row({-7, 4, 3}, 11),
make_row({-8, 5, 3}, 12),
make_row({-9, 6, 3}, 13),
make_row({-5, 7, -2}, 4),
make_row({-5, 8, -3}, 3),
make_row({-5, 9, -4}, 2),
make_row({-5, 10, -5}, 1)
{{3, -6, 3}, 1},
{{2, -5, 3}, 2},
{{1, -4, 3}, 3},
{{0, -3, 3}, 4},
{{-5, -2, 7}, 13},
{{-5, -1, 6}, 12},
{{-5, 0, 5}, 11},
{{-5, 1, 4}, 10},
{{-5, 2, 3}, 9},
{{-6, 3, 3}, 10},
{{-7, 4, 3}, 11},
{{-8, 5, 3}, 12},
{{-9, 6, 3}, 13},
{{-5, 7, -2}, 4},
{{-5, 8, -3}, 3},
{{-5, 9, -4}, 2},
{{-5, 10, -5}, 1}
]
|> Enum.map(fn {coords, row_length} ->
make_row(coords, row_length)
end)
|> List.flatten()
end

Expand Down

0 comments on commit ea41ded

Please sign in to comment.