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