# Trying to understand certain concepts through a Mastermind game sub-problem

I am a complete Elixir beginner. I am trying to understand certain concepts with the following Mastermind sub-problem.

Suppose I have two lists `code = [0, 3, 2 ,3, 4, 5]` and `guess = [0, 4, 2, 4, 3, 3]`.

My approach is, we compare the two lists and assign two values, one for both correct guess and position (CP), and the other for correct guess but for wrong position (CG). In the above example: CP is 2 (the first and third guess), and the CG is 3 (two 3s and one 4).

I guess there are numerous ways to approach. What I wanted to achieve was this: We traverse the two lists to determine correct positional guesses and produce two new lists. For the above example, that would be:

`[:true, 3, :true, 3, 4, 5]` and `[:true, 4, :true, 4, 3, 3]`

Then, we traverse the two lists for the second time and produce a third list, such that we take each item from the new `guess` list and check if the `code` list contains it. If so, the new list returns, say, :guess for those guesses. So the third list will be:

`[:true, :guess, :true, :guess, :guess: 5]` and `[:true, :ok, :true, 4, :ok, :ok]`

And now we count the :true, and :guess.

1. What is the Elixir way to produce the second list and the third list.
2. How else can I approach it anyway (again, in an idiomatic Elixir way)?
1 Like

Nice problem to learn some Enum functions.

The ‘CP’ problem can easily be solved with `zip`

``````{cps, other} = Enum.zip(code, guess) |> Enum.split_with(fn {c,g} -> c == g end)
cp = length(cps)
``````

‘CG’ is a little more involved, there are several functions in Enum one could use. There is most likely some better approach, but for example this seems to work:

``````{code, guess} = Enum.unzip(other)
code_freqs = Enum.frequencies(code)
guess_freqs = Enum.frequencies(guess)
cg = Enum.reduce(guess_freqs, 0, fn {guess, guess_freq}, score ->
code_freq = Map.get(code_freqs, guess, 0)
score + Enum.min([code_freq, guess_freq])
end)
``````

Have a look at the other functions in `Enum`. Try to solve 'CG" without the help of `frequencies`.

This may help: Elixir Enum Cheatsheet

2 Likes

My surely not so Elixir way approach:

``````firstcodes = [0, 3, 2 ,3, 4, 5]
firstguesses = [0, 4, 2, 4, 3, 3]

defmodule Try do

def correct_positional_guess({same, same}), do: {:true, :true}
def correct_positional_guess({code, guess}), do: {code, guess}

def correct_guess(codes, :true), do: {codes, :true}
def correct_guess(codes, guess) do

Enum.map_reduce(codes, guess, fn elem, acc ->
cond do
elem == :true -> {elem, acc}
acc == :guess -> {elem, acc}
elem == acc -> {:guess, :ok}
true -> {elem, acc}
end
end)
end

end

IO.inspect(firstcodes, label: "1st codes")
IO.inspect(firstguesses, label: "1st guesses")

second_lists =
Enum.zip(firstcodes, firstguesses)
|> Enum.map(&Try.correct_positional_guess/1)

{codes2, guesses2} = second_lists |> Enum.unzip()

IO.inspect(codes2, label: "2nd codes")
IO.inspect(guesses2, label: "2nd guesses")

{third_lists, finalacc} =
second_lists
|> Enum.map_reduce(codes2, fn {code, guess}, codes ->
cond do
code == guess == :true -> {{code, guess}, codes}
true ->
{newcodes, newguess} = Try.correct_guess(codes, guess)
{{code, newguess}, newcodes}
end

end)

thirdcodes = finalacc

thirdguesses =
third_lists
|> Enum.unzip()
|> Kernel.elem(1)

IO.inspect(thirdcodes, label: "3rd codes")
IO.inspect(thirdguesses, label: "3rd guesses")

``````

Will produce something like:

``````1st codes: [0, 3, 2, 3, 4, 5]
1st guesses: [0, 4, 2, 4, 3, 3]
2nd codes: [true, 3, true, 3, 4, 5]
2nd guesses: [true, 4, true, 4, 3, 3]
3rd codes: [true, :guess, true, :guess, :guess, 5]
3rd guesses: [true, :ok, true, 4, :ok, :ok]

``````

Thanks for the opportunity to read up again about `Enum` and `map_reduce` I love these games. I wrote a mastermind game & solver a few years ago in JS and the evaluation fn
relied heavily on mutating arrays. (also recently wrote a wordle knockoff & solver in Elm that used an approach similar to the `Enum.frequencies` approach above).

Here’s my crack at the evaluation in Elixir, but I’m also still new to the language.

``````defmodule Mastermind do
def evaluate(guess, code) do
{ correct_position, remaining } = get_correct_positions(guess, code)

{ remaining_guess, remaining_code } = Enum.unzip(remaining)
correct_guesses = get_correct_guesses(remaining_guess, remaining_code)

# return the results
correct_position ++ correct_guesses
end

# right number in the right spot
defp get_correct_positions(guess, code) do
{ correct_position, remaining } = Enum.zip(guess, code)
|> Enum.split_with(fn { guess_digit, code_digit } ->
code_digit == guess_digit
end)

correct_position = Enum.map(correct_position, fn _ -> :correct_position end)
{ correct_position, remaining }
end

# recursion finished
defp get_correct_guesses(remaining_guess, remaining_code, results \\ [])
defp get_correct_guesses([], _code, results), do: results
defp get_correct_guesses([ guess_digit | remaining_guess], remaining_code, results) do
index = Enum.find_index(remaining_code, &(&1 == guess_digit))
# found this digit in the guess
if index do
get_correct_guesses(
remaining_guess,
# remove this found digit from the code so we don't count it again
List.delete_at(remaining_code, index),
# push a correct_guess onto the results
[:correct_guess | results]
)
else
# no hits on this digit, continue
get_correct_guesses(remaining_guess, remaining_code, results)
end
end
end
``````

I really like Sebb’s approach with `Enum.split_with`, otherwise I would have just done one round of mapping the correct positions to an atom, another round of reducing to come up with atoms for the correct guesses, and then rejected everything from the list that wasn’t an atom (just using `Kernel.is_atom/1`)

@blackened

I just had a thought and I’m pretty sure you can do this to get the remaining correct guesses (as in, right number, wrong place)

``````  defp get_correct_guesses(remaining_guess, remaining_code) do
(remaining_guess -- remaining_code) |> Enum.map(fn _ -> :correct_guess end)
end
``````