soma.cell

Yahtzee — score a roll of five dice in any category counts() buckets the five dice into face frequencies

The category scorers read those buckets: upper boxes, n-of-a-kind, full house, straights, and the 50-point Yahtzee. All pure integer logic. Run: soma run yahtzee.cell --signal demo Test: soma test yahtzee.cell

A Soma program from the games part of the verified corpus: 99 lines, cells Yahtzee, YahtzeeTests. It passes soma check with soma 3.2.4.

How it is built

The cell Yahtzee declares 7 signals in its face: counts(dice: List) -> List, n_of_a_kind(dice: List, n: Int) -> Int, full_house(dice: List) -> Int, small_straight(dice: List) -> Int, yahtzee(dice: List) -> Int, chance(dice: List) -> Int and demo() -> Map. Handlers: counts (line 24), n_of_a_kind (line 33), full_house (line 39), small_straight (line 48), yahtzee (line 59), chance (line 65) and demo (line 69).

The file also holds 1 test cell (YahtzeeTests) with 10 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

yahtzee.cell

// ============================================================================
// Yahtzee — score a roll of five dice in any category
// ============================================================================
//
// counts() buckets the five dice into face frequencies. The category scorers
// read those buckets: upper boxes, n-of-a-kind, full house, straights, and
// the 50-point Yahtzee. All pure integer logic.
//
// Run:  soma run yahtzee.cell --signal demo
// Test: soma test yahtzee.cell

cell Yahtzee {
    face {
        signal counts(dice: List) -> List
        signal n_of_a_kind(dice: List, n: Int) -> Int
        signal full_house(dice: List) -> Int
        signal small_straight(dice: List) -> Int
        signal yahtzee(dice: List) -> Int
        signal chance(dice: List) -> Int
        signal demo() -> Map
    }

    // Frequency of each face 1..6, indexed 0..5.
    on counts(dice: List) {
        let c = [0, 0, 0, 0, 0, 0]
        for d in dice {
            c[d - 1] = c[d - 1] + 1
        }
        return c
    }

    // Sum of all dice if some face appears >= n times, else 0.
    on n_of_a_kind(dice: List, n: Int) {
        let c = counts(dice)
        if any(c, x => x >= n) { return sum(dice) }
        return 0
    }

    on full_house(dice: List) {
        let c = counts(dice)
        let has3 = any(c, x => x == 3)
        let has2 = any(c, x => x == 2)
        if has3 && has2 { return 25 }
        return 0
    }

    // Four consecutive faces present → 30 points.
    on small_straight(dice: List) {
        let c = counts(dice)
        let runs = [
            c[0] >= 1 && c[1] >= 1 && c[2] >= 1 && c[3] >= 1,
            c[1] >= 1 && c[2] >= 1 && c[3] >= 1 && c[4] >= 1,
            c[2] >= 1 && c[3] >= 1 && c[4] >= 1 && c[5] >= 1
        ]
        if any(runs, r => r) { return 30 }
        return 0
    }

    on yahtzee(dice: List) {
        let c = counts(dice)
        if any(c, x => x == 5) { return 50 }
        return 0
    }

    on chance(dice: List) {
        return sum(dice)
    }

    on demo() {
        let d = [3, 3, 3, 5, 5]
        return map(
            "full_house", full_house(d),
            "three_kind", n_of_a_kind(d, 3),
            "chance", chance(d)
        )
    }
}

cell test YahtzeeTests {
    rules {
        // five of a kind
        assert yahtzee([4, 4, 4, 4, 4]) == 50
        assert yahtzee([4, 4, 4, 4, 5]) == 0
        // full house 3+2
        assert full_house([3, 3, 3, 5, 5]) == 25
        assert full_house([3, 3, 3, 3, 5]) == 0
        // three of a kind scores the whole roll's sum
        assert n_of_a_kind([2, 2, 2, 6, 1], 3) == 13
        assert n_of_a_kind([2, 2, 3, 6, 1], 3) == 0
        // four of a kind
        assert n_of_a_kind([5, 5, 5, 5, 1], 4) == 21
        // small straight 2-3-4-5
        assert small_straight([2, 3, 4, 5, 5]) == 30
        assert small_straight([1, 1, 4, 5, 6]) == 0
        // chance is just the total
        assert chance([1, 2, 3, 4, 5]) == 15
    }
}

What the toolchain says

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

$ soma check yahtzee.cell
✓ All checks passed.
$ soma test yahtzee.cell
test YahtzeeTests ...
  ✓ assert yahtzee([4, 4, 4, 4, 4]) == 50
  ✓ assert yahtzee([4, 4, 4, 4, 5]) == 0
  ✓ assert full_house([3, 3, 3, 5, 5]) == 25
  ✓ assert full_house([3, 3, 3, 3, 5]) == 0
  ✓ assert n_of_a_kind([2, 2, 2, 6, 1], 3) == 13
  ✓ assert n_of_a_kind([2, 2, 3, 6, 1], 3) == 0
  ✓ assert n_of_a_kind([5, 5, 5, 5, 1], 4) == 21
  ✓ assert small_straight([2, 3, 4, 5, 5]) == 30
  ✓ assert small_straight([1, 1, 4, 5, 6]) == 0
  ✓ assert chance([1, 2, 3, 4, 5]) == 15

10 tests: 10 passed, 0 failed
$ soma verify yahtzee.cell
Verifying yahtzee.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

any, map, sum

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