3
\$\begingroup\$

I am a novice coder and I am very new to algorithms. Something about my code leads me to believe that it is unnecessarily verbose and/or inefficient. I am building a command line Mastermind game, and part of the project is to provide feedback for a user's guess. In the game, the codemaker creates a secret code with four "pegs" of any of the six predefined colors.

The codebreaker has 12 chances to guess the correct code while gradually improving their guess with the help of feedback from the codemaker each time. The feedback consists of four "key pegs", which must be either black or white.

The codemaker puts down a black peg to indicate that the player chose the correct position and the correct color. A white key peg indicates that the user selected a color that exists in the secret code but is in the wrong position.

However, only one white peg is put down for each correct color guess, even if there is many of that color in the secret code.

For example, if:

secret_code = ["Red", "Blue", "Green", "Red"]
guess = ["Blue", "Green", "Red", "Red"],
#=> feedback is ["White, "White", "Black", "White"] in any order

Here is another example:

secret_code = ["Red", "Blue", "Red", "Red"]
guess = ["Blue", "Red", "Green", "Green"]

p computer.provide_feedback(secret_code, guess)
#=> ["White", "White", " ", " "]

The first step of my code is to turn the two arrays into hashes with the colors as values and an array of their indices as the values. This way I can compare the frequency and position of each color in each array.

The next step is to iterate through the guess hash, and for each iteration create an intersection array that contains the elements that exist in both. The number of intersected elements determines the number of black pegs to be placed into the feedback array.

To find the number of white pegs, the program checks if the array for the color indices in the guess array is less in size than the secret_code indices to avoid counting more of that color than is in the secret_code. Then the program finds the number of elements left over that aren't intersected, and that determines the number of white pegs.

Here is the code for this specific portion of my game:

def provide_feedback(secret_code, guess)
  secret_indices = hash_of_indices(secret_code)
  guess_indices = hash_of_indices(guess)
  feedback = format_feedback(calculate_feedback(secret_indices, guess_indices))
  feedback
end

def hash_of_indices(array)
  array.each_with_index.inject(Hash.new {Array.new}) do |hash, (elem, i)|
    hash[elem] += [i]
    hash
  end
end

def calculate_feedback(secret_hash, guess_hash)
  feedback = []
  black = "Black"
  white = "White"
  guess_hash.each do |k, v|
    intersection = (v & secret_hash[k])
    intersection.length.times { feedback << black }
    if v.length <= secret_hash[k].length
      (v.length - intersection.length).times { feedback << white }
    end
  end
  feedback
end

def format_feedback(feedback)
  formatted = feedback
  formatted << " " until formatted.length == 4
  formatted
end
\$\endgroup\$
2
\$\begingroup\$

There are definitely some inefficiencies in your code, but it's a good start. The first thing I'd say is that there is no reason for you to create variables black and white in calculate_feedback. All they hold is the names of the colors and it's no more efficient. The issue is that you seemed to have started to code this before fully thinking of the implementation.

From a basic perspective, here are the rules:

  • Guesser has 12 guesses
  • Each guess gets compared to the correct code
  • If the guess is the correct color in the correct position a black peg comes up in that spot, otherwise a white peg comes up (at most once per color).

So, let's break this down into an algorithm.

  1. Create secret code and create counter for user's guesses
  2. Get user's guess
  3. Compare user's guess to secret code.
  4. Go back to step 2 until the user either guessed the code correctly or has hit 12 guesses.

The biggest part of the algorithm to focus on is part 3, which is comparing the user's guess to the secret code. Luckily, because you're using Ruby, you have some great resources for this. Let's break this step down into two parts.

First: Find out how many black pegs should be there. This can be done with one line in ruby.

black = code.zip(guess).count{|i| i.inject(:eql?)}

This might look a bit confusing but what it does is it takes the secret code (represented by the array code and zips it into an array with guess. So for the above example code.zip(guess) would give us [["Red", "Blue"], ["Blue", "Green"], ["Green", "Red"], ["Red", "Red"]]. Then count{|i| i.inject(:eql?)} means count all of the arrays within the array where the elements are equal. This is only true for ["Red", "Red"] and so it gives us a count of 1 which is the number of black pegs to be returned. Now, we have to talk about the white pegs. Because there can be at most 1 per color guessed, we should consider eliminating duplicates in the array of the user's guess. Thus, we can do this

white = guess.uniq.count{|i| code.include?(i)}

So great, with only two lines we have the number of white and black pegs we need for each turn. Now let's put this all together into a concise program.

def provide_feedback(code, guess)
  black = code.zip(guess).count{|i| i.inject(:eql?)}
  white = guess.uniq.count{|i| code.include?(i)}
  result = Array.new(4, '')
  black.times { result.unshift('black').pop }
  white.times { result.unshift('white').pop }
  result
end

Of course, this is still a bit more verbose than I'd prefer. Why not tighten it up a bit more by removing the black and white variables all together.

def provide_feedback(code, guess)
  result = Array.new(4, '')
  code.zip(guess).each{|i| result.unshift('black').pop if i.inject(:eql?)}
  guess.uniq.each{|i| result.unshift('white').pop if code.include?(i) && result[-1] == ''}
  result
end

That's the most concise and Ruby way I could think of.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.