鱗目界域-龍論壇

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

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

公告

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

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

Tips:龙的梦想 龙的故乡

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

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

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.

离线

论坛页尾