feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
-- 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 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 ev.key == " " then 18 else py))
|
|
|
|
|
on keydown(ev) -> paused = if ev.key == "p" then (if paused then 0 else 1) else paused
|
|
|
|
|
|
|
|
|
|
-- ================================================================
|
2026-02-27 12:20:09 -08:00
|
|
|
-- LAYER 4: DERIVED SIGNALS — Collision, gravity, lock, line clear
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
-- ================================================================
|
|
|
|
|
|
2026-02-27 12:20:09 -08:00
|
|
|
-- Collision detection signal: is the row BELOW the piece occupied?
|
|
|
|
|
-- For a standard piece (3 wide, 2 tall like T), the bottom cells are at py+1.
|
|
|
|
|
-- We check if grid row py+2 has any blocks at the piece's x positions.
|
|
|
|
|
-- `blocked` = 1 means can't move down, 0 means safe to drop.
|
|
|
|
|
--
|
|
|
|
|
-- Since grid rows are individual signals, we branch on py to check the right row.
|
|
|
|
|
-- Also blocked at the bottom wall (py >= 18 for standard pieces).
|
|
|
|
|
let blocked = 0
|
|
|
|
|
|
|
|
|
|
-- Check collision: at bottom wall OR frozen cells below
|
|
|
|
|
-- T/J/L/S/Z pieces: bottom at py+1, so check py+2
|
|
|
|
|
-- I piece: bottom at py+1, check py+2
|
|
|
|
|
-- Piece bottom row = py+1, next row down = py+2
|
|
|
|
|
every 33 -> blocked = if py > 17 then 1 else (if py == 17 then (if g19[px] > 0 then 1 else (if g19[px + 1] > 0 then 1 else 0)) else (if py == 16 then (if g18[px] > 0 then 1 else (if g18[px + 1] > 0 then 1 else 0)) else (if py == 15 then (if g17[px] > 0 then 1 else (if g17[px + 1] > 0 then 1 else 0)) else (if py == 14 then (if g16[px] > 0 then 1 else (if g16[px + 1] > 0 then 1 else 0)) else (if py == 13 then (if g15[px] > 0 then 1 else (if g15[px + 1] > 0 then 1 else 0)) else (if py == 12 then (if g14[px] > 0 then 1 else (if g14[px + 1] > 0 then 1 else 0)) else 0))))))
|
|
|
|
|
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
-- Gravity tick
|
|
|
|
|
every 33 -> gravityTick = if paused then gravityTick else (if gameOver then gravityTick else gravityTick + 1)
|
|
|
|
|
|
2026-02-27 12:20:09 -08:00
|
|
|
-- 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)))
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
every 33 -> gravityTick = if gravityTick > dropInterval then 0 else gravityTick
|
|
|
|
|
|
2026-02-27 12:20:09 -08:00
|
|
|
-- 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]
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
|
|
2026-02-27 12:20:09 -08:00
|
|
|
-- Shift rows down on clear: copy row 18 into row 19
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
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]
|
2026-02-27 12:20:09 -08:00
|
|
|
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]
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
|
|
|
|
|
-- 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)
|
|
|
|
|
|
2026-02-27 12:20:09 -08:00
|
|
|
-- Game over: any cell in row 0 filled
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
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)" } []
|
|
|
|
|
|
2026-02-27 12:20:09 -08:00
|
|
|
-- 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
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
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" }
|
|
|
|
|
|
2026-02-27 12:26:01 -08:00
|
|
|
-- Signal debug panel (live values)
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
text "SIGNALS" { variant: "subtitle" }
|
2026-02-27 12:26:01 -08:00
|
|
|
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" }
|
feat: tetris — signal composition showcase with 6 reactive layers
game-tetris.ds demonstrates DreamStack signal composition:
- Layer 1: Data signals (20 grid rows, piece state, next piece)
- Layer 2: Physics signals (gravity tick, drop speed, level scaling)
- Layer 3: Input signals (keyboard events -> movement commands)
- Layer 4: Derived signals (lock detection, line clear, game over)
- Layer 5: Sound signals (lock, clear, game over tones)
- Layer 6: Stream signals (30+ signals broadcast for spectators)
Board: 10x20 grid, 7 piece types, ghost piece indicator
Controls: Arrow keys, space (hard drop), P (pause)
Side panel: next piece preview, stats, signal layer key
Compiles to 76KB HTML+JS
2026-02-27 12:13:22 -08:00
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
-- 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 = 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" }
|
|
|
|
|
]
|