Skip to content

Instantly share code, notes, and snippets.

@rob-brown
Last active August 15, 2022 08:43
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rob-brown/4820919c3ad51506656d to your computer and use it in GitHub Desktop.
Save rob-brown/4820919c3ad51506656d to your computer and use it in GitHub Desktop.
A Mastermind game implemented in Elixir.
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
@tallakt
Copy link

tallakt commented Nov 11, 2014

@rob-brown
Copy link
Author

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