lib/impl/game.ex

defmodule Hangman.Impl.Game do
  alias Hangman.Type

  # structure to return status of the game
  @type t :: %__MODULE__{
          turns_left: Integer,
          game_state: Type.state(),
          letters: list(String.t()),
          used: MapSet.t(String.t())
        }

  defstruct(
    turns_left: 7,
    game_state: :initializing,
    letters: [],
    used: MapSet.new()
  )

  @spec new_game() :: t
  def new_game do
    new_game(Dictionary.generate_random())
  end

  @spec new_game(String.t()) :: t
  def new_game(word) do
    %__MODULE__{
      letters: word |> String.codepoints()
    }
  end

  @spec make_move(t, String.t()) :: {t, Type.tally()}
  def make_move(game = %{game_state: state}, _guess) when state in [:won, :lost] do
    game |> return_tally
  end

  # make a movue
  def make_move(game, guess) do
    accept_guess(game, guess, MapSet.member?(game.used, guess))
    |> return_tally
  end

  # check used guess
  defp accept_guess(game, _guess, true) do
    %{game | game_state: :already_used}
  end

  # accept new guess
  defp accept_guess(game, guess, _never_used) do
    %{game | used: MapSet.put(game.used, guess)}
    |> game_score(Enum.member?(game.letters, guess))
  end

  # check good guess
  defp game_score(game, _guessed_value = true) do
    new_state = check_win(MapSet.subset?(MapSet.new(game.letters), game.used))
    %{game | game_state: new_state}
  end

  # check bad guess
  defp game_score(game = %{turns_left: 1}, _guessed_value) do
    %{game | game_state: :lost, turns_left: 0}
  end

  defp game_score(game, _guessed_value) do
    %{game | game_state: :bad_guess, turns_left: game.turns_left - 1}
  end

  # return game current info
  def tally(game) do
    %{
      turns_left: game.turns_left,
      game_state: game.game_state,
      letters: reveal_letter(game),
      used: game.used |> MapSet.to_list() |> Enum.sort()
    }
  end

  # return the whole game tally or curent details
  defp return_tally(game) do
    {game, tally(game)}
  end

  # real letter if guessed
  defp reveal_letter(game = %{game_state: :lost}) do
    game.letters
  end
  # real letter if guessed
  defp reveal_letter(game) do
    game.letters
    |> Enum.map(fn letter ->
      MapSet.member?(game.used, letter)
      |> popurate_value(letter)
    end)
  end



  # popurate value
  defp popurate_value(true, letter), do: letter
  defp popurate_value(_, _letter), do: "_"

  # check if user won the game or is a one good guess
  defp check_win(true), do: :won
  defp check_win(_), do: :good_guess
end