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