lib/hangman/text/client/player.ex

defmodule Hangman.Text.Client.Player do
  @moduledoc """
  Plays a game until it is won, lost or stopped.
  """

  alias Hangman.{Game, Engine}
  alias Hangman.Text.Client.{Mover, Prompter, State, Summary}

  @alphabet Enum.map(?a..?z, &<<&1>>)
  @bad :light_red
  @emit? true
  @good :light_green
  @lost :light_magenta
  @rep :light_yellow
  @won :light_cyan

  @doc """
  Plays a game until it is won, lost or stopped.
  """
  # won, lost, good guess, bad guess, already used, initializing...
  @spec play(State.t()) :: no_return
  def play(%State{tally: %{game_state: :won}} = state),
    do: end_game(state, [@won, "Bravo, you WON!"])

  def play(%State{tally: %{game_state: :lost}} = state),
    do: end_game(state, [@lost, "Sorry, you lost."])

  def play(%State{tally: %{game_state: :good_guess}} = state),
    do: continue(state, [@good, "Good guess!"])

  def play(%State{tally: %{game_state: :bad_guess}} = state),
    do: continue(state, [@bad, "Bad guess..."])

  def play(%State{tally: %{game_state: :already_used}} = state),
    do: continue(state, [@rep, "Already used..."])

  # initializing
  def play(%State{} = state), do: continue(state)

  @doc """
  Displays a `message` and ends the game normally.
  """
  @spec end_game(State.t(), IO.ANSI.ansidata()) :: no_return
  def end_game(%State{tally: tally, game_name: game_name}, message) do
    message |> IO.ANSI.format(@emit?) |> IO.puts()
    tally = win_lose(tally, game_name)
    IO.puts("\nThe word was: #{Enum.join(tally.letters, " ")}")
    Engine.end_game(game_name)
    self() |> Process.exit(:normal)
  end

  ## Private functions

  @spec continue(State.t(), IO.ANSI.ansidata()) :: no_return
  defp continue(state, message) do
    message |> IO.ANSI.format(@emit?) |> IO.puts()
    continue(state)
  end

  @spec continue(State.t()) :: no_return
  defp continue(state) do
    state
    |> Summary.display()
    |> Prompter.accept_move()
    |> Mover.make_move()
    |> play()
  end

  @spec win_lose(Game.tally(), Game.name()) :: Game.tally()
  defp win_lose(%{game_state: game_state} = tally, _game_name)
       when game_state in [:won, :lost],
       do: tally

  defp win_lose(%{guesses: guesses}, game_name) do
    [guess | _] = @alphabet -- guesses
    Engine.make_move(game_name, guess) |> win_lose(game_name)
  end
end