Task

Write a player of the Bulls and Cows game, rather than a scorer. The player should give intermediate answers that respect the scores to previous attempts.

One method is to generate a list of all possible numbers that could be the answer, then to prune the list by keeping only those numbers that would give an equivalent score to how your last guess was scored. Your next guess can be any number from the pruned list.

Either you guess correctly or run out of numbers to guess, which indicates a problem with the scoring.



Related tasks

   Bulls and cows
   Guess the number
   Guess the number/With Feedback (Player)


defmodule Bulls_and_cows do
  def player(size \\ 4) do
    possibility = permute(size) |> Enum.shuffle
    player(size, possibility, 1)
  end
 
  def player(size, possibility, i) do
    guess = hd(possibility)
    IO.puts "Guess #{i} is #{Enum.join(guess)}  (from #{length(possibility)} possibilities)"
    case get_score(size) do
      {^size, 0} -> IO.puts "Solved!"
      score ->
        case select(size, possibility, guess, score) do
          [] -> IO.puts "Sorry! I can't find a solution. Possible mistake in the scoring."
          selected -> player(size, selected, i+1)
        end
    end
  end
 
  defp get_score(size) do
    IO.gets("Answer (Bulls, cows)? ")
    |> String.split(~r/\D/, trim: true)
    |> Enum.map(&String.to_integer/1)
    |> case do
         [bulls, cows] when bulls+cows in 0..size -> {bulls, cows}
         _ -> get_score(size)
       end
  end
 
  defp select(size, possibility, guess, score) do
    Enum.filter(possibility, fn x ->
      bulls = Enum.zip(x, guess) |> Enum.count(fn {n,g} -> n==g end)
      cows = size - length(x -- guess) - bulls
      {bulls, cows} == score
    end)
  end
 
  defp permute(size), do: permute(size, Enum.to_list(1..9))
  defp permute(0, _), do: [[]]
  defp permute(size, list) do
    for x <- list, y <- permute(size-1, list--[x]), do: [x|y]
  end
end
 
Bulls_and_cows.player