1

First, some necessary background. I'm trying to make a number-based version of the game Mastermind as a way of learning to code in Ruby. My code basically works like this:

  1. The computer generates an array (@computer_sequence) of 4 random numbers from 1-5
  2. The user enters a 4 digit sequence, which winds up in an array called @user_array.
  3. A method, called compare, iterates through @user_array, comparing the value and index of each number to those in @computer_sequence. The program then tells the user how many of their numbers have the correct value and the correct position, or how many numbers have the correct value only.

The problem: If there are multiple instances of a number in an array, they get the same index, right? Like if I have the array [1, 3, 3, 4], the number three has an index of 1, even though there are two 3s. For this program to work, though, each number has to have a unique position (is index even the word I want here?) in the array, even if the number occurs multiple times. Does that make sense?

Also, here's the code for the compare method:

def compare
    value_only = 0
    value_and_place = 0
    puts "The computer's values are: #{@computer_sequence}"
    puts "The user's values are: #{@user_array}"


    @user_array.each do |candidate|
        @computer_sequence.each do |computer_number|
            if candidate == computer_number && @user_array.index(candidate) == @computer_sequence.index(computer_number)
                value_and_place +=1
            elsif candidate == computer_number && @user_array.index(candidate) != @computer_sequence.index(computer_number)
                value_only +=1
            end
        end
    end
Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121

3 Answers3

2

Suppose

n = 4
computer = Array.new(n) { [1,2,3,4,5].sample }
  #=> [3, 2, 3, 3]
user_digits = [2, 4, 2, 3]

First compute pairs of elements at the same index of computer and user_digits.

pairs = computer.zip(user_digits)
  #=> [[3, 2], [2, 4], [3, 2], [3, 3]]

Compute number of values that match at the same position

pairs.count { |c,u| c==u }
  #=> 1

Compute number of values that match at different positions

First remove the matches at the same positions of computer and user_digits.

comp, users = pairs.reject { |c,u| c==u }.transpose
  #=> [[3, 2, 3], [2, 4, 2]] 

meaning

comp  #=> [3, 2, 3] 
users #=> [2, 4, 2] 

Now step through users removing the first matching element in comp (if there is one).

users.each do |n|
  i = comp.index(n)
  comp.delete_at(i) if i
end

So now:

comp #=> [3,3] 

meaning that the number of elements that match at different positions is:

users.size-comp.size
  #=> 1 

Notice that we could alternatively compute the number of values that match at the same position as

n - users.size

For n equal to 4 this doesn’t offer any significant time saving, but it would if we had a problem with the same structure and n were large.

Alternative calculation

After computing

comp, users = pairs.reject { |c,u| c==u }.transpose

we could write

users.size - comp.difference(users).size
  #=> 1

where Array#difference is as I defined it in my answer here.

Here

comp.difference(users)
  #=> [3,3]
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • Thanks a lot. I actually find this approach a lot more direct than using the nested `if` statements, but I am studying your example carefully to understand the syntax, especially the `zip` method, which I hadn't encountered before. – equator.bear Apr 19 '16 at 19:41
1

No, equal elements in an array don't have the same index. Maybe you're thinking that because Array#index only returns the index of the first element equal to its argument. But there are many ways to see that other equal elements have their own indexes. For example,

a = [1, 3, 3, 4]
a[1] == 3 # true
a[2] == 3 # also true

Aside from that issue, your algorithm doesn't quite match the rules of Mastermind. If there is one three in the computer's sequence and the player guesses two threes, both in different positions than the three in the computer's sequence, the player should be told that only one element of their sequence matches the computer's sequence in value but not position.

Given the above, plus that I think it would be clearer to calculate the two numbers separately, I'd do it like this:

value_and_place = 4.times { |i| @user_array[i] == @computer_sequence[i] }
value_only = (@user_array & @computer_sequence).length - value_and_place

That's less efficient than the approach you're taking, but CPU efficiency isn't important for 4-element arrays.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
  • Thanks for your feedback. Perhaps we learned different versions of the game? In the example you gave, I would have the computer tell the player that two of their numbers had the right value, but weren't in the right place. – equator.bear Apr 18 '16 at 02:04
  • I actually wasn't sure before I answered; it's been a long time since I played the game. So I checked Wikipedia, which has the rule I cited. Come to think of it, I should link to that article. – Dave Schweisguth Apr 18 '16 at 04:19
0

You can pass in the index value to your loop for each candidate using the each_with_index method. So when the first 3 is passed in, index will be 1 and when the second 3 is passed in, index will be 2.

The problem with using .index(candidate) is it returns the first index.

Try this:

@user_array.each_with_index do |candidate, index|
    @computer_sequence.each do |computer_number|
        if candidate == computer_number && candidate == @computer_sequence[index]
            value_and_place +=1
        elsif candidate == computer_number && candidate != @computer_sequence[index]
            value_only +=1
        end
    end
end
Joseph
  • 760
  • 1
  • 4
  • 9
  • Yes! I think I get what you're saying. Am I correctly assuming that the `.each_with_index` automatically take the actual index as an argument? Also, I'm a little confused about why your `if` statement has `@computer_sequence[index]`. If you did `.each_with_index` by iterating through the `@user_array`, how would this evaluate the index of the number in the `@computer_sequence`? – equator.bear Apr 18 '16 at 02:14
  • `each_with_index` passes in each element and its array index value as a second variable to the loop, which you can assign whatever you want after `do`, e.g., `|candidate, index|` or `|candidate, i|`. – Joseph Apr 18 '16 at 02:34
  • `@computer_sequence[index]`does not evaluate the index number in `@computer_sequence` -- it references the value in `@computer_sequence` at that index number. So if you want to check if `candidate` equals the corresponding value (by position) in `@computer_sequence` you can use this syntax. In your example array `[1, 3, 3, 4]` the first 3 is passed in with `index` value of 1 and the second one is passed in with the `index` value of 2 -- `candidate == @computer_sequence[index]` compares the first 3 against the 2nd element of `@computer_sequence` and the second 3 against the 3rd. – Joseph Apr 18 '16 at 02:37
  • Oh, that makes a lot of sense. Still getting the hang of the syntax. Thanks so much! – equator.bear Apr 18 '16 at 02:50