Rewrote game-tetris.ds from scratch using a single flat 200-element grid array instead of 20 separate row signals, eliminating all row dispatch chains. Added SRS-standard rotations for all 7 pieces via array lookups, full collision detection (down/left/right/rotation), line clear with slice/concat cascade, computed ghost piece with togglable visibility (G key or button), hard drop, per-piece colors, and next piece preview. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
411 lines
38 KiB
Text
411 lines
38 KiB
Text
-- DreamStack Tetris
|
||
-- Blocks fall. They collide. They freeze. Lines clear.
|
||
--
|
||
-- Run:
|
||
-- Tab 1: cargo run -p ds-stream (relay)
|
||
-- Tab 2: dreamstack dev examples/game-tetris.ds (player)
|
||
|
||
import { Badge } from "../registry/components/badge"
|
||
|
||
-- ================================================================
|
||
-- GRID: flat 200-element array (20 rows × 10 cols)
|
||
-- grid[row * 10 + col] = 0 (empty) or 1-7 (piece color)
|
||
-- ================================================================
|
||
|
||
let grid = [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,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, 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,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, 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,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, 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,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]
|
||
|
||
-- 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
|
||
let nextPiece = 1
|
||
|
||
-- Game state
|
||
let score = 0
|
||
let lines = 0
|
||
let level = 1
|
||
let gameOver = 0
|
||
let paused = 0
|
||
let gravityTick = 0
|
||
let dropInterval = 24
|
||
let lockTick = 0
|
||
let clearDelay = 0
|
||
let ghostY = 0
|
||
let showGhost = 1
|
||
|
||
-- ================================================================
|
||
-- PIECE SHAPES: array lookup — sd[cell][x/y][shapeIndex]
|
||
-- Index = (piece - 1) * 4 + rotation (28 entries, pieces 1-7 × rot 0-3)
|
||
-- SRS standard rotations for all 7 tetrominoes
|
||
-- ================================================================
|
||
|
||
-- Cell 0 offsets (dx, dy)
|
||
let sd0x = [0, 2, 0, 1, 0, 1, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 2, 0, 1]
|
||
let sd0y = [1, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0]
|
||
-- Cell 1 offsets
|
||
let sd1x = [1, 2, 1, 1, 0, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 0, 0, 1, 1, 0, 1, 1, 1, 0]
|
||
let sd1y = [1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
|
||
-- Cell 2 offsets
|
||
let sd2x = [2, 2, 2, 1, 1, 1, 2, 0, 1, 1, 2, 1, 0, 0, 0, 0, 0, 2, 0, 1, 1, 2, 2, 1, 1, 2, 1, 1]
|
||
let sd2y = [1, 2, 2, 2, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1]
|
||
-- Cell 3 offsets
|
||
let sd3x = [3, 2, 3, 1, 2, 1, 2, 1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 0]
|
||
let sd3y = [1, 3, 2, 3, 1, 2, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2]
|
||
|
||
-- Color arrays for piece-type rendering (index 0=empty, 1-7=piece)
|
||
let colorL = ["#171717", "#22d3ee", "#60a5fa", "#fb923c", "#facc15", "#4ade80", "#c084fc", "#f87171"]
|
||
let colorD = ["#171717", "#06b6d4", "#3b82f6", "#f97316", "#eab308", "#22c55e", "#a855f7", "#ef4444"]
|
||
|
||
-- ================================================================
|
||
-- DERIVED: cell offsets from shape arrays
|
||
-- ================================================================
|
||
|
||
let si = 0
|
||
let c0dx = 0
|
||
let c0dy = 0
|
||
let c1dx = 0
|
||
let c1dy = 0
|
||
let c2dx = 0
|
||
let c2dy = 0
|
||
let c3dx = 0
|
||
let c3dy = 0
|
||
|
||
every 33 -> si = (piece - 1) * 4 + rotation
|
||
every 33 -> c0dx = sd0x[si]
|
||
every 33 -> c0dy = sd0y[si]
|
||
every 33 -> c1dx = sd1x[si]
|
||
every 33 -> c1dy = sd1y[si]
|
||
every 33 -> c2dx = sd2x[si]
|
||
every 33 -> c2dy = sd2y[si]
|
||
every 33 -> c3dx = sd3x[si]
|
||
every 33 -> c3dy = sd3y[si]
|
||
|
||
-- ================================================================
|
||
-- COLLISION: can the piece move down?
|
||
-- Check floor bounds + grid occupancy for all 4 cells
|
||
-- ================================================================
|
||
|
||
let blocked = 0
|
||
every 33 -> blocked = if py + c0dy + 1 >= 20 then 1 else (if py + c1dy + 1 >= 20 then 1 else (if py + c2dy + 1 >= 20 then 1 else (if py + c3dy + 1 >= 20 then 1 else (if grid[(py + c0dy + 1) * 10 + px + c0dx] > 0 then 1 else (if grid[(py + c1dy + 1) * 10 + px + c1dx] > 0 then 1 else (if grid[(py + c2dy + 1) * 10 + px + c2dx] > 0 then 1 else (if grid[(py + c3dy + 1) * 10 + px + c3dx] > 0 then 1 else 0)))))))
|
||
|
||
-- Left collision: wall + grid
|
||
let leftOk = 0
|
||
every 33 -> leftOk = if px + c0dx - 1 < 0 then 0 else (if px + c1dx - 1 < 0 then 0 else (if px + c2dx - 1 < 0 then 0 else (if px + c3dx - 1 < 0 then 0 else (if grid[(py + c0dy) * 10 + px + c0dx - 1] > 0 then 0 else (if grid[(py + c1dy) * 10 + px + c1dx - 1] > 0 then 0 else (if grid[(py + c2dy) * 10 + px + c2dx - 1] > 0 then 0 else (if grid[(py + c3dy) * 10 + px + c3dx - 1] > 0 then 0 else 1)))))))
|
||
|
||
-- Right collision: wall + grid
|
||
let rightOk = 0
|
||
every 33 -> rightOk = if px + c0dx + 1 >= 10 then 0 else (if px + c1dx + 1 >= 10 then 0 else (if px + c2dx + 1 >= 10 then 0 else (if px + c3dx + 1 >= 10 then 0 else (if grid[(py + c0dy) * 10 + px + c0dx + 1] > 0 then 0 else (if grid[(py + c1dy) * 10 + px + c1dx + 1] > 0 then 0 else (if grid[(py + c2dy) * 10 + px + c2dx + 1] > 0 then 0 else (if grid[(py + c3dy) * 10 + px + c3dx + 1] > 0 then 0 else 1)))))))
|
||
|
||
-- Rotation collision: check if next rotation fits
|
||
let nrot = 0
|
||
let nsi = 0
|
||
let nc0dx = 0
|
||
let nc0dy = 0
|
||
let nc1dx = 0
|
||
let nc1dy = 0
|
||
let nc2dx = 0
|
||
let nc2dy = 0
|
||
let nc3dx = 0
|
||
let nc3dy = 0
|
||
let rotOk = 0
|
||
|
||
every 33 -> nrot = (rotation + 1) % 4
|
||
every 33 -> nsi = (piece - 1) * 4 + nrot
|
||
every 33 -> nc0dx = sd0x[nsi]
|
||
every 33 -> nc0dy = sd0y[nsi]
|
||
every 33 -> nc1dx = sd1x[nsi]
|
||
every 33 -> nc1dy = sd1y[nsi]
|
||
every 33 -> nc2dx = sd2x[nsi]
|
||
every 33 -> nc2dy = sd2y[nsi]
|
||
every 33 -> nc3dx = sd3x[nsi]
|
||
every 33 -> nc3dy = sd3y[nsi]
|
||
every 33 -> rotOk = if px + nc0dx < 0 then 0 else (if px + nc0dx >= 10 then 0 else (if py + nc0dy >= 20 then 0 else (if px + nc1dx < 0 then 0 else (if px + nc1dx >= 10 then 0 else (if py + nc1dy >= 20 then 0 else (if px + nc2dx < 0 then 0 else (if px + nc2dx >= 10 then 0 else (if py + nc2dy >= 20 then 0 else (if px + nc3dx < 0 then 0 else (if px + nc3dx >= 10 then 0 else (if py + nc3dy >= 20 then 0 else (if grid[(py + nc0dy) * 10 + px + nc0dx] > 0 then 0 else (if grid[(py + nc1dy) * 10 + px + nc1dx] > 0 then 0 else (if grid[(py + nc2dy) * 10 + px + nc2dx] > 0 then 0 else (if grid[(py + nc3dy) * 10 + px + nc3dx] > 0 then 0 else 1)))))))))))))))
|
||
|
||
-- ================================================================
|
||
-- GHOST: compute landing position (try to push ghostY down 19 times)
|
||
-- ================================================================
|
||
|
||
every 33 -> ghostY = py
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
every 33 -> ghostY = if ghostY + c0dy + 1 >= 20 then ghostY else (if ghostY + c1dy + 1 >= 20 then ghostY else (if ghostY + c2dy + 1 >= 20 then ghostY else (if ghostY + c3dy + 1 >= 20 then ghostY else (if grid[(ghostY + c0dy + 1) * 10 + px + c0dx] > 0 then ghostY else (if grid[(ghostY + c1dy + 1) * 10 + px + c1dx] > 0 then ghostY else (if grid[(ghostY + c2dy + 1) * 10 + px + c2dx] > 0 then ghostY else (if grid[(ghostY + c3dy + 1) * 10 + px + c3dx] > 0 then ghostY else ghostY + 1)))))))
|
||
|
||
-- ================================================================
|
||
-- INPUT: keyboard controls
|
||
-- ================================================================
|
||
|
||
on keydown(ev) -> px = if gameOver then px else (if paused then px else (if ev.key == "ArrowLeft" then (if leftOk 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 rightOk 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 (if rotOk then nrot else rotation) else rotation))
|
||
on keydown(ev) -> py = if gameOver then py else (if paused then py else (if ev.key == "ArrowDown" then (if blocked then py else py + 1) else (if ev.key == " " then ghostY else py)))
|
||
on keydown(ev) -> paused = if ev.key == "p" then (if paused then 0 else 1) else paused
|
||
on keydown(ev) -> showGhost = if ev.key == "g" then (if showGhost then 0 else 1) else showGhost
|
||
|
||
-- ================================================================
|
||
-- PHYSICS: gravity pulls pieces down, contact freezes them
|
||
-- ================================================================
|
||
|
||
-- Gravity tick
|
||
every 33 -> gravityTick = if paused then gravityTick else (if gameOver then gravityTick else gravityTick + 1)
|
||
|
||
-- Auto-drop: gravity pulls piece down
|
||
every 33 -> py = if paused then py else (if gameOver then py else (if blocked then py else (if gravityTick > dropInterval then py + 1 else py)))
|
||
every 33 -> gravityTick = if gravityTick > dropInterval then 0 else gravityTick
|
||
|
||
-- Lock: when blocked (contact!), count frames then freeze
|
||
every 33 -> lockTick = if blocked then lockTick + 1 else 0
|
||
|
||
-- ================================================================
|
||
-- FREEZE: on contact, write piece cells into grid
|
||
-- ================================================================
|
||
|
||
every 33 -> grid[(py + c0dy) * 10 + px + c0dx] = if lockTick == 3 then piece else grid[(py + c0dy) * 10 + px + c0dx]
|
||
every 33 -> grid[(py + c1dy) * 10 + px + c1dx] = if lockTick == 3 then piece else grid[(py + c1dy) * 10 + px + c1dx]
|
||
every 33 -> grid[(py + c2dy) * 10 + px + c2dx] = if lockTick == 3 then piece else grid[(py + c2dy) * 10 + px + c2dx]
|
||
every 33 -> grid[(py + c3dy) * 10 + px + c3dx] = if lockTick == 3 then piece else grid[(py + c3dy) * 10 + px + c3dx]
|
||
|
||
-- Spawn new piece after freeze
|
||
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: detect full rows, cascade with slice/concat
|
||
-- ================================================================
|
||
|
||
-- Row fullness: check all 10 cells per row
|
||
let f0 = 0
|
||
let f1 = 0
|
||
let f2 = 0
|
||
let f3 = 0
|
||
let f4 = 0
|
||
let f5 = 0
|
||
let f6 = 0
|
||
let f7 = 0
|
||
let f8 = 0
|
||
let f9 = 0
|
||
let f10 = 0
|
||
let f11 = 0
|
||
let f12 = 0
|
||
let f13 = 0
|
||
let f14 = 0
|
||
let f15 = 0
|
||
let f16 = 0
|
||
let f17 = 0
|
||
let f18 = 0
|
||
let f19 = 0
|
||
|
||
every 33 -> f0 = if grid[0] > 0 then (if grid[1] > 0 then (if grid[2] > 0 then (if grid[3] > 0 then (if grid[4] > 0 then (if grid[5] > 0 then (if grid[6] > 0 then (if grid[7] > 0 then (if grid[8] > 0 then (if grid[9] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f1 = if grid[10] > 0 then (if grid[11] > 0 then (if grid[12] > 0 then (if grid[13] > 0 then (if grid[14] > 0 then (if grid[15] > 0 then (if grid[16] > 0 then (if grid[17] > 0 then (if grid[18] > 0 then (if grid[19] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f2 = if grid[20] > 0 then (if grid[21] > 0 then (if grid[22] > 0 then (if grid[23] > 0 then (if grid[24] > 0 then (if grid[25] > 0 then (if grid[26] > 0 then (if grid[27] > 0 then (if grid[28] > 0 then (if grid[29] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f3 = if grid[30] > 0 then (if grid[31] > 0 then (if grid[32] > 0 then (if grid[33] > 0 then (if grid[34] > 0 then (if grid[35] > 0 then (if grid[36] > 0 then (if grid[37] > 0 then (if grid[38] > 0 then (if grid[39] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f4 = if grid[40] > 0 then (if grid[41] > 0 then (if grid[42] > 0 then (if grid[43] > 0 then (if grid[44] > 0 then (if grid[45] > 0 then (if grid[46] > 0 then (if grid[47] > 0 then (if grid[48] > 0 then (if grid[49] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f5 = if grid[50] > 0 then (if grid[51] > 0 then (if grid[52] > 0 then (if grid[53] > 0 then (if grid[54] > 0 then (if grid[55] > 0 then (if grid[56] > 0 then (if grid[57] > 0 then (if grid[58] > 0 then (if grid[59] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f6 = if grid[60] > 0 then (if grid[61] > 0 then (if grid[62] > 0 then (if grid[63] > 0 then (if grid[64] > 0 then (if grid[65] > 0 then (if grid[66] > 0 then (if grid[67] > 0 then (if grid[68] > 0 then (if grid[69] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f7 = if grid[70] > 0 then (if grid[71] > 0 then (if grid[72] > 0 then (if grid[73] > 0 then (if grid[74] > 0 then (if grid[75] > 0 then (if grid[76] > 0 then (if grid[77] > 0 then (if grid[78] > 0 then (if grid[79] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f8 = if grid[80] > 0 then (if grid[81] > 0 then (if grid[82] > 0 then (if grid[83] > 0 then (if grid[84] > 0 then (if grid[85] > 0 then (if grid[86] > 0 then (if grid[87] > 0 then (if grid[88] > 0 then (if grid[89] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f9 = if grid[90] > 0 then (if grid[91] > 0 then (if grid[92] > 0 then (if grid[93] > 0 then (if grid[94] > 0 then (if grid[95] > 0 then (if grid[96] > 0 then (if grid[97] > 0 then (if grid[98] > 0 then (if grid[99] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f10 = if grid[100] > 0 then (if grid[101] > 0 then (if grid[102] > 0 then (if grid[103] > 0 then (if grid[104] > 0 then (if grid[105] > 0 then (if grid[106] > 0 then (if grid[107] > 0 then (if grid[108] > 0 then (if grid[109] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f11 = if grid[110] > 0 then (if grid[111] > 0 then (if grid[112] > 0 then (if grid[113] > 0 then (if grid[114] > 0 then (if grid[115] > 0 then (if grid[116] > 0 then (if grid[117] > 0 then (if grid[118] > 0 then (if grid[119] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f12 = if grid[120] > 0 then (if grid[121] > 0 then (if grid[122] > 0 then (if grid[123] > 0 then (if grid[124] > 0 then (if grid[125] > 0 then (if grid[126] > 0 then (if grid[127] > 0 then (if grid[128] > 0 then (if grid[129] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f13 = if grid[130] > 0 then (if grid[131] > 0 then (if grid[132] > 0 then (if grid[133] > 0 then (if grid[134] > 0 then (if grid[135] > 0 then (if grid[136] > 0 then (if grid[137] > 0 then (if grid[138] > 0 then (if grid[139] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f14 = if grid[140] > 0 then (if grid[141] > 0 then (if grid[142] > 0 then (if grid[143] > 0 then (if grid[144] > 0 then (if grid[145] > 0 then (if grid[146] > 0 then (if grid[147] > 0 then (if grid[148] > 0 then (if grid[149] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f15 = if grid[150] > 0 then (if grid[151] > 0 then (if grid[152] > 0 then (if grid[153] > 0 then (if grid[154] > 0 then (if grid[155] > 0 then (if grid[156] > 0 then (if grid[157] > 0 then (if grid[158] > 0 then (if grid[159] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f16 = if grid[160] > 0 then (if grid[161] > 0 then (if grid[162] > 0 then (if grid[163] > 0 then (if grid[164] > 0 then (if grid[165] > 0 then (if grid[166] > 0 then (if grid[167] > 0 then (if grid[168] > 0 then (if grid[169] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f17 = if grid[170] > 0 then (if grid[171] > 0 then (if grid[172] > 0 then (if grid[173] > 0 then (if grid[174] > 0 then (if grid[175] > 0 then (if grid[176] > 0 then (if grid[177] > 0 then (if grid[178] > 0 then (if grid[179] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f18 = if grid[180] > 0 then (if grid[181] > 0 then (if grid[182] > 0 then (if grid[183] > 0 then (if grid[184] > 0 then (if grid[185] > 0 then (if grid[186] > 0 then (if grid[187] > 0 then (if grid[188] > 0 then (if grid[189] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
every 33 -> f19 = if grid[190] > 0 then (if grid[191] > 0 then (if grid[192] > 0 then (if grid[193] > 0 then (if grid[194] > 0 then (if grid[195] > 0 then (if grid[196] > 0 then (if grid[197] > 0 then (if grid[198] > 0 then (if grid[199] > 0 then 1 else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0) else 0
|
||
|
||
-- Find bottommost full row
|
||
let clearRow = -1
|
||
every 33 -> clearRow = if f19 then 19 else (if f18 then 18 else (if f17 then 17 else (if f16 then 16 else (if f15 then 15 else (if f14 then 14 else (if f13 then 13 else (if f12 then 12 else (if f11 then 11 else (if f10 then 10 else (if f9 then 9 else (if f8 then 8 else (if f7 then 7 else (if f6 then 6 else (if f5 then 5 else (if f4 then 4 else (if f3 then 3 else (if f2 then 2 else (if f1 then 1 else (if f0 then 0 else -1)))))))))))))))))))
|
||
|
||
-- Clear row: slice out the full row, prepend empty row
|
||
every 33 -> clearDelay = if clearRow >= 0 then clearDelay + 1 else 0
|
||
every 33 -> grid = if clearDelay == 2 then concat([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], concat(slice(grid, 0, clearRow * 10), slice(grid, (clearRow + 1) * 10, 200))) else grid
|
||
every 33 -> score = if clearDelay == 2 then score + 100 else score
|
||
every 33 -> lines = if clearDelay == 2 then lines + 1 else lines
|
||
every 33 -> clearDelay = if clearDelay == 2 then 0 else clearDelay
|
||
|
||
-- Level and speed
|
||
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: spawn area occupied
|
||
every 33 -> gameOver = if grid[3] > 0 then 1 else (if grid[4] > 0 then 1 else (if grid[5] > 0 then 1 else (if grid[6] > 0 then 1 else gameOver)))
|
||
|
||
-- ================================================================
|
||
-- SOUND
|
||
-- ================================================================
|
||
|
||
every 33 -> play_tone(if lockTick == 3 then 220 else 0, 80)
|
||
every 33 -> play_tone(if gameOver then 110 else 0, 500, "sawtooth")
|
||
|
||
-- ================================================================
|
||
-- STREAM: spectator broadcast
|
||
-- ================================================================
|
||
|
||
stream tetris on "ws://localhost:9100/peer/tetris" {
|
||
mode: signal,
|
||
output: piece, rotation, px, py, nextPiece, score, lines, level, gameOver, paused, grid
|
||
}
|
||
|
||
-- ================================================================
|
||
-- VIEW
|
||
-- ================================================================
|
||
|
||
view tetris_game = column [
|
||
text "DreamStack Tetris" { variant: "title" }
|
||
|
||
row [
|
||
Badge { label: "Score: {score}", variant: "success" }
|
||
Badge { label: "Lines: {lines}", variant: "info" }
|
||
Badge { label: "Level: {level}", variant: "warning" }
|
||
]
|
||
|
||
row [
|
||
-- Main Board: 300x600
|
||
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
|
||
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 0
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:1px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[c]]}; opacity:{if grid[c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 1
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:31px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[10 + c]]}; opacity:{if grid[10 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 2
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:61px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[20 + c]]}; opacity:{if grid[20 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 3
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:91px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[30 + c]]}; opacity:{if grid[30 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 4
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:121px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[40 + c]]}; opacity:{if grid[40 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 5
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:151px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[50 + c]]}; opacity:{if grid[50 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 6
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:181px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[60 + c]]}; opacity:{if grid[60 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 7
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:211px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[70 + c]]}; opacity:{if grid[70 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 8
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:241px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[80 + c]]}; opacity:{if grid[80 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 9
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:271px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[90 + c]]}; opacity:{if grid[90 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 10
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:301px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[100 + c]]}; opacity:{if grid[100 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 11
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:331px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[110 + c]]}; opacity:{if grid[110 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 12
|
||
for c in [0,1,2,3,4,5,6,7,8,9] ->
|
||
column { style: "position:absolute; width:28px; height:28px; top:361px; left:{c * 30 + 1}px; border-radius:4px; background:{colorD[grid[120 + c]]}; opacity:{if grid[120 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 13
|
||
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:{colorD[grid[130 + c]]}; opacity:{if grid[130 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- 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:{colorD[grid[140 + c]]}; opacity:{if grid[140 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- 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:{colorD[grid[150 + c]]}; opacity:{if grid[150 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- 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:{colorD[grid[160 + c]]}; opacity:{if grid[160 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- 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:{colorD[grid[170 + c]]}; opacity:{if grid[170 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- 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:{colorD[grid[180 + c]]}; opacity:{if grid[180 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
-- Row 19
|
||
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:{colorD[grid[190 + c]]}; opacity:{if grid[190 + c] > 0 then 1 else 0}; transition:opacity 0.15s; box-shadow:0 0 8px rgba(99,102,241,0.3)" } []
|
||
|
||
-- Ghost piece: shows where piece will land
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; border:2px dashed {colorD[piece]}; opacity:{if showGhost then 0.3 else 0}; top:{(ghostY + c0dy) * 30 + 1}px; left:{(px + c0dx) * 30 + 1}px" } []
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; border:2px dashed {colorD[piece]}; opacity:{if showGhost then 0.3 else 0}; top:{(ghostY + c1dy) * 30 + 1}px; left:{(px + c1dx) * 30 + 1}px" } []
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; border:2px dashed {colorD[piece]}; opacity:{if showGhost then 0.3 else 0}; top:{(ghostY + c2dy) * 30 + 1}px; left:{(px + c2dx) * 30 + 1}px" } []
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; border:2px dashed {colorD[piece]}; opacity:{if showGhost then 0.3 else 0}; top:{(ghostY + c3dy) * 30 + 1}px; left:{(px + c3dx) * 30 + 1}px" } []
|
||
|
||
-- Active piece: 4 cells positioned by offsets
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:{colorL[piece]}; box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(py + c0dy) * 30 + 1}px; left:{(px + c0dx) * 30 + 1}px" } []
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:{colorL[piece]}; box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(py + c1dy) * 30 + 1}px; left:{(px + c1dx) * 30 + 1}px" } []
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:{colorL[piece]}; box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(py + c2dy) * 30 + 1}px; left:{(px + c2dx) * 30 + 1}px" } []
|
||
column { style: "position:absolute; width:28px; height:28px; border-radius:4px; background:{colorL[piece]}; box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s,top 0.05s; top:{(py + c3dy) * 30 + 1}px; left:{(px + c3dx) * 30 + 1}px" } []
|
||
]
|
||
|
||
-- Side panel
|
||
column [
|
||
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:{sd0y[(nextPiece - 1) * 4] * 26 + 10}px; left:{sd0x[(nextPiece - 1) * 4] * 26 + 10}px; background:{colorL[nextPiece]}" } []
|
||
column { style: "position:absolute; width:24px; height:24px; border-radius:3px; top:{sd1y[(nextPiece - 1) * 4] * 26 + 10}px; left:{sd1x[(nextPiece - 1) * 4] * 26 + 10}px; background:{colorL[nextPiece]}" } []
|
||
column { style: "position:absolute; width:24px; height:24px; border-radius:3px; top:{sd2y[(nextPiece - 1) * 4] * 26 + 10}px; left:{sd2x[(nextPiece - 1) * 4] * 26 + 10}px; background:{colorL[nextPiece]}" } []
|
||
column { style: "position:absolute; width:24px; height:24px; border-radius:3px; top:{sd3y[(nextPiece - 1) * 4] * 26 + 10}px; left:{sd3x[(nextPiece - 1) * 4] * 26 + 10}px; background:{colorL[nextPiece]}" } []
|
||
]
|
||
|
||
text "STATS" { variant: "subtitle" }
|
||
text "Score: {score}"
|
||
text "Lines: {lines}"
|
||
text "Level: {level}"
|
||
text "Speed: {dropInterval}" { variant: "muted" }
|
||
|
||
text "DEBUG" { variant: "subtitle" }
|
||
text "piece:{piece} rot:{rotation}" { variant: "muted" }
|
||
text "px:{px} py:{py}" { variant: "muted" }
|
||
text "blk:{blocked} lock:{lockTick}" { variant: "muted" }
|
||
text "si:{si} clr:{clearRow}" { variant: "muted" }
|
||
]
|
||
]
|
||
|
||
-- Controls
|
||
row [
|
||
button (if paused then "Resume" else "Pause") {
|
||
click: paused = if paused then 0 else 1,
|
||
variant: "primary"
|
||
}
|
||
button "Left" { click: px = if leftOk then px - 1 else px, variant: "secondary" }
|
||
button "Right" { click: px = if rightOk then px + 1 else px, variant: "secondary" }
|
||
button "Rotate" { click: rotation = if rotOk then nrot else rotation, variant: "secondary" }
|
||
button "Drop" { click: py = ghostY, variant: "secondary" }
|
||
button (if showGhost then "Ghost: ON" else "Ghost: OFF") { click: showGhost = if showGhost then 0 else 1, variant: "outline" }
|
||
button "Reset" {
|
||
click: score = 0; lines = 0; level = 1; gameOver = 0; paused = 0; piece = 6; nextPiece = 1; px = 3; py = 0; rotation = 0; grid = [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,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, 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,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, 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,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, 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,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],
|
||
variant: "destructive"
|
||
}
|
||
]
|
||
|
||
when gameOver > 0 -> text "GAME OVER — Score: {score}" { variant: "title" }
|
||
]
|