soma.cell

Conway's Game of Life — one generation step on a fixed grid

The grid is a flat row-major list of 0/1 over an r×c board. step() applies the B3/S23 rule with bounded (non-wrapping) neighbour counts. A blinker oscillates with period 2; a block is still life. Run: soma run conway_life.cell --signal demo Test: soma test conway_life.cell

A Soma program from the games part of the verified corpus: 82 lines, cells ConwayLife, ConwayTests. It passes soma check with soma 3.2.4.

How it is built

The cell ConwayLife declares 4 signals in its face: neighbors(grid: List, r: Int, c: Int, y: Int, x: Int) -> Int, step(grid: List, r: Int, c: Int) -> List, alive_count(grid: List) -> Int and demo() -> List. Handlers: neighbors (line 21), alive_count (line 37), step (line 41) and demo (line 57).

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

conway_life.cell

// ============================================================================
// Conway's Game of Life — one generation step on a fixed grid
// ============================================================================
//
// The grid is a flat row-major list of 0/1 over an r×c board. step() applies
// the B3/S23 rule with bounded (non-wrapping) neighbour counts. A blinker
// oscillates with period 2; a block is still life.
//
// Run:  soma run conway_life.cell --signal demo
// Test: soma test conway_life.cell

cell ConwayLife {
    face {
        signal neighbors(grid: List, r: Int, c: Int, y: Int, x: Int) -> Int
        signal step(grid: List, r: Int, c: Int) -> List
        signal alive_count(grid: List) -> Int
        signal demo() -> List
    }

    // Count live cells in the 8-neighbourhood of (y,x); edges are dead.
    on neighbors(grid: List, r: Int, c: Int, y: Int, x: Int) {
        let n = 0
        for dy in range(-1, 2) {
            for dx in range(-1, 2) {
                if dy != 0 || dx != 0 {
                    let ny = y + dy
                    let nx = x + dx
                    if ny >= 0 && ny < r && nx >= 0 && nx < c {
                        n = n + grid[ny * c + nx]
                    }
                }
            }
        }
        return n
    }

    on alive_count(grid: List) {
        return sum(grid)
    }

    on step(grid: List, r: Int, c: Int) {
        let out = []
        for y in range(0, r) {
            for x in range(0, c) {
                let live = grid[y * c + x]
                let n = neighbors(grid, r, c, y, x)
                // B3/S23: born on 3, survives on 2 or 3.
                let next = 0
                if live == 1 && (n == 2 || n == 3) { next = 1 }
                if live == 0 && n == 3 { next = 1 }
                out = push(out, next)
            }
        }
        return out
    }

    on demo() {
        // vertical blinker in a 3x3 grid → horizontal blinker
        let g = [0, 1, 0, 0, 1, 0, 0, 1, 0]
        return step(g, 3, 3)
    }
}

cell test ConwayTests {
    rules {
        // Vertical blinker (column) becomes a horizontal blinker (middle row).
        // 3x3, live at (0,1),(1,1),(2,1):
        assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[3] == 1
        assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[4] == 1
        assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[5] == 1
        assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[1] == 0
        // Two steps returns to the original blinker (period 2).
        assert step(step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3), 3, 3)[1] == 1
        // A 2x2 block in a 4x4 grid is a still life.
        // block at (1,1),(1,2),(2,1),(2,2):
        assert alive_count(step(
            [0,0,0,0, 0,1,1,0, 0,1,1,0, 0,0,0,0], 4, 4)) == 4
        // neighbour count at the centre of the block is 3 (the other 3 block cells)
        assert neighbors([0,0,0,0, 0,1,1,0, 0,1,1,0, 0,0,0,0], 4, 4, 1, 1) == 3
    }
}

What the toolchain says

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

$ soma check conway_life.cell
✓ All checks passed.
$ soma test conway_life.cell
test ConwayTests ...
  ✓ assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[3] == 1
  ✓ assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[4] == 1
  ✓ assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[5] == 1
  ✓ assert step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3)[1] == 0
  ✓ assert step(step([0, 1, 0, 0, 1, 0, 0, 1, 0], 3, 3), 3, 3)[1] == 1
  ✓ assert alive_count(step( [0,0,0,0, 0,1,1,0, 0,1,1,0, 0,0,0,0], 4, 4)) == 4
  ✓ assert neighbors([0,0,0,0, 0,1,1,0, 0,1,1,0, 0,0,0,0], 4, 4, 1, 1) == 3

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

push, range, sum

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