Speed mode is now working

This commit is contained in:
Mikko Ahlroth 2020-03-24 20:36:06 +02:00
parent b4dc18cb04
commit c4935ff6a7
4 changed files with 98 additions and 19 deletions

View file

@ -42,7 +42,10 @@ config :ex_speed_game,
score_pro: 100,
# Maximum amount of ticks you can be "behind" before the game is stopped.
max_waiting: 20,
max_queue: 20,
# Time to show win LEDs for at minimum
win_delay: 2_000,
# Interface to show IP for when using ShowIP
iface: "usb0",
@ -51,7 +54,10 @@ config :ex_speed_game,
crash_pattern: {false, true, true, false},
# Time to show crash pattern for
crash_pattern_delay: 3_000
crash_pattern_delay: 3_000,
# LED pattern to show when game ends
fail_pattern: {true, true, true, true}
# Customize non-Elixir parts of the firmware. See
# https://hexdocs.pm/nerves/advanced-configuration.html for details.

View file

@ -1,7 +1,6 @@
defmodule ExSpeedGame.Game.ButtonInput do
use GenServer
alias ExSpeedGame.Game.Types
require Logger
defmodule Options do
@type t :: %__MODULE__{
@ -81,9 +80,7 @@ defmodule ExSpeedGame.Game.ButtonInput do
@spec handle_info({:circuits_gpio, Types.pin(), integer(), 0 | 1}, %State{active: true}) ::
{:noreply, State.t()}
def handle_info({:circuits_gpio, pin, _time, value} = msg, %State{active: true} = state) do
Logger.debug(inspect(msg))
def handle_info({:circuits_gpio, pin, _time, value}, %State{active: true} = state) do
timer_ref = Map.get(state.pin_timers, pin)
if not is_nil(timer_ref) do
@ -144,4 +141,14 @@ defmodule ExSpeedGame.Game.ButtonInput do
def release(server) do
GenServer.call(server, :release)
end
@doc """
Convert given button pin into choice value using the given pins tuple.
"""
@spec get_choice(Types.pin(), Types.pins()) :: Types.choice()
def get_choice(pin, pins)
def get_choice(pin, pins) when pin == elem(pins, 0), do: 1
def get_choice(pin, pins) when pin == elem(pins, 1), do: 2
def get_choice(pin, pins) when pin == elem(pins, 2), do: 3
def get_choice(pin, pins) when pin == elem(pins, 3), do: 4
end

View file

@ -3,6 +3,8 @@ defmodule ExSpeedGame.Game.Modes.Speed do
require Logger
alias ExSpeedGame.Game.{Types, Lights, ButtonInput, Randomiser}
@max_queue Application.get_env(:ex_speed_game, :max_queue)
defmodule Options do
@type t :: %__MODULE__{
initial_score: integer(),
@ -16,11 +18,13 @@ defmodule ExSpeedGame.Game.Modes.Speed do
@type t :: %__MODULE__{
score: integer(),
delay: float(),
queue: [Types.choice()],
previous: Types.choice()
queue: :queue.queue(Types.pin()),
queue_len: integer(),
previous: Types.choice(),
active: boolean()
}
@enforce_keys [:score, :delay]
defstruct [:score, :delay, queue: [], previous: nil]
defstruct [:score, :delay, queue: :queue.new(), queue_len: 0, previous: nil, active: true]
end
@spec start_link(Options.t()) :: :ignore | {:error, any} | {:ok, pid}
@ -43,22 +47,68 @@ defmodule ExSpeedGame.Game.Modes.Speed do
{:ok, %State{score: score, delay: next_delay}}
end
# Tick handling
@impl true
def handle_info(:tick, %State{delay: delay, previous: previous} = state) do
Logger.debug("Tick #{delay}")
choice = Randomiser.get(previous)
Lights.set_index(Lights, choice)
next_delay = schedule_tick(delay)
{:noreply, %State{state | delay: next_delay, previous: choice}}
@spec handle_info(:tick, State.t()) :: {:noreply, State.t()}
def handle_info(:tick, %State{queue_len: queue_len, active: true} = state)
when queue_len >= @max_queue do
end_game(state)
end
@impl true
def handle_info({:input, _}, state) do
def handle_info(:tick, %State{active: true} = state) do
choice = Randomiser.get(state.previous)
Lights.set_index(Lights, choice)
new_queue = :queue.in(choice, state.queue)
next_delay = schedule_tick(state.delay)
{:noreply,
%State{
state
| delay: next_delay,
previous: choice,
queue: new_queue,
queue_len: state.queue_len + 1
}}
end
@impl true
@spec handle_info(:tick, %State{active: false}) :: {:noreply, State.t()}
def handle_info(:tick, %State{active: false} = state), do: {:noreply, state}
# Input handling
@impl true
@spec handle_info({:input, any()}, %State{active: false}) :: {:stop, :normal, State.t()}
def handle_info({:input, _}, %State{active: false} = state) do
{:stop, :normal, state}
end
@impl true
@spec handle_info({:input, Types.pin()}, %State{active: true}) :: {:noreply, :normal, State.t()}
def handle_info({:input, pin}, %State{active: true} = state) do
choice = ButtonInput.get_choice(pin, Application.get_env(:ex_speed_game, :button_pins))
case :queue.peek(state.queue) do
{:value, ^choice} ->
score = state.score + 1
Logger.debug("Score: #{score}")
{:noreply,
%State{
state
| queue: :queue.drop(state.queue),
queue_len: state.queue_len - 1,
score: score
}}
_ ->
end_game(state)
end
end
@impl true
@spec terminate(any(), any()) :: term()
def terminate(_reason, _state) do
@ -78,4 +128,16 @@ defmodule ExSpeedGame.Game.Modes.Speed do
defp get_next_delay(delay) when delay > 192, do: delay * 0.9985
defp get_next_delay(delay) when delay > 1, do: delay - 1
defp get_next_delay(delay), do: delay
@spec end_game(State.t()) :: {:noreply, State.t()}
defp end_game(state) do
# Don't listen to button inputs while showing failure lights
Logger.debug("Game ended with score #{state.score}.")
ButtonInput.release(ButtonInput)
Lights.set(Lights, Application.get_env(:ex_speed_game, :fail_pattern))
Process.sleep(Application.get_env(:ex_speed_game, :win_delay))
ButtonInput.acquire(ButtonInput)
{:noreply, %State{state | active: false}}
end
end

View file

@ -10,7 +10,11 @@ defmodule ExSpeedGame.Game.Randomiser do
If previous given is nil, any choice can be returned.
"""
@spec get(Types.choice()) :: Types.choice()
@spec get(Types.choice() | nil) :: Types.choice()
def get(previous)
def get(nil), do: Enum.random(@choices)
def get(previous) do
pick = previous - @choices_first + Enum.random(@choices_first..(@choices_size - 1))
rem(pick, @choices_size) + @choices_first