鱗目界域-龍論壇

游態龍的錫安山。龍的力量、智慧、野性、與優雅

您尚未登录。 (登录 | 注册)

公告

mb 爪機版     |    論壇指南     |    Discord     |    QQ群

《龙魂志》第一期
《龙魂志》第二期

Tips:龙有、龙治、龙享

#1 2026-03-08 18:19:44  |  只看该作者

龍爪翻書
会员
来自 台北
Registered: 2011-07-10
Posts: 2,963

AI小遊戲樓

這樓用來發一些AI寫的小遊戲~

仿懷舊太空射擊遊戲





<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Dragon Space Shooter</title>
  <style>
    * { box-sizing: border-box; }
    body {
      margin: 0;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      background: radial-gradient(circle at top, #1a2342 0%, #090d18 55%, #03050a 100%);
      color: #f3f4f6;
      font-family: Arial, "Noto Sans TC", sans-serif;
    }
    .wrap {
      text-align: center;
      padding: 16px;
    }
    h1 {
      margin: 0 0 10px;
      font-size: 28px;
      letter-spacing: 1px;
    }
    .hint {
      margin: 0 0 12px;
      color: #cbd5e1;
      font-size: 14px;
    }
    canvas {
      display: block;
      margin: 0 auto;
      border: 3px solid #7dd3fc;
      border-radius: 12px;
      background: linear-gradient(to bottom, #07111f, #08101a 45%, #03060d);
      box-shadow: 0 0 24px rgba(125, 211, 252, 0.25);
      image-rendering: pixelated;
    }
    .controls {
      margin-top: 14px;
      display: flex;
      justify-content: center;
      gap: 12px;
      flex-wrap: wrap;
    }
    button {
      border: none;
      background: #f97316;
      color: white;
      padding: 10px 18px;
      border-radius: 999px;
      font-size: 15px;
      cursor: pointer;
      font-weight: bold;
      box-shadow: 0 6px 14px rgba(249, 115, 22, 0.3);
    }
    button:hover { filter: brightness(1.08); }
    .note {
      margin-top: 10px;
      font-size: 13px;
      color: #94a3b8;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <h1>🐉 Dragon Space Shooter</h1>
    <p class="hint">操作:← → 移動,空白鍵發射火球</p>
    <canvas id="game" width="480" height="640"></canvas>
    <div class="controls">
      <button id="restartBtn">重新開始</button>
    </div>
    <div class="note">復古 2D 太空射擊風格,單一 HTML 即可運行</div>
  </div>

  <script>
    const canvas = document.getElementById('game');
    const ctx = canvas.getContext('2d');
    const restartBtn = document.getElementById('restartBtn');

    const WIDTH = canvas.width;
    const HEIGHT = canvas.height;

    const keys = {
      ArrowLeft: false,
      ArrowRight: false,
      Space: false,
    };

    let stars = [];
    let player;
    let bullets;
    let enemies;
    let score;
    let gameOver;
    let enemySpawnTimer;
    let enemySpawnInterval;
    let fireCooldown;
    let animationId;

    function resetGame() {
      player = {
        x: WIDTH / 2 - 20,
        y: HEIGHT - 90,
        width: 40,
        height: 48,
        speed: 5.5,
      };

      bullets = [];
      enemies = [];
      score = 0;
      gameOver = false;
      enemySpawnTimer = 0;
      enemySpawnInterval = 42;
      fireCooldown = 0;

      stars = Array.from({ length: 70 }, () => ({
        x: Math.random() * WIDTH,
        y: Math.random() * HEIGHT,
        size: Math.random() * 2 + 1,
        speed: Math.random() * 1.4 + 0.6,
      }));
    }

    function clamp(value, min, max) {
      return Math.max(min, Math.min(max, value));
    }

    function rectsOverlap(a, b) {
      return (
        a.x < b.x + b.width &&
        a.x + a.width > b.x &&
        a.y < b.y + b.height &&
        a.y + a.height > b.y
      );
    }

    function spawnEnemy() {
      const size = 26 + Math.random() * 18;
      enemies.push({
        x: Math.random() * (WIDTH - size),
        y: -size,
        width: size,
        height: size,
        speed: 1.6 + Math.random() * 2.3 + Math.min(score * 0.02, 2.2),
        wobble: Math.random() * Math.PI * 2,
        wobbleSpeed: 0.03 + Math.random() * 0.04,
      });
    }

    function shoot() {
      bullets.push({
        x: player.x + player.width / 2 - 4,
        y: player.y + 8,
        width: 8,
        height: 18,
        speed: 8.5,
      });
    }

    function update() {
      updateStars();

      if (gameOver) return;

      if (keys.ArrowLeft) player.x -= player.speed;
      if (keys.ArrowRight) player.x += player.speed;
      player.x = clamp(player.x, 0, WIDTH - player.width);

      if (fireCooldown > 0) fireCooldown--;
      if (keys.Space && fireCooldown <= 0) {
        shoot();
        fireCooldown = 10;
      }

      bullets.forEach((bullet) => {
        bullet.y -= bullet.speed;
      });
      bullets = bullets.filter((bullet) => bullet.y + bullet.height > 0);

      enemySpawnTimer++;
      if (enemySpawnTimer >= enemySpawnInterval) {
        spawnEnemy();
        enemySpawnTimer = 0;
        enemySpawnInterval = Math.max(18, 42 - Math.floor(score / 5));
      }

      enemies.forEach((enemy) => {
        enemy.y += enemy.speed;
        enemy.wobble += enemy.wobbleSpeed;
        enemy.x += Math.sin(enemy.wobble) * 1.2;
        enemy.x = clamp(enemy.x, 0, WIDTH - enemy.width);
      });

      for (let i = enemies.length - 1; i >= 0; i--) {
        const enemy = enemies[i];

        if (rectsOverlap(player, enemy)) {
          gameOver = true;
          break;
        }

        let destroyed = false;
        for (let j = bullets.length - 1; j >= 0; j--) {
          if (rectsOverlap(bullets[j], enemy)) {
            bullets.splice(j, 1);
            enemies.splice(i, 1);
            score += 10;
            destroyed = true;
            break;
          }
        }
        if (destroyed) continue;
      }

      enemies = enemies.filter((enemy) => enemy.y < HEIGHT + enemy.height);
    }

    function updateStars() {
      for (const star of stars) {
        star.y += star.speed;
        if (star.y > HEIGHT) {
          star.y = -star.size;
          star.x = Math.random() * WIDTH;
          star.size = Math.random() * 2 + 1;
          star.speed = Math.random() * 1.4 + 0.6;
        }
      }
    }

    function drawBackground() {
      ctx.clearRect(0, 0, WIDTH, HEIGHT);

      for (const star of stars) {
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(star.x, star.y, star.size, star.size);
      }
    }

    function drawPlayer(p) {
      const x = p.x;
      const y = p.y;

      // wings
      ctx.fillStyle = '#7c3aed';
      ctx.beginPath();
      ctx.moveTo(x + 8, y + 22);
      ctx.lineTo(x - 4, y + 40);
      ctx.lineTo(x + 10, y + 36);
      ctx.closePath();
      ctx.fill();

      ctx.beginPath();
      ctx.moveTo(x + p.width - 8, y + 22);
      ctx.lineTo(x + p.width + 4, y + 40);
      ctx.lineTo(x + p.width - 10, y + 36);
      ctx.closePath();
      ctx.fill();

      // body
      ctx.fillStyle = '#22c55e';
      ctx.beginPath();
      ctx.moveTo(x + p.width / 2, y);
      ctx.lineTo(x + p.width - 2, y + 18);
      ctx.lineTo(x + p.width - 7, y + 42);
      ctx.lineTo(x + p.width / 2, y + p.height);
      ctx.lineTo(x + 7, y + 42);
      ctx.lineTo(x + 2, y + 18);
      ctx.closePath();
      ctx.fill();

      // belly
      ctx.fillStyle = '#fde68a';
      ctx.beginPath();
      ctx.moveTo(x + p.width / 2, y + 10);
      ctx.lineTo(x + p.width - 11, y + 28);
      ctx.lineTo(x + p.width / 2, y + p.height - 6);
      ctx.lineTo(x + 11, y + 28);
      ctx.closePath();
      ctx.fill();

      // horns
      ctx.fillStyle = '#f59e0b';
      ctx.fillRect(x + 10, y + 6, 4, 8);
      ctx.fillRect(x + p.width - 14, y + 6, 4, 8);

      // eyes
      ctx.fillStyle = '#111827';
      ctx.fillRect(x + 14, y + 18, 4, 4);
      ctx.fillRect(x + p.width - 18, y + 18, 4, 4);

      // fire tail glow
      ctx.fillStyle = '#fb923c';
      ctx.beginPath();
      ctx.moveTo(x + p.width / 2, y + p.height);
      ctx.lineTo(x + p.width / 2 - 6, y + p.height + 14);
      ctx.lineTo(x + p.width / 2 + 6, y + p.height + 14);
      ctx.closePath();
      ctx.fill();
    }

    function drawBullets() {
      bullets.forEach((bullet) => {
        ctx.fillStyle = '#f97316';
        ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
        ctx.fillStyle = '#fde68a';
        ctx.fillRect(bullet.x + 2, bullet.y - 4, bullet.width - 4, 6);
      });
    }

    function drawEnemy(enemy) {
      const x = enemy.x;
      const y = enemy.y;
      const w = enemy.width;
      const h = enemy.height;

      ctx.fillStyle = '#ef4444';
      ctx.beginPath();
      ctx.moveTo(x + w / 2, y);
      ctx.lineTo(x + w, y + h * 0.4);
      ctx.lineTo(x + w * 0.8, y + h);
      ctx.lineTo(x + w * 0.2, y + h);
      ctx.lineTo(x, y + h * 0.4);
      ctx.closePath();
      ctx.fill();

      ctx.fillStyle = '#fecaca';
      ctx.fillRect(x + w * 0.28, y + h * 0.34, w * 0.14, h * 0.14);
      ctx.fillRect(x + w * 0.58, y + h * 0.34, w * 0.14, h * 0.14);
      ctx.fillStyle = '#7f1d1d';
      ctx.fillRect(x + w * 0.42, y + h * 0.54, w * 0.16, h * 0.12);
    }

    function drawHUD() {
      ctx.fillStyle = 'rgba(15, 23, 42, 0.7)';
      ctx.fillRect(12, 12, 130, 42);
      ctx.strokeStyle = '#7dd3fc';
      ctx.strokeRect(12, 12, 130, 42);
      ctx.fillStyle = '#e2e8f0';
      ctx.font = 'bold 22px Arial';
      ctx.fillText('Score: ' + score, 24, 40);
    }

    function drawGameOver() {
      ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
      ctx.fillRect(0, 0, WIDTH, HEIGHT);

      ctx.fillStyle = '#fff';
      ctx.textAlign = 'center';
      ctx.font = 'bold 42px Arial';
      ctx.fillText('GAME OVER', WIDTH / 2, HEIGHT / 2 - 24);
      ctx.font = '24px Arial';
      ctx.fillText('最終分數:' + score, WIDTH / 2, HEIGHT / 2 + 18);
      ctx.font = '18px Arial';
      ctx.fillText('按下「重新開始」再玩一次', WIDTH / 2, HEIGHT / 2 + 58);
      ctx.textAlign = 'start';
    }

    function draw() {
      drawBackground();
      drawHUD();
      drawBullets();
      enemies.forEach(drawEnemy);
      drawPlayer(player);

      if (gameOver) {
        drawGameOver();
      }
    }

    function gameLoop() {
      update();
      draw();
      animationId = requestAnimationFrame(gameLoop);
    }

    window.addEventListener('keydown', (e) => {
      if (e.code === 'ArrowLeft' || e.code === 'ArrowRight' || e.code === 'Space') {
        e.preventDefault();
      }
      if (e.code in keys) {
        keys[e.code] = true;
      }
    });

    window.addEventListener('keyup', (e) => {
      if (e.code in keys) {
        keys[e.code] = false;
      }
    });

    restartBtn.addEventListener('click', () => {
      resetGame();
    });

    resetGame();
    cancelAnimationFrame(animationId);
    gameLoop();
  </script>
</body>
</html>



有 2 位朋友喜欢这篇文章:shiningdracon, 闪耀


←目前頭像感謝安雅贈圖。

Quit, dont quit... Noodles, dont noodles... 
There is a saying: yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present.

离线

#2 昨天 19:57:49  |  只看该作者

龍爪翻書
会员
来自 台北
Registered: 2011-07-10
Posts: 2,963

回应: AI小遊戲樓

參考貪喫蛇遊戲






<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>東方龍貪吃蛇</title>
  <style>
    :root {
      --bg1: #0f172a;
      --bg2: #1e293b;
      --panel: rgba(15, 23, 42, 0.82);
      --gold: #fbbf24;
      --gold2: #fde68a;
      --jade: #34d399;
      --danger: #fb7185;
      --text: #e2e8f0;
      --muted: #94a3b8;
    }

    * { box-sizing: border-box; }

    body {
      margin: 0;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: "Segoe UI", "Noto Sans TC", sans-serif;
      color: var(--text);
      background:
        radial-gradient(circle at top, rgba(251, 191, 36, 0.18), transparent 28%),
        radial-gradient(circle at bottom right, rgba(52, 211, 153, 0.14), transparent 26%),
        linear-gradient(180deg, var(--bg2), var(--bg1));
      padding: 20px;
    }

    .wrap {
      width: min(92vw, 760px);
      background: var(--panel);
      border: 1px solid rgba(255,255,255,0.08);
      border-radius: 24px;
      box-shadow: 0 24px 60px rgba(0,0,0,0.35);
      backdrop-filter: blur(8px);
      padding: 20px;
    }

    .topbar {
      display: flex;
      gap: 12px;
      flex-wrap: wrap;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 16px;
    }

    .title h1 {
      margin: 0;
      font-size: clamp(26px, 4vw, 36px);
      color: var(--gold2);
      letter-spacing: 1px;
    }

    .title p {
      margin: 6px 0 0;
      color: var(--muted);
      font-size: 14px;
    }

    .stats {
      display: flex;
      gap: 10px;
      flex-wrap: wrap;
    }

    .chip {
      background: rgba(255,255,255,0.06);
      border: 1px solid rgba(255,255,255,0.08);
      border-radius: 999px;
      padding: 10px 14px;
      font-weight: 700;
      font-size: 14px;
    }

    .chip span {
      color: var(--gold2);
      margin-left: 6px;
    }

    .game-box {
      position: relative;
      display: grid;
      place-items: center;
    }

    canvas {
      width: min(92vw, 640px);
      height: min(92vw, 640px);
      max-width: 640px;
      max-height: 640px;
      aspect-ratio: 1 / 1;
      background:
        linear-gradient(180deg, rgba(15, 23, 42, 0.94), rgba(2, 6, 23, 0.96));
      border-radius: 20px;
      border: 2px solid rgba(251, 191, 36, 0.25);
      box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.35);
    }

    .overlay {
      position: absolute;
      inset: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      pointer-events: none;
    }

    .panel {
      pointer-events: auto;
      text-align: center;
      width: min(84%, 420px);
      padding: 24px 22px;
      border-radius: 22px;
      background: rgba(15, 23, 42, 0.9);
      border: 1px solid rgba(255,255,255,0.08);
      box-shadow: 0 16px 40px rgba(0,0,0,0.4);
    }

    .panel h2 {
      margin: 0 0 10px;
      color: var(--gold2);
      font-size: 28px;
    }

    .panel p {
      margin: 8px 0;
      color: var(--text);
      line-height: 1.6;
    }

    .actions {
      display: flex;
      gap: 12px;
      justify-content: center;
      flex-wrap: wrap;
      margin-top: 16px;
    }

    button {
      border: none;
      border-radius: 999px;
      padding: 12px 20px;
      font-size: 15px;
      font-weight: 800;
      cursor: pointer;
      color: #0f172a;
      background: linear-gradient(135deg, var(--gold2), var(--gold));
      box-shadow: 0 10px 24px rgba(251, 191, 36, 0.22);
      transition: transform 0.15s ease, filter 0.15s ease;
    }

    button:hover {
      transform: translateY(-1px);
      filter: brightness(1.03);
    }

    .ghost {
      background: rgba(255,255,255,0.08);
      color: var(--text);
      box-shadow: none;
      border: 1px solid rgba(255,255,255,0.08);
    }

    .footer {
      margin-top: 14px;
      color: var(--muted);
      font-size: 13px;
      text-align: center;
      line-height: 1.6;
    }

    .hidden {
      display: none;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <div class="topbar">
      <div class="title">
        <h1>東方龍貪吃蛇</h1>
        <p>以方向鍵或 WASD 操控龍身,吃靈珠變長,別撞到自己或牆壁。</p>
      </div>
      <div class="stats">
        <div class="chip">分數 <span id="score">0</span></div>
        <div class="chip">最高分 <span id="best">0</span></div>
        <div class="chip">速度 <span id="speed">1</span></div>
      </div>
    </div>

    <div class="game-box">
      <canvas id="game" width="640" height="640"></canvas>

      <div class="overlay" id="startOverlay">
        <div class="panel">
          <h2>開始遊戲</h2>
          <p>你將操控一條東方龍在夜空中穿梭,吞食靈珠成長。</p>
          <p>操作:方向鍵 / WASD</p>
          <div class="actions">
            <button id="startBtn">開始</button>
          </div>
        </div>
      </div>

      <div class="overlay hidden" id="gameOverOverlay">
        <div class="panel">
          <h2>遊戲結束</h2>
          <p>本次分數:<strong id="finalScore">0</strong></p>
          <p id="finalMessage">龍撞上障礙了,再試一次。</p>
          <div class="actions">
            <button id="restartBtn">重新開始</button>
            <button class="ghost" id="closeBtn">查看畫面</button>
          </div>
        </div>
      </div>
    </div>

    <div class="footer">
      小提醒:每吃 4 顆靈珠,速度會提升一級。<br />
      直接把這個 HTML 存成檔案後,用瀏覽器打開即可遊玩。
    </div>
  </div>

  <script>
    const canvas = document.getElementById('game');
    const ctx = canvas.getContext('2d');

    const scoreEl = document.getElementById('score');
    const bestEl = document.getElementById('best');
    const speedEl = document.getElementById('speed');
    const startOverlay = document.getElementById('startOverlay');
    const gameOverOverlay = document.getElementById('gameOverOverlay');
    const finalScoreEl = document.getElementById('finalScore');
    const finalMessageEl = document.getElementById('finalMessage');

    const startBtn = document.getElementById('startBtn');
    const restartBtn = document.getElementById('restartBtn');
    const closeBtn = document.getElementById('closeBtn');

    const gridCount = 20;
    const cell = canvas.width / gridCount;

    let dragon = [];
    let direction = { x: 1, y: 0 };
    let nextDirection = { x: 1, y: 0 };
    let orb = { x: 10, y: 10 };
    let score = 0;
    let best = Number(localStorage.getItem('dragonSnakeBest') || 0);
    let level = 1;
    let gameOver = false;
    let started = false;
    let frameTimer = 0;
    let stepMs = 160;
    let lastTime = 0;

    bestEl.textContent = best;

    function resetGame() {
      dragon = [
        { x: 5, y: 10 },
        { x: 4, y: 10 },
        { x: 3, y: 10 },
        { x: 2, y: 10 }
      ];
      direction = { x: 1, y: 0 };
      nextDirection = { x: 1, y: 0 };
      score = 0;
      level = 1;
      stepMs = 160;
      gameOver = false;
      scoreEl.textContent = score;
      speedEl.textContent = level;
      spawnOrb();
      draw();
    }

    function spawnOrb() {
      let valid = false;
      while (!valid) {
        orb = {
          x: Math.floor(Math.random() * gridCount),
          y: Math.floor(Math.random() * gridCount)
        };
        valid = !dragon.some(part => part.x === orb.x && part.y === orb.y);
      }
    }

    function startGame() {
      resetGame();
      started = true;
      startOverlay.classList.add('hidden');
      gameOverOverlay.classList.add('hidden');
      lastTime = performance.now();
      frameTimer = 0;
      requestAnimationFrame(loop);
    }

    function endGame(reason) {
      gameOver = true;
      started = false;
      if (score > best) {
        best = score;
        localStorage.setItem('dragonSnakeBest', String(best));
        bestEl.textContent = best;
      }
      finalScoreEl.textContent = score;
      finalMessageEl.textContent = reason;
      gameOverOverlay.classList.remove('hidden');
    }

    function update() {
      direction = nextDirection;
      const head = { ...dragon[0] };
      const newHead = {
        x: head.x + direction.x,
        y: head.y + direction.y
      };

      if (
        newHead.x < 0 || newHead.x >= gridCount ||
        newHead.y < 0 || newHead.y >= gridCount
      ) {
        endGame('東方龍撞上邊界了。');
        return;
      }

      if (dragon.some(part => part.x === newHead.x && part.y === newHead.y)) {
        endGame('東方龍撞到自己的龍身了。');
        return;
      }

      dragon.unshift(newHead);

      if (newHead.x === orb.x && newHead.y === orb.y) {
        score += 10;
        scoreEl.textContent = score;
        if ((score / 10) % 4 === 0) {
          level += 1;
          speedEl.textContent = level;
          stepMs = Math.max(70, stepMs - 12);
        }
        spawnOrb();
      } else {
        dragon.pop();
      }
    }

    function drawBackground() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      const bg = ctx.createLinearGradient(0, 0, 0, canvas.height);
      bg.addColorStop(0, '#0f172a');
      bg.addColorStop(1, '#020617');
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      for (let y = 0; y < gridCount; y++) {
        for (let x = 0; x < gridCount; x++) {
          ctx.fillStyle = (x + y) % 2 === 0 ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.012)';
          ctx.fillRect(x * cell, y * cell, cell, cell);
        }
      }

      for (let i = 0; i < 24; i++) {
        const sx = (i * 97) % canvas.width;
        const sy = (i * 53) % canvas.height;
        ctx.fillStyle = 'rgba(255,255,255,0.18)';
        ctx.beginPath();
        ctx.arc(sx, sy, 1.4, 0, Math.PI * 2);
        ctx.fill();
      }
    }

    function drawOrb() {
      const cx = orb.x * cell + cell / 2;
      const cy = orb.y * cell + cell / 2;

      const glow = ctx.createRadialGradient(cx, cy, 2, cx, cy, cell * 0.45);
      glow.addColorStop(0, 'rgba(253, 224, 71, 0.95)');
      glow.addColorStop(1, 'rgba(253, 224, 71, 0)');
      ctx.fillStyle = glow;
      ctx.beginPath();
      ctx.arc(cx, cy, cell * 0.48, 0, Math.PI * 2);
      ctx.fill();

      ctx.fillStyle = '#fde68a';
      ctx.beginPath();
      ctx.arc(cx, cy, cell * 0.22, 0, Math.PI * 2);
      ctx.fill();

      ctx.strokeStyle = '#fbbf24';
      ctx.lineWidth = 2;
      ctx.stroke();
    }

    function drawDragon() {
      dragon.forEach((part, index) => {
        const px = part.x * cell;
        const py = part.y * cell;
        const isHead = index === 0;

        if (isHead) {
          const headGrad = ctx.createLinearGradient(px, py, px + cell, py + cell);
          headGrad.addColorStop(0, '#fde68a');
          headGrad.addColorStop(1, '#f59e0b');
          ctx.fillStyle = headGrad;
          roundRect(px + 3, py + 3, cell - 6, cell - 6, 10, true, false);

          ctx.fillStyle = '#7c2d12';
          const eyeOffsetX = direction.x === -1 ? 9 : direction.x === 1 ? cell - 13 : 10;
          const eye2OffsetX = direction.x === -1 ? 9 : direction.x === 1 ? cell - 13 : cell - 14;
          const eyeY = direction.y === 1 ? cell - 13 : 11;
          ctx.beginPath();
          ctx.arc(px + eyeOffsetX, py + eyeY, 2.3, 0, Math.PI * 2);
          ctx.arc(px + eye2OffsetX, py + eyeY, 2.3, 0, Math.PI * 2);
          ctx.fill();

          ctx.strokeStyle = '#ef4444';
          ctx.lineWidth = 2;
          ctx.beginPath();
          if (direction.x === 1) {
            ctx.moveTo(px + cell - 4, py + cell / 2);
            ctx.lineTo(px + cell + 6, py + cell / 2 - 3);
            ctx.lineTo(px + cell + 9, py + cell / 2);
            ctx.lineTo(px + cell + 6, py + cell / 2 + 3);
          } else if (direction.x === -1) {
            ctx.moveTo(px + 4, py + cell / 2);
            ctx.lineTo(px - 6, py + cell / 2 - 3);
            ctx.lineTo(px - 9, py + cell / 2);
            ctx.lineTo(px - 6, py + cell / 2 + 3);
          } else if (direction.y === -1) {
            ctx.moveTo(px + cell / 2, py + 4);
            ctx.lineTo(px + cell / 2 - 3, py - 6);
            ctx.lineTo(px + cell / 2, py - 9);
            ctx.lineTo(px + cell / 2 + 3, py - 6);
          } else {
            ctx.moveTo(px + cell / 2, py + cell - 4);
            ctx.lineTo(px + cell / 2 - 3, py + cell + 6);
            ctx.lineTo(px + cell / 2, py + cell + 9);
            ctx.lineTo(px + cell / 2 + 3, py + cell + 6);
          }
          ctx.stroke();

          ctx.strokeStyle = '#fef3c7';
          ctx.lineWidth = 2;
          ctx.beginPath();
          ctx.moveTo(px + 8, py + 7);
          ctx.lineTo(px + 11, py + 2);
          ctx.moveTo(px + cell - 8, py + 7);
          ctx.lineTo(px + cell - 11, py + 2);
          ctx.stroke();
        } else {
          const t = index / dragon.length;
          const bodyGrad = ctx.createLinearGradient(px, py, px + cell, py + cell);
          bodyGrad.addColorStop(0, `rgba(52, 211, 153, ${0.95 - t * 0.25})`);
          bodyGrad.addColorStop(1, `rgba(16, 185, 129, ${0.9 - t * 0.25})`);
          ctx.fillStyle = bodyGrad;
          roundRect(px + 4, py + 4, cell - 8, cell - 8, 9, true, false);

          ctx.strokeStyle = 'rgba(253, 230, 138, 0.55)';
          ctx.lineWidth = 1.6;
          ctx.beginPath();
          ctx.arc(px + cell / 2, py + cell / 2, cell * 0.18, Math.PI * 0.2, Math.PI * 1.8);
          ctx.stroke();
        }
      });
    }

    function roundRect(x, y, w, h, r, fill, stroke) {
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + w, y, x + w, y + h, r);
      ctx.arcTo(x + w, y + h, x, y + h, r);
      ctx.arcTo(x, y + h, x, y, r);
      ctx.arcTo(x, y, x + w, y, r);
      ctx.closePath();
      if (fill) ctx.fill();
      if (stroke) ctx.stroke();
    }

    function draw() {
      drawBackground();
      drawOrb();
      drawDragon();
    }

    function loop(timestamp) {
      if (!started) return;
      const delta = timestamp - lastTime;
      lastTime = timestamp;
      frameTimer += delta;

      while (frameTimer >= stepMs && !gameOver) {
        update();
        frameTimer -= stepMs;
      }

      draw();

      if (!gameOver) {
        requestAnimationFrame(loop);
      }
    }

    function setDirection(x, y) {
      if (!started && startOverlay.classList.contains('hidden') === false) return;
      if (direction.x === -x && direction.y === -y) return;
      nextDirection = { x, y };
    }

    document.addEventListener('keydown', (e) => {
      const key = e.key.toLowerCase();
      if (["arrowup", "arrowdown", "arrowleft", "arrowright", "w", "a", "s", "d", " "].includes(key)) {
        e.preventDefault();
      }

      if (!started && (key === ' ' || key === 'enter')) {
        startGame();
        return;
      }

      if (key === 'arrowup' || key === 'w') setDirection(0, -1);
      else if (key === 'arrowdown' || key === 's') setDirection(0, 1);
      else if (key === 'arrowleft' || key === 'a') setDirection(-1, 0);
      else if (key === 'arrowright' || key === 'd') setDirection(1, 0);
    });

    startBtn.addEventListener('click', startGame);
    restartBtn.addEventListener('click', startGame);
    closeBtn.addEventListener('click', () => gameOverOverlay.classList.add('hidden'));

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



←目前頭像感謝安雅贈圖。

Quit, dont quit... Noodles, dont noodles... 
There is a saying: yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present.

离线

论坛页尾