Command-line 'Set' Game in Kotlin 3

February 11, 2025

Part 3: The Business Logic Layer For Set Evaluation

What Makes a Set and What Doesn't

While I tried my best to brute force this with code, I came to the conclusion to ask GPT yet again, and after some prompting it gave me a working answer. Pretty cool. I then demanded over and over it be simplified! Finally, I got something I could really read and see how it works.

fun areCardsASet(card1: Card, card2: Card, card3: Card): Boolean {
    return card1.shape.allSameOrAllDifferent(card2.shape, card3.shape) &&
            card1.color.allSameOrAllDifferent(card2.color, card3.color) &&
            card1.fill.allSameOrAllDifferent(card2.fill, card3.fill) &&
            card1.count.allSameOrAllDifferent(card2.count, card3.count)
}

fun CardFaceValue.allSameOrAllDifferent(value2: CardFaceValue, value3: CardFaceValue): Boolean {
    return (this == value2 && value2 == value3) || (this != value2 && value2 != value3 && this != value3)
}

How nice and simple this looks, but how deceptive the second piece of logic really is. What it's doing is simply brute forcing across our characteristics. The second function has some syntactical sugar but does exactly what it says, returns true if they are all the same or true if they are all different. The key here is the nesting and != negations.

Backtracking through every possible comparison

Once we deal our cards then what? We simply loop through our grid and compare each card to each other card in sets of threes. This wasn't quite obvious to me, but here we have a simple x - y loop nested in an outer loop. The outer for will break when we've encountered the first Set.

    val dealCards = createHashMap().shuffleAndDeal12()

    // Brute force to find a valid set of three cards
    outer@for (i in 0 until dealCards.size - 2) {
        for (j in i + 1 until dealCards.size - 1) {
            for (k in j + 1 until dealCards.size) {
                val card1 = dealCards[i].value.first
                val card2 = dealCards[j].value.first
                val card3 = dealCards[k].value.first
                if (areCardsASet(card1, card2, card3)) {
                    println("\nFound a valid set: ${card1.toCardStr()}, ${card2.toCardStr()}, ${card3.toCardStr()}")
                    break@outer
                }
            }
        }
    }

This was a surprise to me that this worked. Running the main method in MainSetEvaluation.kt will produce a String representation of a matching set that was shuffled and produced. Sometimes it doesn't find any, but this is very rare.

Check part 4 to see how we actually play our set game!

© 2025Brooks DuBois™