oneshotlm
Compare all models on this prompt →

Tetris · kimi-k2.5

Build a playable Tetris on a canvas with all seven tetrominoes, rotation, line clears, increasing speed, a next-piece preview, and score.

⚠ no runnable output

This one-shot run produced no runnable artifact.

agent produced no index.html

The transcript below shows what the model did — read it to see why the one shot fell short.

transcript · 2 messages
user

Build a playable Tetris on a canvas with all seven tetrominoes, rotation, line clears, increasing speed, a next-piece preview, and score.

assistant
thinking
The user wants a complete, playable Tetris game in a single `index.html` file. Let's break down the requirements:

1. **Canvas-based**: Use HTML5 Canvas for rendering.
2. **Seven tetrominoes**: I, O, T, S, Z, J, L - all standard shapes.
3. **Rotation**: Ability to rotate pieces (with wall kicks ideally, but basic rotation is acceptable).
4. **Line clears**: When a horizontal line is filled, it should clear and everything above drops down.
5. **Increasing speed**: Game speed should increase as lines are cleared or score increases.
6. **Next-piece preview**: Show what piece is coming next.
7. **Score**: Track and display score (and ideally level/line count).

Additional considerations:
- Controls: Left/Right/Down for movement, Up for rotation, Space for hard drop (nice to have).
- Game loop: Use `requestAnimationFrame`.
- Collision detection: Check boundaries and locked pieces.
- Game over: Detection and restart capability.

Architecture:
- Single HTML file with inline CSS and JS.
- No external resources/CDNs.

Let's design the code structure:

**CSS**: 
- Dark theme, centered canvas, some UI styling for score/next piece.

**HTML**:
- Canvas for game board.
- Div for next piece preview (could be another small canvas or just drawn differently, but a second canvas is cleaner).
- Div for score/level display.
- Maybe instructions.

**JavaScript**:
- Constants: Board dimensions (10x20 standard), block size, colors for each tetromino.
- Tetromino definitions: 7 shapes with their rotation states or rotation matrices.
- State variables: board (2D array), current piece, next piece, score, lines, level, game speed, game over flag.
- Functions:
  - `createPiece(type)`: Generate a piece object.
  - `rotate(piece)`: Rotate matrix.
  - `isValidMove(piece, x, y)`: Collision detection.
  - `lockPiece()`: Add to board, check lines.
  - `clearLines()`: Remove full rows, shift down, update score.
  - `draw()`: Render board and current piece.
  - `drawNext()`: Render next piece in preview area.
  - `update()`: Game loop, handle automatic falling.
  - `move(dir)`, `rotate()`, `hardDrop()`: Input handlers.
  - `reset()`: Initialize/Restart game.

Tetromino representations (4x4 grid typically):
- I: `[ [0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0] ]` - cyan
- O: `[ [1,1], [1,1] ]` - yellow
- T: `[ [0,1,0], [1,1,1], [0,0,0] ]` - purple
- S: `[ [0,1,1], [1,1,0], [0,0,0] ]` - green
- Z: `[ [1,1,0], [0,1,1], [0,0,0] ]` - red
- J: `[ [1,0,0], [1,1,1], [0,0,0] ]` - blue
- L: `[ [0,0,1], [1,1,1], [0,0,0] ]` - orange

Rotation logic:
Simple rotation: transpose then reverse rows (or reverse rows then transpose depending on direction).
Standard rotation: `matrix[0].map((val, index) => matrix.map(row => row[index]).reverse())` for clockwise.

Wall kicks: For simplicity, basic collision check with 0,0 offset is fine. If rotation fails at current position, try shifting left/right/up slightly? To keep it simple but playable, I'll implement basic wall kick: if rotation collides, try moving left 1, right 1, up 1 (in that order). Actually, Super Rotation System (SRS) is complex, but simple kicks are good enough.

Speed calculation:
`dropInterval = 1000 / (level * 0.5 + 1)` or similar, capped at some minimum.
Or: `dropInterval = Math.max(100, 1000 - (level - 1) * 100)`.

Scoring:
- Single: 100 * level
- Double: 300 * level
- Triple: 500 * level
- Tetris (4): 800 * level
- Soft drop: 1 per cell
- Hard drop: 2 per cell

Let's write the code.

Structure:
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Tetris</title>
  <style>
    /* Dark theme, flexbox centering, retro font */
  </style>
</head>
<body>
  <div id="game-container">
    <canvas id="game-board"></canvas>
    <div id="side-panel">
      <div>Next:</div>
      <canvas id="next-piece"></canvas>
      <div>Score: <span id="score">0</span></div>
      <div>Lines: <span id="lines">0</span></div>
      <div>Level: <span id="level">1</span></div>
      <div id="controls">Controls: Arrows to move, Up to rotate, Space to drop</div>
    </div>
  </div>
  <script>
    // All JS code here
  </script>
</body>
</html>
```

Canvas sizing:
- Board: 10 cols x 20 rows. If block size is 30px: 300x600.
- Next piece: 4x4 blocks: 120x120 or scaled.

JS Implementation details:

1. **Constants**:
   ```javascript
   const COLS = 10;
   const ROWS = 20;
   const BLOCK_SIZE = 30;
   const COLORS = [
     null,
     '#00f0f0', // I - Cyan
     '#f0f000', // O - Yellow
     '#a000f0', // T - Purple
     '#00f000', // S - Green
     '#f00000', // Z - Red
     '#0000f0', // J - Blue
     '#f0a000'  // L - Orange
   ];
   const SHAPES = [
     null,
     [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], // I
     [[1,1],[1,1]], // O
     [[0,1,0],[1,1,1],[0,0,0]], // T
     [[0,1,1],[1,1,0],[0,0,0]], // S
     [[1,1,0],[0,1,1],[0,0,0]], // Z
     [[1,0,0],[1,1,1],[0,0,0]], // J
     [[0,0,1],[1,1,1],[0,0,0]]  // L
   ];
   ```

2. **Piece Class/Object**:
   ```javascript
   function newPiece(type) {
     return {
       matrix: SHAPES[type],
       x: (COLS / 2 | 0) - ((SHAPES[type][0].length / 2) | 0),
       y: 0,
       color: type
     };
   }
   ```

3. **Game State**:
   - `arena` (2D array, 0 means empty, other numbers are color indices)
   - `player` (current piece)
   - `nextType` (1-7)
   - `score`, `lines`, `level`, `dropCounter`, `dropInterval`, `lastTime`

4. **Functions**:
   - `createMatrix(w, h)`: Returns filled with 0s.
   - `drawMatrix(matrix, offset, ctx)`: Draws to a context.
   - `draw()`: Clears board, draws arena, draws player.
   - `merge(arena, player)`: Copies player matrix into arena at player x,y.
   - `rotate(matrix, dir)`: Rotate logic.
   - `playerRotate(dir)`: Rotate with collision/wall kick.
   - `playerDrop()`: Move down, if collision lock and spawn new.
   - `playerMove(dir)`: Move left/right.
   - `hardDrop()`: While valid, move down, then lock.
   - `lock()`: Merge, check lines, reset player, check game over.
   - `arenaSweep()`: Clear lines, update score.
   - `update(time = 0)`: Game loop.

5. **Collision Detection**:
   ```javascript
   function collide(arena, player) {
     const [m, o] = [player.matrix, player];
     for (let y = 0; y < m.length; ++y) {
       for (let x = 0; x < m[y].length; ++x) {
         if (m[y][x] !== 0 &&
             (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
           return true;
         }
       }
     }
     return false;
   }
   ```

6. **Input**:
   Event listeners for keydown.
   - ArrowLeft: playerMove(-1)
   - ArrowRight: playerMove(1)
   - ArrowDown: playerDrop() (and maybe add soft drop score)
   - ArrowUp: playerRotate(1)
   - Space: hardDrop()

7. **Randomization**:
   Simple `Math.random() * 7 + 1 | 0`. Or bag system? 7-bag is better but random is fine for this scope. Let's do simple random to avoid complexity, but ensure we don't get same piece twice? No, simple random is acceptable for a basic implementation. Actually, standard Tetris uses a "bag" of 7 pieces. I'll use a simple bag system: generate array [1..7], shuffle, pop until empty then refill. This ensures fair distribution.

8. **Scoring**:
   Standard Nintendo scoring system lines: 40, 100, 300, 1200 for 1-4 lines multiplied by (level + 1). Let's use that or simplified.

9. **Speed**:
   `dropInterval = Math.max(100, 1000 - (level - 1) * 100);` Every 10 lines, level up.

10. **Reset**:
    Clear arena, reset score, reset player with next piece, generate new next piece.

Let's write the actual code carefully.

Styling:
- Background: #111 or dark gray.
- Canvas border: white or light gray.
- Side panel next to canvas.
- Use `display: flex` on container.

Wall Kick implementation:
When rotating, if collides, try shifting x by -1, 1, -2, 2 (and maybe y by -1). Simple version: try x-1, x+1, x-2, x+2. If any works, accept rotation and shift.

Rotation function:
```javascript
function rotate(matrix, dir) {
  for (let y = 0; y < matrix.length; ++y) {
    for (let x = 0; x < y; ++x) {
      [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
    }
  }
  if (dir > 0) {
    matrix.forEach(row => row.reverse());
  } else {
    matrix.reverse();
  }
}
```

Wait, that's for 90 deg clockwise. Actually:
Transpose (swap rows/cols), then reverse each row for clockwise.
Or reverse each row then transpose for counter-clockwise.
The code above swaps symmetric elements (transpose), then if dir>0 reverses rows (which is actually columns originally... wait).
Standard clockwise rotation:
1. Transpose matrix.
2. Reverse each row.
My code above:
First loop transposes.
Then if dir > 0, reverses each row (`row.reverse()`).
This is correct for clockwise.
For counter-clockwise:
1. Reverse each row.
2. Transpose.
Or Transpose then reverse column order.
But for simplicity, I'll only implement one direction (clockwise) with Up arrow.

Actually, for Tetris, both directions are nice but one is sufficient. I'll stick to clockwise only.

Piece spawn position:
Center top. `x = (arena[0].length / 2 | 0) - (matrix[0].length / 2 | 0)`, `y = 0`.

Game Over check:
If collide immediately on spawn.

Colors:
Use the standard Tetris colors.

Drawing:
- FillRect with main color.
- StrokeRect with lighter/darker for 3D effect (optional but nice).

Next piece canvas:
Separate canvas, size 4x4 blocks (or scaled).
Draw the next piece matrix centered there.

Let's assemble the final code.

Refining the structure:

HTML:
- Container div holding game canvas and side div.
- Side div holds next canvas, score lines, etc.

CSS:
- Use a nice monospace font.
- Dark background #111, text #eee.
- Canvas border 2px solid #555.
- Side panel padding.

JS:
- Setup canvases and contexts.
- Create arena.
- Create player object.
- Variables for next piece type, bag, score, lines, level, dropCounter, lastTime.
- Functions: collide, drawMatrix, draw, merge, rotate, playerRotate, playerDrop, playerMove, hardDrop, lock, arenaSweep, update, reset, randomPiece.
- Event listeners.

randomPiece():
```javascript
let bag = [];
function randomPiece() {
  if (bag.length === 0) {
    bag = [1, 2, 3, 4, 5, 6, 7];
    // Shuffle
    for (let i = bag.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [bag[i], bag[j]] = [bag[j], bag[i]];
    }
  }
  return bag.pop();
}
```

playerReset():
```javascript
function playerReset() {
  const type = nextType !== null ? nextType : randomPiece();
  nextType = randomPiece();
  player.matrix = SHAPES[type];
  player.color = type;
  player.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);
  player.y = 0;
  
  // Draw next
  drawNext();
  
  if (collide(arena, player)) {
    // Game Over
    arena.forEach(row => row.fill(0));
    score = 0;
    lines = 0;
    level = 1;
    updateScore();
    dropInterval = 1000;
  }
}
```

arenaSweep():
```javascript
function arenaSweep() {
  let rowCount = 0;
  outer: for (let y = arena.length - 1; y > 0; --y) {
    for (let x = 0; x < arena[y].length; ++x) {
      if (arena[y][x] === 0) {
        continue outer;
      }
    }
    const row = arena.splice(y, 1)[0].fill(0);
    arena.unshift(row);
    ++y;
    rowCount++;
  }
  
  if (rowCount > 0) {
    // Scoring
    const lineScores = [0, 40, 100, 300, 1200];
    score += lineScores[rowCount] * level;
    lines += rowCount;
    level = Math.floor(lines / 10) + 1;
    dropInterval = Math.max(100, 1000 - (level - 1) * 100);
    updateScore();
  }
}
```

updateScore():
Updates DOM elements.

draw():
```javascript
function draw() {
  // Clear game board
  ctx.fillStyle = '#000';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  drawMatrix(arena, {x: 0, y: 0}, ctx);
  drawMatrix(player.matrix, {x: player.x, y: player.y}, ctx, player.color);
}
```

drawMatrix():
```javascript
function drawMatrix(matrix, offset, context, colorOverride) {
  matrix.forEach((row, y) => {
    row.forEach((value, x) => {
      if (value !== 0) {
        context.fillStyle = colorOverride ? COLORS[colorOverride] : COLORS[value];
        context.fillRect((x + offset.x) * BLOCK_SIZE, (y + offset.y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
        context.lineWidth = 2;
        context.strokeStyle = '#000';
        context.strokeRect((x + offset.x) * BLOCK_SIZE, (y + offset.y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
      }
    });
  });
}
```

update(time):
```javascript
function update(time = 0) {
  const deltaTime = time - lastTime;
  lastTime = time;
  
  dropCounter += deltaTime;
  if (dropCounter > dropInterval) {
    playerDrop();
  }
  
  draw();
  requestAnimationFrame(update);
}
```

Controls:
Prevent default for arrow keys to stop scrolling.

Hard drop:
```javascript
function hardDrop() {
  while (!collide(arena, player)) {
    player.y++;
  }
  player.y--;
  lock();
}
```

Soft drop score:
When ArrowDown pressed, drop by 1 and add 1 to score (optional).

Let's finalize the code in index.html.

Check canvas sizes:
- Game canvas: COLS * BLOCK_SIZE = 300, ROWS * BLOCK_SIZE = 600.
- Next canvas: 4 * BLOCK_SIZE = 120, 4 * BLOCK_SIZE = 120.

One detail: In `playerRotate`, we need to clone the matrix or handle rotation properly without mutating if we need to revert. Actually, we rotate, check collision, if collides rotate back (or clone before).

```javascript
function playerRotate(dir) {
  const pos = player.x;
  let offset = 1;
  rotate(player.matrix, dir);
  while (collide(arena, player)) {
    player.x += offset;
    offset = -(offset + (offset > 0 ? 1 : -1));
    if (offset > player.matrix[0].length) {
      rotate(player.matrix, -dir);
      player.x = pos;
      return;
    }
  }
}
```

This attempts wall kick by shifting horizontally.

Actually, `rotate` modifies in place. So if we fail all kicks, we rotate back.

I think this is a complete plan. I will now write the file.

I'll create a complete, self-contained Tetris game in a single HTML file with canvas rendering, all seven tetrominoes, rotation with wall kicks, line clearing, increasing speed, next-piece preview, and scoring. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tetris</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #111; color: #eee; font-family: 'Courier New', monospace; display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; } #container { display: flex; gap: 20px; align-items: flex-start; } #game-board { border: 3px solid #555; background: #000; box-shadow: 0 0 20px rgba(0, 255, 255, 0.2); } #side-panel { display: flex; flex-direction: column; gap: 15px; padding: 10px; } .panel-box { background: #222; border: 2px solid #444; padding: 15px; border-radius: 4px; min-width: 140px; } .label { font-size: 12px; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 5px; } .value { font-size: 20px; font-weight: bold; color: #0ff; } #next-canvas { background: #000; border: 2px solid #444; display: block; margin: 0 auto; } #controls { font-size: 11px; color: #666; line-height: 1.5; max-width: 140px; } #game-over { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); border: 2px solid #f00; padding: 30px; text-align: center; display: none; z-index: 10; } #game-over h2 { color: #f00; margin-bottom: 10px; font-size: 24px; } #game-over button { margin-top: 15px; padding: 10px 20px; background: #222; border: 2px solid #f00; color: #f00; cursor: pointer; font-family: inherit; font-weight: bold; } #game-over button:hover { background: #f00; color: #000; } </style> </head> <body> <div id="container"> <canvas id="game-board" width="300" height="600"></canvas> <div id="side-panel"> <div class="panel-box"> <div class="label">Next</div> <canvas id="next-canvas" width="120" height="120"></canvas> </div> <div class="panel-box"> <div class="label">Score</div> <div class="value" id="score">0</div> </div> <div class="panel-box"> <div class="label">Lines</div> <div class="value" id="lines">0</div> </div> <div class="panel-box"> <div class="label">Level</div> <div class="value" id="level">1</div> </div> <div class="panel-box" id="controls"> <strong>Controls:</strong><br> ← → Move<br> ↓ Soft Drop<br> ↑ Rotate<br> Space Hard Drop<br> P Pause </div> </div> </div> <div id="game-over"> <h2>GAME OVER</h2> <div>Final Score: <span id="final-score">0</span></div> <button onclick="resetGame()">New Game</button> </div> <script> // Game Constants const COLS = 10; const ROWS = 20; const BLOCK_SIZE = 30; const PREVIEW_BLOCK_SIZE = 25; // Tetromino colors (I, O, T, S, Z, J, L) const COLORS = [ null, '#00f0f0', // I - Cyan '#f0f000', // O - Yellow '#a000f0', // T - Purple '#00f000', // S - Green '#f00000', // Z - Red '#0000f0', // J - Blue '#f0a000' // L - Orange ]; // Tetromino shapes (4x4 matrices, 1 = filled, 0 = empty) const SHAPES = [ null, [ // I [0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0] ], [ // O [1,1], [1,1] ], [ // T [0,1,0], [1,1,1], [0,0,0] ], [ // S [0,1,1], [1,1,0], [0,0,0] ], [ // Z [1,1,0], [0,1,1], [0,0,0] ], [ // J [1,0,0], [1,1,1], [0,0,0] ], [ // L [0,0,1], [1,1,1], [0,0,0] ] ]; // Canvas setup const canvas = document.getElementById('game-board'); const ctx = canvas.getContext('2d'); const nextCanvas = document.getElementById('next-canvas'); const nextCtx = nextCanvas.getContext('2d'); // Game state let arena = createMatrix(COLS, ROWS); let player = { matrix: null, pos: {x: 0, y: 0}, color: 0 }; let nextPiece = null; let bag = []; let score = 0; let lines = 0; let level = 1; let dropCounter = 0; let dropInterval = 1000; let lastTime = 0; let gameOver = false; let paused = false; // Create empty matrix function createMatrix(w, h) { const matrix = []; for (let y = 0; y < h; y++) { matrix.push(new Array(w).fill(0)); } return matrix; } // Random piece generator (7-bag system for fairness) function randomPiece() { if (bag.length === 0) { bag = [1, 2, 3, 4, 5, 6, 7]; // Fisher-Yates shuffle for (let i = bag.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [bag[i], bag[j]] = [bag[j], bag[i]]; } } return bag.pop(); } // Spawn new piece function playerReset() { if (nextPiece === null) { nextPiece = randomPiece(); } const type = nextPiece; nextPiece = randomPiece(); player.matrix = SHAPES[type].map(row => [...row]); player.color = type; player.pos.y = 0; player.pos.x = Math.floor(COLS / 2) - Math.floor(player.matrix[0].length / 2); drawNext(); // Check immediate collision (Game Over) if (collide(arena, player)) { gameOver = true; document.getElementById('final-score').textContent = score; document.getElementById('game-over').style.display = 'block'; } } // Draw next piece preview function drawNext() { nextCtx.fillStyle = '#000'; nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height); if (nextPiece !== null) { const matrix = SHAPES[nextPiece]; const offsetX = (nextCanvas.width / PREVIEW_BLOCK_SIZE - matrix[0].length) / 2; const offsetY = (nextCanvas.height / PREVIEW_BLOCK_SIZE - matrix.length) / 2; drawMatrix(matrix, {x: offsetX, y: offsetY}, nextCtx, nextPiece, PREVIEW_BLOCK_SIZE); } } // Collision detection function collide(arena, player) { const [m, o] = [player.matrix, player.pos]; for (let y = 0; y < m.length; y++) { for (let x = 0; x < m[y].length; x++) { if (m[y][x] !== 0 && (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) { return true; } } } return false; } // Merge piece into arena function merge(arena, player) { player.matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { arena[y + player.pos.y][x + player.pos.x] = player.color; } }); }); } // Rotate matrix (clockwise) function rotate(matrix) { for (let y = 0; y < matrix.length; y++) { for (let x = 0; x < y; x++) { [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]]; } } matrix.forEach(row => row.reverse()); } // Player rotation with wall kicks function playerRotate() { const pos = player.pos.x; let offset = 1; rotate(player.matrix); // Wall kick: try shifting horizontally if rotation causes collision while (collide(arena, player)) { player.pos.x += offset; offset = -(offset + (offset > 0 ? 1 : -1)); if (Math.abs(offset) > player.matrix[0].length) { // Revert rotation if no valid position found rotate(player.matrix); rotate(player.matrix); rotate(player.matrix); player.pos.x = pos; return; } } } // Move player function playerMove(dir) { player.pos.x += dir; if (collide(arena, player)) { player.pos.x -= dir; } } // Drop piece one row function playerDrop() { player.pos.y++; if (collide(arena, player)) { player.pos.y--; lock(); } dropCounter = 0; } // Hard drop (instant) function hardDrop() { while (!collide(arena, player)) { player.pos.y++; score += 2; // 2 points per cell for hard drop } player.pos.y--; lock(); updateScore(); } // Lock piece and clear lines function lock() { merge(arena, player); arenaSweep(); playerReset(); } // Clear completed lines function arenaSweep() { let rowCount = 0; outer: for (let y = arena.length - 1; y > 0; y--) { for (let x = 0; x < arena[y].length; x++) { if (arena[y][x] === 0) { continue outer; } } const row = arena.splice(y, 1)[0].fill(0); arena.unshift(row); y++; rowCount++; } if (rowCount > 0) { // Standard Nintendo scoring system (40, 100, 300, 1200) × level const lineScores = [0, 40, 100, 300, 1200]; score += lineScores[rowCount] * level; lines += rowCount; level = Math.floor(lines / 10) + 1; // Increase speed (cap at 100ms minimum) dropInterval = Math.max(100, 1000 - (level - 1) * 100); updateScore(); } } // Update UI function updateScore() { document.getElementById('score').textContent = score; document.getElementById('lines').textContent = lines; document.getElementById('level').textContent = level; } // Draw matrix (arena or piece) function drawMatrix(matrix, offset, context, colorOverride = null, blockSize = BLOCK_SIZE) { matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { const color = colorOverride ? COLORS[colorOverride] : COLORS[value]; // Main block context.fillStyle = color; context.fillRect( (x + offset.x) * blockSize, (y + offset.y) * blockSize, blockSize, blockSize ); // Highlight (3D effect) context.fillStyle = 'rgba(255, 255, 255, 0.3)'; context.fillRect( (x + offset.x) * blockSize, (y + offset.y) * blockSize, blockSize, 3 ); context.fillRect( (x + offset.x) * blockSize, (y + offset.y) * blockSize, 3, blockSize ); // Shadow context.fillStyle = 'rgba(0, 0, 0, 0.3)'; context.fillRect( (x + offset.x) * blockSize, (y + offset.y) * blockSize + blockSize - 3, blockSize, 3 ); context.fillRect( (x + offset.x) * blockSize + blockSize - 3, (y + offset.y) * blockSize, 3, blockSize ); // Border context.strokeStyle = '#000'; context.lineWidth = 1; context.strokeRect( (x + offset.x) * blockSize, (y + offset.y) * blockSize, blockSize, blockSize ); } }); }); } // Main draw function function draw() { // Clear canvas ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw grid lines (subtle) ctx.strokeStyle = '#111'; ctx.lineWidth = 1; for (let x = 0; x <= COLS; x++) { ctx.beginPath(); ctx.moveTo(x * BLOCK_SIZE, 0); ctx.lineTo(x * BLOCK_SIZE, canvas.height); ctx.stroke(); } for (let y = 0; y <= ROWS; y++) { ctx.beginPath(); ctx.moveTo(0, y * BLOCK_SIZE); ctx.lineTo(canvas.width, y * BLOCK_SIZE); ctx.stroke(); } // Draw locked pieces drawMatrix(arena, {x: 0, y: 0}, ctx); // Draw current piece drawMatrix(player.matrix, player.pos, ctx, player.color); // Draw ghost piece (where piece will land) if (!gameOver) { const ghostPos = {x: player.pos.x, y: player.pos.y}; while (!collide(arena, {matrix: player.matrix, pos: ghostPos})) { ghostPos.y++; } ghostPos.y--; ctx.globalAlpha = 0.3; drawMatrix(player.matrix, ghostPos, ctx, player.color); ctx.globalAlpha = 1.0; } } // Game loop function update(time = 0) { if (gameOver || paused) return; const deltaTime = time - lastTime; lastTime = time; dropCounter += deltaTime; if (dropCounter > dropInterval) { playerDrop(); } draw(); requestAnimationFrame(update); } // Input handling document.addEventListener('keydown', event => { if (gameOver) return; if (event.key === 'p' || event.key === 'P') { paused = !paused; if (!paused) { lastTime = performance.now(); update(); } return; } if (paused) return; switch(event.key) { case 'ArrowLeft': event.preventDefault(); playerMove(-1); break; case 'ArrowRight': event.preventDefault(); playerMove(1); break; case 'ArrowDown': event.preventDefault(); playerDrop(); score += 1; // Soft drop bonus updateScore(); break; case 'ArrowUp': event.preventDefault(); playerRotate(); break; case ' ': event.preventDefault(); hardDrop(); break; } }); // Reset game function resetGame() { arena = createMatrix(COLS, ROWS); score = 0; lines = 0; level = 1; dropInterval = 1000; dropCounter = 0; gameOver = false; paused = false; bag = []; nextPiece = null; document.getElementById('game-over').style.display = 'none'; updateScore(); playerReset(); lastTime = performance.now(); update(); } // Initialize playerReset(); update(); </script> </body> </html> ```

TAKE IT TO CODE

This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.

RUN

modelkimi-k2.5
vendorMoonshot · Kimi
harnesspi-agent
generated2026-07-22

METRICS

runtime
tokens in173
tokens out8.3k
cached1.0k
cost / run$0.0171