lib/chip8.ex

defmodule Chip8 do
  @moduledoc "README.md"
             |> File.read!()
             |> String.split("<!-- MDOC !-->")
             |> Enum.fetch!(1)

  alias Chip8.Interpreter
  alias Chip8.Interpreter.Display
  alias Chip8.Interpreter.Keyboard

  @doc """
  Changes the cycle rate of an interpreter.

  ```elixir
    iex> Chip8.change_cycle_rate(interpreter, 6)
    %Chip8.Interpreter{}
  ```
  """
  @spec change_cycle_rate(Interpreter.t(), pos_integer()) :: Interpreter.t()
  defdelegate change_cycle_rate(interpreter, cycle_rate), to: Interpreter

  @doc """
  Executes a single instruction cycle.

  A cycle is composed of 3 steps: fetch the instruction from memory, decode the
  instruction bytes and executes it by changing the interpreter's state.
  See the cycle section at `Chip8.Interpreter` for more information.

  ```elixir
    iex> Chip8.cycle(interpreter)
    %Chip8.Interpreter{}
  ```
  """
  @spec cycle(Interpreter.t()) :: {:ok, Interpreter.t()} | {:error, atom()}
  defdelegate cycle(interpreter), to: Interpreter

  @doc """
  Shortcut to `Chip8.initialize/2`.

  ```elixir
    iex> program = File.read!(path)
    iex> Chip8.initialize(program)
    %Chip8.Interpreter{}
  ```
  """
  @spec initialize(bitstring()) :: Interpreter.t()
  defdelegate initialize(program), to: Interpreter

  @doc """
  Creates an interpreter instance with the given program loaded into its memory.

  ## Options

  * `cycle_rate` - amount of instructions that are going to be executed each
    cycle. Defaults to 10 when not specified.

  ```elixir
    iex> program = File.read!(path)
    iex> Chip8.initialize(program, cycle_rate: 10)
    %Chip8.Interpreter{}
  ```
  """
  @spec initialize(bitstring(), Keyword.t()) :: Interpreter.t()
  defdelegate initialize(program, opts), to: Interpreter

  @doc """
  Returns a pixelmap with the current content of the display.

  This pixelmap is a list matrix with each pixel "state" of the whole display.
  See `Chip8.Interpreter.Display` for more information.

  ```elixir
    iex> Chip8.pixelmap(interpreter)
    [[0, 0, 1, ...], [0, 0, ...], [1, ...], [...], ...]
  ```
  """
  @spec pixelmap(Interpreter.t()) :: Display.pixelmap()
  defdelegate pixelmap(interpreter), to: Interpreter

  @doc """
  Computes a key press event on the hexadecimal keyboard.

  See `Chip8.Interpreter.Keyboard` for more information.

  ```elixir
    iex> Chip8.press_key(interpreter, 0xA)
    %Chip8.Interpreter{}
  ```
  """
  @spec press_key(Interpreter.t(), Keyboard.key()) :: Interpreter.t()
  defdelegate press_key(interpreter, key), to: Interpreter

  @doc """
  Computes a key release event on the hexadecimal keyboard.

  See `Chip8.Interpreter.Keyboard` for more information.

  ```elixir
    iex> Chip8.release_key(interpreter, 0xA)
    %Chip8.Interpreter{}
  ```
  """
  @spec release_key(Interpreter.t(), Keyboard.key()) :: Interpreter.t()
  defdelegate release_key(interpreter, key), to: Interpreter

  @doc """
  Performs the tick logic for all timers.

  This function does not set timers to decrement at the required rate, only
  performing the tick itself.
  See `Chip8.Interpreter.Timer` for more information on timers.

  ```elixir
    iex> Chip8.tick_timers(interpreter)
    %Chip8.Interpreter{}
  ```
  """
  @spec tick_timers(Interpreter.t()) :: Interpreter.t()
  defdelegate tick_timers(interpreter), to: Interpreter

  @doc """
  Returns a `boolean()` representing the current state of the given timer.
  This is usefull to run custom logic based on a timer state, for example
  emitting sound when the sound timer is active.

  See `Chip8.Interpreter.Timer` for more information on timers.

  ```elixir
    iex> Chip8.is_timer_active?(interpreter, :dt)
    false

    iex> Chip8.is_timer_active?(interpreter, :st)
    true
  ```
  """
  @spec is_timer_active?(Interpreter.t(), Interpreter.timers()) :: boolean()
  defdelegate is_timer_active?(interpreter, timer), to: Interpreter

  @doc """
  Returns a list containing all the pixels from the second interpreter that has
  a different state from their equivalent pixels on the first interpreter. The
  list is composed of tuples, each tuple has its first element being the
  pixel's coordinates (`t:Display.Coordinates.t/0`) and the second element is
  the state they have in the second interpreter.

  This is useful when rendering only the pixels that changed during a cycle.

  ```elixir
    iex> interpreter = Chip8.initialize()
    iex> Chip8.display_changes(interpreter, interpreter)
    []

    iex> cycled_interpreter = Chip8.cycle(interpreter)
    iex> Chip8.display_changes(interpreter, cycled_interpreter)
    [...]
  ```
  """
  @spec display_changes(Interpreter.t(), Interpreter.t()) :: Display.diff()
  defdelegate display_changes(interpreter_a, interpreter_b), to: Interpreter

  @doc """
  Returns a map with information about components of the interpreter.

  ```elixir
    iex> Chip8.info()
    %{display: %{height: 32, width: 64}, keyboard: %{keys: Enum.to_list(0x0..0xF)}}
  ```
  """
  defdelegate info(), to: Interpreter
end