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.
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 = truemastermind.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
$ 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)