diff --git a/compiler/ds-codegen/src/js_emitter.rs b/compiler/ds-codegen/src/js_emitter.rs index 02f19bf..4d4d299 100644 --- a/compiler/ds-codegen/src/js_emitter.rs +++ b/compiler/ds-codegen/src/js_emitter.rs @@ -483,9 +483,12 @@ impl JsEmitter { // Destructure props into local signal-compatible variables // Props may be raw values, signals, or callback functions for p in &comp.props { - // Create a signal-like wrapper: if prop is a function, keep as-is; if already a signal, use it; otherwise wrap + // Create a signal-like wrapper: + // - If prop is a function (reactive getter), wrap as { get value() { return props.x(); } } + // - If already a signal object, use it directly + // - Otherwise wrap raw value as a getter self.emit_line(&format!( - "const {} = (typeof props.{} === 'function') ? props.{} : (props.{} !== undefined && props.{} !== null) ? (typeof props.{} === 'object' && 'value' in props.{} ? props.{} : {{ get value() {{ return props.{}; }} }}) : {{ get value() {{ return \"\"; }} }};", + "const {} = (typeof props.{} === 'function') ? {{ get value() {{ return props.{}(); }} }} : (props.{} !== undefined && props.{} !== null) ? (typeof props.{} === 'object' && 'value' in props.{} ? props.{} : {{ get value() {{ return props.{}; }} }}) : {{ get value() {{ return \"\"; }} }};", p.name, p.name, p.name, p.name, p.name, p.name, p.name, p.name, p.name )); } @@ -572,7 +575,14 @@ impl JsEmitter { } "style" => { let js = self.emit_expr(val); - self.emit_line(&format!("{}.style.cssText = {};", node_var, js)); + if js.contains(".value") { + self.emit_line(&format!( + "DS.effect(() => {{ {}.style.cssText = {}; }});", + node_var, js + )); + } else { + self.emit_line(&format!("{}.style.cssText = {};", node_var, js)); + } } _ => { // Layout props (x, y, width, height) or arbitrary style @@ -940,7 +950,13 @@ impl JsEmitter { let handler_js = self.emit_event_handler_expr(v); format!("{}: () => {{ {}; DS.flush() }}", k, handler_js) } else { - format!("{}: {}", k, self.emit_expr(v)) + let js = self.emit_expr(v); + // If prop expression references signals, wrap as reactive getter + if js.contains(".value") { + format!("{}: () => {}", k, js) + } else { + format!("{}: {}", k, js) + } } }) .collect(); diff --git a/examples/game-breakout.ds b/examples/game-breakout.ds index e65181e..2243621 100644 --- a/examples/game-breakout.ds +++ b/examples/game-breakout.ds @@ -2,6 +2,9 @@ -- Classic brick-breaker game with keyboard controls, sound effects, and streaming -- Paddle, ball, and bricks are CSS-positioned elements inside a stack container -- +-- Layout: Blue (top/far) → Green → Yellow → Orange → Red (bottom/near paddle) +-- Ball hits red first, then works up — classic Breakout style +-- -- Run: -- Tab 1: cargo run -p ds-stream (relay) -- Tab 2: dreamstack dev examples/game-breakout.ds (player) @@ -11,14 +14,19 @@ import { Badge } from "../registry/components/badge" -- ── State ── let paddleX = 250 let ballX = 300 -let ballY = 350 -let bvx = 3 -let bvy = -3 +let ballY = 320 +let bvx = 4 +let bvy = -4 let score = 0 let lives = 3 let paused = 0 -- 5 rows × 10 columns = 50 bricks (1 = alive, 0 = destroyed) +-- r0 = red (bottom, closest to paddle) y=200..216 +-- r1 = orange y=180..196 +-- r2 = yellow y=160..176 +-- r3 = green y=140..156 +-- r4 = blue (top, farthest) y=120..136 let r0 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] let r1 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] let r2 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] @@ -26,7 +34,7 @@ let r3 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] let r4 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] -- ── Keyboard controls ── -on keydown(ev) -> paddleX = if ev.key == "ArrowLeft" then (if paddleX > 0 then paddleX - 30 else paddleX) else (if ev.key == "ArrowRight" then (if paddleX < 520 then paddleX + 30 else paddleX) else paddleX) +on keydown(ev) -> paddleX = if ev.key == "ArrowLeft" then (if paddleX > 0 then paddleX - 40 else paddleX) else (if ev.key == "ArrowRight" then (if paddleX < 520 then paddleX + 40 else paddleX) else paddleX) on keydown(ev) -> paused = if ev.key == " " then (if paused then 0 else 1) else paused -- ── Game loop at ~30fps ── @@ -34,58 +42,99 @@ every 33 -> ballX = if paused then ballX else (if lives > 0 then ballX + bvx els every 33 -> ballY = if paused then ballY else (if lives > 0 then ballY + bvy else ballY) -- Wall bounces (left/right/ceiling) -every 33 -> bvx = if ballX < 4 then 3 else bvx -every 33 -> bvx = if ballX > 590 then -3 else bvx -every 33 -> bvy = if ballY < 4 then 3 else bvy +every 33 -> bvx = if ballX < 4 then 4 else bvx +every 33 -> bvx = if ballX > 590 then -4 else bvx +every 33 -> bvy = if ballY < 4 then 4 else bvy -- Ball lost (bottom) — lose a life and reset -every 33 -> lives = if ballY > 410 then lives - 1 else lives -every 33 -> ballX = if ballY > 410 then 300 else ballX -every 33 -> ballY = if ballY > 410 then 350 else ballY -every 33 -> bvx = if ballY > 410 then 3 else bvx -every 33 -> bvy = if ballY > 410 then -3 else bvy +every 33 -> lives = if ballY > 395 then lives - 1 else lives +every 33 -> ballX = if ballY > 395 then 300 else ballX +every 33 -> ballY = if ballY > 395 then 320 else ballY +every 33 -> bvx = if ballY > 395 then 4 else bvx +every 33 -> bvy = if ballY > 395 then -4 else bvy --- Paddle hit -every 33 -> bvy = if ballY > 365 then (if ballX > paddleX then (if ballX < paddleX + 80 then -3 else bvy) else bvy) else bvy +-- Paddle hit (paddle at y=360) +every 33 -> bvy = if ballY > 348 then (if ballX > paddleX then (if ballX < paddleX + 80 then -4 else bvy) else bvy) else bvy --- ── Brick collision: row 0 (y=30..48) ── --- Score + bounce FIRST (before brick is cleared) -every 33 -> score = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if r0[floor(ballX / 60)] then score + 10 else score) else score) else score) else score -every 33 -> bvy = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if r0[floor(ballX / 60)] then 3 else bvy) else bvy) else bvy) else bvy +-- ────────────────────────────────────────────────────── +-- Brick collision for all 5 rows +-- Each row: score+bounce FIRST, then destroy per-brick +-- ────────────────────────────────────────────────────── --- Then destroy the brick -every 33 -> r0[0] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 0 then (if ballX < 60 then (if r0[0] then 0 else r0[0]) else r0[0]) else r0[0]) else r0[0]) else r0[0]) else r0[0] -every 33 -> r0[1] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 60 then (if ballX < 120 then (if r0[1] then 0 else r0[1]) else r0[1]) else r0[1]) else r0[1]) else r0[1]) else r0[1] -every 33 -> r0[2] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 120 then (if ballX < 180 then (if r0[2] then 0 else r0[2]) else r0[2]) else r0[2]) else r0[2]) else r0[2]) else r0[2] -every 33 -> r0[3] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 180 then (if ballX < 240 then (if r0[3] then 0 else r0[3]) else r0[3]) else r0[3]) else r0[3]) else r0[3]) else r0[3] -every 33 -> r0[4] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 240 then (if ballX < 300 then (if r0[4] then 0 else r0[4]) else r0[4]) else r0[4]) else r0[4]) else r0[4]) else r0[4] -every 33 -> r0[5] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 300 then (if ballX < 360 then (if r0[5] then 0 else r0[5]) else r0[5]) else r0[5]) else r0[5]) else r0[5]) else r0[5] -every 33 -> r0[6] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 360 then (if ballX < 420 then (if r0[6] then 0 else r0[6]) else r0[6]) else r0[6]) else r0[6]) else r0[6]) else r0[6] -every 33 -> r0[7] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 420 then (if ballX < 480 then (if r0[7] then 0 else r0[7]) else r0[7]) else r0[7]) else r0[7]) else r0[7]) else r0[7] -every 33 -> r0[8] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 480 then (if ballX < 540 then (if r0[8] then 0 else r0[8]) else r0[8]) else r0[8]) else r0[8]) else r0[8]) else r0[8] -every 33 -> r0[9] = if bvy < 0 then (if ballY < 48 then (if ballY > 28 then (if ballX > 540 then (if ballX < 600 then (if r0[9] then 0 else r0[9]) else r0[9]) else r0[9]) else r0[9]) else r0[9]) else r0[9] +-- ── Row 0: RED (bottom, y=200..216) — hit first by ball ── +every 33 -> score = if bvy < 0 then (if ballY < 218 then (if ballY > 198 then (if r0[floor(ballX / 60)] then score + 10 else score) else score) else score) else score +every 33 -> bvy = if bvy < 0 then (if ballY < 218 then (if ballY > 198 then (if r0[floor(ballX / 60)] then 4 else bvy) else bvy) else bvy) else bvy +every 33 -> r0[0] = if ballY < 218 then (if ballY > 198 then (if ballX > 0 then (if ballX < 60 then (if r0[0] then 0 else r0[0]) else r0[0]) else r0[0]) else r0[0]) else r0[0] +every 33 -> r0[1] = if ballY < 218 then (if ballY > 198 then (if ballX > 60 then (if ballX < 120 then (if r0[1] then 0 else r0[1]) else r0[1]) else r0[1]) else r0[1]) else r0[1] +every 33 -> r0[2] = if ballY < 218 then (if ballY > 198 then (if ballX > 120 then (if ballX < 180 then (if r0[2] then 0 else r0[2]) else r0[2]) else r0[2]) else r0[2]) else r0[2] +every 33 -> r0[3] = if ballY < 218 then (if ballY > 198 then (if ballX > 180 then (if ballX < 240 then (if r0[3] then 0 else r0[3]) else r0[3]) else r0[3]) else r0[3]) else r0[3] +every 33 -> r0[4] = if ballY < 218 then (if ballY > 198 then (if ballX > 240 then (if ballX < 300 then (if r0[4] then 0 else r0[4]) else r0[4]) else r0[4]) else r0[4]) else r0[4] +every 33 -> r0[5] = if ballY < 218 then (if ballY > 198 then (if ballX > 300 then (if ballX < 360 then (if r0[5] then 0 else r0[5]) else r0[5]) else r0[5]) else r0[5]) else r0[5] +every 33 -> r0[6] = if ballY < 218 then (if ballY > 198 then (if ballX > 360 then (if ballX < 420 then (if r0[6] then 0 else r0[6]) else r0[6]) else r0[6]) else r0[6]) else r0[6] +every 33 -> r0[7] = if ballY < 218 then (if ballY > 198 then (if ballX > 420 then (if ballX < 480 then (if r0[7] then 0 else r0[7]) else r0[7]) else r0[7]) else r0[7]) else r0[7] +every 33 -> r0[8] = if ballY < 218 then (if ballY > 198 then (if ballX > 480 then (if ballX < 540 then (if r0[8] then 0 else r0[8]) else r0[8]) else r0[8]) else r0[8]) else r0[8] +every 33 -> r0[9] = if ballY < 218 then (if ballY > 198 then (if ballX > 540 then (if ballX < 600 then (if r0[9] then 0 else r0[9]) else r0[9]) else r0[9]) else r0[9]) else r0[9] --- ── Brick collision: row 1 (y=50..68) ── --- Score + bounce FIRST -every 33 -> score = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if r1[floor(ballX / 60)] then score + 10 else score) else score) else score) else score -every 33 -> bvy = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if r1[floor(ballX / 60)] then 3 else bvy) else bvy) else bvy) else bvy +-- ── Row 1: ORANGE (y=180..196) ── +every 33 -> score = if bvy < 0 then (if ballY < 198 then (if ballY > 178 then (if r1[floor(ballX / 60)] then score + 10 else score) else score) else score) else score +every 33 -> bvy = if bvy < 0 then (if ballY < 198 then (if ballY > 178 then (if r1[floor(ballX / 60)] then 4 else bvy) else bvy) else bvy) else bvy +every 33 -> r1[0] = if ballY < 198 then (if ballY > 178 then (if ballX > 0 then (if ballX < 60 then (if r1[0] then 0 else r1[0]) else r1[0]) else r1[0]) else r1[0]) else r1[0] +every 33 -> r1[1] = if ballY < 198 then (if ballY > 178 then (if ballX > 60 then (if ballX < 120 then (if r1[1] then 0 else r1[1]) else r1[1]) else r1[1]) else r1[1]) else r1[1] +every 33 -> r1[2] = if ballY < 198 then (if ballY > 178 then (if ballX > 120 then (if ballX < 180 then (if r1[2] then 0 else r1[2]) else r1[2]) else r1[2]) else r1[2]) else r1[2] +every 33 -> r1[3] = if ballY < 198 then (if ballY > 178 then (if ballX > 180 then (if ballX < 240 then (if r1[3] then 0 else r1[3]) else r1[3]) else r1[3]) else r1[3]) else r1[3] +every 33 -> r1[4] = if ballY < 198 then (if ballY > 178 then (if ballX > 240 then (if ballX < 300 then (if r1[4] then 0 else r1[4]) else r1[4]) else r1[4]) else r1[4]) else r1[4] +every 33 -> r1[5] = if ballY < 198 then (if ballY > 178 then (if ballX > 300 then (if ballX < 360 then (if r1[5] then 0 else r1[5]) else r1[5]) else r1[5]) else r1[5]) else r1[5] +every 33 -> r1[6] = if ballY < 198 then (if ballY > 178 then (if ballX > 360 then (if ballX < 420 then (if r1[6] then 0 else r1[6]) else r1[6]) else r1[6]) else r1[6]) else r1[6] +every 33 -> r1[7] = if ballY < 198 then (if ballY > 178 then (if ballX > 420 then (if ballX < 480 then (if r1[7] then 0 else r1[7]) else r1[7]) else r1[7]) else r1[7]) else r1[7] +every 33 -> r1[8] = if ballY < 198 then (if ballY > 178 then (if ballX > 480 then (if ballX < 540 then (if r1[8] then 0 else r1[8]) else r1[8]) else r1[8]) else r1[8]) else r1[8] +every 33 -> r1[9] = if ballY < 198 then (if ballY > 178 then (if ballX > 540 then (if ballX < 600 then (if r1[9] then 0 else r1[9]) else r1[9]) else r1[9]) else r1[9]) else r1[9] --- Then destroy the brick -every 33 -> r1[0] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 0 then (if ballX < 60 then (if r1[0] then 0 else r1[0]) else r1[0]) else r1[0]) else r1[0]) else r1[0]) else r1[0] -every 33 -> r1[1] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 60 then (if ballX < 120 then (if r1[1] then 0 else r1[1]) else r1[1]) else r1[1]) else r1[1]) else r1[1]) else r1[1] -every 33 -> r1[2] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 120 then (if ballX < 180 then (if r1[2] then 0 else r1[2]) else r1[2]) else r1[2]) else r1[2]) else r1[2]) else r1[2] -every 33 -> r1[3] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 180 then (if ballX < 240 then (if r1[3] then 0 else r1[3]) else r1[3]) else r1[3]) else r1[3]) else r1[3]) else r1[3] -every 33 -> r1[4] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 240 then (if ballX < 300 then (if r1[4] then 0 else r1[4]) else r1[4]) else r1[4]) else r1[4]) else r1[4]) else r1[4] -every 33 -> r1[5] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 300 then (if ballX < 360 then (if r1[5] then 0 else r1[5]) else r1[5]) else r1[5]) else r1[5]) else r1[5]) else r1[5] -every 33 -> r1[6] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 360 then (if ballX < 420 then (if r1[6] then 0 else r1[6]) else r1[6]) else r1[6]) else r1[6]) else r1[6]) else r1[6] -every 33 -> r1[7] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 420 then (if ballX < 480 then (if r1[7] then 0 else r1[7]) else r1[7]) else r1[7]) else r1[7]) else r1[7]) else r1[7] -every 33 -> r1[8] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 480 then (if ballX < 540 then (if r1[8] then 0 else r1[8]) else r1[8]) else r1[8]) else r1[8]) else r1[8]) else r1[8] -every 33 -> r1[9] = if bvy < 0 then (if ballY < 68 then (if ballY > 48 then (if ballX > 540 then (if ballX < 600 then (if r1[9] then 0 else r1[9]) else r1[9]) else r1[9]) else r1[9]) else r1[9]) else r1[9] +-- ── Row 2: YELLOW (y=160..176) ── +every 33 -> score = if bvy < 0 then (if ballY < 178 then (if ballY > 158 then (if r2[floor(ballX / 60)] then score + 10 else score) else score) else score) else score +every 33 -> bvy = if bvy < 0 then (if ballY < 178 then (if ballY > 158 then (if r2[floor(ballX / 60)] then 4 else bvy) else bvy) else bvy) else bvy +every 33 -> r2[0] = if ballY < 178 then (if ballY > 158 then (if ballX > 0 then (if ballX < 60 then (if r2[0] then 0 else r2[0]) else r2[0]) else r2[0]) else r2[0]) else r2[0] +every 33 -> r2[1] = if ballY < 178 then (if ballY > 158 then (if ballX > 60 then (if ballX < 120 then (if r2[1] then 0 else r2[1]) else r2[1]) else r2[1]) else r2[1]) else r2[1] +every 33 -> r2[2] = if ballY < 178 then (if ballY > 158 then (if ballX > 120 then (if ballX < 180 then (if r2[2] then 0 else r2[2]) else r2[2]) else r2[2]) else r2[2]) else r2[2] +every 33 -> r2[3] = if ballY < 178 then (if ballY > 158 then (if ballX > 180 then (if ballX < 240 then (if r2[3] then 0 else r2[3]) else r2[3]) else r2[3]) else r2[3]) else r2[3] +every 33 -> r2[4] = if ballY < 178 then (if ballY > 158 then (if ballX > 240 then (if ballX < 300 then (if r2[4] then 0 else r2[4]) else r2[4]) else r2[4]) else r2[4]) else r2[4] +every 33 -> r2[5] = if ballY < 178 then (if ballY > 158 then (if ballX > 300 then (if ballX < 360 then (if r2[5] then 0 else r2[5]) else r2[5]) else r2[5]) else r2[5]) else r2[5] +every 33 -> r2[6] = if ballY < 178 then (if ballY > 158 then (if ballX > 360 then (if ballX < 420 then (if r2[6] then 0 else r2[6]) else r2[6]) else r2[6]) else r2[6]) else r2[6] +every 33 -> r2[7] = if ballY < 178 then (if ballY > 158 then (if ballX > 420 then (if ballX < 480 then (if r2[7] then 0 else r2[7]) else r2[7]) else r2[7]) else r2[7]) else r2[7] +every 33 -> r2[8] = if ballY < 178 then (if ballY > 158 then (if ballX > 480 then (if ballX < 540 then (if r2[8] then 0 else r2[8]) else r2[8]) else r2[8]) else r2[8]) else r2[8] +every 33 -> r2[9] = if ballY < 178 then (if ballY > 158 then (if ballX > 540 then (if ballX < 600 then (if r2[9] then 0 else r2[9]) else r2[9]) else r2[9]) else r2[9]) else r2[9] + +-- ── Row 3: GREEN (y=140..156) ── +every 33 -> score = if bvy < 0 then (if ballY < 158 then (if ballY > 138 then (if r3[floor(ballX / 60)] then score + 10 else score) else score) else score) else score +every 33 -> bvy = if bvy < 0 then (if ballY < 158 then (if ballY > 138 then (if r3[floor(ballX / 60)] then 4 else bvy) else bvy) else bvy) else bvy +every 33 -> r3[0] = if ballY < 158 then (if ballY > 138 then (if ballX > 0 then (if ballX < 60 then (if r3[0] then 0 else r3[0]) else r3[0]) else r3[0]) else r3[0]) else r3[0] +every 33 -> r3[1] = if ballY < 158 then (if ballY > 138 then (if ballX > 60 then (if ballX < 120 then (if r3[1] then 0 else r3[1]) else r3[1]) else r3[1]) else r3[1]) else r3[1] +every 33 -> r3[2] = if ballY < 158 then (if ballY > 138 then (if ballX > 120 then (if ballX < 180 then (if r3[2] then 0 else r3[2]) else r3[2]) else r3[2]) else r3[2]) else r3[2] +every 33 -> r3[3] = if ballY < 158 then (if ballY > 138 then (if ballX > 180 then (if ballX < 240 then (if r3[3] then 0 else r3[3]) else r3[3]) else r3[3]) else r3[3]) else r3[3] +every 33 -> r3[4] = if ballY < 158 then (if ballY > 138 then (if ballX > 240 then (if ballX < 300 then (if r3[4] then 0 else r3[4]) else r3[4]) else r3[4]) else r3[4]) else r3[4] +every 33 -> r3[5] = if ballY < 158 then (if ballY > 138 then (if ballX > 300 then (if ballX < 360 then (if r3[5] then 0 else r3[5]) else r3[5]) else r3[5]) else r3[5]) else r3[5] +every 33 -> r3[6] = if ballY < 158 then (if ballY > 138 then (if ballX > 360 then (if ballX < 420 then (if r3[6] then 0 else r3[6]) else r3[6]) else r3[6]) else r3[6]) else r3[6] +every 33 -> r3[7] = if ballY < 158 then (if ballY > 138 then (if ballX > 420 then (if ballX < 480 then (if r3[7] then 0 else r3[7]) else r3[7]) else r3[7]) else r3[7]) else r3[7] +every 33 -> r3[8] = if ballY < 158 then (if ballY > 138 then (if ballX > 480 then (if ballX < 540 then (if r3[8] then 0 else r3[8]) else r3[8]) else r3[8]) else r3[8]) else r3[8] +every 33 -> r3[9] = if ballY < 158 then (if ballY > 138 then (if ballX > 540 then (if ballX < 600 then (if r3[9] then 0 else r3[9]) else r3[9]) else r3[9]) else r3[9]) else r3[9] + +-- ── Row 4: BLUE (top, y=120..136) — hit last by ball ── +every 33 -> score = if bvy < 0 then (if ballY < 138 then (if ballY > 118 then (if r4[floor(ballX / 60)] then score + 10 else score) else score) else score) else score +every 33 -> bvy = if bvy < 0 then (if ballY < 138 then (if ballY > 118 then (if r4[floor(ballX / 60)] then 4 else bvy) else bvy) else bvy) else bvy +every 33 -> r4[0] = if ballY < 138 then (if ballY > 118 then (if ballX > 0 then (if ballX < 60 then (if r4[0] then 0 else r4[0]) else r4[0]) else r4[0]) else r4[0]) else r4[0] +every 33 -> r4[1] = if ballY < 138 then (if ballY > 118 then (if ballX > 60 then (if ballX < 120 then (if r4[1] then 0 else r4[1]) else r4[1]) else r4[1]) else r4[1]) else r4[1] +every 33 -> r4[2] = if ballY < 138 then (if ballY > 118 then (if ballX > 120 then (if ballX < 180 then (if r4[2] then 0 else r4[2]) else r4[2]) else r4[2]) else r4[2]) else r4[2] +every 33 -> r4[3] = if ballY < 138 then (if ballY > 118 then (if ballX > 180 then (if ballX < 240 then (if r4[3] then 0 else r4[3]) else r4[3]) else r4[3]) else r4[3]) else r4[3] +every 33 -> r4[4] = if ballY < 138 then (if ballY > 118 then (if ballX > 240 then (if ballX < 300 then (if r4[4] then 0 else r4[4]) else r4[4]) else r4[4]) else r4[4]) else r4[4] +every 33 -> r4[5] = if ballY < 138 then (if ballY > 118 then (if ballX > 300 then (if ballX < 360 then (if r4[5] then 0 else r4[5]) else r4[5]) else r4[5]) else r4[5]) else r4[5] +every 33 -> r4[6] = if ballY < 138 then (if ballY > 118 then (if ballX > 360 then (if ballX < 420 then (if r4[6] then 0 else r4[6]) else r4[6]) else r4[6]) else r4[6]) else r4[6] +every 33 -> r4[7] = if ballY < 138 then (if ballY > 118 then (if ballX > 420 then (if ballX < 480 then (if r4[7] then 0 else r4[7]) else r4[7]) else r4[7]) else r4[7]) else r4[7] +every 33 -> r4[8] = if ballY < 138 then (if ballY > 118 then (if ballX > 480 then (if ballX < 540 then (if r4[8] then 0 else r4[8]) else r4[8]) else r4[8]) else r4[8]) else r4[8] +every 33 -> r4[9] = if ballY < 138 then (if ballY > 118 then (if ballX > 540 then (if ballX < 600 then (if r4[9] then 0 else r4[9]) else r4[9]) else r4[9]) else r4[9]) else r4[9] -- ── Sound effects ── -every 33 -> play_tone(if ballY > 365 then (if ballX > paddleX then (if ballX < paddleX + 80 then 440 else 0) else 0) else 0, 60) -every 33 -> play_tone(if ballY < 68 then (if ballY > 28 then 880 else 0) else 0, 40) -every 33 -> play_tone(if ballY > 410 then 110 else 0, 300, "sawtooth") +every 33 -> play_tone(if ballY > 348 then (if ballX > paddleX then (if ballX < paddleX + 80 then 440 else 0) else 0) else 0, 60) +every 33 -> play_tone(if ballY < 218 then (if ballY > 118 then 880 else 0) else 0, 40) +every 33 -> play_tone(if ballY > 395 then 110 else 0, 300, "sawtooth") -- Stream for viewers stream breakout on "ws://localhost:9100/peer/breakout" { @@ -104,28 +153,28 @@ view breakout_game = column [ -- ── The Court ── stack { style: "position:relative; width:600px; height:420px; background:linear-gradient(180deg,#0c0c1d,#1a1a3e); border:2px solid #334155; border-radius:16px; margin:0.5rem auto; overflow:hidden; box-shadow:0 0 40px rgba(168,85,247,0.15)" } [ - -- Row 0 bricks (red) — positioned with inline style only + -- Row 4: Blue bricks (TOP — farthest from paddle, hit last) for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -> - column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:32px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#f87171,#ef4444); opacity:{if r0[i] then 1 else 0}; transition:opacity 0.2s" } [] + column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:120px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#60a5fa,#3b82f6); opacity:{if r4[i] then 1 else 0}; transition:opacity 0.2s" } [] - -- Row 1 bricks (orange) + -- Row 3: Green bricks for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -> - column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:52px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#fb923c,#f97316); opacity:{if r1[i] then 1 else 0}; transition:opacity 0.2s" } [] + column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:140px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#4ade80,#22c55e); opacity:{if r3[i] then 1 else 0}; transition:opacity 0.2s" } [] - -- Row 2 bricks (yellow) + -- Row 2: Yellow bricks for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -> - column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:72px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#fde68a,#facc15); opacity:{if r2[i] then 1 else 0}; transition:opacity 0.2s" } [] + column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:160px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#fde68a,#facc15); opacity:{if r2[i] then 1 else 0}; transition:opacity 0.2s" } [] - -- Row 3 bricks (green) + -- Row 1: Orange bricks for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -> - column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:92px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#4ade80,#22c55e); opacity:{if r3[i] then 1 else 0}; transition:opacity 0.2s" } [] + column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:180px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#fb923c,#f97316); opacity:{if r1[i] then 1 else 0}; transition:opacity 0.2s" } [] - -- Row 4 bricks (blue) + -- Row 0: Red bricks (BOTTOM — closest to paddle, hit first) for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -> - column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:112px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#60a5fa,#3b82f6); opacity:{if r4[i] then 1 else 0}; transition:opacity 0.2s" } [] + column { style: "position:absolute; width:56px; height:16px; border-radius:4px; top:200px; left:{i * 60 + 2}px; background:linear-gradient(180deg,#f87171,#ef4444); opacity:{if r0[i] then 1 else 0}; transition:opacity 0.2s" } [] -- Paddle (purple glow) - column { style: "position:absolute; width:80px; height:12px; top:380px; background:linear-gradient(180deg,#c084fc,#a855f7); border-radius:6px; box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s", left: paddleX } [] + column { style: "position:absolute; width:80px; height:12px; top:360px; background:linear-gradient(180deg,#c084fc,#a855f7); border-radius:6px; box-shadow:0 0 12px rgba(168,85,247,0.5); transition:left 0.05s", left: paddleX } [] -- Ball (white glow) column { style: "position:absolute; width:12px; height:12px; background:radial-gradient(circle,#fff,#e2e8f0); border-radius:50%; box-shadow:0 0 12px rgba(255,255,255,0.6)", top: ballY, left: ballX } [] @@ -138,15 +187,15 @@ view breakout_game = column [ variant: "primary" } button "⬅️ Left" { - click: paddleX = if paddleX > 0 then paddleX - 30 else paddleX, + click: paddleX = if paddleX > 0 then paddleX - 40 else paddleX, variant: "secondary" } button "➡️ Right" { - click: paddleX = if paddleX < 520 then paddleX + 30 else paddleX, + click: paddleX = if paddleX < 520 then paddleX + 40 else paddleX, variant: "secondary" } button "🔄 Reset" { - click: score = 0; lives = 3; paused = 0; bvx = 3; bvy = -3; ballX = 300; ballY = 350; paddleX = 250; r0[0] = 1; r0[1] = 1; r0[2] = 1; r0[3] = 1; r0[4] = 1; r0[5] = 1; r0[6] = 1; r0[7] = 1; r0[8] = 1; r0[9] = 1; r1[0] = 1; r1[1] = 1; r1[2] = 1; r1[3] = 1; r1[4] = 1; r1[5] = 1; r1[6] = 1; r1[7] = 1; r1[8] = 1; r1[9] = 1; r2[0] = 1; r2[1] = 1; r2[2] = 1; r2[3] = 1; r2[4] = 1; r2[5] = 1; r2[6] = 1; r2[7] = 1; r2[8] = 1; r2[9] = 1; r3[0] = 1; r3[1] = 1; r3[2] = 1; r3[3] = 1; r3[4] = 1; r3[5] = 1; r3[6] = 1; r3[7] = 1; r3[8] = 1; r3[9] = 1; r4[0] = 1; r4[1] = 1; r4[2] = 1; r4[3] = 1; r4[4] = 1; r4[5] = 1; r4[6] = 1; r4[7] = 1; r4[8] = 1; r4[9] = 1, + click: score = 0; lives = 3; paused = 0; bvx = 4; bvy = -4; ballX = 300; ballY = 320; paddleX = 250; r0[0] = 1; r0[1] = 1; r0[2] = 1; r0[3] = 1; r0[4] = 1; r0[5] = 1; r0[6] = 1; r0[7] = 1; r0[8] = 1; r0[9] = 1; r1[0] = 1; r1[1] = 1; r1[2] = 1; r1[3] = 1; r1[4] = 1; r1[5] = 1; r1[6] = 1; r1[7] = 1; r1[8] = 1; r1[9] = 1; r2[0] = 1; r2[1] = 1; r2[2] = 1; r2[3] = 1; r2[4] = 1; r2[5] = 1; r2[6] = 1; r2[7] = 1; r2[8] = 1; r2[9] = 1; r3[0] = 1; r3[1] = 1; r3[2] = 1; r3[3] = 1; r3[4] = 1; r3[5] = 1; r3[6] = 1; r3[7] = 1; r3[8] = 1; r3[9] = 1; r4[0] = 1; r4[1] = 1; r4[2] = 1; r4[3] = 1; r4[4] = 1; r4[5] = 1; r4[6] = 1; r4[7] = 1; r4[8] = 1; r4[9] = 1, variant: "destructive" } ]