Hi so I just started to learn coding and ruby is my first language. I am trying to make a game called "Game of Set" the rules can be checked in here or wikipedia
I am able to get users cards and enter them in different arrays. However, I have been spending a lot of time trying to figure out how to validate the set. I tried to compare the arrays index by index but the result is not what I wanted. Example:
first = ['two', 'purple', 'stripe', 'diamond']
second = ['one', 'purple', 'solid', 'diamond']
third = ['three', 'purple', 'empty', 'diamond']
this should be correct because all are purple, all are diamond, different number, and different filters.
first = ['one', 'green', 'stripe', 'squiggle']
second = ['one', 'purple', 'solid', 'diamond']
third = ['two', 'green', 'empty', 'oval']
This is wrong because because two have the same number and one does not, similarly with the color.
This is the game of set that I want to achieve here
Please help thank you!
CodePudding user response:
In this game, a __set is defined as:
Several games can be played with these cards, all involving the concept of a set. A set consists of three cards satisfying all of these conditions:
- They all have the same number or have three different numbers.
- They all have the same shape or have three different shapes.
- They all have the same shading or have three different shadings.
- They all have the same color or have three different colors.
-- Wikipedia
That can be checked with a one-liner:
first = ['two', 'purple', 'stripe', 'diamond']
second = ['one', 'purple', 'solid', 'diamond']
third = ['three', 'purple', 'empty', 'diamond']
first.zip(second, third).all? { |array| [1,3].include?(array.uniq.length) }
#=> true
first = ['one', 'green', 'stripe', 'squiggle']
second = ['one', 'purple', 'solid', 'diamond']
third = ['two', 'green', 'empty', 'oval']
first.zip(second, third).all? { |array| [1,3].include?(array.uniq.length) }
#=> false
How does this work in detail?
first
.zip(second, third) #=> [["two", "one", "three"], ["purple", "purple", "purple"], ["stripe", "solid", "empty"], ["diamond", "diamond", "diamond"]]
.all? { |array|
# do all nested arrays comply with the set conditions and
# contain only the same element or three different ones?
[1,3].include?(array.uniq.length)
}
CodePudding user response:
We are given
first = ['two', 'purple', 'stripe', 'diamond']
second = ['one', 'purple', 'solid', 'diamond']
third = ['three', 'purple', 'empty', 'diamond']
Let
arr = [first, second, third]
#=> [["two", "purple", "stripe", "diamond"],
# ["one", "purple", "solid", "diamond"],
# ["three", "purple", "empty", "diamond"]]
We wish to check if every "column" of arr
contains either the same string for every "row" or a different string in each row.
Ruby doesn't have a concept of array "columns" and "rows", only elements. arr
has three elements, namely the arrays first
, second
and third
. We can, however, send the method Array#transpose1 to the object arr
to obtain something we can use:
a = arr.transpose
#=> [["two", "one", "three"],
# ["purple", "purple", "purple"],
# ["stripe", "solid", "empty"],
# ["diamond", "diamond", "diamond"]]
We now just have to see if all four elements of a
pass the test.
For one, we wish to know if
b = a[1]
#=> ["purple", "purple", "purple"]
satisfies the required property. A convenient way of doing that is to compute the unique elements of the array b
with the method Array#uniq:
c = b.uniq
#=> ["purple"]
b
meets the requirement if the number of elements in c
equals either 1
or the number of elements in b
, which is b.size #=> 3
. As c.size #=> 1
, c
meets the requirement.
Notice that b.size
is the same as arr.size
.
We can put this together in a method:
def valid?(arr)
nbr_elements = arr.size
a = arr.transpose
a.all? do |b|
sz = b.uniq.size
sz == 1 || sz == nbr_elements
end
end
See Enumerable#all?2
Let's try it.
valid?(arr)
#=> true
To illustrate the calculations performed by valid?
I will run it again after salting the method with puts
statements.
def valid?(arr)
nbr_elements = arr.size
puts "nbr_elements = #{nbr_elements}"
a = arr.transpose
a.all? do |b|
sz = b.uniq.size
puts "b = #{b}"
puts " sz = #{sz}"
puts " #{sz == 1} || #{sz == nbr_elements} => #{sz == 1 || sz == nbr_elements}"
sz == 1 || sz == nbr_elements
end
end
valid?(arr)
#=> true
nbr_elements = 3
b = ["two", "one", "three"]
sz = 3
false || true => true
b = ["purple", "purple", "purple"]
sz = 1
true || false => true
b = ["stripe", "solid", "empty"]
sz = 3
false || true => true
b = ["diamond", "diamond", "diamond"]
sz = 1
true || false => true
Let's also try the second example.
arr = [["one", "green", "stripe", "squiggle"],
["one", "purple", "solid", "diamond" ],
["two", "green", "empty", "oval" ]]
valid?(arr)
#=> false
nbr_elements = 3
b = ["one", "one", "two"]
sz = 2
false || false => false
In practice we probably would not have the statement a = arr.transpose
but instead would chain arr.transpose
to all?
:
def valid?(arr)
nbr_elements = arr.size
arr.transpose.all? do |b|
sz = b.uniq.size
sz.zero? || sz == nbr_elements
end
end
1 When all elements of an array are arrays having the same number of elements, the methods transpose
and Array#zip can be used more-or-less interchangeably. @spickerman has used zip
in his answer.
2 Enumerable
is a module containing instance methods. Any class that includes that module (don't worry about the details now) has use of all of Enumerable
's methods, more or less as though they had been defined in the including class.