← run 2026-06-30T130601Z-3ea440b-minimal

qwen3.6-27b@or — web-platformer

rep 1 UNGRADED

pi 802.6s · verify 0.0s · 5026 tok · responded: qwen/qwen3.6-27b · transcript

smoke —

verify output
index.html not found in workspace (the model did not create it)
produced code

(no files produced)

rep 2 UNGRADED

pi 460.0s · verify 0.0s · 2297 tok · responded: qwen/qwen3.6-27b · transcript

smoke —

verify output
index.html not found in workspace (the model did not create it)
produced code

(no files produced)

rep 3 UNGRADED

pi 606.4s · verify 4.5s · 160341 tok · responded: qwen/qwen3.6-27b · transcript

smoke 3/3

screenshot

verify output
CHECK PASS: canvas #game exists and is sized
CHECK PASS: no uncaught JS errors on load
CHECK PASS: keyboard input does not throw
score: 3/3 (100%)
produced code
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Pixel Adventure</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  background: #1a1a2e;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  font-family: monospace;
  overflow: hidden;
}
#game {
  border: 3px solid #333;
  border-radius: 4px;
  display: block;
  image-rendering: pixelated;
  cursor: none;
}
</style>
</head>
<body>
<canvas id="game" width="800" height="450"></canvas>
<script>
(function() {
  "use strict";

  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const W = 800, H = 450;

  // ─── Input ───
  const keys = {};
  window.addEventListener("keydown", e => {
    keys[e.code] = true;
    if (["Space","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].indexOf(e.code) !== -1) {
      e.preventDefault();
    }
  });
  window.addEventListener("keyup", e => { keys[e.code] = false; });

  function isLeft() { return keys["ArrowLeft"] || keys["KeyA"]; }
  function isRight() { return keys["ArrowRight"] || keys["KeyD"]; }
  function isJump() { return keys["Space"] || keys["ArrowUp"] || keys["KeyW"]; }

  // ─── Constants ───
  const GRAVITY = 0.55;
  const JUMP_FORCE = -11;
  const MOVE_SPEED = 4.2;
  const MAX_FALL = 12;
  const WORLD_W = 4800;

  // ─── Camera ───
  let camX = 0;

  function setCam(target) {
    camX = target - W * 0.35;
    camX = Math.max(0, Math.min(WORLD_W - W, camX));
  }

  // ─── Platforms ───
  // Each: { x, y, w, h, color, topColor?, pattern? }
  const platforms = [];

  // Ground segments (with gaps)
  function ground(x, w, h) {
    h = h || 32;
    platforms.push({ x, y: H - h, w, h, color: "#5a8f3c", topColor: "#6abf42", pattern: "grass" });
  }

  ground(0, 600);
  ground(680, 500);
  ground(1260, 400);
  ground(1720, 600);
  ground(2400, 350);
  ground(2840, 500);
  ground(3420, 400);
  ground(3900, 900); // final stretch

  // Raised platforms
  function plat(x, y, w, color) {
    color = color || "#c47a4a";
    const h = 18;
    platforms.push({ x, y, w, h, color, topColor: "#e8a86a" });
  }

  // Level layout
  plat(200, 320, 100);
  plat(380, 260, 80);
  plat(520, 200, 120, "#8a6ead");
  plat(700, 310, 90);
  plat(830, 250, 100);
  plat(970, 190, 80, "#8a6ead");

  plat(1320, 330, 100);
  plat(1460, 270, 80);
  plat(1600, 210, 120, "#8a6ead");

  plat(1780, 300, 90);
  plat(1920, 240, 100);
  plat(2060, 180, 80, "#8a6ead");
  plat(2200, 300, 100);

  plat(2480, 320, 90);
  plat(2620, 260, 80);
  plat(2760, 200, 100, "#8a6ead");

  plat(2920, 310, 90);
  plat(3060, 250, 100);
  plat(3200, 190, 80, "#8a6ead");

  plat(3500, 300, 100);
  plat(3680, 240, 90);
  plat(3820, 180, 80, "#8a6ead");
  plat(3960, 310, 100);
  plat(4120, 250, 120, "#4a8ac4");
  plat(4300, 200, 80, "#8a6ead");

  // ─── Coins ───
  const coins = [];
  function coin(x, y) {
    coins.push({ x, y, collected: false, bobPhase: Math.random() * Math.PI * 2 });
  }

  // Coins on/above platforms
  coin(230, 290); coin(240, 290);
  coin(410, 230); coin(420, 230);
  coin(550, 170); coin(560, 170); coin(570, 170);
  coin(730, 280); coin(740, 280);
  coin(860, 220); coin(870, 220);
  coin(1000, 160); coin(1010, 160);

  coin(1350, 300); coin(1360, 300);
  coin(1490, 240); coin(1500, 240);
  coin(1630, 180); coin(1640, 180); coin(1650, 180);

  coin(1810, 270); coin(1820, 270);
  coin(1950, 210); coin(1960, 210);
  coin(2090, 150); coin(2100, 150);
  coin(2230, 270); coin(2240, 270);

  coin(2510, 290); coin(2520, 290);
  coin(2650, 230); coin(2660, 230);
  coin(2790, 170); coin(2800, 170); coin(2810, 170);

  coin(2950, 280); coin(2960, 280);
  coin(3090, 220); coin(3100, 220);
  coin(3230, 160); coin(3240, 160);

  coin(3530, 270); coin(3540, 270);
  coin(3710, 210); coin(3720, 210);
  coin(3850, 150); coin(3860, 150);
  coin(3990, 280); coin(4000, 280);
  coin(4150, 220); coin(4160, 220);
  coin(4330, 170); coin(4340, 170);

  // Coins in the air (jumping challenges)
  coin(640, 200);
  coin(1200, 250);
  coin(2350, 180);
  coin(3350, 200);

  let score = 0;
  let coinsCollected = 0;
  let totalCoins = coins.length;

  // ─── Enemies (simple goombas) ───
  const enemies = [];
  function enemy(x, y, range) {
    enemies.push({ x, y, startX: x, range, dir: 1, w: 28, h: 26, alive: true, squishTimer: 0 });
  }

  enemy(350, H - 32 - 26, 80);
  enemy(800, H - 32 - 26, 100);
  enemy(1380, H - 32 - 26, 60);
  enemy(1900, H - 32 - 26, 90);
  enemy(2500, H - 32 - 26, 70);
  enemy(2900, H - 32 - 26, 80);
  enemy(3500, H - 32 - 26, 100);
  enemy(4050, H - 32 - 26, 60);
  enemy(4350, H - 32 - 26, 50);

  // ─── Flag ───
  const flag = { x: 4580, y: H - 32 - 110, h: 110, reached: false, hit: false };

  // ─── Decorations (clouds, bushes, hills) ───
  const clouds = [];
  for (let i = 0; i < 30; i++) {
    clouds.push({
      x: i * 180 + Math.random() * 100,
      y: 30 + Math.random() * 80,
      w: 60 + Math.random() * 80,
      h: 25 + Math.random() * 15,
      speed: 0.1 + Math.random() * 0.2
    });
  }

  const bushes = [];
  for (let i = 0; i < 20; i++) {
    bushes.push({
      x: i * 260 + Math.random() * 100,
      w: 40 + Math.random() * 50,
      h: 18 + Math.random() * 12
    });
  }

  const bgHills = [];
  for (let i = 0; i < 15; i++) {
    bgHills.push({
      x: i * 350 + Math.random() * 100,
      w: 180 + Math.random() * 120,
      h: 60 + Math.random() * 50
    });
  }

  // ─── Particles ───
  const particles = [];
  function spawnParticles(x, y, color, count, spread) {
    for (let i = 0; i < count; i++) {
      particles.push({
        x, y,
        vx: (Math.random() - 0.5) * spread,
        vy: -Math.random() * spread * 0.8,
        life: 30 + Math.random() * 20,
        maxLife: 50,
        color,
        size: 2 + Math.random() * 3
      });
    }
  }

  // ─── Stars (floating decoration) ───
  const stars = [];
  for (let i = 0; i < 40; i++) {
    stars.push({
      x: Math.random() * WORLD_W,
      y: Math.random() * 150,
      size: 1 + Math.random() * 2,
      twinkle: Math.random() * Math.PI * 2
    });
  }

  // ─── Player ───
  const player = {
    x: 80, y: H - 60,
    vx: 0, vy: 0,
    w: 24, h: 32,
    grounded: false,
    facing: 1, // 1 = right, -1 = left
    animFrame: 0,
    animTimer: 0,
    alive: true,
    deadTimer: 0
  };

  let gameState = "playing"; // "playing", "won", "dead"
  let winTimer = 0;
  let deathTimer = 0;

  // ─── Collision helpers ───
  function rectOverlap(a, b) {
    return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
  }

  // ─── Update ───
  let lastTime = 0;
  let fixedAccum = 0;
  const STEP = 1000 / 60;

  function update(dt) {
    if (gameState === "won") {
      winTimer++;
      if (winTimer > 120) gameState = "title";
      return;
    }

    if (gameState === "dead") {
      deathTimer++;
      if (deathTimer > 90) {
        respawn();
      }
      return;
    }

    // Player horizontal movement
    let moveX = 0;
    if (isLeft()) moveX -= MOVE_SPEED;
    if (isRight()) moveX += MOVE_SPEED;
    player.vx = moveX;
    if (moveX !== 0) player.facing = moveX > 0 ? 1 : -1;

    // Jumping
    if (isJump() && player.grounded) {
      player.vy = JUMP_FORCE;
      player.grounded = false;
    }

    // Gravity
    player.vy += GRAVITY;
    if (player.vy > MAX_FALL) player.vy = MAX_FALL;

    // Move X
    player.x += player.vx;
    // Clamp to world
    if (player.x < 0) player.x = 0;
    if (player.x + player.w > WORLD_W) player.x = WORLD_W - player.w;

    // Collision X
    player.grounded = false;
    for (const p of platforms) {
      if (rectOverlap(player, p)) {
        if (player.vx > 0) {
          player.x = p.x - player.w;
        } else if (player.vx < 0) {
          player.x = p.x + p.w;
        }
        player.vx = 0;
      }
    }

    // Move Y
    player.y += player.vy;

    // Collision Y
    for (const p of platforms) {
      if (rectOverlap(player, p)) {
        if (player.vy > 0) {
          player.y = p.y - player.h;
          player.vy = 0;
          player.grounded = true;
        } else if (player.vy < 0) {
          player.y = p.y + p.h;
          player.vy = 0;
        }
      }
    }

    // Fall into pit
    if (player.y > H + 50) {
      killPlayer();
      return;
    }

    // Animation
    if (player.grounded && Math.abs(player.vx) > 0.5) {
      player.animTimer += dt;
      if (player.animTimer > 120) {
        player.animTimer = 0;
        player.animFrame = (player.animFrame + 1) % 4;
      }
    } else {
      player.animFrame = 0;
      player.animTimer = 0;
    }

    // Camera
    setCam(player.x);

    // Clouds
    for (const c of clouds) {
      c.x += c.speed;
      if (c.x > WORLD_W + 100) c.x = -100;
    }

    // Coins
    for (const c of coins) {
      if (c.collected) continue;
      c.bobPhase += 0.05;
      const coinRect = { x: c.x - 8, y: c.y + Math.sin(c.bobPhase) * 3 - 8, w: 16, h: 16 };
      if (rectOverlap(player, coinRect)) {
        c.collected = true;
        coinsCollected++;
        score += 100;
        spawnParticles(c.x, c.y, "#ffd700", 8, 5);
      }
    }

    // Enemies
    for (const e of enemies) {
      if (!e.alive) {
        if (e.squishTimer > 0) e.squishTimer--;
        continue;
      }
      e.x += e.dir * 1.2;
      if (e.x > e.startX + e.range || e.x + e.w < e.startX) {
        e.dir *= -1;
      }
      // Collision with player
      if (rectOverlap(player, e)) {
        // Stomp from above?
        if (player.vy > 0 && player.y + player.h - e.y < 12) {
          e.alive = false;
          e.squishTimer = 30;
          player.vy = -7;
          score += 200;
          spawnParticles(e.x + e.w/2, e.y, "#c47a4a", 6, 4);
        } else {
          killPlayer();
          return;
        }
      }
    }

    // Flag
    if (!flag.reached && !flag.hit) {
      const flagRect = { x: flag.x - 5, y: flag.y, w: 20, h: flag.h };
      if (rectOverlap(player, flagRect) || Math.abs(player.x + player.w/2 - flag.x) < 20 && player.y + player.h > flag.y + flag.h - 30) {
        flag.reached = true;
        score += 1000;
        spawnParticles(flag.x, flag.y + 20, "#ff4444", 20, 8);
        spawnParticles(flag.x, flag.y + 40, "#ffd700", 15, 6);
        gameState = "won";
      }
    }

    // Particles
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.x += p.vx;
      p.y += p.vy;
      p.vy += 0.15;
      p.life--;
      if (p.life <= 0) particles.splice(i, 1);
    }
  }

  function killPlayer() {
    gameState = "dead";
    deathTimer = 0;
    spawnParticles(player.x + player.w/2, player.y + player.h/2, "#ff6644", 12, 6);
  }

  function respawn() {
    gameState = "playing";
    player.x = 80;
    player.y = H - 60;
    player.vx = 0;
    player.vy = 0;
    player.alive = true;
    deathTimer = 0;
    camX = 0;
  }

  // ─── Drawing helpers ───
  function drawSky() {
    const grad = ctx.createLinearGradient(0, 0, 0, H);
    grad.addColorStop(0, "#4a90d9");
    grad.addColorStop(0.6, "#87ceeb");
    grad.addColorStop(1, "#b8e4f0");
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, W, H);
  }

  function drawHills() {
    for (const h of bgHills) {
      const sx = h.x - camX * 0.3;
      if (sx > -h.w && sx < W + h.w) {
        ctx.fillStyle = "#5a9f55";
        ctx.beginPath();
        ctx.ellipse(sx + h.w/2, H - 20, h.w/2, h.h, 0, Math.PI, 0);
        ctx.fill();
        ctx.fillStyle = "#4d8b47";
        ctx.beginPath();
        ctx.ellipse(sx + h.w/2, H - 15, h.w/2 * 0.8, h.h * 0.7, 0, Math.PI, 0);
        ctx.fill();
      }
    }
  }

  function drawClouds() {
    for (const c of clouds) {
      const sx = c.x - camX * 0.15;
      if (sx > -c.w && sx < W + c.w) {
        ctx.fillStyle = "rgba(255,255,255,0.85)";
        ctx.beginPath();
        ctx.ellipse(sx, c.y, c.w/2, c.h/2, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.ellipse(sx - c.w*0.25, c.y + 4, c.w*0.35, c.h*0.4, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.ellipse(sx + c.w*0.25, c.y + 2, c.w*0.3, c.h*0.35, 0, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }

  function drawBushes() {
    for (const b of bushes) {
      const sx = b.x - camX;
      if (sx > -b.w && sx < W + b.w) {
        ctx.fillStyle = "#3d7a35";
        ctx.beginPath();
        ctx.ellipse(sx + b.w/2, H - 28, b.w/2, b.h, 0, Math.PI, 0);
        ctx.fill();
        ctx.fillStyle = "#4d9a42";
        ctx.beginPath();
        ctx.ellipse(sx + b.w/2 - 5, H - 30, b.w*0.35, b.h*0.7, 0, Math.PI, 0);
        ctx.fill();
      }
    }
  }

  function drawStars() {
    const t = performance.now() / 1000;
    for (const s of stars) {
      const sx = s.x - camX * 0.05;
      const alpha = 0.4 + Math.sin(t * 2 + s.twinkle) * 0.3;
      ctx.fillStyle = `rgba(255,255,200,${alpha})`;
      ctx.fillRect(sx, s.y, s.size, s.size);
    }
  }

  function drawPlatform(p) {
    const sx = p.x - camX;
    if (sx > -p.w - 10 && sx < W + 10) {
      // Main body
      ctx.fillStyle = p.color || "#c47a4a";
      ctx.fillRect(sx, p.y, p.w, p.h);

      // Top
      ctx.fillStyle = p.topColor || "#e8a86a";
      ctx.fillRect(sx, p.y, p.w, 4);

      // Grass texture on ground
      if (p.pattern === "grass") {
        // Darker underlayer
        ctx.fillStyle = "#4a6f2e";
        ctx.fillRect(sx, p.y + 4, p.w, p.h - 4);

        // Grass blades
        ctx.fillStyle = "#6abf42";
        for (let gx = sx; gx < sx + p.w; gx += 8) {
          const gh = 3 + ((gx * 7) % 5);
          ctx.fillRect(gx, p.y - gh, 4, gh);
        }

        // Dirt detail
        ctx.fillStyle = "#5a7f3c";
        for (let dx = sx + 4; dx < sx + p.w - 4; dx += 20) {
          ctx.fillRect(dx, p.y + 10, 8, 3);
          ctx.fillRect(dx + 10, p.y + 20, 6, 2);
        }
      }

      // Side edges
      ctx.fillStyle = shadeColor(p.color || "#c47a4a", -20);
      ctx.fillRect(sx, p.y + 4, 2, p.h - 4);
      ctx.fillRect(sx + p.w - 2, p.y + 4, 2, p.h - 4);

      // Bottom
      ctx.fillStyle = shadeColor(p.color || "#c47a4a", -30);
      ctx.fillRect(sx, p.y + p.h - 2, p.w, 2);
    }
  }

  function shadeColor(hex, amount) {
    let r = parseInt(hex.slice(1,3), 16) + amount;
    let g = parseInt(hex.slice(3,5), 16) + amount;
    let b = parseInt(hex.slice(5,7), 16) + amount;
    r = Math.max(0, Math.min(255, r));
    g = Math.max(0, Math.min(255, g));
    b = Math.max(0, Math.min(255, b));
    return "#" + r.toString(16).padStart(2,'0') + g.toString(16).padStart(2,'0') + b.toString(16).padStart(2,'0');
  }

  function drawCoin(c) {
    if (c.collected) return;
    const sx = c.x - camX;
    const sy = c.y + Math.sin(c.bobPhase) * 3;
    if (sx > -20 && sx < W + 20) {
      // Glow
      ctx.fillStyle = "rgba(255,215,0,0.3)";
      ctx.beginPath();
      ctx.arc(sx, sy, 12, 0, Math.PI * 2);
      ctx.fill();

      // Coin body
      const stretch = 0.7 + Math.abs(Math.sin(c.bobPhase * 1.5)) * 0.3;
      ctx.fillStyle = "#ffd700";
      ctx.beginPath();
      ctx.ellipse(sx, sy, 7 * stretch, 7, 0, 0, Math.PI * 2);
      ctx.fill();

      ctx.fillStyle = "#ffec44";
      ctx.beginPath();
      ctx.ellipse(sx - 1, sy - 1, 4 * stretch, 4, 0, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  function drawEnemy(e) {
    const sx = e.x - camX;
    if (sx > -40 && sx < W + 40) {
      if (!e.alive) {
        if (e.squishTimer > 0) {
          ctx.fillStyle = "#8B5E3C";
          ctx.fillRect(sx, e.y + e.h - 6, e.w, 6);
        }
        return;
      }

      // Body
      ctx.fillStyle = "#8B4513";
      ctx.beginPath();
      ctx.ellipse(sx + e.w/2, e.y + e.h * 0.5, e.w/2, e.h * 0.5, 0, 0, Math.PI * 2);
      ctx.fill();

      // Lighter top
      ctx.fillStyle = "#a0652a";
      ctx.beginPath();
      ctx.ellipse(sx + e.w/2, e.y + e.h * 0.35, e.w/2 - 2, e.h * 0.35, 0, Math.PI, 0);
      ctx.fill();

      // Eyes
      ctx.fillStyle = "white";
      ctx.beginPath();
      ctx.arc(sx + e.w/2 - 6, e.y + e.h * 0.4, 4, 0, Math.PI * 2);
      ctx.arc(sx + e.w/2 + 6, e.y + e.h * 0.4, 4, 0, Math.PI * 2);
      ctx.fill();

      // Pupils
      ctx.fillStyle = "#111";
      const pupilOff = e.dir * 2;
      ctx.beginPath();
      ctx.arc(sx + e.w/2 - 6 + pupilOff, e.y + e.h * 0.42, 2, 0, Math.PI * 2);
      ctx.arc(sx + e.w/2 + 6 + pupilOff, e.y + e.h * 0.42, 2, 0, Math.PI * 2);
      ctx.fill();

      // Angry eyebrows
      ctx.strokeStyle = "#333";
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(sx + e.w/2 - 10, e.y + e.h * 0.22);
      ctx.lineTo(sx + e.w/2 - 3, e.y + e.h * 0.3);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(sx + e.w/2 + 10, e.y + e.h * 0.22);
      ctx.lineTo(sx + e.w/2 + 3, e.y + e.h * 0.3);
      ctx.stroke();

      // Feet
      const footAnim = Math.sin(performance.now() / 100) * 3;
      ctx.fillStyle = "#5a3510";
      ctx.fillRect(sx + 2, e.y + e.h - 5, 8, 5);
      ctx.fillRect(sx + e.w - 10, e.y + e.h - 5, 8, 5);
    }
  }

  function drawFlag() {
    const sx = flag.x - camX;
    if (sx > -30 && sx < W + 30) {
      // Pole
      ctx.fillStyle = "#888";
      ctx.fillRect(sx - 2, flag.y, 5, flag.h);

      // Ball on top
      ctx.fillStyle = "#ffd700";
      ctx.beginPath();
      ctx.arc(sx + 1, flag.y, 6, 0, Math.PI * 2);
      ctx.fill();

      // Flag
      const waveOffset = Math.sin(performance.now() / 200) * 4;
      ctx.fillStyle = flag.reached ? "#44ff44" : "#ff4444";
      ctx.beginPath();
      ctx.moveTo(sx + 3, flag.y + 5);
      ctx.lineTo(sx + 35 + waveOffset, flag.y + 18);
      ctx.lineTo(sx + 3, flag.y + 35);
      ctx.closePath();
      ctx.fill();

      // Flag highlight
      ctx.fillStyle = flag.reached ? "#88ff88" : "#ff8888";
      ctx.beginPath();
      ctx.moveTo(sx + 3, flag.y + 8);
      ctx.lineTo(sx + 20 + waveOffset * 0.5, flag.y + 18);
      ctx.lineTo(sx + 3, flag.y + 28);
      ctx.closePath();
      ctx.fill();

      // Star on flag
      ctx.fillStyle = "#fff";
      drawStar(sx + 18 + waveOffset * 0.5, flag.y + 18, 5, 2, 5);
    }
  }

  function drawStar(cx, cy, innerR, outerR, points) {
    ctx.beginPath();
    for (let i = 0; i < points * 2; i++) {
      const r = i % 2 === 0 ? outerR : innerR;
      const angle = (i * Math.PI / points) - Math.PI / 2;
      if (i === 0) ctx.moveTo(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
      else ctx.lineTo(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
    }
    ctx.closePath();
    ctx.fill();
  }

  function drawPlayer() {
    if (gameState === "dead") return;

    const sx = player.x - camX;
    const sy = player.y;
    const f = player.facing;

    ctx.save();

    // Shadow
    ctx.fillStyle = "rgba(0,0,0,0.2)";
    ctx.beginPath();
    ctx.ellipse(sx + player.w/2, sy + player.h + 1, player.w * 0.5, 3, 0, 0, Math.PI * 2);
    ctx.fill();

    // Body colors
    const skinColor = "#ffcc99";
    const shirtColor = "#cc3333";
    const pantsColor = "#3344aa";
    const shoeColor = "#8B4513";

    // Legs
    if (!player.grounded) {
      // Jump pose
      ctx.fillStyle = pantsColor;
      ctx.fillRect(sx + 4, sy + 22, 7, 8);
      ctx.fillRect(sx + 13, sy + 22, 7, 8);
      ctx.fillStyle = shoeColor;
      ctx.fillRect(sx + 2, sy + 26, 9, 6);
      ctx.fillRect(sx + 13, sy + 26, 9, 6);
    } else if (Math.abs(player.vx) > 0.5) {
      // Running animation
      const legAnim = player.animFrame;
      ctx.fillStyle = pantsColor;
      if (legAnim === 0) {
        ctx.fillRect(sx + 5, sy + 20, 6, 8);
        ctx.fillRect(sx + 13, sy + 20, 6, 8);
      } else if (legAnim === 1) {
        ctx.fillRect(sx + 3, sy + 20, 7, 8);
        ctx.fillRect(sx + 14, sy + 22, 7, 7);
      } else if (legAnim === 2) {
        ctx.fillRect(sx + 7, sy + 20, 6, 8);
        ctx.fillRect(sx + 11, sy + 20, 6, 8);
      } else {
        ctx.fillRect(sx + 14, sy + 20, 7, 8);
        ctx.fillRect(sx + 3, sy + 22, 7, 7);
      }
      // Shoes
      ctx.fillStyle = shoeColor;
      const shoeOffsets = legAnim === 1 ? [-1, 2] : legAnim === 3 ? [2, -1] : [0, 0];
      ctx.fillRect(sx + 4 + shoeOffsets[0], sy + 27, 8, 5);
      ctx.fillRect(sx + 12 + shoeOffsets[1], sy + 27, 8, 5);
    } else {
      // Standing
      ctx.fillStyle = pantsColor;
      ctx.fillRect(sx + 5, sy + 20, 6, 8);
      ctx.fillRect(sx + 13, sy + 20, 6, 8);
      ctx.fillStyle = shoeColor;
      ctx.fillRect(sx + 4, sy + 26, 8, 6);
      ctx.fillRect(sx + 12, sy + 26, 8, 6);
    }

    // Torso / shirt
    ctx.fillStyle = shirtColor;
    ctx.fillRect(sx + 3, sy + 12, player.w - 6, 10);

    // Belt
    ctx.fillStyle = "#aa2222";
    ctx.fillRect(sx + 4, sy + 19, player.w - 8, 3);

    // Arms
    ctx.fillStyle = skinColor;
    if (!player.grounded) {
      ctx.fillRect(sx - 2, sy + 12, 6, 2);
      ctx.fillRect(sx + player.w - 4, sy + 12, 6, 2);
    } else {
      const armSwing = Math.abs(player.vx) > 0.5 ? Math.sin(performance.now() / 80) * 4 : 0;
      ctx.fillRect(sx - 2, sy + 13 + armSwing, 6, 2);
      ctx.fillRect(sx + player.w - 4, sy + 13 - armSwing, 6, 2);
    }

    // Head
    ctx.fillStyle = skinColor;
    ctx.beginPath();
    ctx.arc(sx + player.w/2, sy + 8, 8, 0, Math.PI * 2);
    ctx.fill();

    // Hat
    ctx.fillStyle = "#cc0000";
    ctx.fillRect(sx + 2, sy - 1, player.w - 4, 6);
    // Hat brim
    ctx.fillRect(sx - f === 1 ? -1 : player.w - 3, sy + 1, 6, 3);

    // Hat logo 'S'
    ctx.fillStyle = "#fff";
    ctx.font = "bold 7px monospace";
    ctx.textAlign = "center";
    ctx.fillText("S", sx + player.w/2, sy + 5);

    // Eyes
    ctx.fillStyle = "#222";
    const eyeX = f === 1 ? 4 : -4;
    ctx.fillRect(sx + player.w/2 + eyeX - 1, sy + 7, 3, 3);

    ctx.restore();
  }

  function drawParticles() {
    for (const p of particles) {
      const sx = p.x - camX;
      const alpha = p.life / p.maxLife;
      ctx.globalAlpha = alpha;
      ctx.fillStyle = p.color;
      ctx.fillRect(sx - p.size/2, p.y - p.size/2, p.size, p.size);
    }
    ctx.globalAlpha = 1;
  }

  function drawHUD() {
    // Score bar background
    ctx.fillStyle = "rgba(0,0,0,0.5)";
    ctx.fillRect(0, 0, W, 36);
    ctx.fillStyle = "rgba(0,0,0,0.3)";
    ctx.fillRect(0, 34, W, 2);

    // Coins icon
    ctx.fillStyle = "#ffd700";
    ctx.beginPath();
    ctx.arc(28, 18, 10, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = "#ffec44";
    ctx.beginPath();
    ctx.arc(26, 16, 5, 0, Math.PI * 2);
    ctx.fill();

    // Coin count
    ctx.fillStyle = "#fff";
    ctx.font = "bold 18px monospace";
    ctx.textAlign = "left";
    ctx.fillText("× " + coinsCollected + " / " + totalCoins, 48, 24);

    // Score
    ctx.fillStyle = "#ffd700";
    ctx.font = "bold 20px monospace";
    ctx.textAlign = "center";
    ctx.fillText("SCORE: " + score, W / 2, 25);

    // Lives indicator
    ctx.fillStyle = "#ff6644";
    ctx.font = "bold 16px monospace";
    ctx.textAlign = "right";
    ctx.fillText("🏁 FLAG", W - 15, 24);

    // Progress bar
    const progress = Math.min(1, player.x / flag.x);
    ctx.fillStyle = "rgba(255,255,255,0.2)";
    ctx.fillRect(W - 160, 30, 140, 6);
    ctx.fillStyle = "#44dd44";
    ctx.fillRect(W - 160, 30, 140 * progress, 6);
  }

  function drawWinScreen() {
    const alpha = Math.min(1, winTimer / 60);
    ctx.fillStyle = "rgba(0,0,0," + (alpha * 0.6) + ")";
    ctx.fillRect(0, 0, W, H);

    ctx.globalAlpha = alpha;
    ctx.fillStyle = "#ffd700";
    ctx.font = "bold 48px monospace";
    ctx.textAlign = "center";
    ctx.fillText("🎉 YOU WIN! 🎉", W/2, H/2 - 30);

    ctx.fillStyle = "#fff";
    ctx.font = "bold 24px monospace";
    ctx.fillText("Score: " + score, W/2, H/2 + 20);
    ctx.fillText("Coins: " + coinsCollected + " / " + totalCoins, W/2, H/2 + 55);

    ctx.fillStyle = "#aaa";
    ctx.font = "16px monospace";
    ctx.fillText("Press R to play again", W/2, H/2 + 100);
    ctx.globalAlpha = 1;

    if (keys["KeyR"]) {
      resetGame();
    }
  }

  function drawDeathScreen() {
    ctx.fillStyle = "rgba(60,0,0,0.5)";
    ctx.fillRect(0, 0, W, H);

    ctx.fillStyle = "#ff4444";
    ctx.font = "bold 36px monospace";
    ctx.textAlign = "center";
    ctx.fillText("OUCH!", W/2, H/2 - 10);
    ctx.fillStyle = "#ddd";
    ctx.font = "18px monospace";
    ctx.fillText("Respawning...", W/2, H/2 + 25);
  }

  function drawTitle() {
    drawSky();
    drawHills();
    drawClouds();
    drawBushes();

    for (const p of platforms) {
      if (p.x - camX >= -p.w - 10 && p.x - camX < W + 10) {
        drawPlatform(p);
      }
    }

    ctx.fillStyle = "rgba(0,0,0,0.5)";
    ctx.fillRect(0, 0, W, H);

    ctx.fillStyle = "#ffd700";
    ctx.font = "bold 44px monospace";
    ctx.textAlign = "center";
    ctx.fillText("SUPER PIXEL", W/2, H/2 - 60);
    ctx.fillText("ADVENTURE", W/2, H/2 - 10);

    ctx.fillStyle = "#fff";
    ctx.font = "18px monospace";
    ctx.fillText("Arrow Keys / WASD to move", W/2, H/2 + 40);
    ctx.fillText("Space / Up / W to jump", W/2, H/2 + 65);

    ctx.fillStyle = "#aaa";
    ctx.font = "14px monospace";
    ctx.fillText("Collect coins, stomp enemies, reach the flag!", W/2, H/2 + 100);

    ctx.fillStyle = "#88ff88";
    ctx.font = "bold 20px monospace";
    const blink = Math.sin(performance.now() / 300) > 0;
    if (blink) ctx.fillText("Press any key to start", W/2, H/2 + 145);
  }

  let titleShown = true;
  let startPressed = false;

  function resetGame() {
    score = 0;
    coinsCollected = 0;
    gameState = "playing";
    winTimer = 0;
    deathTimer = 0;
    player.x = 80;
    player.y = H - 60;
    player.vx = 0;
    player.vy = 0;
    player.alive = true;
    player.grounded = false;
    camX = 0;
    flag.reached = false;
    flag.hit = false;
    particles.length = 0;
    for (const c of coins) c.collected = false;
    for (const e of enemies) {
      e.alive = true;
      e.squishTimer = 0;
      e.x = e.startX;
    }
    startPressed = false;
    titleShown = true;
  }

  // ─── Main Loop ───
  function gameLoop(timestamp) {
    if (!lastTime) lastTime = timestamp;
    let dt = timestamp - lastTime;
    lastTime = timestamp;
    if (dt > 100) dt = 100;

    // Title screen
    if (titleShown && !startPressed) {
      drawTitle();
      if (Object.values(keys).some(v => v)) {
        startPressed = true;
      }
      requestAnimationFrame(gameLoop);
      return;
    }

    // Fixed timestep update
    fixedAccum += dt;
    while (fixedAccum >= STEP) {
      update(STEP);
      fixedAccum -= STEP;
    }

    // Draw
    ctx.clearRect(0, 0, W, H);

    if (gameState === "playing" || gameState === "won" || gameState === "dead") {
      drawSky();
      drawStars();
      drawHills();
      drawClouds();
      drawBushes();

      for (const p of platforms) {
        drawPlatform(p);
      }

      for (const c of coins) {
        drawCoin(c);
      }

      for (const e of enemies) {
        drawEnemy(e);
      }

      drawFlag();
      drawParticles();
      drawPlayer();
      drawHUD();

      if (gameState === "won") drawWinScreen();
      if (gameState === "dead") drawDeathScreen();
    }

    requestAnimationFrame(gameLoop);
  }

  // ─── Prevent all keys from doing things on title ───
  window.addEventListener("keydown", e => {
    if (!startPressed) startPressed = true;
  });

  // Start
  camX = 0;
  requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>

rep 4 UNGRADED

pi 129.5s · verify 4.2s · 31659 tok · responded: qwen/qwen3.6-27b · transcript

smoke 3/3

screenshot

verify output
CHECK PASS: canvas #game exists and is sized
CHECK PASS: no uncaught JS errors on load
CHECK PASS: keyboard input does not throw
score: 3/3 (100%)
produced code
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Mini Mario</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #1a1a2e;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    font-family: 'Courier New', monospace;
    overflow: hidden;
  }
  #game {
    border: 3px solid #0f0f23;
    border-radius: 4px;
    display: block;
  }
  #info {
    color: #8888aa;
    text-align: center;
    margin-top: 12px;
    font-size: 13px;
  }
  #info span { color: #ffcc00; }
</style>
</head>
<body>
<canvas id="game" width="800" height="450"></canvas>
<div id="info">
  <span>&larr; &rarr;</span> or <span>A D</span> to move &middot;
  <span>Space</span> or <span>&uarr;</span> to jump &middot;
  Collect coins and reach the <span>&#x1F3F7;</span> flag! &#x1F3AF;
</div>
<script>
(function() {
  "use strict";

  // ─── Canvas ───
  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const W = canvas.width;
  const H = canvas.height;

  // ─── Constants ───
  const GRAVITY = 0.6;
  const MAX_FALL = 12;
  const MOVE_SPEED = 4.5;
  const JUMP_FORCE = -12;
  const COIN_COUNT = 25;

  // ─── Level geometry ───
  const LEVEL_W = 3800;

  // Platforms: {x, y, w, h, color}
  const platforms = [
    // Ground segments
    { x: 0,      y: 400, w: 900,  h: 50, color: "#4a8c3f" },
    { x: 1000,   y: 400, w: 1300, h: 50, color: "#4a8c3f" },
    { x: 2400,   y: 400, w: 1400, h: 50, color: "#4a8c3f" },

    // Raised platforms
    { x: 300,    y: 310, w: 120,  h: 24, color: "#b5651d" },
    { x: 520,    y: 250, w: 120,  h: 24, color: "#b5651d" },
    { x: 730,    y: 190, w: 160,  h: 24, color: "#b5651d" },
    { x: 1050,   y: 310, w: 200,  h: 24, color: "#b5651d" },
    { x: 1300,   y: 240, w: 100,  h: 24, color: "#b5651d" },
    { x: 1480,   y: 180, w: 100,  h: 24, color: "#b5651d" },
    { x: 1660,   y: 310, w: 140,  h: 24, color: "#b5651d" },
    { x: 1850,   y: 250, w: 120,  h: 24, color: "#b5651d" },
    { x: 2050,   y: 190, w: 100,  h: 24, color: "#b5651d" },
    { x: 2200,   y: 310, w: 140,  h: 24, color: "#b5651d" },
    { x: 2450,   y: 310, w: 180,  h: 24, color: "#b5651d" },
    { x: 2680,   y: 240, w: 100,  h: 24, color: "#b5651d" },
    { x: 2860,   y: 180, w: 120,  h: 24, color: "#b5651d" },
    // Final approach
    { x: 3100,   y: 310, w: 260,  h: 24, color: "#b5651d" },
    { x: 3400,   y: 340, w: 300,  h: 60, color: "#5a4a3a" },
  ];

  // Pipes
  const pipes = [
    { x: 700,  y: 330, w: 50, h: 70 },
    { x: 1500, y: 330, w: 50, h: 70 },
    { x: 2700, y: 330, w: 50, h: 70 },
  ];

  // Question blocks (decorative)
  const qBlocks = [
    { x: 400, y: 270 },
    { x: 1150, y: 260 },
    { x: 2500, y: 260 },
  ];

  // Coins
  const coins = [];
  function spawnCoins() {
    const coinPositions = [
      [320, 280], [340, 280], [360, 280],
      [540, 220], [560, 220], [580, 220],
      [750, 160], [770, 160], [790, 160], [810, 160],
      [1080, 280], [1100, 280], [1120, 280],
      [1320, 210], [1340, 210],
      [1500, 150], [1520, 150],
      [1690, 280], [1710, 280], [1730, 280], [1750, 280],
      [1880, 220], [1900, 220],
      [2070, 160], [2090, 160],
      [2230, 280], [2250, 280], [2270, 280],
      [2480, 280], [2500, 280], [2520, 280],
      [2700, 210], [2720, 210],
      [2890, 150], [2910, 150], [2930, 150],
      [3140, 280], [3160, 280],
      [3450, 310], [3470, 310],
    ];
    coinPositions.forEach(function(p) {
      coins.push({ x: p[0], y: p[1], collected: false, bobPhase: Math.random() * Math.PI * 2 });
    });
  }
  spawnCoins();

  // Flag (goal)
  const flag = { x: 3630, y: 230, w: 10, h: 110 };

  // ─── Player ───
  const player = {
    x: 80,
    y: 350,
    w: 24,
    h: 36,
    vx: 0,
    vy: 0,
    grounded: false,
    facing: 1, // 1 = right, -1 = left
    animTimer: 0,
    alive: true,
  };

  // ─── Camera ───
  let camera = { x: 0 };

  // ─── State ───
  let score = 0;
  let gameWon = false;
  let winTimer = 0;
  let deathTimer = 0;
  let particles = [];

  // ─── Input ───
  const keys = {};
  window.addEventListener("keydown", function(e) {
    keys[e.code] = true;
    if (["Space","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].indexOf(e.code) !== -1) {
      e.preventDefault();
    }
  });
  window.addEventListener("keyup", function(e) {
    keys[e.code] = false;
  });

  function isLeft()  { return keys["ArrowLeft"]  || keys["KeyA"]; }
  function isRight() { return keys["ArrowRight"] || keys["KeyD"]; }
  function isJump()  { return keys["Space"]      || keys["ArrowUp"] || keys["KeyW"]; }

  // ─── Collision helpers ───
  function rectOverlap(a, b) {
    return a.x < b.x + b.w && a.x + a.w > b.x &&
           a.y < b.y + b.h && a.y + a.h > b.y;
  }

  // ─── Particles ───
  function spawnParticles(x, y, color, count) {
    for (var i = 0; i < count; i++) {
      particles.push({
        x: x, y: y,
        vx: (Math.random() - 0.5) * 6,
        vy: -Math.random() * 5 - 1,
        life: 30 + Math.random() * 20,
        maxLife: 30 + Math.random() * 20,
        color: color,
        size: 2 + Math.random() * 4,
      });
    }
  }

  function updateParticles() {
    for (var i = particles.length - 1; i >= 0; i--) {
      var p = particles[i];
      p.x += p.vx;
      p.y += p.vy;
      p.vy += 0.15;
      p.life--;
      if (p.life <= 0) particles.splice(i, 1);
    }
  }

  function drawParticles() {
    particles.forEach(function(p) {
      var alpha = p.life / p.maxLife;
      ctx.globalAlpha = alpha;
      ctx.fillStyle = p.color;
      ctx.fillRect(p.x - camera.x, p.y, p.size, p.size);
    });
    ctx.globalAlpha = 1;
  }

  // ─── Physics / Update ───
  function update() {
    if (gameWon) {
      winTimer++;
      if (winTimer > 120 && (isJump() || keys["Enter"])) {
        resetGame();
      }
      return;
    }

    if (!player.alive) {
      deathTimer++;
      if (deathTimer > 60) resetGame();
      player.vy += GRAVITY;
      player.y += player.vy;
      updateParticles();
      return;
    }

    // Horizontal movement
    if (isLeft()) {
      player.vx = -MOVE_SPEED;
      player.facing = -1;
    } else if (isRight()) {
      player.vx = MOVE_SPEED;
      player.facing = 1;
    } else {
      player.vx = 0;
    }

    player.animTimer += Math.abs(player.vx) * 0.1;

    // Apply horizontal movement
    player.x += player.vx;

    // Clamp to level bounds
    if (player.x < 0) player.x = 0;
    if (player.x + player.w > LEVEL_W) player.x = LEVEL_W - player.w;

    // Horizontal collision with platforms
    checkCollisionX();

    // Jumping
    if (isJump() && player.grounded) {
      player.vy = JUMP_FORCE;
      player.grounded = false;
      spawnParticles(player.x + player.w / 2, player.y + player.h, "#aaa", 5);
    }

    // Gravity
    player.vy += GRAVITY;
    if (player.vy > MAX_FALL) player.vy = MAX_FALL;

    // Apply vertical movement
    player.y += player.vy;

    // Vertical collision
    player.grounded = false;
    checkCollisionY();

    // Fall into pit
    if (player.y > H + 50) {
      player.alive = false;
      player.vy = -8;
    }

    // Coin collection
    coins.forEach(function(c) {
      if (c.collected) return;
      var bobY = c.y + Math.sin(c.bobPhase + (Date.now() * 0.003)) * 3;
      var coinRect = { x: c.x - 8, y: bobY - 8, w: 16, h: 16 };
      if (rectOverlap(player, coinRect)) {
        c.collected = true;
        score += 10;
        spawnParticles(c.x, bobY, "#ffcc00", 8);
      }
    });

    // Flag collision (win)
    var flagRect = { x: flag.x - 5, y: flag.y, w: flag.w + 10, h: flag.h };
    if (rectOverlap(player, flagRect)) {
      gameWon = true;
      winTimer = 0;
      spawnParticles(player.x + player.w / 2, player.y, "#ffcc00", 20);
      spawnParticles(player.x + player.w / 2, player.y, "#ff4444", 15);
      spawnParticles(player.x + player.w / 2, player.y, "#44ff44", 15);
    }

    // Camera
    var targetCam = player.x - W / 3;
    if (targetCam < 0) targetCam = 0;
    if (targetCam > LEVEL_W - W) targetCam = LEVEL_W - W;
    camera.x += (targetCam - camera.x) * 0.12;

    updateParticles();
  }

  function checkCollisionX() {
    var allSolids = platforms.concat(pipes.map(function(p) { return { x: p.x, y: p.y, w: p.w, h: p.h }; }));
    allSolids.forEach(function(s) {
      if (rectOverlap(player, s)) {
        if (player.vx > 0) {
          player.x = s.x - player.w;
        } else if (player.vx < 0) {
          player.x = s.x + s.w;
        }
        player.vx = 0;
      }
    });
  }

  function checkCollisionY() {
    var allSolids = platforms.concat(pipes.map(function(p) { return { x: p.x, y: p.y, w: p.w, h: p.h }; }));
    allSolids.forEach(function(s) {
      if (rectOverlap(player, s)) {
        if (player.vy > 0) {
          player.y = s.y - player.h;
          player.vy = 0;
          player.grounded = true;
        } else if (player.vy < 0) {
          player.y = s.y + s.h;
          player.vy = 0;
        }
      }
    });
  }

  function resetGame() {
    player.x = 80;
    player.y = 350;
    player.vx = 0;
    player.vy = 0;
    player.alive = true;
    player.grounded = false;
    score = 0;
    gameWon = false;
    winTimer = 0;
    deathTimer = 0;
    particles = [];
    coins.forEach(function(c) { c.collected = false; });
    camera.x = 0;
  }

  // ─── Drawing ───

  function drawSky() {
    // Sky gradient
    var grd = ctx.createLinearGradient(0, 0, 0, H);
    grd.addColorStop(0, "#1a1a4e");
    grd.addColorStop(0.4, "#2a4a7a");
    grd.addColorStop(0.7, "#5a9ad5");
    grd.addColorStop(1, "#87CEEB");
    ctx.fillStyle = grd;
    ctx.fillRect(0, 0, W, H);

    // Clouds
    var cloudPositions = [
      [100, 60, 80], [350, 40, 100], [700, 80, 60],
      [1000, 50, 90], [1400, 70, 70], [1800, 40, 110],
      [2200, 60, 80], [2600, 50, 100], [3000, 70, 70],
      [3400, 45, 90],
    ];
    cloudPositions.forEach(function(c) {
      var cx = c[0] - (camera.x * 0.4); // Parallax
      // Wrap around
      if (cx < -150) cx += W + 300;
      if (cx > W + 50) cx -= W + 300;
      ctx.fillStyle = "rgba(255,255,255,0.7)";
      drawCloud(cx, c[1], c[2]);
    });
  }

  function drawCloud(x, y, size) {
    ctx.beginPath();
    ctx.arc(x, y, size * 0.4, 0, Math.PI * 2);
    ctx.arc(x - size * 0.25, y + 5, size * 0.3, 0, Math.PI * 2);
    ctx.arc(x + size * 0.25, y + 3, size * 0.35, 0, Math.PI * 2);
    ctx.arc(x + size * 0.1, y - 8, size * 0.3, 0, Math.PI * 2);
    ctx.fill();
  }

  function drawMountains() {
    // Background mountains (parallax)
    ctx.fillStyle = "#2d4a1a";
    var mountainPositions = [
      [200, 380, 200], [700, 360, 250], [1300, 370, 180],
      [1900, 350, 280], [2500, 370, 200], [3200, 360, 240],
    ];
    mountainPositions.forEach(function(m) {
      var mx = m[0] - (camera.x * 0.2);
      ctx.beginPath();
      ctx.moveTo(mx - m[2] / 2, m[1]);
      ctx.lineTo(mx, m[1] - m[2] * 0.6);
      ctx.lineTo(mx + m[2] / 2, m[1]);
      ctx.closePath();
      ctx.fill();
    });
  }

  function drawPlatform(p) {
    var x = p.x - camera.x;
    // Main block
    ctx.fillStyle = p.color;
    ctx.fillRect(x, p.y, p.w, p.h);

    // Top edge highlight
    ctx.fillStyle = "rgba(255,255,255,0.2)";
    ctx.fillRect(x, p.y, p.w, 4);

    // Bottom shadow
    ctx.fillStyle = "rgba(0,0,0,0.2)";
    ctx.fillRect(x, p.y + p.h - 4, p.w, 4);

    // Grass on top for ground platforms
    if (p.h >= 40) {
      ctx.fillStyle = "#5cb840";
      ctx.fillRect(x, p.y, p.w, 6);
      // Grass blades
      ctx.fillStyle = "#3a7d28";
      for (var gx = x; gx < x + p.w; gx += 12) {
        ctx.fillRect(gx, p.y - 3, 3, 6);
      }
    } else {
      // Brick pattern on raised platforms
      ctx.strokeStyle = "rgba(0,0,0,0.15)";
      ctx.lineWidth = 1;
      for (var bx = x; bx < x + p.w; bx += 20) {
        ctx.beginPath();
        ctx.moveTo(bx, p.y);
        ctx.lineTo(bx, p.y + p.h);
        ctx.stroke();
      }
      for (var by = p.y + 10; by < p.y + p.h; by += 10) {
        ctx.beginPath();
        ctx.moveTo(x, by);
        ctx.lineTo(x + p.w, by);
        ctx.stroke();
      }
    }
  }

  function drawPipe(p) {
    var x = p.x - camera.x;
    // Pipe body
    ctx.fillStyle = "#1a8a1a";
    ctx.fillRect(x + 3, p.y + 28, p.w - 6, p.h - 28);
    // Pipe top
    ctx.fillStyle = "#1a8a1a";
    ctx.fillRect(x, p.y, p.w, 28);
    // Pipe highlight
    ctx.fillStyle = "#2aad2a";
    ctx.fillRect(x + 5, p.y + 2, 8, 24);
    ctx.fillRect(x + 8, p.y + 30, 6, p.h - 32);
    // Pipe rim
    ctx.fillStyle = "#0e6b0e";
    ctx.fillRect(x, p.y, p.w, 3);
    ctx.fillRect(x, p.y + 25, p.w, 3);
  }

  function drawQBlock(b) {
    var x = b.x - camera.x;
    // Block
    ctx.fillStyle = "#e8a000";
    ctx.fillRect(x, b.y, 28, 28);
    ctx.strokeStyle = "#8a5e00";
    ctx.lineWidth = 2;
    ctx.strokeRect(x, b.y, 28, 28);
    // Question mark
    ctx.fillStyle = "#fff";
    ctx.font = "bold 18px Courier New";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText("?", x + 14, b.y + 15);
  }

  function drawPlayer() {
    var x = player.x - camera.x;
    var y = player.y;

    ctx.save();
    if (player.facing === -1) {
      ctx.translate(x + player.w / 2, 0);
      ctx.scale(-1, 1);
      ctx.translate(-(x + player.w / 2), 0);
    }

    // Shoes
    ctx.fillStyle = "#6a2c00";
    ctx.fillRect(x + 2, y + player.h - 8, 8, 8);
    ctx.fillRect(x + player.w - 10, y + player.h - 8, 8, 8);

    // Body (blue overalls)
    ctx.fillStyle = "#2255cc";
    ctx.fillRect(x + 3, y + 16, player.w - 6, 14);

    // Belt
    ctx.fillStyle = "#e8c800";
    ctx.fillRect(x + 5, y + 26, player.w - 10, 3);

    // Arms
    ctx.fillStyle = "#ff9944";
    if (!player.grounded) {
      // Arms up when jumping
      ctx.fillRect(x - 2, y + 10, 6, 4);
      ctx.fillRect(x + player.w - 4, y + 10, 6, 4);
    } else if (Math.abs(player.vx) > 0) {
      // Running animation
      var armPhase = Math.sin(player.animTimer) * 4;
      ctx.fillRect(x - 2, y + 18 + armPhase, 6, 4);
      ctx.fillRect(x + player.w - 4, y + 18 - armPhase, 6, 4);
    } else {
      ctx.fillRect(x - 2, y + 18, 6, 4);
      ctx.fillRect(x + player.w - 4, y + 18, 6, 4);
    }

    // Legs
    ctx.fillStyle = "#2255cc";
    if (Math.abs(player.vx) > 0 && player.grounded) {
      var legAnim = Math.sin(player.animTimer) * 3;
      ctx.fillRect(x + 4, y + 28, 7, 8 + legAnim);
      ctx.fillRect(x + player.w - 11, y + 28, 7, 8 - legAnim);
    } else if (!player.grounded) {
      ctx.fillRect(x + 4, y + 28, 7, 6);
      ctx.fillRect(x + player.w - 11, y + 28, 7, 6);
    } else {
      ctx.fillRect(x + 4, y + 28, 7, 8);
      ctx.fillRect(x + player.w - 11, y + 28, 7, 8);
    }

    // Head
    ctx.fillStyle = "#ff9944";
    ctx.fillRect(x + 4, y + 4, player.w - 8, 14);

    // Hat
    ctx.fillStyle = "#dd2222";
    ctx.fillRect(x + 2, y - 2, player.w - 4, 8);
    ctx.fillRect(x + player.w - 10, y, 12, 5); // brim

    // Eyes
    ctx.fillStyle = "#fff";
    ctx.fillRect(x + player.w - 10, y + 8, 5, 5);
    ctx.fillStyle = "#111";
    ctx.fillRect(x + player.w - 8, y + 9, 3, 3);

    ctx.restore();
  }

  function drawCoins() {
    var now = Date.now() * 0.003;
    coins.forEach(function(c) {
      if (c.collected) return;
      var x = c.x - camera.x;
      var bobY = c.y + Math.sin(c.bobPhase + now) * 3;

      // Glow
      ctx.fillStyle = "rgba(255,204,0,0.15)";
      ctx.beginPath();
      ctx.arc(x, bobY, 12, 0, Math.PI * 2);
      ctx.fill();

      // Coin body
      var stretch = Math.abs(Math.cos(c.bobPhase + now));
      var cw = 10 * (0.5 + stretch * 0.5);
      ctx.fillStyle = "#ffcc00";
      ctx.beginPath();
      ctx.ellipse(x, bobY, cw, 10, 0, 0, Math.PI * 2);
      ctx.fill();

      // Highlight
      ctx.fillStyle = "#ffe866";
      ctx.beginPath();
      ctx.ellipse(x - 2, bobY - 2, cw * 0.4, 5, 0, 0, Math.PI * 2);
      ctx.fill();

      // $ sign
      if (cw > 4) {
        ctx.fillStyle = "#aa7700";
        ctx.font = "bold 10px Courier New";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText("$", x, bobY + 1);
      }
    });
  }

  function drawFlag() {
    var x = flag.x - camera.x;

    // Pole
    ctx.fillStyle = "#888";
    ctx.fillRect(x, flag.y, flag.w, flag.h);

    // Pole top ball
    ctx.fillStyle = "#ffcc00";
    ctx.beginPath();
    ctx.arc(x + flag.w / 2, flag.y, 6, 0, Math.PI * 2);
    ctx.fill();

    // Flag (wavy)
    ctx.fillStyle = "#ff3333";
    ctx.beginPath();
    ctx.moveTo(x + flag.w, flag.y + 5);
    var now = Date.now() * 0.004;
    ctx.quadraticCurveTo(x + flag.w + 30, flag.y + 10 + Math.sin(now) * 4, x + flag.w + 50, flag.y + 15);
    ctx.quadraticCurveTo(x + flag.w + 30, flag.y + 25 + Math.sin(now + 1) * 4, x + flag.w, flag.y + 35);
    ctx.closePath();
    ctx.fill();

    // Flag star
    ctx.fillStyle = "#ffcc00";
    ctx.font = "14px Courier New";
    ctx.textAlign = "center";
    ctx.fillText("★", x + flag.w + 22, flag.y + 23);
  }

  function drawHUD() {
    // Score background
    ctx.fillStyle = "rgba(0,0,0,0.5)";
    ctx.fillRect(10, 10, 180, 36);
    ctx.strokeStyle = "#ffcc00";
    ctx.lineWidth = 2;
    ctx.strokeRect(10, 10, 180, 36);

    // Score text
    ctx.fillStyle = "#ffcc00";
    ctx.font = "bold 18px Courier New";
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";
    var collected = coins.filter(function(c) { return c.collected; }).length;
    ctx.fillText("SCORE: " + score, 22, 29);

    // Coin counter
    ctx.fillStyle = "#fff";
    ctx.font = "14px Courier New";
    ctx.fillText("Coins: " + collected + "/" + coins.filter(function(c){return true}).length, 110, 48);

    // Direction arrow to flag if off screen
    var flagScreenX = flag.x - camera.x;
    if (flagScreenX > W) {
      ctx.fillStyle = "#ff4444";
      ctx.font = "20px Courier New";
      ctx.textAlign = "center";
      ctx.fillText("&rarr;", W - 30, H - 30);
    }
  }

  function drawWinScreen() {
    ctx.fillStyle = "rgba(0,0,0,0.6)";
    ctx.fillRect(0, 0, W, H);

    ctx.fillStyle = "#ffcc00";
    ctx.font = "bold 48px Courier New";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText("YOU WIN!", W / 2, H / 2 - 40);

    ctx.fillStyle = "#fff";
    ctx.font = "24px Courier New";
    ctx.fillText("Score: " + score, W / 2, H / 2 + 20);
    ctx.fillText("Coins: " + coins.filter(function(c) { return c.collected; }).length + "/" + coins.length, W / 2, H / 2 + 55);

    if (winTimer > 120) {
      ctx.fillStyle = "#888";
      ctx.font = "16px Courier New";
      ctx.fillText("Press Space or Enter to play again", W / 2, H / 2 + 100);
    }
  }

  function drawDeath() {
    ctx.fillStyle = "rgba(0,0,0,0.3)";
    ctx.fillRect(0, 0, W, H);
    ctx.fillStyle = "#ff4444";
    ctx.font = "bold 32px Courier New";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText("OOPS!", W / 2, H / 2);
  }

  // ─── Main Loop ───
  function gameLoop() {
    update();

    // Clear
    ctx.clearRect(0, 0, W, H);

    // Draw world
    drawSky();
    drawMountains();

    // Platforms
    platforms.forEach(drawPlatform);

    // Decorative blocks
    qBlocks.forEach(drawQBlock);

    // Pipes
    pipes.forEach(drawPipe);

    // Coins
    drawCoins();

    // Flag
    drawFlag();

    // Player
    if (player.alive || deathTimer < 60) {
      drawPlayer();
    }

    // Particles
    drawParticles();

    // HUD
    drawHUD();

    // Overlays
    if (gameWon) {
      drawWinScreen();
    }
    if (!player.alive) {
      drawDeath();
    }

    requestAnimationFrame(gameLoop);
  }

  // ─── Start ───
  requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>

rep 5 UNGRADED

pi 377.0s · verify 4.2s · 33275 tok · responded: qwen/qwen3.6-27b · transcript

smoke 3/3

screenshot

verify output
CHECK PASS: canvas #game exists and is sized
CHECK PASS: no uncaught JS errors on load
CHECK PASS: keyboard input does not throw
score: 3/3 (100%)
produced code
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Mini Mario</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #111;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh;
    font-family: monospace;
    overflow: hidden;
  }
  #game {
    border: 2px solid #333;
    image-rendering: pixelated;
    display: block;
  }
  #instructions {
    color: #777;
    margin-top: 10px;
    font-size: 14px;
    text-align: center;
  }
</style>
</head>
<body>
<canvas id="game" width="800" height="450"></canvas>
<div id="instructions">Arrow keys / WASD to move · Space / Up to jump · Collect coins and reach the flag!</div>

<script>
(function() {
  "use strict";

  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const W = canvas.width;   // 800
  const H = canvas.height;  // 450

  // --- Constants ---
  const GRAVITY = 0.55;
  const FRICTION = 0.82;
  const MOVE_ACCEL = 0.7;
  const MAX_SPEED = 4.5;
  const JUMP_FORCE = -10.5;
  const TILE = 32;

  // --- Input ---
  const keys = {};
  window.addEventListener("keydown", function(e) {
    keys[e.code] = true;
    if (["Space","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].indexOf(e.code) !== -1) {
      e.preventDefault();
    }
  });
  window.addEventListener("keyup", function(e) {
    keys[e.code] = false;
  });

  function isLeft()  { return keys["ArrowLeft"] || keys["KeyA"]; }
  function isRight() { return keys["ArrowRight"] || keys["KeyD"]; }
  function isJump()  { return keys["Space"] || keys["ArrowUp"] || keys["KeyW"]; }

  // --- Game state ---
  let score = 0;
  let gameState = "playing"; // "playing" | "won" | "dead"
  let deathTimer = 0;
  let winTimer = 0;
  let particles = [];
  let camera = { x: 0 };

  // --- World definition ---
  // Map: each tile = 32px. '.' = air, '#' = ground/brick, '=' = raiseable platform, 
  //       'P' = pipe, '!' = coin, 'F' = flag, 'S' = spawn, '^' = spike

  const WORLD_W = 120; // tiles
  const WORLD_H = Math.ceil(H / TILE); // ~14

  // Build world tile array
  var world = [];
  for (var r = 0; r < WORLD_H; r++) {
    world[r] = [];
    for (var c = 0; c < WORLD_W; c++) {
      world[r][c] = '.';
    }
  }

  // Ground rows (rows 12 and 13)
  // Put gaps at columns 28-30 and 65-67
  for (var c = 0; c < WORLD_W; c++) {
    if ((c >= 28 && c <= 30) || (c >= 65 && c <= 67)) continue; // pits
    world[13][c] = '#';
    world[12][c] = '#';
  }

  function placePlatform(col, row, len) {
    for (var i = 0; i < len; i++) {
      if (col + i < WORLD_W && row >= 0 && row < WORLD_H) {
        world[row][col + i] = '=';
      }
    }
  }

  function placeBricks(col, row, len) {
    for (var i = 0; i < len; i++) {
      if (col + i < WORLD_W && row >= 0 && row < WORLD_H) {
        world[row][col + i] = '#';
      }
    }
  }

  // Platforms
  placePlatform(8, 9, 4);
  placePlatform(16, 7, 5);
  placePlatform(24, 5, 3);
  placePlatform(32, 8, 4); // bridge over pit partially
  placePlatform(31, 10, 2); // stepping stone over pit
  placePlatform(35, 9, 5);
  placePlatform(42, 6, 6);
  placePlatform(50, 8, 4);
  placePlatform(55, 5, 3);
  placePlatform(60, 9, 6);
  placePlatform(69, 9, 5); // after second pit
  placePlatform(75, 7, 4);
  placePlatform(80, 5, 5);
  placePlatform(88, 8, 4);
  placePlatform(93, 6, 4);
  placePlatform(98, 9, 5);

  // Staircase before flag
  for (var s = 0; s < 6; s++) {
    for (var ss = 0; ss <= s; ss++) {
      var cr = 12 - ss;
      var cc = 105 + s;
      if (cr < 12 && cc < WORLD_W) world[cr][cc] = '#';
    }
  }

  // Pipes
  function placePipe(col, topRow) {
    for (var r = topRow; r <= 12; r++) {
      world[r][col] = 'P';
      if (col + 1 < WORLD_W) world[r][col + 1] = 'P';
    }
  }
  placePipe(14, 10);
  placePipe(46, 11);
  placePipe(84, 9);

  // Spikes on ground
  world[12][44] = '^';
  world[12][45] = '^';
  world[12][86] = '^';

  // Coins
  var coins = [];
  function placeCoin(col, row) {
    coins.push({ x: col * TILE + TILE/2, y: row * TILE + TILE/2, collected: false });
  }
  // Coins over platforms
  placeCoin(9, 8); placeCoin(10, 8);
  placeCoin(17, 6); placeCoin(18, 6);
  placeCoin(24, 4); placeCoin(25, 4);
  placeCoin(7, 11); placeCoin(8, 10);
  placeCoin(33, 7); placeCoin(34, 7);
  placeCoin(36, 8); placeCoin(37, 8);
  placeCoin(43, 5); placeCoin(44, 5); placeCoin(45, 5);
  placeCoin(51, 7);
  placeCoin(55, 4); placeCoin(56, 4);
  placeCoin(61, 8); placeCoin(62, 8);
  placeCoin(70, 8); placeCoin(71, 8);
  placeCoin(76, 6); placeCoin(77, 6);
  placeCoin(81, 4); placeCoin(82, 4); placeCoin(83, 4);
  placeCoin(89, 7); placeCoin(90, 7);
  placeCoin(94, 5); placeCoin(95, 5);
  placeCoin(99, 8); placeCoin(100, 8);
  // Some coins in the air for extra challenge
  placeCoin(12, 11); placeCoin(22, 9); placeCoin(68, 11);

  // Flag position
  var flag = { x: 111 * TILE, baseY: 12 * TILE, topY: 3 * TILE };

  // --- Player ---
  var player = {
    x: 3 * TILE,
    y: 11 * TILE,
    w: 20,
    h: 28,
    vx: 0,
    vy: 0,
    onGround: false,
    facing: 1,
    frame: 0,
    frameTimer: 0
  };

  function resetPlayer() {
    player.x = 3 * TILE;
    player.y = 11 * TILE;
    player.vx = 0;
    player.vy = 0;
    player.onGround = false;
    player.facing = 1;
    camera.x = 0;
  }

  function resetGame() {
    score = 0;
    gameState = "playing";
    coins.forEach(function(c) { c.collected = false; });
    particles = [];
    resetPlayer();
  }

  // --- Tile helpers ---
  function tileAt(col, row) {
    if (col < 0 || col >= WORLD_W || row < 0 || row >= WORLD_H) return '.';
    return world[row][col];
  }

  function isSolid(t) { return t === '#' || t === '=' || t === 'P'; }

  function playerTileCol() { return Math.floor((player.x + player.w/2) / TILE); }
  function playerTileRow() { return Math.floor((player.y + player.h) / TILE); }

  // --- Collision ---
  function collide() {
    player.onGround = false;

    // Horizontal
    player.x += player.vx;

    var left = Math.floor(player.x / TILE);
    var right = Math.floor((player.x + player.w - 1) / TILE);
    var top = Math.floor(player.y / TILE);
    var bottom = Math.floor((player.y + player.h - 1) / TILE);

    for (var r = top; r <= bottom; r++) {
      for (var c = left; c <= right; c++) {
        if (isSolid(tileAt(c, r))) {
          if (player.vx > 0) {
            player.x = c * TILE - player.w;
            player.vx = 0;
          } else if (player.vx < 0) {
            player.x = (c + 1) * TILE;
            player.vx = 0;
          }
        }
      }
    }

    // Vertical
    player.y += player.vy;
    player.vx *= 1; // keep horizontal position

    left = Math.floor(player.x / TILE);
    right = Math.floor((player.x + player.w - 1) / TILE);
    top = Math.floor(player.y / TILE);
    bottom = Math.floor((player.y + player.h - 1) / TILE);

    for (var r = top; r <= bottom; r++) {
      for (var c = left; c <= right; c++) {
        var t = tileAt(c, r);
        if (isSolid(t)) {
          if (player.vy > 0) {
            player.y = r * TILE - player.h;
            player.vy = 0;
            player.onGround = true;
          } else if (player.vy < 0) {
            player.y = (r + 1) * TILE;
            player.vy = 0;
          }
        }
        // Spike check
        if (t === '^') {
          die();
          return;
        }
      }
    }

    // Keep in bounds horizontally
    if (player.x < 0) { player.x = 0; player.vx = 0; }
    if (player.x + player.w > WORLD_W * TILE) { player.x = WORLD_W * TILE - player.w; player.vx = 0; }
  }

  function die() {
    if (gameState !== "playing") return;
    gameState = "dead";
    deathTimer = 90;
    // spawn death particles
    for (var i = 0; i < 12; i++) {
      particles.push({
        x: player.x + player.w/2,
        y: player.y + player.h/2,
        vx: (Math.random() - 0.5) * 6,
        vy: -Math.random() * 8 - 2,
        life: 40 + Math.random() * 20,
        color: "#e44",
        size: 3 + Math.random() * 3
      });
    }
  }

  function win() {
    if (gameState !== "playing") return;
    gameState = "won";
    winTimer = 0;
    for (var i = 0; i < 30; i++) {
      var hue = Math.random() * 360;
      particles.push({
        x: flag.x + TILE/2,
        y: flag.topY + TILE/2,
        vx: (Math.random() - 0.5) * 5,
        vy: -Math.random() * 6 - 1,
        life: 50 + Math.random() * 40,
        color: "hsl(" + hue + ",90%,60%)",
        size: 3 + Math.random() * 4
      });
    }
  }

  // --- Coin collection ---
  function checkCoins() {
    for (var i = 0; i < coins.length; i++) {
      var c = coins[i];
      if (c.collected) continue;
      var dx = (player.x + player.w/2) - c.x;
      var dy = (player.y + player.h/2) - c.y;
      if (dx * dx + dy * dy < 400) { // ~20px radius
        c.collected = true;
        score += 10;
        // Sparkle particles
        for (var j = 0; j < 6; j++) {
          particles.push({
            x: c.x, y: c.y,
            vx: (Math.random() - 0.5) * 4,
            vy: -Math.random() * 3 - 1,
            life: 15 + Math.random() * 10,
            color: "#fd4",
            size: 2 + Math.random() * 2
          });
        }
      }
    }
  }

  function checkFlag() {
    if (player.x + player.w > flag.x && player.x < flag.x + TILE) {
      win();
    }
  }

  // --- Update ---
  function update() {
    // Update particles always
    for (var i = particles.length - 1; i >= 0; i--) {
      var p = particles[i];
      p.x += p.vx;
      p.y += p.vy;
      p.vy += 0.15;
      p.life--;
      if (p.life <= 0) particles.splice(i, 1);
    }

    if (gameState === "dead") {
      deathTimer--;
      if (deathTimer <= 0) {
        resetGame();
      }
      return;
    }
    if (gameState === "won") {
      winTimer++;
      return;
    }

    // Movement
    if (isLeft()) {
      player.vx -= MOVE_ACCEL;
      player.facing = -1;
    }
    if (isRight()) {
      player.vx += MOVE_ACCEL;
      player.facing = 1;
    }

    // Clamp speed
    if (player.vx > MAX_SPEED) player.vx = MAX_SPEED;
    if (player.vx < -MAX_SPEED) player.vx = -MAX_SPEED;

    // Jump
    if (isJump() && player.onGround) {
      player.vy = JUMP_FORCE;
      player.onGround = false;
    }

    // Apply gravity
    player.vy += GRAVITY;

    // Friction on ground
    if (player.onGround) {
      player.vx *= FRICTION;
      if (Math.abs(player.vx) < 0.1) player.vx = 0;
    } else {
      player.vx *= 0.96; // slight air friction
    }

    collide();
    checkCoins();
    checkFlag();

    // Animation
    if (Math.abs(player.vx) > 0.5) {
      player.frameTimer++;
      if (player.frameTimer > 6) {
        player.frameTimer = 0;
        player.frame = (player.frame + 1) % 4;
      }
    } else {
      player.frame = 0;
    }

    // Fall into abyss
    if (player.y > WORLD_H * TILE + 64) {
      die();
    }

    // Camera
    var targetCam = player.x - W * 0.38;
    if (targetCam < 0) targetCam = 0;
    if (targetCam > WORLD_W * TILE - W) targetCam = WORLD_W * TILE - W;
    camera.x += (targetCam - camera.x) * 0.1;
  }

  // --- Drawing helpers ---
  function drawBg() {
    // Sky gradient
    var grad = ctx.createLinearGradient(0, 0, 0, H);
    grad.addColorStop(0, "#5c94fc");
    grad.addColorStop(0.7, "#87CEEB");
    grad.addColorStop(1, "#b8e4ff");
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, W, H);

    // Clouds (parallax)
    ctx.fillStyle = "rgba(255,255,255,0.8)";
    var cloudPositions = [
      {x: 200, y: 60, w: 80}, {x: 600, y: 40, w: 100},
      {x: 1100, y: 80, w: 70}, {x: 1500, y: 50, w: 90},
      {x: 2000, y: 70, w: 80}, {x: 2500, y: 55, w: 100},
      {x: 3000, y: 45, w: 75}, {x: 3500, y: 65, w: 85},
      {x: 4000, y: 50, w: 95}
    ];
    for (var i = 0; i < cloudPositions.length; i++) {
      var cl = cloudPositions[i];
      var cx = cl.x - camera.x * 0.3;
      var cy = cl.y;
      drawCloud(cx, cy, cl.w);
    }

    // Distant hills (parallax)
    ctx.fillStyle = "rgba(60,140,60,0.35)";
    var hillPositions = [300, 800, 1400, 2100, 2800, 3400, 4100];
    for (var i = 0; i < hillPositions.length; i++) {
      var hx = hillPositions[i] - camera.x * 0.15;
      drawHill(hx, H - 60, 120 + (i % 3) * 40, 80 + (i % 2) * 30);
    }
  }

  function drawCloud(x, y, w) {
    var h = w * 0.45;
    ctx.beginPath();
    ctx.arc(x, y, h * 0.5, 0, Math.PI * 2);
    ctx.arc(x + w * 0.3, y - h * 0.2, h * 0.6, 0, Math.PI * 2);
    ctx.arc(x + w * 0.6, y, h * 0.45, 0, Math.PI * 2);
    ctx.arc(x + w * 0.35, y + h * 0.1, h * 0.35, 0, Math.PI * 2);
    ctx.fill();
  }

  function drawHill(x, baseY, w, h) {
    ctx.beginPath();
    ctx.moveTo(x - w, baseY);
    ctx.quadraticCurveTo(x, baseY - h, x + w, baseY);
    ctx.fill();
  }

  function drawTile(c, r, screenX, screenY) {
    var t = world[r][c];
    if (t === '.') return;

    if (t === '#') {
      // Ground / brick
      var isTop = !isSolid(tileAt(c, r - 1));
      if (isTop) {
        // Top surface - grass
        ctx.fillStyle = "#6ab84a";
        ctx.fillRect(screenX, screenY, TILE, 6);
        ctx.fillStyle = "#e8a830";
        ctx.fillRect(screenX, screenY + 6, TILE, TILE - 6);
        // Brick pattern
        ctx.strokeStyle = "#c48420";
        ctx.lineWidth = 1;
        ctx.strokeRect(screenX + 1, screenY + 8, TILE - 2, TILE - 10);
        ctx.beginPath();
        ctx.moveTo(screenX + TILE/2, screenY + 8);
        ctx.lineTo(screenX + TILE/2, screenY + TILE);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(screenX, screenY + 18);
        ctx.lineTo(screenX + TILE * 0.5, screenY + 18);
        ctx.stroke();
      } else {
        // Underground or side surface
        ctx.fillStyle = "#c48420";
        ctx.fillRect(screenX, screenY, TILE, TILE);
        ctx.strokeStyle = "#a06818";
        ctx.lineWidth = 1;
        ctx.strokeRect(screenX + 1, screenY + 1, TILE - 2, TILE - 2);
        ctx.beginPath();
        ctx.moveTo(screenX + TILE/2, screenY + 1);
        ctx.lineTo(screenX + TILE/2, screenY + TILE);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(screenX, screenY + TILE/2);
        ctx.lineTo(screenX + TILE, screenY + TILE/2);
        ctx.stroke();
      }
    } else if (t === '=') {
      // Raised platform (mushroom block style)
      ctx.fillStyle = "#d4922a";
      ctx.fillRect(screenX, screenY, TILE, TILE);
      ctx.fillStyle = "#e4a830";
      ctx.fillRect(screenX + 2, screenY + 2, TILE - 4, TILE - 4);
      ctx.fillStyle = "#c48020";
      // question mark
      ctx.font = "bold 18px monospace";
      ctx.textAlign = "center";
      ctx.fillText("?", screenX + TILE/2, screenY + TILE/2 + 6);
      ctx.strokeStyle = "#a06010";
      ctx.lineWidth = 1;
      ctx.strokeRect(screenX, screenY, TILE, TILE);
    } else if (t === 'P') {
      // Pipe
      var sx = screenX;
      var sy = screenY;
      // Pipe body
      ctx.fillStyle = "#2a8a2a";
      ctx.fillRect(sx + 2, sy, TILE - 2, TILE);
      ctx.fillStyle = "#3cb83c";
      ctx.fillRect(sx + 4, sy, TILE - 10, TILE);
      // Highlight
      ctx.fillStyle = "#5cd85c";
      ctx.fillRect(sx + 6, sy, 3, TILE);
      // Top rim
      if (!isSolid(tileAt(c, r - 1)) || r === 0) {
        ctx.fillStyle = "#2a8a2a";
        ctx.fillRect(sx - 3, sy, TILE + 4, 10);
        ctx.fillStyle = "#3cb83c";
        ctx.fillRect(sx - 1, sy + 2, TILE, 6);
        ctx.fillStyle = "#5cd85c";
        ctx.fillRect(sx + 2, sy + 2, 3, 6);
      }
    } else if (t === '^') {
      // Spike
      ctx.fillStyle = "#c44";
      ctx.beginPath();
      ctx.moveTo(screenX + 4, screenY + TILE);
      ctx.lineTo(screenX + TILE/2, screenY + 4);
      ctx.lineTo(screenX + TILE - 4, screenY + TILE);
      ctx.closePath();
      ctx.fill();
      ctx.fillStyle = "#e66";
      ctx.beginPath();
      ctx.moveTo(screenX + 10, screenY + TILE);
      ctx.lineTo(screenX + TILE/2, screenY + 10);
      ctx.lineTo(screenX + TILE - 10, screenY + TILE);
      ctx.closePath();
      ctx.fill();
    }
  }

  function drawCoin(x, y) {
    var t = Date.now() / 300;
    var scaleX = Math.abs(Math.cos(t));
    ctx.save();
    ctx.translate(x, y);
    ctx.scale(scaleX, 1);
    ctx.fillStyle = "#ffd700";
    ctx.beginPath();
    ctx.arc(0, 0, 9, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = "#c4a000";
    ctx.beginPath();
    ctx.arc(0, 0, 7, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = "#ffe44d";
    ctx.beginPath();
    ctx.arc(-1, -1, 4, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  function drawFlag() {
    var fx = flag.x - camera.x;
    var fy = flag.topY;
    var baseY = flag.baseY;

    // Pole
    ctx.fillStyle = "#aaa";
    ctx.fillRect(fx + 14, fy, 4, baseY - fy);
    // Ball on top
    ctx.fillStyle = "#666";
    ctx.beginPath();
    ctx.arc(fx + 16, fy, 5, 0, Math.PI * 2);
    ctx.fill();
    // Flag (fluttering)
    var flutter = Math.sin(Date.now() / 200) * 3;
    ctx.fillStyle = "#e44";
    ctx.beginPath();
    ctx.moveTo(fx + 18, fy + 8);
    ctx.lineTo(fx + 48 + flutter, fy + 18);
    ctx.lineTo(fx + 18, fy + 38);
    ctx.closePath();
    ctx.fill();
    // Star on flag
    ctx.fillStyle = "#ff4";
    ctx.font = "14px monospace";
    ctx.textAlign = "center";
    ctx.fillText("★", fx + 32 + flutter/2, fy + 28);

    // Base
    ctx.fillStyle = "#888";
    ctx.fillRect(fx + 6, baseY - 4, TILE - 4, TILE);
    ctx.fillStyle = "#666";
    ctx.fillRect(fx + 8, baseY - 2, TILE - 8, TILE - 2);
  }

  function drawPlayer() {
    if (gameState === "dead") return;

    var px = player.x - camera.x;
    var py = player.y;
    var f = player.facing;

    ctx.save();
    ctx.translate(px + player.w/2, py + player.h/2);
    if (f === -1) ctx.scale(-1, 1);

    // Body
    ctx.fillStyle = "#e02020";
    ctx.fillRect(-8, -6, 16, 14);

    // Head
    ctx.fillStyle = "#fca044";
    ctx.fillRect(-7, -14, 14, 10);

    // Hat
    ctx.fillStyle = "#e02020";
    ctx.fillRect(-8, -16, 16, 5);
    ctx.fillRect(-4, -18, 12, 3);

    // Hat brim
    ctx.fillStyle = "#c01818";
    ctx.fillRect(-9, -13, 18, 2);

    // Eye
    ctx.fillStyle = "#111";
    ctx.fillRect(1, -12, 3, 3);

    // Mustache
    ctx.fillStyle = "#8b4513";
    ctx.fillRect(-1, -8, 8, 2);

    // Legs (animated when walking)
    ctx.fillStyle = "#4440cc";
    var legOffset = 0;
    if (!player.onGround) {
      // Jump pose
      ctx.fillRect(-7, 8, 6, 6);
      ctx.fillRect(2, 5, 6, 6);
    } else if (Math.abs(player.vx) > 0.5) {
      legOffset = Math.sin(player.frame * Math.PI * 0.5) * 3;
    }
    if (!(!player.onGround)) {
      ctx.fillRect(-7 + legOffset, 7, 6, 7);
      ctx.fillRect(1 - legOffset, 7, 6, 7);
    }

    // Shoes
    ctx.fillStyle = "#a0522d";
    if (!player.onGround) {
      ctx.fillRect(-8, 12, 7, 4);
      ctx.fillRect(2, 9, 7, 4);
    } else {
      ctx.fillRect(-7 + legOffset, 13, 7, 3);
      ctx.fillRect(1 - legOffset, 13, 7, 3);
    }

    // Belt
    ctx.fillStyle = "#222";
    ctx.fillRect(-8, 6, 16, 2);

    ctx.restore();
  }

  function drawParticles() {
    for (var i = 0; i < particles.length; i++) {
      var p = particles[i];
      ctx.globalAlpha = Math.min(1, p.life / 15);
      ctx.fillStyle = p.color;
      ctx.fillRect(p.x - camera.x - p.size/2, p.y - p.size/2, p.size, p.size);
    }
    ctx.globalAlpha = 1;
  }

  function drawHUD() {
    // Score background
    ctx.fillStyle = "rgba(0,0,0,0.5)";
    ctx.fillRect(8, 8, 180, 36);
    ctx.strokeStyle = "#ffd700";
    ctx.lineWidth = 2;
    ctx.strokeRect(8, 8, 180, 36);

    // Coin icon
    ctx.fillStyle = "#ffd700";
    ctx.beginPath();
    ctx.arc(30, 26, 8, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = "#c4a000";
    ctx.beginPath();
    ctx.arc(30, 26, 6, 0, Math.PI * 2);
    ctx.fill();

    // Score text
    ctx.fillStyle = "#fff";
    ctx.font = "bold 20px monospace";
    ctx.textAlign = "left";
    ctx.fillText("x " + score, 46, 32);

    // Remaining coins
    var remaining = 0;
    for (var i = 0; i < coins.length; i++) {
      if (!coins[i].collected) remaining++;
    }
    var totalCoins = coins.length;

    ctx.fillStyle = "rgba(0,0,0,0.5)";
    var text = "Coins: " + (totalCoins - remaining) + "/" + totalCoins;
    var textW = ctx.measureText(text).width + 16;
    ctx.fillRect(W - textW - 8, 8, textW, 36);
    ctx.fillStyle = "#ffd700";
    ctx.fillText(text, W - textW, 32);
  }

  function drawDeathScreen() {
    ctx.fillStyle = "rgba(0,0,0,0.5)";
    ctx.fillRect(0, 0, W, H);

    ctx.fillStyle = "#e44";
    ctx.font = "bold 48px monospace";
    ctx.textAlign = "center";
    ctx.fillText("OOF!", W/2, H/2 - 20);

    ctx.fillStyle = "#fff";
    ctx.font = "20px monospace";
    ctx.fillText("Respawning...", W/2, H/2 + 25);
  }

  function drawWinScreen() {
    ctx.fillStyle = "rgba(0,0,0,0.45)";
    ctx.fillRect(0, 0, W, H);

    ctx.fillStyle = "#ffd700";
    ctx.font = "bold 52px monospace";
    ctx.textAlign = "center";
    ctx.fillText("🏆 YOU WIN! 🏆", W/2, H/2 - 30);

    ctx.fillStyle = "#fff";
    ctx.font = "22px monospace";
    ctx.fillText("Score: " + score, W/2, H/2 + 15);

    ctx.font = "18px monospace";
    ctx.fillStyle = "#aaa";
    var restartHint = winTimer > 90 ? "Press R or Space to play again" : "";
    ctx.fillText(restartHint, W/2, H/2 + 50);

    if (winTimer > 90 && (keys["KeyR"] || keys["Space"])) {
      keys["KeyR"] = false;
      keys["Space"] = false;
      resetGame();
    }
  }

  // --- Draw world (optimized: only visible tiles) ---
  function drawWorld() {
    var startCol = Math.max(0, Math.floor(camera.x / TILE) - 1);
    var endCol = Math.min(WORLD_W, Math.ceil((camera.x + W) / TILE) + 1);

    for (var r = 0; r < WORLD_H; r++) {
      for (var c = startCol; c < endCol; c++) {
        var t = world[r][c];
        if (t === '.') continue;
        var sx = c * TILE - camera.x;
        var sy = r * TILE;
        drawTile(c, r, sx, sy);
      }
    }
  }

  function drawCoins() {
    for (var i = 0; i < coins.length; i++) {
      var c = coins[i];
      if (c.collected) continue;
      var cx = c.x - camera.x;
      if (cx < -20 || cx > W + 20) continue;
      drawCoin(cx, c.y);
    }
  }

  // --- Main render ---
  function draw() {
    drawBg();
    drawWorld();
    drawFlag();
    drawCoins();
    drawPlayer();
    drawParticles();
    drawHUD();

    if (gameState === "dead") drawDeathScreen();
    if (gameState === "won") drawWinScreen();
  }

  // --- Game loop ---
  var lastTime = 0;
  var fixedDt = 1000 / 60;
  var accumulator = 0;

  function gameLoop(timestamp) {
    if (!lastTime) lastTime = timestamp;
    var dt = timestamp - lastTime;
    lastTime = timestamp;
    if (dt > 100) dt = 100; // prevent huge jumps

    accumulator += dt;

    while (accumulator >= fixedDt) {
      update();
      accumulator -= fixedDt;
    }

    draw();
    requestAnimationFrame(gameLoop);
  }

  // --- Start ---
  resetPlayer();
  requestAnimationFrame(gameLoop);

})();
</script>
</body>
</html>