# GLIDER — the Life core. # # Conway's Game of Life on a torus: cells are U32 (0 dead, 1 alive), rows top # to bottom, and the grid wraps on both axes, so every operation is total and # no edge cases exist. Neighbor sums are rotation-based (no index arithmetic), # which keeps the code small, parallel-friendly and proof-friendly. import Base type Board is Data: B{rows: List<&2, List<&2, U32>>} # --- list glue -------------------------------------------------------------- def app(a, -A: Kind(a), xs: List, ys: List) -> List: match xs: case Nil{}: ys case h <> t: h <> app(a, A, t, ys) def rev.go(a, -A: Kind(a), xs: List, acc: List) -> List: match xs: case Nil{}: acc case h <> t: rev.go(a, A, t, (h <> acc)) def rev(a, -A: Kind(a), xs: List) -> List: rev.go(a, A, xs, Nil{}) def rotl(a, -A: Kind(a), xs: List) -> List: match xs: case Nil{}: Nil{} case h <> t: app(a, A, t, (h <> Nil{})) def rotr(a, -A: Kind(a), xs: List) -> List: rev(a, A, rotl(a, A, rev(a, A, xs))) def rotr_row(r: List<&2, U32>) -> List<&2, U32>: rotr(&2, U32, r) def zadd(xs: List<&2, U32>, ys: List<&2, U32>) -> List<&2, U32>: match xs ys: case Nil{} _: Nil{} case _ Nil{}: Nil{} case h1 <> t1 h2 <> t2: (U32.add(h1, h2)) <> zadd(t1, t2) def zsub(xs: List<&2, U32>, ys: List<&2, U32>) -> List<&2, U32>: match xs ys: case Nil{} _: Nil{} case _ Nil{}: Nil{} case h1 <> t1 h2 <> t2: (U32.sub(h1, h2)) <> zsub(t1, t2) def zsum(xs: List<&2, U32>) -> U32: match xs: case Nil{}: 0 case h <> t: U32.add(h, zsum(t)) def total.rows(rs: List<&2, List<&2, U32>>) -> U32: match rs: case Nil{}: 0 case h <> t: U32.add(zsum(h), total.rows(t)) def total(b: Board) -> U32: B{rows} = b total.rows(rows) # map over the rows with a template function def zmap(~f: List<&2, U32> -> List<&2, U32>, xs: List<&2, List<&2, U32>>) -> List<&2, List<&2, U32>>: match xs: case Nil{}: Nil{} case h <> t: f(h) <> zmap(~f, t) # --- the rule --------------------------------------------------------------- def b2u(b: Bool) -> U32: match b: case False{}: 0 case True{}: 1 def rule.go(+n: U32, z: Bool) -> U32: match z: case True{}: b2u(U32.is_eq(n, 3)) case False{}: b2u(Bool.or(U32.is_eq(n, 2), U32.is_eq(n, 3))) # the new value of a cell: c its current value, n its live-neighbor count def rule(c: U32, n: U32) -> U32: rule.go(n, U32.is_zero(c)) def zrule(selfs: List<&2, U32>, ns: List<&2, U32>) -> List<&2, U32>: match selfs ns: case Nil{} _: Nil{} case _ Nil{}: Nil{} case c <> t1 n <> t2: rule(c, n) <> zrule(t1, t2) # --- the step --------------------------------------------------------------- # per column, the sum of a cell with its left and right neighbors (0..3) def hsum(+r: List<&2, U32>) -> List<&2, U32>: zadd(rotl(&2, U32, r), zadd(r, rotr(&2, U32, r))) # one row of the next generation: full 3x3 count minus the cell itself def step_row(+up: List<&2, U32>, +mid: List<&2, U32>, +down: List<&2, U32>) -> List<&2, U32>: +full = zadd(hsum(up), zadd(hsum(mid), hsum(down))) zrule(mid, zsub(full, mid)) def zmap3(up: List<&2, List<&2, U32>>, mid: List<&2, List<&2, U32>>, down: List<&2, List<&2, U32>>) -> List<&2, List<&2, U32>>: match up mid down: case Nil{} _ _: Nil{} case _ Nil{} _: Nil{} case _ _ Nil{}: Nil{} case u <> tu m <> tm d <> td: step_row(u, m, d) <> zmap3(tu, tm, td) # one generation: every row's neighborhood spans the rows above and below def step(b: Board) -> Board: B{+rows} = b B{zmap3(rotl(&2, List<&2, U32>, rows), rows, rotr(&2, List<&2, U32>, rows))} # n generations def steps(n: Nat, b: Board) -> Board: match n: case 0n: b case 1n+p: steps(p, step(b)) # --- patterns --------------------------------------------------------------- def sea(n: Nat) -> List<&2, U32>: match n: case 0n: Nil{} case 1n+p: 0 <> sea(p) def dead() -> Board: B{[sea(6n), sea(6n), sea(6n), sea(6n), sea(6n), sea(6n)]} def block() -> Board: B{[ [0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0], [0, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0] ]} def single() -> Board: B{[ [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0] ]} # the doubling of a Nat, spelled out so proofs can unfold it structurally def double(n: Nat) -> Nat: match n: case 0n: 0n case 1n+p: 1n + (1n + double(p)) def blinker() -> Board: B{[ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0] ]} def glider() -> Board: B{[ [0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0] ]} # the same board with every row and every cell shifted one step def shift_dr(b: Board) -> Board: B{+rows} = b B{zmap(~rotr_row, rotr(&2, List<&2, U32>, rows))} # the glider, moved one step down-right def glider4() -> Board: shift_dr(glider()) def glider8() -> Board: shift_dr(glider4()) # --- rendering -------------------------------------------------------------- def cell_str.go(z: Bool) -> String: match z: case True{}: "." case False{}: "#" def cell_str(c: U32) -> String: cell_str.go(U32.is_zero(c)) def nl() -> String: SCon{Chr{10}, SNil{}} def row_str(r: List<&2, U32>) -> String: match r: case Nil{}: SNil{} case Con{c, t}: cell_str(c) ++ row_str(t) def rows_str(rs: List<&2, List<&2, U32>>) -> String: match rs: case Nil{}: SNil{} case h <> t: row_str(h) ++ nl() ++ rows_str(t) def show_b(b: Board) -> String: B{rows} = b rows_str(rows)