Skip to content

Instantly share code, notes, and snippets.

@opie4624
Last active August 3, 2024 03:20
Show Gist options
  • Save opie4624/f78b489b82f2a08703ed01e0658d8992 to your computer and use it in GitHub Desktop.
Save opie4624/f78b489b82f2a08703ed01e0658d8992 to your computer and use it in GitHub Desktop.
defmodule IslandsEngine.GameSupervisor do
use DynamicSupervisor
alias IslandsEngine.Game
def start_link(_options), do: DynamicSupervisor.start_link(__MODULE__, :ok, name: __MODULE__)
def init(:ok), do: DynamicSupervisor.init(strategy: :one_for_one)
def start_game(name), do: DynamicSupervisor.start_child(__MODULE__, Game.child_spec(name))
def stop_game(game), do: Supervisor.terminate_child(__MODULE__, pid_from_name(game))
defp pid_from_name(name),
do:
name
|> Game.via_tuple()
|> GenServer.whereis()
end
iex(5)> {:ok, game} = GameSupervisor.start_game("Cassatt")
** (MatchError) no match of right hand side value: {:error, {:undef, [{IslandsEngine.Game, :start_link, [], []}, {DynamicSupervisor, :start_child, 3, [file: ~c"lib/dynamic_supervisor.ex", line: 795]}, {DynamicSupervisor, :handle_start_child, 2, [file: ~c"lib/dynamic_supervisor.ex", line: 781]}, {:gen_server, :try_handle_call, 4, [file: ~c"gen_server.erl", line: 2209]}, {:gen_server, :handle_msg, 6, [file: ~c"gen_server.erl", line: 2238]}, {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 329]}]}}
(stdlib 6.0.1) erl_eval.erl:652: :erl_eval.expr/6
iex:5: (file)
defmodule IslandsEngine.Game do
use GenServer, start: {__MODULE__, :start_link, []}, restart: :transient
alias IslandsEngine.{Board, Guesses, Rules, Coordinate, Island}
@players [:player1, :player2]
@timeout 60 * 60 * 24 * 1_000
# Interface
def start_link(name) when is_binary(name),
do: GenServer.start_link(__MODULE__, name, name: via_tuple(name))
def init(name) do
send(self(), {:set_state, name})
{:ok, fresh_state(name)}
end
def add_player(game, name) when is_binary(name), do: GenServer.call(game, {:add_player, name})
def position_island(game, player, key, row, col) when player in @players,
do: GenServer.call(game, {:position_island, player, key, row, col})
def set_islands(game, player) when player in @players,
do: GenServer.call(game, {:set_islands, player})
def guess_coordinate(game, player, row, col) when player in @players,
do: GenServer.call(game, {:guess_coordinate, player, row, col})
def via_tuple(name), do: {:via, Registry, {Registry.Game, name}}
def terminate({:shutdown, :timeout}, state_data) do
:ets.delete(:game_state, state_data.player1.name)
:ok
end
def terminate(_reason, _state), do: :ok
# Private
defp update_player2_name(state_data, name), do: put_in(state_data.player2.name, name)
defp update_rules(state_data, rules), do: %{state_data | rules: rules}
defp reply_success(state_data, reply) do
:ets.insert(:game_state, {state_data.player1.name, state_data})
{:reply, reply, state_data, @timeout}
end
defp player_board(state_data, player), do: Map.get(state_data, player).board
defp update_board(state_data, player, board),
do: Map.update!(state_data, player, fn player -> %{player | board: board} end)
defp opponent(:player1), do: :player2
defp opponent(:player2), do: :player1
defp update_guesses(state_data, player_key, hit_or_miss, coordinate) do
update_in(state_data[player_key].guesses, fn guesses ->
Guesses.add(guesses, hit_or_miss, coordinate)
end)
end
defp fresh_state(name) do
player1 = %{name: name, board: Board.new(), guesses: Guesses.new()}
player2 = %{name: nil, board: Board.new(), guesses: Guesses.new()}
%{player1: player1, player2: player2, rules: %Rules{}}
end
# Behaviour
def handle_call({:add_player, name}, _from, state_data) do
with {:ok, rules} <- Rules.check(state_data.rules, :add_player) do
state_data
|> update_player2_name(name)
|> update_rules(rules)
|> reply_success(:ok)
else
:error -> {:reply, :error, state_data}
end
end
def handle_call({:position_island, player, key, row, col}, _from, state_data) do
board = player_board(state_data, player)
with {:ok, rules} <-
Rules.check(state_data.rules, {:position_islands, player}),
{:ok, coordinate} <-
Coordinate.new(row, col),
{:ok, island} <-
Island.new(key, coordinate),
%{} = board <-
Board.position_island(board, key, island) do
state_data
|> update_board(player, board)
|> update_rules(rules)
|> reply_success(:ok)
else
:error ->
{:reply, :error, state_data, @timeout}
{:error, :invalid_coordinate} ->
{:reply, {:error, :invalid_coordinate}, state_data, @timeout}
{:error, :invalid_island_type} ->
{:reply, {:error, :invalid_island_type}, state_data, @timeout}
end
end
def handle_call({:set_islands, player}, _from, state_data) do
board = player_board(state_data, player)
with {:ok, rules} <- Rules.check(state_data.rules, {:set_islands, player}),
true <- Board.all_islands_positioned?(board) do
state_data
|> update_rules(rules)
|> reply_success({:ok, board})
else
:error -> {:reply, :error, state_data}
false -> {:reply, {:error, :not_all_islands_positioned}, state_data, @timeout}
end
end
def handle_call({:guess_coordinate, player_key, row, col}, _from, state_data) do
opponent_key = opponent(player_key)
opponent_board = player_board(state_data, opponent_key)
with {:ok, rules} <- Rules.check(state_data.rules, {:guess_coordinate, player_key}),
{:ok, coordinate} <- Coordinate.new(row, col),
{hit_or_miss, forested_island, win_status, opponent_board} <-
Board.guess(opponent_board, coordinate),
{:ok, rules} <-
Rules.check(rules, {:win_check, win_status}) do
state_data
|> update_board(opponent_key, opponent_board)
|> update_guesses(player_key, hit_or_miss, coordinate)
|> update_rules(rules)
|> reply_success({hit_or_miss, forested_island, win_status})
else
:error ->
{:reply, :error, state_data, @timeout}
{:error, :invalid_coordinate} ->
{:reply, {:error, :invalid_coordinate}, state_data, @timeout}
end
end
def handle_info(:timeout, state_data), do: {:stop, {:shutdown, :timeout}, state_data}
def handle_info({:set_state, name}, _state_data) do
state_data =
case :ets.lookup(:game_state, name) do
[] -> fresh_state(name)
[{_key, state}] -> state
end
:ets.insert(:game_state, {name, state_data})
{:noreply, state_data, @timeout}
end
end
defmodule IslandsEngine.GameSupervisor do
use Supervisor
alias IslandsEngine.Game
def start_link(_options), do: Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
def init(:ok), do: Supervisor.init([Game], strategy: :simple_one_for_one)
def start_game(name), do: Supervisor.start_child(__MODULE__, [name])
def stop_game(game), do: Supervisor.terminate_child(__MODULE__, pid_from_name(game))
defp pid_from_name(name),
do:
name
|> Game.via_tuple()
|> GenServer.whereis()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment