Rock-paper-scissors — round judging and best-of-N scoring judge(a, b) returns "p1", "p2", or "tie" from the two throws
play_match scores a list of round pairs and reports the winner. Pure and deterministic. Run: soma run rock_paper_scissors.cell --signal demo Test: soma test rock_paper_scissors.cell
A Soma program from the games part of the verified corpus: 72 lines, cells RockPaperScissors, RpsTests. It passes soma check with soma 3.2.4.
How it is built
The cell RockPaperScissors declares 4 signals in its face: judge(a: String, b: String) -> String, beats(a: String, b: String) -> Bool, play_match(rounds: List) -> Map and demo() -> Map. Handlers: beats (line 20), judge (line 29), play_match (line 36) and demo (line 48).
The file also holds 1 test cell (RpsTests) with 6 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 = truerock_paper_scissors.cell
// ============================================================================
// Rock-paper-scissors — round judging and best-of-N scoring
// ============================================================================
//
// judge(a, b) returns "p1", "p2", or "tie" from the two throws. play_match
// scores a list of round pairs and reports the winner. Pure and deterministic.
//
// Run: soma run rock_paper_scissors.cell --signal demo
// Test: soma test rock_paper_scissors.cell
cell RockPaperScissors {
face {
signal judge(a: String, b: String) -> String
signal beats(a: String, b: String) -> Bool
signal play_match(rounds: List) -> Map
signal demo() -> Map
}
// Does throw a beat throw b?
on beats(a: String, b: String) {
return match a {
"rock" -> b == "scissors"
"paper" -> b == "rock"
"scissors" -> b == "paper"
_ -> false
}
}
on judge(a: String, b: String) {
if a == b { return "tie" }
if beats(a, b) { return "p1" }
return "p2"
}
// rounds is a list of {p1, p2} maps; tally wins and name the victor.
on play_match(rounds: List) {
let s1 = 0
let s2 = 0
for r in rounds {
let w = judge(r.p1, r.p2)
if w == "p1" { s1 = s1 + 1 }
if w == "p2" { s2 = s2 + 1 }
}
let winner = if s1 > s2 { "p1" } else if s2 > s1 { "p2" } else { "tie" }
return map("p1", s1, "p2", s2, "winner", winner)
}
on demo() {
return play_match([
map("p1", "rock", "p2", "scissors"),
map("p1", "paper", "p2", "scissors"),
map("p1", "scissors", "p2", "rock")
])
}
}
cell test RpsTests {
rules {
assert judge("rock", "scissors") == "p1"
assert judge("rock", "paper") == "p2"
assert judge("paper", "paper") == "tie"
assert beats("scissors", "paper") == true
assert beats("scissors", "rock") == false
// best-of-3: p1 takes round 1, loses 2 & 3 → p2 wins the match
assert play_match([
map("p1", "rock", "p2", "scissors"),
map("p1", "rock", "p2", "paper"),
map("p1", "scissors", "p2", "rock")
]).winner == "p2"
}
}What the toolchain says
$ soma check rock_paper_scissors.cell
✓ All checks passed.$ soma test rock_paper_scissors.cell
test RpsTests ...
✓ assert judge("rock", "scissors") == "p1"
✓ assert judge("rock", "paper") == "p2"
✓ assert judge("paper", "paper") == "tie"
✓ assert beats("scissors", "paper") == true
✓ assert beats("scissors", "rock") == false
✓ assert play_match([ map("p1", "rock", "p2", "scissors"), map("p1", "rock", "p2", "paper"), map("p1", "scissors", "p2", "rock") ]).winner == "p2"
6 tests: 6 passed, 0 failed$ soma verify rock_paper_scissors.cell
Verifying rock_paper_scissors.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)