soma.cell

2048 — slide-and-merge a single row (the core move primitive)

slide_left() compacts a row toward the left, merging equal adjacent tiles exactly once per move (left-to-right), and reports the points scored (sum of merged tiles). A full board move is four row slides; this is that primitive. Run: soma run game_2048.cell --signal demo Test: soma test game_2048.cell

A Soma program from the games part of the verified corpus: 77 lines, cells Game2048, Game2048Tests. It passes soma check with soma 3.2.4.

How it is built

The cell Game2048 declares 3 signals in its face: compact(row: List) -> List, slide_left(row: List) -> Map and demo() -> Map. Handlers: compact (line 20), slide_left (line 28) and demo (line 51).

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

game_2048.cell

// ============================================================================
// 2048 — slide-and-merge a single row (the core move primitive)
// ============================================================================
//
// slide_left() compacts a row toward the left, merging equal adjacent tiles
// exactly once per move (left-to-right), and reports the points scored (sum of
// merged tiles). A full board move is four row slides; this is that primitive.
//
// Run:  soma run game_2048.cell --signal demo
// Test: soma test game_2048.cell

cell Game2048 {
    face {
        signal compact(row: List) -> List
        signal slide_left(row: List) -> Map
        signal demo() -> Map
    }

    // Drop zeros, preserving order of non-zero tiles.
    on compact(row: List) {
        let out = []
        for v in row {
            if v != 0 { out = push(out, v) }
        }
        return out
    }

    on slide_left(row: List) {
        let tiles = compact(row)
        let merged = []
        let score = 0
        let i = 0
        while i < len(tiles) {
            if i + 1 < len(tiles) && tiles[i] == tiles[i + 1] {
                let v = tiles[i] * 2
                merged = push(merged, v)
                score = score + v
                i = i + 2
            } else {
                merged = push(merged, tiles[i])
                i = i + 1
            }
        }
        // pad back to the original width with zeros
        while len(merged) < len(row) {
            merged = push(merged, 0)
        }
        return map("row", merged, "score", score)
    }

    on demo() {
        // [2,2,4,0] → [4,4,0,0] scoring 4
        return slide_left([2, 2, 4, 0])
    }
}

cell test Game2048Tests {
    rules {
        // basic merge of the leading pair
        assert slide_left([2, 2, 4, 0]).row[0] == 4
        assert slide_left([2, 2, 4, 0]).row[1] == 4
        assert slide_left([2, 2, 4, 0]).score == 4
        // a tile only merges once: [2,2,2,2] → [4,4,0,0] scoring 8
        assert slide_left([2, 2, 2, 2]).row[0] == 4
        assert slide_left([2, 2, 2, 2]).row[1] == 4
        assert slide_left([2, 2, 2, 2]).row[2] == 0
        assert slide_left([2, 2, 2, 2]).score == 8
        // gaps collapse, no merge
        assert slide_left([0, 4, 0, 2]).row[0] == 4
        assert slide_left([0, 4, 0, 2]).row[1] == 2
        assert slide_left([0, 4, 0, 2]).score == 0
        // already-merged trailing pair: [4,2,2,0] → [4,4,0,0]
        assert slide_left([4, 2, 2, 0]).score == 4
        assert compact([0, 0, 8, 0])[0] == 8
    }
}

What the toolchain says

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

$ soma check game_2048.cell
✓ All checks passed.
$ soma test game_2048.cell
test Game2048Tests ...
  ✓ assert slide_left([2, 2, 4, 0]).row[0] == 4
  ✓ assert slide_left([2, 2, 4, 0]).row[1] == 4
  ✓ assert slide_left([2, 2, 4, 0]).score == 4
  ✓ assert slide_left([2, 2, 2, 2]).row[0] == 4
  ✓ assert slide_left([2, 2, 2, 2]).row[1] == 4
  ✓ assert slide_left([2, 2, 2, 2]).row[2] == 0
  ✓ assert slide_left([2, 2, 2, 2]).score == 8
  ✓ assert slide_left([0, 4, 0, 2]).row[0] == 4
  ✓ assert slide_left([0, 4, 0, 2]).row[1] == 2
  ✓ assert slide_left([0, 4, 0, 2]).score == 0
  ✓ assert slide_left([4, 2, 2, 0]).score == 4
  ✓ assert compact([0, 0, 8, 0])[0] == 8

12 tests: 12 passed, 0 failed
$ soma verify game_2048.cell
Verifying game_2048.cell...

Game2048/termination:

  ⚠ handler `slide_left`: while-loop without provable termination bound (write `while [loop_bound(N)] cond { … }`, or a for-loop over a bounded range)
  ⚠ `demo` calls `Game2048.slide_left`, which may not terminate

0 passed, 2 warnings, 0 failures
Temporal: 0 passed, 0 failed
VERIFY OK

Builtins used

len, map, push

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