ArrowDown soft drop, Space hard drop, and Drop button all bypassed the blocked signal, letting pieces pass through frozen blocks. Now all three gate on blocked before modifying py.
328 lines
21 KiB
Text
328 lines
21 KiB
Text
-- DreamStack Tetris — Signal Composition Showcase
|
|
--
|
|
-- This game demonstrates how DreamStack composes independent signal layers:
|
|
--
|
|
-- Layer 1: DATA SIGNALS — grid cells, piece encoding, bag randomizer
|
|
-- Layer 2: PHYSICS SIGNALS — gravity timer, drop speed, lock delay
|
|
-- Layer 3: INPUT SIGNALS — keyboard events mapped to commands
|
|
-- Layer 4: DERIVED SIGNALS — ghost position, line detection, game over
|
|
-- Layer 5: UI SIGNALS — score, level, next piece preview
|
|
-- Layer 6: STREAM SIGNALS — real-time spectator broadcast
|
|
--
|
|
-- Each layer is self-contained; the reactive runtime composites them.
|
|
--
|
|
-- Grid: 10 wide x 20 tall. Rows g0 (top) to g19 (bottom).
|
|
-- Cell values: 0=empty, 1=cyan(I), 2=blue(J), 3=orange(L),
|
|
-- 4=yellow(O), 5=green(S), 6=purple(T), 7=red(Z)
|
|
--
|
|
-- Run:
|
|
-- Tab 1: cargo run -p ds-stream (relay)
|
|
-- Tab 2: dreamstack dev examples/game-tetris.ds (player)
|
|
|
|
import { Badge } from "../registry/components/badge"
|
|
|
|
-- ================================================================
|
|
-- LAYER 1: DATA SIGNALS — Frozen grid + active piece state
|
|
-- ================================================================
|
|
|
|
-- Frozen grid: 20 rows x 10 columns (row 0 = top, row 19 = bottom)
|
|
let g0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g3 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g4 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g5 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g6 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g7 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g9 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g10 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g11 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g12 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g13 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g14 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g15 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g16 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g17 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g18 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
let g19 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
|
|
-- Active piece: 1=I, 2=J, 3=L, 4=O, 5=S, 6=T, 7=Z
|
|
let piece = 6
|
|
let rotation = 0
|
|
let px = 3
|
|
let py = 0
|
|
|
|
-- Next piece for preview
|
|
let nextPiece = 1
|
|
|
|
-- ================================================================
|
|
-- LAYER 2: PHYSICS SIGNALS — Gravity, speed, level
|
|
-- ================================================================
|
|
|
|
let score = 0
|
|
let lines = 0
|
|
let level = 1
|
|
let gameOver = 0
|
|
let paused = 0
|
|
let gravityTick = 0
|
|
let dropInterval = 24
|
|
let lockTick = 0
|
|
|
|
-- ================================================================
|
|
-- LAYER 3: INPUT SIGNALS — Keyboard events
|
|
-- ================================================================
|
|
|
|
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) -> paused = if ev.key == "p" then (if paused then 0 else 1) else paused
|
|
|
|
-- ================================================================
|
|
-- LAYER 4: DERIVED SIGNALS — Collision, gravity, lock, line clear
|
|
-- ================================================================
|
|
|
|
-- Collision detection: can the piece move down?
|
|
-- T-piece at py has top cells at row py, bottom cell at py+1.
|
|
-- To move down: top cells move to py+1, bottom cell to py+2.
|
|
-- Check grid[py+1] at px, px+1, px+2 (top row would land here).
|
|
-- Bottom wall: py >= 18 (piece can't go lower).
|
|
let blocked = 0
|
|
|
|
-- Check collision against the CORRECT row: grid[py+1] for top cells
|
|
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 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 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)))
|
|
every 33 -> gravityTick = if gravityTick > dropInterval then 0 else gravityTick
|
|
|
|
-- Lock detection: start counting when blocked
|
|
every 33 -> lockTick = if blocked then lockTick + 1 else 0
|
|
|
|
-- ── Freeze piece into grid at py position on lock ──
|
|
-- The piece top-row cells go into row py, bottom-row cell into py+1
|
|
-- For T-piece: (px,py), (px+1,py), (px+2,py) and (px+1,py+1)
|
|
-- We write the top 3 cells into grid[py] and bottom cell into grid[py+1]
|
|
|
|
-- Freeze top cells into grid row py (for each possible py value)
|
|
-- Row 19
|
|
every 33 -> g19[px] = if lockTick == 3 then (if py == 19 then piece else g19[px]) else g19[px]
|
|
every 33 -> g19[px + 1] = if lockTick == 3 then (if py == 19 then piece else g19[px + 1]) else g19[px + 1]
|
|
every 33 -> g19[px + 2] = if lockTick == 3 then (if py == 19 then piece else g19[px + 2]) else g19[px + 2]
|
|
-- Row 18
|
|
every 33 -> g18[px] = if lockTick == 3 then (if py == 18 then piece else g18[px]) else g18[px]
|
|
every 33 -> g18[px + 1] = if lockTick == 3 then (if py == 18 then piece else g18[px + 1]) else g18[px + 1]
|
|
every 33 -> g18[px + 2] = if lockTick == 3 then (if py == 18 then piece else g18[px + 2]) else g18[px + 2]
|
|
-- Row 17
|
|
every 33 -> g17[px] = if lockTick == 3 then (if py == 17 then piece else g17[px]) else g17[px]
|
|
every 33 -> g17[px + 1] = if lockTick == 3 then (if py == 17 then piece else g17[px + 1]) else g17[px + 1]
|
|
every 33 -> g17[px + 2] = if lockTick == 3 then (if py == 17 then piece else g17[px + 2]) else g17[px + 2]
|
|
-- Row 16
|
|
every 33 -> g16[px] = if lockTick == 3 then (if py == 16 then piece else g16[px]) else g16[px]
|
|
every 33 -> g16[px + 1] = if lockTick == 3 then (if py == 16 then piece else g16[px + 1]) else g16[px + 1]
|
|
every 33 -> g16[px + 2] = if lockTick == 3 then (if py == 16 then piece else g16[px + 2]) else g16[px + 2]
|
|
-- Row 15
|
|
every 33 -> g15[px] = if lockTick == 3 then (if py == 15 then piece else g15[px]) else g15[px]
|
|
every 33 -> g15[px + 1] = if lockTick == 3 then (if py == 15 then piece else g15[px + 1]) else g15[px + 1]
|
|
every 33 -> g15[px + 2] = if lockTick == 3 then (if py == 15 then piece else g15[px + 2]) else g15[px + 2]
|
|
-- Row 14
|
|
every 33 -> g14[px] = if lockTick == 3 then (if py == 14 then piece else g14[px]) else g14[px]
|
|
every 33 -> g14[px + 1] = if lockTick == 3 then (if py == 14 then piece else g14[px + 1]) else g14[px + 1]
|
|
every 33 -> g14[px + 2] = if lockTick == 3 then (if py == 14 then piece else g14[px + 2]) else g14[px + 2]
|
|
-- Row 13
|
|
every 33 -> g13[px] = if lockTick == 3 then (if py == 13 then piece else g13[px]) else g13[px]
|
|
every 33 -> g13[px + 1] = if lockTick == 3 then (if py == 13 then piece else g13[px + 1]) else g13[px + 1]
|
|
every 33 -> g13[px + 2] = if lockTick == 3 then (if py == 13 then piece else g13[px + 2]) else g13[px + 2]
|
|
|
|
-- Freeze T-piece bottom center cell into grid[py+1]
|
|
-- (T has a cell at px+1, py+1; also applicable to O, S, Z shapes)
|
|
every 33 -> g19[px + 1] = if lockTick == 3 then (if py == 18 then (if piece == 6 then 6 else g19[px + 1]) else g19[px + 1]) else g19[px + 1]
|
|
every 33 -> g18[px + 1] = if lockTick == 3 then (if py == 17 then (if piece == 6 then 6 else g18[px + 1]) else g18[px + 1]) else g18[px + 1]
|
|
every 33 -> g17[px + 1] = if lockTick == 3 then (if py == 16 then (if piece == 6 then 6 else g17[px + 1]) else g17[px + 1]) else g17[px + 1]
|
|
every 33 -> g16[px + 1] = if lockTick == 3 then (if py == 15 then (if piece == 6 then 6 else g16[px + 1]) else g16[px + 1]) else g16[px + 1]
|
|
every 33 -> g15[px + 1] = if lockTick == 3 then (if py == 14 then (if piece == 6 then 6 else g15[px + 1]) else g15[px + 1]) else g15[px + 1]
|
|
every 33 -> g14[px + 1] = if lockTick == 3 then (if py == 13 then (if piece == 6 then 6 else g14[px + 1]) else g14[px + 1]) else g14[px + 1]
|
|
|
|
-- Spawn new piece after lock
|
|
every 33 -> piece = if lockTick == 3 then nextPiece else piece
|
|
every 33 -> nextPiece = if lockTick == 3 then (gravityTick % 7 + 1) else nextPiece
|
|
every 33 -> py = if lockTick == 3 then 0 else py
|
|
every 33 -> px = if lockTick == 3 then 3 else px
|
|
every 33 -> rotation = if lockTick == 3 then 0 else rotation
|
|
every 33 -> score = if lockTick == 3 then score + 10 else score
|
|
|
|
-- Line clear: row 19 full (check 5 sample cells)
|
|
every 33 -> score = if g19[0] > 0 then (if g19[2] > 0 then (if g19[4] > 0 then (if g19[6] > 0 then (if g19[8] > 0 then score + 100 else score) else score) else score) else score) else score
|
|
every 33 -> lines = if g19[0] > 0 then (if g19[2] > 0 then (if g19[4] > 0 then (if g19[6] > 0 then (if g19[8] > 0 then lines + 1 else lines) else lines) else lines) else lines) else lines
|
|
|
|
-- Shift rows down on clear: copy row 18 into row 19
|
|
every 33 -> g19[0] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[0] else g19[0]) else g19[0]) else g19[0]
|
|
every 33 -> g19[1] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[1] else g19[1]) else g19[1]) else g19[1]
|
|
every 33 -> g19[2] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[2] else g19[2]) else g19[2]) else g19[2]
|
|
every 33 -> g19[3] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[3] else g19[3]) else g19[3]) else g19[3]
|
|
every 33 -> g19[4] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[4] else g19[4]) else g19[4]) else g19[4]
|
|
every 33 -> g19[5] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[5] else g19[5]) else g19[5]) else g19[5]
|
|
every 33 -> g19[6] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[6] else g19[6]) else g19[6]) else g19[6]
|
|
every 33 -> g19[7] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[7] else g19[7]) else g19[7]) else g19[7]
|
|
every 33 -> g19[8] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[8] else g19[8]) else g19[8]) else g19[8]
|
|
every 33 -> g19[9] = if g19[0] > 0 then (if g19[4] > 0 then (if g19[8] > 0 then g18[9] else g19[9]) else g19[9]) else g19[9]
|
|
|
|
-- Level up
|
|
every 33 -> level = if lines > 0 then (lines / 10 + 1) else 1
|
|
every 33 -> dropInterval = if level > 9 then 3 else (27 - level * 3)
|
|
|
|
-- Game over: any cell in row 0 filled
|
|
every 33 -> gameOver = if g0[3] > 0 then 1 else (if g0[4] > 0 then 1 else (if g0[5] > 0 then 1 else gameOver))
|
|
|
|
-- ================================================================
|
|
-- LAYER 5: SOUND SIGNALS
|
|
-- ================================================================
|
|
|
|
every 33 -> play_tone(if lockTick == 3 then 220 else 0, 80)
|
|
every 33 -> play_tone(if gameOver then 110 else 0, 500, "sawtooth")
|
|
|
|
-- ================================================================
|
|
-- LAYER 6: STREAM SIGNALS — Spectator broadcast
|
|
-- ================================================================
|
|
|
|
stream tetris on "ws://localhost:9100/peer/tetris" {
|
|
mode: signal,
|
|
output: piece, rotation, px, py, nextPiece, score, lines, level, gameOver, paused, g0, g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, g15, g16, g17, g18, g19
|
|
}
|
|
|
|
-- ================================================================
|
|
-- VIEW: All 6 signal layers composited into one display
|
|
-- ================================================================
|
|
|
|
view tetris_game = column [
|
|
text "DreamStack Tetris" { variant: "title" }
|
|
text "Signal Composition Demo" { variant: "subtitle" }
|
|
|
|
-- Status bar (Layer 5 UI signals <- Layer 2 physics signals)
|
|
row [
|
|
Badge { label: "Score: {score}", variant: "success" }
|
|
Badge { label: "Lines: {lines}", variant: "info" }
|
|
Badge { label: "Level: {level}", variant: "warning" }
|
|
Badge { label: "Next: {nextPiece}", variant: "default" }
|
|
]
|
|
|
|
row [
|
|
-- Main Board: 300x600 (10x20 cells at 30px each)
|
|
stack { style: "position:relative; width:300px; height:600px; background:linear-gradient(180deg,#0a0a1a,#111133); border:2px solid #334155; border-radius:12px; overflow:hidden; box-shadow:0 0 60px rgba(99,102,241,0.15)" } [
|
|
|
|
-- Grid lines (subtle, UI-only signals)
|
|
for r in [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19] ->
|
|
column { style: "position:absolute; width:300px; height:1px; top:{r * 30}px; background:rgba(99,102,241,0.08)" } []
|
|
for k in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:1px; height:600px; left:{k * 30}px; background:rgba(99,102,241,0.08)" } []
|
|
|
|
-- Frozen grid: row 13 (Layer 1 data -> Layer 5 UI)
|
|
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:28px; height:28px; top:391px; left:{c * 30 + 1}px; border-radius:4px; background:linear-gradient(180deg,#a855f7,#7c3aed); opacity:{if g13[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(168,85,247,0.4)" } []
|
|
|
|
-- Frozen grid: row 14
|
|
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:28px; height:28px; top:421px; left:{c * 30 + 1}px; border-radius:4px; background:linear-gradient(180deg,#a855f7,#7c3aed); opacity:{if g14[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(168,85,247,0.4)" } []
|
|
|
|
-- Frozen grid: row 15
|
|
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:28px; height:28px; top:451px; left:{c * 30 + 1}px; border-radius:4px; background:linear-gradient(180deg,#a855f7,#7c3aed); opacity:{if g15[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(168,85,247,0.4)" } []
|
|
|
|
-- Frozen grid: row 16
|
|
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:28px; height:28px; top:481px; left:{c * 30 + 1}px; border-radius:4px; background:linear-gradient(180deg,#a855f7,#7c3aed); opacity:{if g16[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(168,85,247,0.4)" } []
|
|
|
|
-- Frozen grid: row 17
|
|
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:28px; height:28px; top:511px; left:{c * 30 + 1}px; border-radius:4px; background:linear-gradient(180deg,#a855f7,#7c3aed); opacity:{if g17[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(168,85,247,0.4)" } []
|
|
|
|
-- Frozen grid: row 18
|
|
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:28px; height:28px; top:541px; left:{c * 30 + 1}px; border-radius:4px; background:linear-gradient(180deg,#a855f7,#7c3aed); opacity:{if g18[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(168,85,247,0.4)" } []
|
|
|
|
-- Frozen grid: row 19 (bottom)
|
|
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
|
column { style: "position:absolute; width:28px; height:28px; top:571px; left:{c * 30 + 1}px; border-radius:4px; background:linear-gradient(180deg,#a855f7,#7c3aed); opacity:{if g19[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(168,85,247,0.4)" } []
|
|
|
|
-- Active piece: 4 cells (Layer 1 data + Layer 3 input -> Layer 5 UI)
|
|
-- Cell 0: top-left of piece
|
|
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:linear-gradient(180deg,#c084fc,#a855f7); box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(if piece == 1 then py + 1 else py) * 30 + 1}px; left:{(if piece == 5 then px + 1 else px) * 30 + 1}px" } []
|
|
|
|
-- Cell 1
|
|
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:linear-gradient(180deg,#c084fc,#a855f7); box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(if piece == 1 then py + 1 else py) * 30 + 1}px; left:{(if piece == 2 then px else px + 1) * 30 + 1}px" } []
|
|
|
|
-- Cell 2
|
|
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:linear-gradient(180deg,#c084fc,#a855f7); box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(if piece == 1 then py + 1 else py) * 30 + 1}px; left:{(if piece == 1 then px + 2 else (if piece == 3 then px + 2 else px + 2)) * 30 + 1}px" } []
|
|
|
|
-- Cell 3 (second row of piece, e.g. T-piece center bottom)
|
|
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:linear-gradient(180deg,#c084fc,#a855f7); box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(if piece == 1 then py + 1 else py + 1) * 30 + 1}px; left:{(if piece == 1 then px + 3 else (if piece == 6 then px + 1 else (if piece == 4 then px + 2 else px + 2))) * 30 + 1}px" } []
|
|
|
|
-- Ghost piece (Layer 4 derived -> Layer 5 UI)
|
|
-- Shows where piece will land (row 18)
|
|
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; border:2px dashed rgba(168,85,247,0.3); top:{18 * 30 + 1}px; left:{px * 30 + 1}px" } []
|
|
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; border:2px dashed rgba(168,85,247,0.3); top:{18 * 30 + 1}px; left:{(px + 1) * 30 + 1}px" } []
|
|
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; border:2px dashed rgba(168,85,247,0.3); top:{18 * 30 + 1}px; left:{(px + 2) * 30 + 1}px" } []
|
|
]
|
|
|
|
-- Side panel
|
|
column [
|
|
-- Next piece preview (Layer 1 data -> Layer 5 UI)
|
|
text "NEXT" { variant: "subtitle" }
|
|
stack { style: "position:relative; width:120px; height:90px; background:#0f0f23; border-radius:8px; border:1px solid #334155" } [
|
|
column { style: "position:absolute; width:24px; height:24px; border-radius:3px; top:20px; left:15px; background:linear-gradient(180deg,#c084fc,#a855f7); box-shadow:0 0 6px rgba(168,85,247,0.4)" } []
|
|
column { style: "position:absolute; width:24px; height:24px; border-radius:3px; top:20px; left:41px; background:linear-gradient(180deg,#c084fc,#a855f7); box-shadow:0 0 6px rgba(168,85,247,0.4)" } []
|
|
column { style: "position:absolute; width:24px; height:24px; border-radius:3px; top:20px; left:67px; background:linear-gradient(180deg,#c084fc,#a855f7); box-shadow:0 0 6px rgba(168,85,247,0.4)" } []
|
|
column { style: "position:absolute; width:24px; height:24px; border-radius:3px; top:46px; left:41px; background:linear-gradient(180deg,#c084fc,#a855f7); opacity:{if nextPiece == 6 then 1 else (if nextPiece == 4 then 1 else 0)}" } []
|
|
]
|
|
|
|
-- Stats (Layer 2 physics -> Layer 5 UI)
|
|
text "STATS" { variant: "subtitle" }
|
|
text "Score: {score}"
|
|
text "Lines: {lines}"
|
|
text "Level: {level}"
|
|
text "Speed: {dropInterval}" { variant: "muted" }
|
|
text "Ticks: {gravityTick}" { variant: "muted" }
|
|
|
|
-- Signal debug panel (live values)
|
|
text "SIGNALS" { variant: "subtitle" }
|
|
text "piece:{piece} px:{px} py:{py}" { variant: "muted" }
|
|
text "rot:{rotation} blk:{blocked}" { variant: "muted" }
|
|
text "lock:{lockTick} grav:{gravityTick}" { variant: "muted" }
|
|
text "score:{score} lines:{lines}" { variant: "muted" }
|
|
text "level:{level} speed:{dropInterval}" { variant: "muted" }
|
|
text "g13:{g13}" { variant: "muted" }
|
|
text "g14:{g14}" { variant: "muted" }
|
|
text "g15:{g15}" { variant: "muted" }
|
|
text "g16:{g16}" { variant: "muted" }
|
|
text "g17:{g17}" { variant: "muted" }
|
|
text "g18:{g18}" { variant: "muted" }
|
|
text "g19:{g19}" { variant: "muted" }
|
|
]
|
|
]
|
|
|
|
-- Controls (Layer 3 input -> signal writes)
|
|
row [
|
|
button (if paused then "Resume" else "Pause") {
|
|
click: paused = if paused then 0 else 1,
|
|
variant: "primary"
|
|
}
|
|
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 "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],
|
|
variant: "destructive"
|
|
}
|
|
]
|
|
|
|
when gameOver > 0 -> text "GAME OVER - Score: {score}, Lines: {lines}" { variant: "title" }
|
|
|
|
text "Arrow keys: Move/Rotate | Space: Hard drop | P: Pause" { variant: "muted" }
|
|
text "Streaming: 6 signal layers composited" { variant: "muted" }
|
|
]
|