From 9fb65e6a775b568a8e891b14defa5cf06012a577 Mon Sep 17 00:00:00 2001 From: enzotar Date: Fri, 27 Feb 2026 13:57:51 -0800 Subject: [PATCH] =?UTF-8?q?refactor:=20complete=20collision=20system=20rew?= =?UTF-8?q?rite=20=E2=80=94=20decomposed=20sub-signals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced single monolithic blocked expression with 4 composable signals: - blockedWall: piece-type aware bottom wall (T=18, others=19) - blockedTop: grid[py+1] at px,px+1,px+2 (all pieces) - blockedFoot: grid[py+2] at px+1 (T-piece foot only) - blockedI4: grid[py+1] at px+3 (I-piece 4th cell only) - blocked: OR combination of all 4 Fixed auto-drop cap from py<18 to py<19 so flat pieces reach the bottom row. Fixed hard drop and keyboard per piece type. --- examples/game-tetris.ds | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/examples/game-tetris.ds b/examples/game-tetris.ds index c4624ec..6ca2ba6 100644 --- a/examples/game-tetris.ds +++ b/examples/game-tetris.ds @@ -76,28 +76,45 @@ let lockTick = 0 on keydown(ev) -> px = if gameOver then px else (if paused then px else (if ev.key == "ArrowLeft" then (if px > 0 then px - 1 else px) else px)) on keydown(ev) -> px = if gameOver then px else (if paused then px else (if ev.key == "ArrowRight" then (if px < 7 then px + 1 else px) else px)) on keydown(ev) -> rotation = if gameOver then rotation else (if paused then rotation else (if ev.key == "ArrowUp" then (rotation + 1) % 4 else rotation)) -on keydown(ev) -> py = if gameOver then py else (if paused then py else (if blocked then py else (if ev.key == "ArrowDown" then (if py < 18 then py + 1 else py) else py))) -on keydown(ev) -> py = if gameOver then py else (if paused then py else (if blocked then py else (if ev.key == " " then 18 else py))) +on keydown(ev) -> py = if gameOver then py else (if paused then py else (if blocked then py else (if ev.key == "ArrowDown" then (if py < 19 then py + 1 else py) else py))) +on keydown(ev) -> py = if gameOver then py else (if paused then py else (if blocked then py else (if ev.key == " " then (if piece == 6 then 18 else 19) else py))) on keydown(ev) -> paused = if ev.key == "p" then (if paused then 0 else 1) else paused -- ================================================================ -- LAYER 4: DERIVED SIGNALS — Collision, gravity, lock, line clear -- ================================================================ +-- COLLISION SYSTEM (decomposed into composable sub-signals) +-- ================================================================ --- Collision detection: can the piece move down? --- All pieces: check grid[py+1] at px, px+1, px+2 (top row destination). --- T-piece (6) only: also check grid[py+2] at px+1 (protruding bottom cell). --- Bottom wall: py >= 18. Full grid coverage (py 0-17). +-- Sub-signal 1: Bottom wall (piece-type dependent) +-- T-piece stops at py=18 (foot at 19), all others at py=19 +let blockedWall = 0 +every 33 -> blockedWall = if piece == 6 then (if py >= 18 then 1 else 0) else (if py >= 19 then 1 else 0) + +-- Sub-signal 2: Top row collision — check grid[py+1] at px, px+1, px+2 +-- This is the core 3-cell check shared by ALL piece types +let blockedTop = 0 +every 33 -> blockedTop = if py >= 19 then 0 else (if py == 18 then (if g19[px] > 0 then 1 else (if g19[px + 1] > 0 then 1 else (if g19[px + 2] > 0 then 1 else 0))) else (if py == 17 then (if g18[px] > 0 then 1 else (if g18[px + 1] > 0 then 1 else (if g18[px + 2] > 0 then 1 else 0))) else (if py == 16 then (if g17[px] > 0 then 1 else (if g17[px + 1] > 0 then 1 else (if g17[px + 2] > 0 then 1 else 0))) else (if py == 15 then (if g16[px] > 0 then 1 else (if g16[px + 1] > 0 then 1 else (if g16[px + 2] > 0 then 1 else 0))) else (if py == 14 then (if g15[px] > 0 then 1 else (if g15[px + 1] > 0 then 1 else (if g15[px + 2] > 0 then 1 else 0))) else (if py == 13 then (if g14[px] > 0 then 1 else (if g14[px + 1] > 0 then 1 else (if g14[px + 2] > 0 then 1 else 0))) else (if py == 12 then (if g13[px] > 0 then 1 else (if g13[px + 1] > 0 then 1 else (if g13[px + 2] > 0 then 1 else 0))) else (if py == 11 then (if g12[px] > 0 then 1 else (if g12[px + 1] > 0 then 1 else (if g12[px + 2] > 0 then 1 else 0))) else (if py == 10 then (if g11[px] > 0 then 1 else (if g11[px + 1] > 0 then 1 else (if g11[px + 2] > 0 then 1 else 0))) else (if py == 9 then (if g10[px] > 0 then 1 else (if g10[px + 1] > 0 then 1 else (if g10[px + 2] > 0 then 1 else 0))) else (if py == 8 then (if g9[px] > 0 then 1 else (if g9[px + 1] > 0 then 1 else (if g9[px + 2] > 0 then 1 else 0))) else (if py == 7 then (if g8[px] > 0 then 1 else (if g8[px + 1] > 0 then 1 else (if g8[px + 2] > 0 then 1 else 0))) else (if py == 6 then (if g7[px] > 0 then 1 else (if g7[px + 1] > 0 then 1 else (if g7[px + 2] > 0 then 1 else 0))) else (if py == 5 then (if g6[px] > 0 then 1 else (if g6[px + 1] > 0 then 1 else (if g6[px + 2] > 0 then 1 else 0))) else (if py == 4 then (if g5[px] > 0 then 1 else (if g5[px + 1] > 0 then 1 else (if g5[px + 2] > 0 then 1 else 0))) else (if py == 3 then (if g4[px] > 0 then 1 else (if g4[px + 1] > 0 then 1 else (if g4[px + 2] > 0 then 1 else 0))) else (if py == 2 then (if g3[px] > 0 then 1 else (if g3[px + 1] > 0 then 1 else (if g3[px + 2] > 0 then 1 else 0))) else (if py == 1 then (if g2[px] > 0 then 1 else (if g2[px + 1] > 0 then 1 else (if g2[px + 2] > 0 then 1 else 0))) else (if py == 0 then (if g1[px] > 0 then 1 else (if g1[px + 1] > 0 then 1 else (if g1[px + 2] > 0 then 1 else 0))) else 0))))))))))))))))))) + +-- Sub-signal 3: T-piece foot collision — check grid[py+2] at px+1 +-- Only applies to T-piece (piece==6) +let blockedFoot = 0 +every 33 -> blockedFoot = if piece != 6 then 0 else (if py >= 18 then 0 else (if py == 17 then (if g19[px + 1] > 0 then 1 else 0) else (if py == 16 then (if g18[px + 1] > 0 then 1 else 0) else (if py == 15 then (if g17[px + 1] > 0 then 1 else 0) else (if py == 14 then (if g16[px + 1] > 0 then 1 else 0) else (if py == 13 then (if g15[px + 1] > 0 then 1 else 0) else (if py == 12 then (if g14[px + 1] > 0 then 1 else 0) else (if py == 11 then (if g13[px + 1] > 0 then 1 else 0) else (if py == 10 then (if g12[px + 1] > 0 then 1 else 0) else (if py == 9 then (if g11[px + 1] > 0 then 1 else 0) else (if py == 8 then (if g10[px + 1] > 0 then 1 else 0) else (if py == 7 then (if g9[px + 1] > 0 then 1 else 0) else (if py == 6 then (if g8[px + 1] > 0 then 1 else 0) else (if py == 5 then (if g7[px + 1] > 0 then 1 else 0) else (if py == 4 then (if g6[px + 1] > 0 then 1 else 0) else (if py == 3 then (if g5[px + 1] > 0 then 1 else 0) else (if py == 2 then (if g4[px + 1] > 0 then 1 else 0) else (if py == 1 then (if g3[px + 1] > 0 then 1 else 0) else (if py == 0 then (if g2[px + 1] > 0 then 1 else 0) else 0))))))))))))))))))) + +-- Sub-signal 4: I-piece 4th cell collision — check grid[py+1] at px+3 +-- Only applies to I-piece (piece==1) +let blockedI4 = 0 +every 33 -> blockedI4 = if piece != 1 then 0 else (if py >= 19 then 0 else (if py == 18 then (if g19[px + 3] > 0 then 1 else 0) else (if py == 17 then (if g18[px + 3] > 0 then 1 else 0) else (if py == 16 then (if g17[px + 3] > 0 then 1 else 0) else (if py == 15 then (if g16[px + 3] > 0 then 1 else 0) else (if py == 14 then (if g15[px + 3] > 0 then 1 else 0) else (if py == 13 then (if g14[px + 3] > 0 then 1 else 0) else (if py == 12 then (if g13[px + 3] > 0 then 1 else 0) else (if py == 11 then (if g12[px + 3] > 0 then 1 else 0) else (if py == 10 then (if g11[px + 3] > 0 then 1 else 0) else (if py == 9 then (if g10[px + 3] > 0 then 1 else 0) else (if py == 8 then (if g9[px + 3] > 0 then 1 else 0) else (if py == 7 then (if g8[px + 3] > 0 then 1 else 0) else (if py == 6 then (if g7[px + 3] > 0 then 1 else 0) else (if py == 5 then (if g6[px + 3] > 0 then 1 else 0) else (if py == 4 then (if g5[px + 3] > 0 then 1 else 0) else (if py == 3 then (if g4[px + 3] > 0 then 1 else 0) else (if py == 2 then (if g3[px + 3] > 0 then 1 else 0) else (if py == 1 then (if g2[px + 3] > 0 then 1 else 0) else (if py == 0 then (if g1[px + 3] > 0 then 1 else 0) else 0)))))))))))))))))))) + +-- Combined blocked signal: any sub-signal triggers block let blocked = 0 - --- Piece-aware collision: check px+3 for I-piece, py+2 at px+1 for T-piece -every 33 -> blocked = if py > 17 then 1 else (if py == 17 then (if g18[px] > 0 then 1 else (if g18[px + 1] > 0 then 1 else (if g18[px + 2] > 0 then 1 else (if piece == 1 then (if g18[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g19[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 16 then (if g17[px] > 0 then 1 else (if g17[px + 1] > 0 then 1 else (if g17[px + 2] > 0 then 1 else (if piece == 1 then (if g17[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g18[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 15 then (if g16[px] > 0 then 1 else (if g16[px + 1] > 0 then 1 else (if g16[px + 2] > 0 then 1 else (if piece == 1 then (if g16[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g17[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 14 then (if g15[px] > 0 then 1 else (if g15[px + 1] > 0 then 1 else (if g15[px + 2] > 0 then 1 else (if piece == 1 then (if g15[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g16[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 13 then (if g14[px] > 0 then 1 else (if g14[px + 1] > 0 then 1 else (if g14[px + 2] > 0 then 1 else (if piece == 1 then (if g14[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g15[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 12 then (if g13[px] > 0 then 1 else (if g13[px + 1] > 0 then 1 else (if g13[px + 2] > 0 then 1 else (if piece == 1 then (if g13[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g14[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 11 then (if g12[px] > 0 then 1 else (if g12[px + 1] > 0 then 1 else (if g12[px + 2] > 0 then 1 else (if piece == 1 then (if g12[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g13[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 10 then (if g11[px] > 0 then 1 else (if g11[px + 1] > 0 then 1 else (if g11[px + 2] > 0 then 1 else (if piece == 1 then (if g11[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g12[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 9 then (if g10[px] > 0 then 1 else (if g10[px + 1] > 0 then 1 else (if g10[px + 2] > 0 then 1 else (if piece == 1 then (if g10[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g11[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 8 then (if g9[px] > 0 then 1 else (if g9[px + 1] > 0 then 1 else (if g9[px + 2] > 0 then 1 else (if piece == 1 then (if g9[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g10[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 7 then (if g8[px] > 0 then 1 else (if g8[px + 1] > 0 then 1 else (if g8[px + 2] > 0 then 1 else (if piece == 1 then (if g8[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g9[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 6 then (if g7[px] > 0 then 1 else (if g7[px + 1] > 0 then 1 else (if g7[px + 2] > 0 then 1 else (if piece == 1 then (if g7[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g8[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 5 then (if g6[px] > 0 then 1 else (if g6[px + 1] > 0 then 1 else (if g6[px + 2] > 0 then 1 else (if piece == 1 then (if g6[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g7[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 4 then (if g5[px] > 0 then 1 else (if g5[px + 1] > 0 then 1 else (if g5[px + 2] > 0 then 1 else (if piece == 1 then (if g5[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g6[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 3 then (if g4[px] > 0 then 1 else (if g4[px + 1] > 0 then 1 else (if g4[px + 2] > 0 then 1 else (if piece == 1 then (if g4[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g5[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 2 then (if g3[px] > 0 then 1 else (if g3[px + 1] > 0 then 1 else (if g3[px + 2] > 0 then 1 else (if piece == 1 then (if g3[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g4[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 1 then (if g2[px] > 0 then 1 else (if g2[px + 1] > 0 then 1 else (if g2[px + 2] > 0 then 1 else (if piece == 1 then (if g2[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g3[px + 1] > 0 then 1 else 0) else 0))))) else (if py == 0 then (if g1[px] > 0 then 1 else (if g1[px + 1] > 0 then 1 else (if g1[px + 2] > 0 then 1 else (if piece == 1 then (if g1[px + 3] > 0 then 1 else 0) else (if piece == 6 then (if g2[px + 1] > 0 then 1 else 0) else 0))))) else 0)))))))))))))))))) +every 33 -> blocked = if blockedWall then 1 else (if blockedTop then 1 else (if blockedFoot then 1 else (if blockedI4 then 1 else 0))) -- Gravity tick every 33 -> gravityTick = if paused then gravityTick else (if gameOver then gravityTick else gravityTick + 1) --- Auto-drop: only if NOT blocked -every 33 -> py = if paused then py else (if gameOver then py else (if blocked then py else (if gravityTick > dropInterval then (if py < 18 then py + 1 else py) else py))) +-- Auto-drop: py can go up to 19 (not 18) — blocked wall handles per-piece limits +every 33 -> py = if paused then py else (if gameOver then py else (if blocked then py else (if gravityTick > dropInterval then (if py < 19 then py + 1 else py) else py))) every 33 -> gravityTick = if gravityTick > dropInterval then 0 else gravityTick -- Lock detection: start counting when blocked @@ -406,7 +423,7 @@ view tetris_game = column [ } button "Left" { click: px = if px > 0 then px - 1 else px, variant: "secondary" } button "Right" { click: px = if px < 7 then px + 1 else px, variant: "secondary" } - button "Drop" { click: py = if blocked then py else 18, variant: "secondary" } + button "Drop" { click: py = if blocked then py else (if piece == 6 then 18 else 19), variant: "secondary" } button "Rotate" { click: rotation = (rotation + 1) % 4, variant: "secondary" } button "Reset" { click: score = 0; lines = 0; level = 1; gameOver = 0; paused = 0; piece = 6; nextPiece = 1; px = 3; py = 0; rotation = 0; g16 = [0,0,0,0,0,0,0,0,0,0]; g17 = [0,0,0,0,0,0,0,0,0,0]; g18 = [0,0,0,0,0,0,0,0,0,0]; g19 = [0,0,0,0,0,0,0,0,0,0],