Home > Software engineering >  I am trying to make "Game of Set" but don't know how to correctly check the set
I am trying to make "Game of Set" but don't know how to correctly check the set

Time:02-10

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.

  • Related