← back to Model Arena

data/artifacts/04692f72ef1d/gpt.html

611 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tetris Single File</title>
<style>
  body {
    background:#111;
    color:#eee;
    font-family:Arial,Helvetica,sans-serif;
    display:flex;
    justify-content:center;
    align-items:flex-start;
    margin:0;
    padding:10px;
  }
  #game-container {
    display:flex;
    gap:15px;
  }
  #side-panel {
    display:flex;
    flex-direction:column;
    gap:10px;
    font-size:14px;
  }
  canvas {
    background:#000;
    border:2px solid #444;
    image-rendering:pixelated;
  }
  #info {
    border:1px solid #444;
    padding:6px 8px;
    background:#181818;
  }
  .label {
    color:#aaa;
  }
  .value {
    font-weight:bold;
    color:#fff;
  }
  #controls {
    font-size:12px;
    color:#ccc;
  }
  button {
    padding:4px 8px;
    background:#333;
    border:1px solid #666;
    color:#eee;
    cursor:pointer;
  }
  button:hover {
    background:#444;
  }
</style>
</head>
<body>
<div id="game-container">
  <canvas id="game" width="200" height="400"></canvas>
  <div id="side-panel">
    <canvas id="next" width="80" height="80"></canvas>
    <div id="info">
      <div><span class="label">Score:</span> <span class="value" id="score">0</span></div>
      <div><span class="label">Lines:</span> <span class="value" id="lines">0</span></div>
      <div><span class="label">Level:</span> <span class="value" id="level">1</span></div>
    </div>
    <div id="controls">
      <div><b>Controls</b></div>
      <div>← → : Move</div>
      <div>↑ : Rotate</div>
      <div>↓ : Soft drop</div>
      <div>Space : Hard drop</div>
      <div>P : Pause/Resume</div>
      <div>R : Restart</div>
    </div>
    <button id="restartBtn">Restart</button>
  </div>
</div>

<script>
(function() {
  const COLS = 10;
  const ROWS = 20;
  const BLOCK = 20;
  const CANVAS_W = COLS*BLOCK;
  const CANVAS_H = ROWS*BLOCK;

  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const nextCanvas = document.getElementById('next');
  const nctx = nextCanvas.getContext('2d');

  canvas.width = CANVAS_W;
  canvas.height = CANVAS_H;

  const scoreEl = document.getElementById('score');
  const linesEl = document.getElementById('lines');
  const levelEl = document.getElementById('level');
  const restartBtn = document.getElementById('restartBtn');

  const COLORS = {
    0: "#000000",
    I: "#00f0f0",
    J: "#0000f0",
    L: "#f0a000",
    O: "#f0f000",
    S: "#00f000",
    T: "#a000f0",
    Z: "#f00000"
  };

  // Tetromino shapes with rotation states (4x4 matrix each)
  const SHAPES = {
    I: [
      [
        [0,0,0,0],
        [1,1,1,1],
        [0,0,0,0],
        [0,0,0,0]
      ],
      [
        [0,0,1,0],
        [0,0,1,0],
        [0,0,1,0],
        [0,0,1,0]
      ],
      [
        [0,0,0,0],
        [0,0,0,0],
        [1,1,1,1],
        [0,0,0,0]
      ],
      [
        [0,1,0,0],
        [0,1,0,0],
        [0,1,0,0],
        [0,1,0,0]
      ]
    ],
    J: [
      [
        [1,0,0],
        [1,1,1],
        [0,0,0]
      ],
      [
        [0,1,1],
        [0,1,0],
        [0,1,0]
      ],
      [
        [0,0,0],
        [1,1,1],
        [0,0,1]
      ],
      [
        [0,1,0],
        [0,1,0],
        [1,1,0]
      ]
    ],
    L: [
      [
        [0,0,1],
        [1,1,1],
        [0,0,0]
      ],
      [
        [0,1,0],
        [0,1,0],
        [0,1,1]
      ],
      [
        [0,0,0],
        [1,1,1],
        [1,0,0]
      ],
      [
        [1,1,0],
        [0,1,0],
        [0,1,0]
      ]
    ],
    O: [
      [
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ],
      [
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ],
      [
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ],
      [
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
    ],
    S: [
      [
        [0,1,1],
        [1,1,0],
        [0,0,0]
      ],
      [
        [0,1,0],
        [0,1,1],
        [0,0,1]
      ],
      [
        [0,0,0],
        [0,1,1],
        [1,1,0]
      ],
      [
        [1,0,0],
        [1,1,0],
        [0,1,0]
      ]
    ],
    T: [
      [
        [0,1,0],
        [1,1,1],
        [0,0,0]
      ],
      [
        [0,1,0],
        [0,1,1],
        [0,1,0]
      ],
      [
        [0,0,0],
        [1,1,1],
        [0,1,0]
      ],
      [
        [0,1,0],
        [1,1,0],
        [0,1,0]
      ]
    ],
    Z: [
      [
        [1,1,0],
        [0,1,1],
        [0,0,0]
      ],
      [
        [0,0,1],
        [0,1,1],
        [0,1,0]
      ],
      [
        [0,0,0],
        [1,1,0],
        [0,1,1]
      ],
      [
        [0,1,0],
        [1,1,0],
        [1,0,0]
      ]
    ]
  };

  const TETROMINOES = Object.keys(SHAPES);

  let grid, current, nextPiece, dropStart, dropInterval;
  let score, lines, level;
  let gameOver, paused;
  let requestId;

  function resetGrid() {
    grid = [];
    for (let r = 0; r < ROWS; r++) {
      const row = [];
      for (let c = 0; c < COLS; c++) {
        row.push(0);
      }
      grid.push(row);
    }
  }

  function randomPiece() {
    const type = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)];
    return {
      type,
      rotation: 0,
      x: 3,
      y: -2,
      shape: SHAPES[type][0]
    };
  }

  function rotatePiece(p, dir=1) {
    const len = SHAPES[p.type].length;
    const newRot = (p.rotation + dir + len) % len;
    return {
      ...p,
      rotation: newRot,
      shape: SHAPES[p.type][newRot]
    };
  }

  function collide(p, offX = 0, offY = 0) {
    const m = p.shape;
    for (let r = 0; r < m.length; r++) {
      for (let c = 0; c < m[r].length; c++) {
        if (!m[r][c]) continue;
        const nx = p.x + c + offX;
        const ny = p.y + r + offY;
        if (ny >= ROWS || nx < 0 || nx >= COLS) return true;
        if (ny >= 0 && grid[ny][nx]) return true;
      }
    }
    return false;
  }

  function merge(p) {
    const m = p.shape;
    for (let r = 0; r < m.length; r++) {
      for (let c = 0; c < m[r].length; c++) {
        if (!m[r][c]) continue;
        const nx = p.x + c;
        const ny = p.y + r;
        if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) {
          grid[ny][nx] = p.type;
        }
      }
    }
  }

  function clearLines() {
    let cleared = 0;
    outer: for (let r = ROWS - 1; r >= 0; r--) {
      for (let c = 0; c < COLS; c++) {
        if (!grid[r][c]) continue outer;
      }
      grid.splice(r, 1);
      grid.unshift(new Array(COLS).fill(0));
      cleared++;
      r++;
    }
    if (cleared > 0) {
      lines += cleared;
      const lineScores = [0, 40, 100, 300, 1200];
      score += lineScores[cleared] * level;
      const newLevel = 1 + Math.floor(lines / 10);
      if (newLevel !== level) {
        level = newLevel;
        updateDropInterval();
      }
      updateHUD();
    }
  }

  function updateHUD() {
    scoreEl.textContent = score;
    linesEl.textContent = lines;
    levelEl.textContent = level;
  }

  function drawBlock(x, y, color, context = ctx, size = BLOCK) {
    context.fillStyle = color;
    context.fillRect(x * size, y * size, size, size);
    context.strokeStyle = "#222";
    context.lineWidth = 1;
    context.strokeRect(x * size, y * size, size, size);
  }

  function drawGrid() {
    ctx.fillStyle = "#000";
    ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
    for (let r = 0; r < ROWS; r++) {
      for (let c = 0; c < COLS; c++) {
        const cell = grid[r][c];
        if (cell) {
          drawBlock(c, r, COLORS[cell]);
        }
      }
    }
  }

  function drawPiece(p, ghost=false) {
    const m = p.shape;
    let gy = p.y;
    if (ghost) {
      while (!collide(p, 0, gy - p.y + 1)) gy++;
    }
    for (let r = 0; r < m.length; r++) {
      for (let c = 0; c < m[r].length; c++) {
        if (!m[r][c]) continue;
        const nx = p.x + c;
        const ny = (ghost ? gy : p.y) + r;
        if (ny >= 0) {
          const baseColor = COLORS[p.type];
          let color = baseColor;
          if (ghost) {
            // simple ghost effect: darker
            color = shadeColor(baseColor, -50);
          }
          drawBlock(nx, ny, color);
        }
      }
    }
  }

  function shadeColor(col, amt) {
    const num = parseInt(col.slice(1), 16);
    let r = (num >> 16) + amt;
    let g = ((num >> 8) & 0x00FF) + amt;
    let b = (num & 0x0000FF) + amt;
    r = Math.max(Math.min(255, r), 0);
    g = Math.max(Math.min(255, g), 0);
    b = Math.max(Math.min(255, b), 0);
    return "#" + (r<<16 | g<<8 | b).toString(16).padStart(6,'0');
  }

  function drawNext() {
    nctx.fillStyle = "#000";
    nctx.fillRect(0,0,nextCanvas.width,nextCanvas.height);
    const p = nextPiece;
    const m = SHAPES[p.type][0];
    const size = 16;
    const cols = m[0].length;
    const rows = m.length;
    const offsetX = Math.floor((nextCanvas.width/size - cols)/2);
    const offsetY = Math.floor((nextCanvas.height/size - rows)/2);
    for (let r = 0; r < m.length; r++) {
      for (let c = 0; c < m[r].length; c++) {
        if (!m[r][c]) continue;
        drawBlock(offsetX + c, offsetY + r, COLORS[p.type], nctx, size);
      }
    }
  }

  function draw() {
    drawGrid();
    if (current) {
      drawPiece(current, true);
      drawPiece(current, false);
    }
    if (gameOver) {
      ctx.fillStyle = "rgba(0,0,0,0.7)";
      ctx.fillRect(0, CANVAS_H/2 - 40, CANVAS_W, 80);
      ctx.fillStyle = "#fff";
      ctx.font = "bold 20px Arial";
      ctx.textAlign = "center";
      ctx.fillText("GAME OVER", CANVAS_W/2, CANVAS_H/2 - 5);
      ctx.font = "14px Arial";
      ctx.fillText("Press R to restart", CANVAS_W/2, CANVAS_H/2 + 18);
    } else if (paused) {
      ctx.fillStyle = "rgba(0,0,0,0.7)";
      ctx.fillRect(0, CANVAS_H/2 - 30, CANVAS_W, 60);
      ctx.fillStyle = "#fff";
      ctx.font = "bold 18px Arial";
      ctx.textAlign = "center";
      ctx.fillText("PAUSED", CANVAS_W/2, CANVAS_H/2);
    }
  }

  function updateDropInterval() {
    // Simple speed curve: min 80ms
    dropInterval = Math.max(80, 800 - (level-1)*70);
  }

  function newPiece() {
    current = nextPiece || randomPiece();
    nextPiece = randomPiece();
    drawNext();
    dropStart = performance.now();
    if (collide(current, 0, 0)) {
      gameOver = true;
    }
  }

  function hardDrop() {
    if (gameOver || paused) return;
    while (!collide(current, 0, 1)) {
      current.y++;
      score += 1;
    }
    lockPiece();
  }

  function lockPiece() {
    merge(current);
    clearLines();
    newPiece();
  }

  function move(dx, dy) {
    if (gameOver || paused) return;
    if (!collide(current, dx, dy)) {
      current.x += dx;
      current.y += dy;
    } else if (dy === 1) {
      lockPiece();
    }
  }

  function rotateCurrent() {
    if (gameOver || paused) return;
    const rotated = rotatePiece(current, 1);
    // basic wall kicks: try shifts -1, +1, -2, +2
    const kicks = [0, -1, 1, -2, 2];
    for (let k of kicks) {
      if (!collide(rotated, k, 0)) {
        rotated.x += k;
        current = rotated;
        return;
      }
    }
  }

  function togglePause() {
    if (gameOver) return;
    paused = !paused;
    if (!paused) {
      dropStart = performance.now();
      loop();
    } else {
      if (requestId) cancelAnimationFrame(requestId);
      draw();
    }
  }

  function restart() {
    resetGrid();
    score = 0;
    lines = 0;
    level = 1;
    updateDropInterval();
    updateHUD();
    gameOver = false;
    paused = false;
    nextPiece = randomPiece();
    newPiece();
    dropStart = performance.now();
    loop();
  }

  function loop(timestamp) {
    if (gameOver || paused) {
      draw();
      return;
    }
    if (!dropStart) dropStart = timestamp;
    const delta = timestamp - dropStart;
    if (delta > dropInterval) {
      move(0,1);
      dropStart = timestamp;
    }
    draw();
    requestId = requestAnimationFrame(loop);
  }

  document.addEventListener('keydown', function(e) {
    switch (e.key) {
      case "ArrowLeft":
        e.preventDefault(); move(-1,0); break;
      case "ArrowRight":
        e.preventDefault(); move(1,0); break;
      case "ArrowDown":
        e.preventDefault(); move(0,1); score += 1; updateHUD(); break;
      case "ArrowUp":
        e.preventDefault(); rotateCurrent(); break;
      case " ":
        e.preventDefault(); hardDrop(); updateHUD(); break;
      case "p":
      case "P":
        e.preventDefault(); togglePause(); break;
      case "r":
      case "R":
        e.preventDefault(); restart(); break;
    }
  });

  restartBtn.addEventListener('click', function() {
    restart();
  });

  // Init
  resetGrid();
  score = 0;
  lines = 0;
  level = 1;
  updateDropInterval();
  updateHUD();
  nextPiece = randomPiece();
  newPiece();
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>