ex_speed_game/lib/game/menu.ex

130 lines
3.6 KiB
Elixir

defmodule ExSpeedGame.Game.Menu do
use GenServer
require Logger
alias ExSpeedGame.Game.{Types, ButtonInput, ShowIP, Lights}
alias ExSpeedGame.Game.Modes.{
Speed
}
@menu {
{"SpeedGame", Speed,
%Speed.Options{
initial_score: 0,
initial_delay: Application.get_env(:ex_speed_game, :delay_start)
}},
{"SpeedGame Pro", Speed,
%Speed.Options{
initial_score: Application.get_env(:ex_speed_game, :score_pro),
initial_delay: Application.get_env(:ex_speed_game, :delay_pro)
}},
{"MemoryGame", Speed, %{}},
{"Show IP", ShowIP, %{}}
}
@menu_size :erlang.tuple_size(@menu)
defmodule Options do
@type t :: %__MODULE__{
name: GenServer.name()
}
@enforce_keys [:name]
defstruct [:name]
end
defmodule State do
@type mode :: :menu | :ingame
@type t :: %__MODULE__{
index: integer(),
button_pins: Types.pins(),
mode: mode(),
game: pid(),
game_ref: reference()
}
@enforce_keys [:button_pins]
defstruct [:button_pins, :game, :game_ref, index: 0, mode: :menu]
end
@spec start_link(Options.t()) :: :ignore | {:error, any} | {:ok, pid}
def start_link(%Options{} = opts) do
GenServer.start_link(
__MODULE__,
%{},
name: opts.name
)
end
@impl true
@spec init(any) :: {:ok, State.t()}
def init(_) do
state = %State{button_pins: Application.get_env(:ex_speed_game, :button_pins)}
ButtonInput.acquire(ButtonInput)
display_index(state.index)
{:ok, state}
end
@impl true
def handle_info(msg, state)
@spec handle_info({:input, Types.pin()}, State.t()) :: {:noreply, State.t()}
def handle_info(
{:input, btn_pin},
%State{index: index, button_pins: {pin1, _, _, pin4}, mode: :menu} = state
)
when btn_pin in [pin1, pin4] do
is_first_button = btn_pin == pin1
is_last_button = btn_pin == pin4
new_index =
cond do
is_first_button and index > 0 -> index - 1
is_last_button and index < @menu_size - 1 -> index + 1
true -> index
end
display_index(new_index)
{:noreply, %State{state | index: new_index}}
end
@spec handle_info({:input, Types.pin()}, State.t()) :: {:noreply, State.t()}
def handle_info({:input, _pin}, %State{index: index, mode: :menu} = state) do
ButtonInput.release(ButtonInput)
{_, module, init_arg} = elem(@menu, index)
{:ok, pid} =
DynamicSupervisor.start_child(ExSpeedGame.Game.Supervisor, module.child_spec(init_arg))
ref = Process.monitor(pid)
{:noreply, %State{state | mode: :ingame, game: pid, game_ref: ref}}
end
@spec handle_info({:DOWN, reference(), :process, any, any}, State.t()) :: {:noreply, State.t()}
def handle_info(
{:DOWN, ref, :process, _, reason},
%State{
index: index,
mode: :ingame,
game_ref: game_ref
} = state
)
when ref == game_ref do
# Ensure input is released in case process crashed violently
ButtonInput.release(ButtonInput)
# If crashed, show crash pattern for a moment
if reason != :normal do
Lights.set(Lights, Application.get_env(:ex_speed_game, :crash_pattern))
Process.sleep(Application.get_env(:ex_speed_game, :crash_pattern_delay))
end
ButtonInput.acquire(ButtonInput)
display_index(index)
{:noreply, %State{state | mode: :menu, game: nil, game_ref: nil}}
end
defp display_index(index) do
Logger.debug("Menu viewing: #{@menu |> elem(index) |> elem(0)}")
Lights.show_binary(Lights, index + 1)
end
end