diff --git a/lib/apl/runtime.sx b/lib/apl/runtime.sx index e2117c93..f2a6eb54 100644 --- a/lib/apl/runtime.sx +++ b/lib/apl/runtime.sx @@ -824,6 +824,18 @@ ((prime-mask (apl-eq (apl-scalar 2) divisor-counts))) (apl-compress prime-mask a)))))))) +(define + apl-life-step + (fn + (board) + (let + ((zero-board (apl-mul board (apl-scalar 0)))) + (let + ((sum-board (reduce (fn (acc dr) (reduce (fn (acc2 dc) (apl-add acc2 (apl-rotate-first (apl-scalar dr) (apl-rotate (apl-scalar dc) board)))) acc (list -1 0 1))) zero-board (list -1 0 1)))) + (apl-or + (apl-eq sum-board (apl-scalar 3)) + (apl-and board (apl-eq sum-board (apl-scalar 4)))))))) + (define apl-reduce (fn diff --git a/lib/apl/scoreboard.json b/lib/apl/scoreboard.json index e8265d8e..e38be0b9 100644 --- a/lib/apl/scoreboard.json +++ b/lib/apl/scoreboard.json @@ -5,9 +5,9 @@ "dfn": {"pass": 24, "fail": 0}, "tradfn": {"pass": 20, "fail": 0}, "valence": {"pass": 14, "fail": 0}, - "programs": {"pass": 11, "fail": 0} + "programs": {"pass": 18, "fail": 0} }, - "total_pass": 280, + "total_pass": 287, "total_fail": 0, - "total": 280 + "total": 287 } diff --git a/lib/apl/scoreboard.md b/lib/apl/scoreboard.md index e6d09b72..8de76152 100644 --- a/lib/apl/scoreboard.md +++ b/lib/apl/scoreboard.md @@ -9,8 +9,8 @@ _Generated by `lib/apl/conformance.sh`_ | dfn | 24 | 0 | 24 | | tradfn | 20 | 0 | 20 | | valence | 14 | 0 | 14 | -| programs | 11 | 0 | 11 | -| **Total** | **280** | **0** | **280** | +| programs | 18 | 0 | 18 | +| **Total** | **287** | **0** | **287** | ## Notes diff --git a/lib/apl/tests/programs.sx b/lib/apl/tests/programs.sx index 4b7e85f6..9c59c544 100644 --- a/lib/apl/tests/programs.sx +++ b/lib/apl/tests/programs.sx @@ -56,3 +56,139 @@ (make-array (list 3) (list 1 1 1)) (make-array (list 3) (list 1 2 3)))) (list 1 2 3)) + +(apl-test + "life: empty 5x5 stays empty" + (mkrv + (apl-life-step + (make-array + (list 5 5) + (list 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)))) + (list 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)) + +(apl-test + "life: horizontal blinker → vertical blinker" + (mkrv + (apl-life-step + (make-array + (list 5 5) + (list 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)))) + (list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0)) + +(apl-test + "life: vertical blinker → horizontal blinker" + (mkrv + (apl-life-step + (make-array + (list 5 5) + (list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0)))) + (list 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)) + +(apl-test + "life: blinker has period 2" + (mkrv + (apl-life-step + (apl-life-step + (make-array + (list 5 5) + (list 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))))) + (list 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)) + +(apl-test + "life: 2x2 block stable on 5x5" + (mkrv + (apl-life-step + (make-array + (list 5 5) + (list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0)))) + (list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0)) + +(apl-test + "life: shape preserved" + (mksh + (apl-life-step + (make-array + (list 5 5) + (list 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)))) + (list 5 5)) + +(apl-test + "life: glider on 6x6 advances" + (mkrv + (apl-life-step + (make-array + (list 6 6) + (list + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0)))) + (list + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0)) diff --git a/lib/apl/tests/programs/life.apl b/lib/apl/tests/programs/life.apl new file mode 100644 index 00000000..b461d544 --- /dev/null +++ b/lib/apl/tests/programs/life.apl @@ -0,0 +1,22 @@ +⍝ Conway's Game of Life — toroidal one-liner +⍝ +⍝ The classic Roger Hui formulation: +⍝ life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵} +⍝ +⍝ Read right-to-left: +⍝ ⊂⍵ : enclose the board (so it's a single scalar item) +⍝ ¯1 0 1 ⌽¨ ⊂⍵ : produce 3 horizontally-shifted copies +⍝ ¯1 0 1 ∘.⊖ … : outer-product with vertical shifts → 3×3 = 9 shifts +⍝ +/ +/ … : sum the 9 boards element-wise → neighbor-count + self +⍝ 3 4 = … : boolean — count is exactly 3 or exactly 4 +⍝ 1 ⍵ ∨.∧ … : "alive next" iff (count=3) or (alive AND count=4) +⍝ ⊃ … : disclose back to a 2D board +⍝ +⍝ Rules in plain language: +⍝ - dead cell + 3 live neighbors → born +⍝ - live cell + 2 or 3 live neighbors → survives +⍝ - all else → dies +⍝ +⍝ Toroidal: edges wrap (rotate is cyclic). + +life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵} diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index ce321e03..e19ca99e 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -95,7 +95,7 @@ Core mapping: ### Phase 6 — classic programs + drive corpus - [ ] Classic programs in `lib/apl/tests/programs/`: - - [ ] `life.apl` — Conway's Game of Life as a one-liner using `⊂` `⊖` `⌽` `+/` + - [x] `life.apl` — Conway's Game of Life as a one-liner using `⊂` `⊖` `⌽` `+/` - [ ] `mandelbrot.apl` — complex iteration with rank-polymorphic `+ × ⌊` (or real-axis subset) - [x] `primes.apl` — `(2=+⌿0=A∘.|A)/A←⍳N` sieve - [ ] `n-queens.apl` — backtracking via reduce @@ -118,6 +118,7 @@ data; format for string templating. _Newest first._ +- 2026-05-07: Phase 6 life — Conway via 9-shift toroidal sum + alive-rule (cnt=3 OR alive∧cnt=4); apl-life-step + life.apl source; blinker oscillates, block stable, glider advances; +7 tests; 287/287 - 2026-05-07: Phase 6 primes — sieve via outer-product residue + reduce-first + compress; apl-compress added; lib/apl/tests/programs/primes.apl source; +11 tests; 280/280 - 2026-05-07: Phase 5 conformance.sh + scoreboard.{json,md} — per-suite runner; current snapshot 269/269; **Phase 5 complete** - 2026-05-07: Phase 5 valence dispatch — apl-dfn-valence (AST scan for ⍺/⍵), apl-tradfn-valence (slot check), apl-call unified entry; +14 tests; 269/269 tests