Set Puzzles are created with a deck of cards from the Set Game™. The object of the puzzle is to find sets of 3 cards in a rectangle of cards that have been dealt face up. 


There are 81 cards in a deck.
Each card contains a unique variation of the following four features: color, symbol, number and shading.

 there are three colors:
    red, green, purple

 there are three symbols:
    oval, squiggle, diamond

 there is a number of symbols on the card:
    one, two, three

 there are three shadings:
    solid, open, striped

Three cards form a set if each feature is either the same on each card, or is different on each card. For instance: all 3 cards are red, all 3 cards have a different symbol, all 3 cards have a different number of symbols, all 3 cards are striped.

There are two degrees of difficulty: basic and advanced. The basic mode deals 9 cards, that contain exactly 4 sets; the advanced mode deals 12 cards that contain exactly 6 sets.

When creating sets you may use the same card more than once.




Task
Write code that deals the cards (9 or 12, depending on selected mode) from a shuffled deck in which the total number of sets that could be found is 4 (or 6, respectively); and print the contents of the cards and the sets.

For instance:



DEALT 9 CARDS:

green, one, oval, striped
green, one, diamond, open
green, one, diamond, striped
green, one, diamond, solid
purple, one, diamond, open
purple, two, squiggle, open
purple, three, oval, open
red, three, oval, open
red, three, diamond, solid

CONTAINING 4 SETS:

green, one, oval, striped
purple, two, squiggle, open
red, three, diamond, solid


green, one, diamond, open
green, one, diamond, striped
green, one, diamond, solid


green, one, diamond, open
purple, two, squiggle, open
red, three, oval, open


purple, one, diamond, open
purple, two, squiggle, open
purple, three, oval, open


import "/dynamic" for Enum
import "/trait" for Comparable
import "/fmt" for Fmt
import "/str" for Str
import "/math" for Nums
import "/sort" for Sort
import "random" for Random
 
var Color   = Enum.create("Color",   ["RED", "GREEN", "PURPLE"])
var Symbol  = Enum.create("Symbol",  ["OVAL", "SQUIGGLE", "DIAMOND"])
var Number  = Enum.create("Number",  ["ONE", "TWO", "THREE"])
var Shading = Enum.create("Shading", ["SOLID", "OPEN", "STRIPED"])
var Degree  = Enum.create("Degree",  ["BASIC", "ADVANCED"])
 
class Card is Comparable {
    static zero { Card.new(Color.RED, Symbol.OVAL, Number.ONE, Shading.SOLID) }
 
    construct new(color, symbol, number, shading) {
        _color   = color
        _symbol  = symbol
        _number  = number
        _shading = shading
        _value   = color * 27 + symbol * 9 + number * 3 + shading
    }
 
    color   { _color }
    symbol  { _symbol }
    number  { _number }
    shading { _shading }
    value   { _value }
 
    compare(other) { (_value - other.value).sign }
 
    toString {
        return Str.lower(Fmt.swrite("$-8s$-10s$-7s$-7s",
            Color.members  [_color],  
            Symbol.members [_symbol],
            Number.members [_number],
            Shading.members[_shading]
        ))
    }
}
 
var createDeck = Fn.new {
    var deck = List.filled(81, null)
    for (i in 0...81) {
        var col = (i/27).floor
        var sym = (i/ 9).floor % 3
        var num = (i/ 3).floor % 3
        var shd = i % 3
        deck[i] = Card.new(col, sym, num, shd)
    }
    return deck
}
 
var rand = Random.new()
 
var isSet = Fn.new { |trio|
    var r1 = Nums.sum(trio.map { |c| c.color   }) % 3
    var r2 = Nums.sum(trio.map { |c| c.symbol  }) % 3
    var r3 = Nums.sum(trio.map { |c| c.number  }) % 3
    var r4 = Nums.sum(trio.map { |c| c.shading }) % 3
    return r1 + r2 + r3 + r4 == 0
}
 
var playGame = Fn.new { |degree|
    var deck = createDeck.call()
    var nCards = (degree == Degree.BASIC) ? 9 : 12
    var nSets = (nCards/2).floor
    var sets = List.filled(nSets, null)
    for (i in 0...nSets) sets[i] = [Card.zero, Card.zero, Card.zero]
    var hand = []
    while (true) {
        rand.shuffle(deck)
        hand = deck.take(nCards).toList
        var count = 0
        var hSize = hand.count
        var outer = false
        for (i in 0...hSize-2) {
            for (j in i+1...hSize-1) {
                for (k in j+1...hSize) {
                    var trio = [hand[i], hand[j], hand[k]]
                    if (isSet.call(trio)) {
                        sets[count] = trio
                        count = count + 1
                        if (count == nSets) {
                            outer = true
                            break
                        }
                    }
                }
                if (outer) break
            }
            if (outer) break
        }
        if (outer) break
    }
    Sort.quick(hand)
    System.print("DEALT %(nCards) CARDS:\n")
    System.print(hand.join("\n"))
    System.print("\nCONTAINING %(nSets) SETS:\n")
    for (s in sets) {
        Sort.quick(s)
        System.print(s.join("\n"))
        System.print()
    }
}
 
playGame.call(Degree.BASIC)
System.print()
playGame.call(Degree.ADVANCED)