Last active
February 11, 2025 09:33
-
-
Save rob-brown/4820919c3ad51506656d to your computer and use it in GitHub Desktop.
A Mastermind game implemented in Elixir.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Mastermind do | |
defstruct rounds: 10, choices: 6, answer: nil, guesses: [] | |
def start, do: game_loop %Mastermind{} | |
defp game_loop(game = %Mastermind{answer: nil}) do | |
game | |
|> start_computer_mastermind | |
|> game_loop | |
end | |
defp game_loop(%Mastermind{answer: answer, guesses: [ guess | _ ]}) when answer == guess do | |
IO.puts "You win!" | |
end | |
defp game_loop(%Mastermind{guesses: guesses, rounds: r}) when length(guesses) >= r do | |
IO.puts "You lose :(" | |
end | |
defp game_loop(g = %Mastermind{guesses: guesses, rounds: r}) when length(guesses) < r do | |
"Input guess #{length(guesses) + 1}: " | |
|> IO.gets | |
|> parse_input(g) | |
|> handle_guess(g) | |
|> print_results | |
|> game_loop | |
end | |
## Helpers | |
defp parse_input(string, game) do | |
n = game.choices | |
r = ~r"^([1-#{n}])\s*([1-#{n}])\s*([1-#{n}])\s*([1-#{n}])$" | |
case Regex.run(r, string) do | |
[ _, a, b, c, d ] -> [ a, b, c, d ] | |
_ -> nil | |
end | |
end | |
defp start_human_mastermind(game) do | |
"Input answer: " | |
|> IO.gets | |
|> parse_input(game) | |
|> handle_answer(game) | |
end | |
defp start_computer_mastermind(game = %Mastermind{choices: c}) do | |
:random.seed :erlang.now | |
rand = &(&1 |> :random.uniform |> Integer.to_string) | |
answer = [ rand.(c), rand.(c), rand.(c), rand.(c) ] | |
%Mastermind{game | answer: answer} | |
end | |
defp handle_answer(nil, game), do: game | |
defp handle_answer(answer, game), do: %Mastermind{game | answer: answer} | |
defp handle_guess(nil, game), do: game | |
defp handle_guess(guess, game), do: %Mastermind{game | guesses: [ guess | game.guesses ]} | |
defp print_results(game = %Mastermind{answer: answer, guesses: [ guess | _ ]}) do | |
_print_results answer, guess | |
game | |
end | |
defp print_results(game), do: game | |
defp _print_results(answer, guess) do | |
matches = match_count answer, guess | |
partials = partial_match_count answer, guess | |
misses = miss_count answer, guess | |
IO.puts(string_times("\x{2713}", matches) <> string_times("~", partials) <> string_times("\x{2717}", misses)) | |
end | |
defp string_times(_string, num) when num <= 0, do: "" | |
defp string_times(string, num) when is_binary(string) and is_integer(num) do | |
for _ <- 1..num, into: "", do: string | |
end | |
defp match_count(answer, guess) do | |
Enum.zip(answer, guess) | |
|> Enum.map(fn | |
{ x, x } -> 1 | |
_ -> 0 | |
end) | |
|> Enum.sum | |
end | |
defp partial_match_count(answer, guess) do | |
_partial_match_count(Enum.sort(answer), Enum.sort(guess), 0) - match_count(answer, guess) | |
end | |
defp _partial_match_count(a, g, count) when a == [] or g == [], do: count | |
defp _partial_match_count([ ah | at ], [ gh | gt ], count) when ah == gh do | |
_partial_match_count(at, gt, count + 1) | |
end | |
defp _partial_match_count([ ah | at ], g = [ gh | _ ], count) when ah < gh do | |
_partial_match_count(at, g, count) | |
end | |
defp _partial_match_count(a = [ ah | _ ], [ gh | gt ], count) when ah > gh do | |
_partial_match_count(a, gt, count) | |
end | |
defp miss_count(answer, guess) do | |
Enum.count(guess) - match_count(answer, guess) - partial_match_count(answer, guess) | |
end | |
end |
Good catch. I didn't know about Enum.sum
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
http://elixir-lang.org/docs/stable/elixir/Enum.html#sum/1 instead of reduce(+/2)?