soma.cell

Mastermind — score a guess with black and white pegs

Black pegs = right colour in the right position. White pegs = right colour in the wrong position, counted with multiplicity via colour frequency matching (the classic rule). Pure scoring over fixed-length code lists. Run: soma run mastermind.cell --signal demo Test: soma test mastermind.cell

A Soma program from the games part of the verified corpus: 92 lines, cells Mastermind, MastermindTests. It passes soma check with soma 3.2.4.

How it is built

The cell Mastermind declares 3 signals in its face: score(secret: List, guess: List) -> Map, solved(secret: List, guess: List) -> Bool and demo() -> Map. Handlers: score (line 19), index_of_in (line 47), without_index (line 56), solved (line 64) and demo (line 68).

The file also holds 1 test cell (MastermindTests) with 9 assertions, run by soma test.

Generated from soma describe on this file.

The source

soma.toml

[package]
name = "games-corpus"
version = "0.1.0"
description = "Training corpus: games & simulations"

# The turn/phase machine is intentionally cyclic (a game loops through
# phases each round) with a `* -> over` escape, so we verify only that no
# reachable state deadlocks — liveness to a terminal is not the contract here.
[verify]
deadlock_free = true

mastermind.cell

// ============================================================================
// Mastermind — score a guess with black and white pegs
// ============================================================================
//
// Black pegs = right colour in the right position. White pegs = right colour
// in the wrong position, counted with multiplicity via colour frequency
// matching (the classic rule). Pure scoring over fixed-length code lists.
//
// Run:  soma run mastermind.cell --signal demo
// Test: soma test mastermind.cell

cell Mastermind {
    face {
        signal score(secret: List, guess: List) -> Map
        signal solved(secret: List, guess: List) -> Bool
        signal demo() -> Map
    }

    on score(secret: List, guess: List) {
        let n = len(secret)
        let black = 0
        // count exact matches, and bucket the non-matched colours on each side
        let sleft = []
        let gleft = []
        for i in range(0, n) {
            if secret[i] == guess[i] {
                black = black + 1
            } else {
                sleft = push(sleft, secret[i])
                gleft = push(gleft, guess[i])
            }
        }
        // white = for each leftover guess colour, a match among leftover secret
        let white = 0
        let pool = sleft
        for g in gleft {
            let idx = index_of_in(pool, g)
            if idx >= 0 {
                white = white + 1
                pool = without_index(pool, idx)
            }
        }
        return map("black", black, "white", white)
    }

    // first index of value v in list xs, or -1
    on index_of_in(xs: List, v: String) {
        let found = -1
        for i in range(0, len(xs)) {
            if xs[i] == v && found == -1 { found = i }
        }
        return found
    }

    // copy of xs with element i removed
    on without_index(xs: List, i: Int) {
        let out = []
        for j in range(0, len(xs)) {
            if j != i { out = push(out, xs[j]) }
        }
        return out
    }

    on solved(secret: List, guess: List) {
        return score(secret, guess).black == len(secret)
    }

    on demo() {
        // secret RGBY, guess GGRR
        return score(["R", "G", "B", "Y"], ["G", "G", "R", "R"])
    }
}

cell test MastermindTests {
    rules {
        // exact match → 4 black, 0 white
        assert score(["R","G","B","Y"], ["R","G","B","Y"]).black == 4
        assert score(["R","G","B","Y"], ["R","G","B","Y"]).white == 0
        assert solved(["R","G","B","Y"], ["R","G","B","Y"]) == true
        // secret RGBY vs guess GGRR: pos2 G exact (1 black);
        // leftover secret R,B,Y vs guess G,R,R → one R matches (1 white)
        assert score(["R","G","B","Y"], ["G","G","R","R"]).black == 1
        assert score(["R","G","B","Y"], ["G","G","R","R"]).white == 1
        // duplicate colours don't over-count whites
        assert score(["R","R","G","G"], ["G","G","G","G"]).black == 2
        assert score(["R","R","G","G"], ["G","G","G","G"]).white == 0
        // total order: a near miss, all whites
        assert score(["A","B","C"], ["C","A","B"]).black == 0
        assert score(["A","B","C"], ["C","A","B"]).white == 3
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check mastermind.cell
✓ All checks passed.
$ soma test mastermind.cell
test MastermindTests ...
  ✓ assert score(["R","G","B","Y"], ["R","G","B","Y"]).black == 4
  ✓ assert score(["R","G","B","Y"], ["R","G","B","Y"]).white == 0
  ✓ assert solved(["R","G","B","Y"], ["R","G","B","Y"]) == true
  ✓ assert score(["R","G","B","Y"], ["G","G","R","R"]).black == 1
  ✓ assert score(["R","G","B","Y"], ["G","G","R","R"]).white == 1
  ✓ assert score(["R","R","G","G"], ["G","G","G","G"]).black == 2
  ✓ assert score(["R","R","G","G"], ["G","G","G","G"]).white == 0
  ✓ assert score(["A","B","C"], ["C","A","B"]).black == 0
  ✓ assert score(["A","B","C"], ["C","A","B"]).white == 3

9 tests: 9 passed, 0 failed
$ soma verify mastermind.cell
Verifying mastermind.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)

Builtins used

len, map, push, range

Source file: /corpus/games/mastermind.cell · examples/corpus/games/mastermind.cell