吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1623|回复: 53
上一主题 下一主题
收起左侧

[其他原创] 网页版魂斗罗游戏,梦回童年!

  [复制链接]
跳转到指定楼层
楼主
Summer000 发表于 2026-7-2 14:51 回帖奖励
本帖最后由 Summer000 于 2026-7-2 15:02 编辑

想起小时候玩的一款游戏,那时候还是游戏机玩的,魂斗罗,真的那时候玩的超级有意思,每天放学自己玩。
试着写了一版本地运行的纯HTML页面,玩了一下挺好。
操作方式
按键           功能
← → / A D        移动
Space / ↑ / W        跳跃
Z        射击
X        炸弹
↓ / S        趴下 / 空中向下瞄准
Enter        开始 / 重新开始
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>魂斗罗-单机版</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; display: flex; align-items: center; justify-content: center; }
canvas { display: block; image-rendering: pixelated; image-rendering: crisp-edges; background: #000; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
// ==================== CONTRA GAME ====================
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

// --- Responsive Canvas ---
const GAME_W = 800, GAME_H = 480;
canvas.width = GAME_W; canvas.height = GAME_H;
function resize() {
  const s = Math.min(window.innerWidth / GAME_W, window.innerHeight / GAME_H);
  canvas.style.width = (GAME_W * s) + 'px';
  canvas.style.height = (GAME_H * s) + 'px';
}
window.addEventListener('resize', resize); resize();

// --- Audio Engine ---
const AudioCtx = window.AudioContext || window.webkitAudioContext;
let audioCtx;
function ensureAudio() { if (!audioCtx) audioCtx = new AudioCtx(); }
function playSound(type) {
  ensureAudio();
  const o = audioCtx.createOscillator();
  const g = audioCtx.createGain();
  o.connect(g); g.connect(audioCtx.destination);
  const t = audioCtx.currentTime;
  switch(type) {
    case 'shoot':
      o.type='square'; o.frequency.setValueAtTime(800,t); o.frequency.exponentialRampToValueAtTime(200,t+0.08);
      g.gain.setValueAtTime(0.15,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.08);
      o.start(t); o.stop(t+0.08); break;
    case 'spread':
      o.type='sawtooth'; o.frequency.setValueAtTime(600,t); o.frequency.exponentialRampToValueAtTime(100,t+0.12);
      g.gain.setValueAtTime(0.12,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
      o.start(t); o.stop(t+0.12); break;
    case 'explosion':
      o.type='sawtooth'; o.frequency.setValueAtTime(200,t); o.frequency.exponentialRampToValueAtTime(30,t+0.3);
      g.gain.setValueAtTime(0.2,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.3);
      o.start(t); o.stop(t+0.3); break;
    case 'powerup':
      o.type='sine'; o.frequency.setValueAtTime(400,t); o.frequency.exponentialRampToValueAtTime(1200,t+0.2);
      g.gain.setValueAtTime(0.15,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.25);
      o.start(t); o.stop(t+0.25); break;
    case 'jump':
      o.type='sine'; o.frequency.setValueAtTime(300,t); o.frequency.exponentialRampToValueAtTime(600,t+0.1);
      g.gain.setValueAtTime(0.1,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.1);
      o.start(t); o.stop(t+0.1); break;
    case 'hit':
      o.type='square'; o.frequency.setValueAtTime(150,t); o.frequency.exponentialRampToValueAtTime(50,t+0.2);
      g.gain.setValueAtTime(0.2,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.2);
      o.start(t); o.stop(t+0.2); break;
    case 'boss':
      o.type='sawtooth'; o.frequency.setValueAtTime(100,t); o.frequency.exponentialRampToValueAtTime(40,t+0.5);
      g.gain.setValueAtTime(0.25,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.5);
      o.start(t); o.stop(t+0.5); break;
  }
}

// --- Input ---
const keys = {};
window.addEventListener('keydown', e => { keys[e.code] = true; e.preventDefault(); });
window.addEventListener('keyup', e => { keys[e.code] = false; e.preventDefault(); });

// --- Game State ---
let gameState = 'title'; // title, playing, gameover, win
let score = 0, lives = 3, hiScore = parseInt(localStorage.getItem('contraHi') || '0');
let cameraX = 0;
const LEVEL_W = 6400;
const GRAVITY = 0.5;
let frameCount = 0;
let screenShake = 0;
let bossDefeated = false;

// --- Platforms ---
const platforms = [];
function generateLevel() {
  platforms.length = 0;
  // Ground
  for (let x = 0; x < LEVEL_W; x += 200) {
    if (x > 400 && x < 600) continue; // gap
    if (x > 1800 && x < 2000) continue; // gap
    if (x > 3200 && x < 3400) continue; // gap
    platforms.push({ x, y: GAME_H - 40, w: 200, h: 40, type: 'ground' });
  }
  // Floating platforms
  const floats = [
    {x:300,y:320,w:120},{x:500,y:280,w:100},{x:700,y:240,w:140},
    {x:950,y:300,w:100},{x:1100,y:220,w:120},{x:1300,y:280,w:100},
    {x:1500,y:200,w:140},{x:1700,y:320,w:100},{x:1900,y:260,w:120},
    {x:2100,y:180,w:100},{x:2300,y:300,w:140},{x:2500,y:240,w:100},
    {x:2700,y:320,w:120},{x:2900,y:200,w:100},{x:3100,y:280,w:140},
    {x:3300,y:160,w:100},{x:3500,y:300,w:120},{x:3700,y:240,w:100},
    {x:3900,y:320,w:140},{x:4100,y:200,w:100},{x:4300,y:280,w:120},
    {x:4500,y:160,w:100},{x:4700,y:300,w:140},{x:4900,y:240,w:100},
    {x:5100,y:320,w:120},{x:5300,y:200,w:100},{x:5500,y:280,w:140},
    {x:5700,y:160,w:120},{x:5900,y:300,w:100}
  ];
  floats.forEach(f => platforms.push({ x:f.x, y:f.y, w:f.w, h:16, type:'platform' }));
  // Walls/barriers
  [800,1600,2400,3600,4800].forEach(x => {
    platforms.push({ x, y:GAME_H-120, w:30, h:80, type:'wall' });
  });
}

// --- Player ---
const player = {
  x: 100, y: 300, w: 28, h: 40, vx: 0, vy: 0,
  dir: 1, onGround: false, jumping: false,
  shooting: false, shootTimer: 0, shootCooldown: 8,
  weapon: 'normal', // normal, spread, rapid, laser
  weaponTimer: 0, invincible: 0,
  prone: false, animFrame: 0, animTimer: 0,
  alive: true, respawnTimer: 0
};

// --- Bullets ---
let playerBullets = [];
let enemyBullets = [];

// --- Enemies ---
let enemies = [];
let boss = null;

// --- Powerups ---
let powerups = [];

// --- Explosions ---
let explosions = [];

// --- Particles ---
let particles = [];

// --- Spawn enemies ---
function spawnEnemies() {
  enemies.length = 0;
  // Soldiers
  for (let i = 0; i < 40; i++) {
    const ex = 600 + i * 140 + Math.random() * 60;
    enemies.push({
      type: 'soldier', x: ex, y: GAME_H - 80, w: 24, h: 36,
      hp: 1, dir: -1, vx: 0, vy: 0, onGround: false,
      shootTimer: Math.random() * 120, alive: true,
      animFrame: 0, patrol: ex - 60, patrolMax: ex + 60,
      state: 'patrol' // patrol, chase, shoot
    });
  }
  // Turrets
  [1000, 1800, 2600, 3400, 4200, 5000, 5600].forEach(tx => {
    enemies.push({
      type: 'turret', x: tx, y: GAME_H - 72, w: 32, h: 32,
      hp: 3, dir: -1, shootTimer: 60, alive: true,
      angle: 0
    });
  });
  // Runners (fast enemies)
  for (let i = 0; i < 15; i++) {
    const ex = 1200 + i * 350 + Math.random() * 100;
    enemies.push({
      type: 'runner', x: ex, y: GAME_H - 76, w: 22, h: 34,
      hp: 1, dir: -1, vx: -3, vy: 0, onGround: false,
      alive: true, animFrame: 0
    });
  }
  // Powerup drops
  [700, 1400, 2200, 3000, 3800, 4600, 5400].forEach((px, i) => {
    const types = ['spread','rapid','laser','life','shield'];
    powerups.push({
      x: px, y: 180 + Math.random() * 80, w: 24, h: 24,
      type: types[i % types.length], alive: true, bobTimer: Math.random() * 100
    });
  });
}

// --- Boss ---
function spawnBoss() {
  boss = {
    x: LEVEL_W - 300, y: GAME_H - 200, w: 120, h: 160,
    hp: 50, maxHp: 50, phase: 0,
    shootTimer: 0, moveTimer: 0, alive: true,
    dir: -1, vy: 0
  };
}

// --- Collision ---
function rectCollide(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;
}

function resolveCollision(entity) {
  entity.onGround = false;
  for (const p of platforms) {
    if (rectCollide(entity, p)) {
      const overlapX = Math.min(entity.x + entity.w - p.x, p.x + p.w - entity.x);
      const overlapY = Math.min(entity.y + entity.h - p.y, p.y + p.h - entity.y);
      if (overlapY < overlapX) {
        if (entity.y + entity.h/2 < p.y + p.h/2) {
          entity.y = p.y - entity.h;
          entity.vy = 0;
          entity.onGround = true;
        } else {
          entity.y = p.y + p.h;
          entity.vy = 0;
        }
      } else {
        if (entity.x + entity.w/2 < p.x + p.w/2) {
          entity.x = p.x - entity.w;
        } else {
          entity.x = p.x + p.w;
        }
        entity.vx = 0;
      }
    }
  }
}

// --- Draw Helpers ---
function drawPixelChar(x, y, dir, frame, prone, shooting) {
  const sx = x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  ctx.save();
  ctx.translate(sx + 14, y);
  ctx.scale(dir, 1);
  if (prone) {
    // Prone position
    ctx.fillStyle = '#1e88e5';
    ctx.fillRect(-14, 24, 28, 12); // body horizontal
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(10, 22, 10, 10); // head
    ctx.fillStyle = '#1565c0';
    ctx.fillRect(-14, 28, 28, 8); // legs
    // Gun
    ctx.fillStyle = '#757575';
    ctx.fillRect(18, 26, 16, 4);
  } else {
    // Head
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(-6, 0, 12, 12);
    // Hair/bandana
    ctx.fillStyle = '#e53935';
    ctx.fillRect(-7, 0, 14, 4);
    ctx.fillRect(-9, 2, 4, 3); // bandana tail
    // Eyes
    ctx.fillStyle = '#000';
    ctx.fillRect(2, 5, 3, 3);
    // Body
    ctx.fillStyle = '#1e88e5';
    ctx.fillRect(-8, 12, 16, 14);
    // Belt
    ctx.fillStyle = '#795548';
    ctx.fillRect(-8, 24, 16, 3);
    // Legs
    const legOff = Math.sin(frame * 0.3) * 4;
    ctx.fillStyle = '#1565c0';
    ctx.fillRect(-7, 27, 6, 13 + (frame % 2 === 0 ? legOff : -legOff));
    ctx.fillRect(1, 27, 6, 13 + (frame % 2 === 0 ? -legOff : legOff));
    // Boots
    ctx.fillStyle = '#4e342e';
    ctx.fillRect(-8, 37, 7, 3);
    ctx.fillRect(1, 37, 7, 3);
    // Gun arm
    ctx.fillStyle = '#757575';
    if (shooting) {
      ctx.fillRect(8, 14, 18, 4);
      // Muzzle flash
      ctx.fillStyle = '#ffeb3b';
      ctx.fillRect(26, 12, 6, 8);
    } else {
      ctx.fillRect(8, 16, 14, 3);
    }
    // Arm
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(6, 14, 4, 6);
  }
  ctx.restore();
}

function drawSoldier(e) {
  const sx = e.x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  ctx.save();
  ctx.translate(sx + 12, e.y);
  ctx.scale(e.dir, 1);
  // Head
  ctx.fillStyle = '#4caf50';
  ctx.fillRect(-5, 0, 10, 10);
  // Helmet
  ctx.fillStyle = '#2e7d32';
  ctx.fillRect(-6, -2, 12, 6);
  // Eyes
  ctx.fillStyle = '#f00';
  ctx.fillRect(1, 4, 3, 2);
  // Body
  ctx.fillStyle = '#388e3c';
  ctx.fillRect(-7, 10, 14, 14);
  // Legs
  const lo = Math.sin(e.animFrame * 0.2) * 3;
  ctx.fillStyle = '#1b5e20';
  ctx.fillRect(-6, 24, 5, 12 + lo);
  ctx.fillRect(1, 24, 5, 12 - lo);
  // Gun
  ctx.fillStyle = '#616161';
  ctx.fillRect(7, 14, 12, 3);
  ctx.restore();
}

function drawTurret(e) {
  const sx = e.x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  // Base
  ctx.fillStyle = '#616161';
  ctx.fillRect(sx, e.y + 16, 32, 16);
  // Dome
  ctx.fillStyle = '#757575';
  ctx.beginPath();
  ctx.arc(sx + 16, e.y + 16, 14, Math.PI, 0);
  ctx.fill();
  // Barrel
  ctx.save();
  ctx.translate(sx + 16, e.y + 14);
  ctx.rotate(e.angle);
  ctx.fillStyle = '#424242';
  ctx.fillRect(0, -3, 24, 6);
  ctx.restore();
  // Light
  ctx.fillStyle = frameCount % 30 < 15 ? '#f44336' : '#ff8a80';
  ctx.fillRect(sx + 13, e.y + 6, 6, 4);
}

function drawRunner(e) {
  const sx = e.x - cameraX;
  if (sx < -50 || sx > GAME_W + 50) return;
  ctx.save();
  ctx.translate(sx + 11, e.y);
  ctx.scale(e.dir, 1);
  // Head
  ctx.fillStyle = '#ff6f00';
  ctx.fillRect(-5, 0, 10, 10);
  // Eyes
  ctx.fillStyle = '#fff';
  ctx.fillRect(1, 3, 4, 4);
  ctx.fillStyle = '#f00';
  ctx.fillRect(2, 4, 2, 2);
  // Body
  ctx.fillStyle = '#e65100';
  ctx.fillRect(-6, 10, 12, 12);
  // Legs (running fast)
  const lo = Math.sin(e.animFrame * 0.5) * 6;
  ctx.fillStyle = '#bf360c';
  ctx.fillRect(-5, 22, 4, 12 + lo);
  ctx.fillRect(1, 22, 4, 12 - lo);
  // Claws
  ctx.fillStyle = '#ff3d00';
  ctx.fillRect(6, 12, 8, 3);
  ctx.restore();
}

function drawBoss(b) {
  if (!b || !b.alive) return;
  const sx = b.x - cameraX;
  ctx.save();
  // Main body
  ctx.fillStyle = '#455a64';
  ctx.fillRect(sx, b.y, b.w, b.h);
  // Armor plates
  ctx.fillStyle = '#37474f';
  ctx.fillRect(sx + 10, b.y + 10, b.w - 20, 30);
  ctx.fillRect(sx + 10, b.y + 60, b.w - 20, 30);
  ctx.fillRect(sx + 10, b.y + 110, b.w - 20, 30);
  // Core (weak point)
  const coreGlow = Math.sin(frameCount * 0.1) * 0.3 + 0.7;
  ctx.fillStyle = `rgba(244, 67, 54, ${coreGlow})`;
  ctx.beginPath();
  ctx.arc(sx + b.w/2, b.y + b.h/2, 20, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillStyle = '#ff8a80';
  ctx.beginPath();
  ctx.arc(sx + b.w/2, b.y + b.h/2, 10, 0, Math.PI * 2);
  ctx.fill();
  // Cannons
  ctx.fillStyle = '#263238';
  ctx.fillRect(sx - 20, b.y + 20, 30, 12);
  ctx.fillRect(sx - 20, b.y + b.h - 40, 30, 12);
  ctx.fillRect(sx + b.w - 10, b.y + 40, 30, 12);
  ctx.fillRect(sx + b.w - 10, b.y + b.h - 60, 30, 12);
  // Eyes
  ctx.fillStyle = '#f44336';
  ctx.fillRect(sx + 25, b.y + 15, 12, 8);
  ctx.fillRect(sx + b.w - 37, b.y + 15, 12, 8);
  // HP bar
  ctx.fillStyle = '#333';
  ctx.fillRect(sx, b.y - 20, b.w, 10);
  ctx.fillStyle = b.hp > b.maxHp * 0.3 ? '#f44336' : '#ff1744';
  ctx.fillRect(sx + 1, b.y - 19, (b.w - 2) * (b.hp / b.maxHp), 8);
  // Name
  ctx.fillStyle = '#fff';
  ctx.font = '10px monospace';
  ctx.fillText('BOSS - ALIEN CORE', sx + 10, b.y - 24);
  ctx.restore();
}

// --- Background ---
function drawBackground() {
  // Sky gradient
  const grad = ctx.createLinearGradient(0, 0, 0, GAME_H);
  grad.addColorStop(0, '#0d1b2a');
  grad.addColorStop(0.4, '#1b2838');
  grad.addColorStop(0.7, '#2d4a3e');
  grad.addColorStop(1, '#1a3a2a');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, GAME_W, GAME_H);

  // Stars
  ctx.fillStyle = '#fff';
  for (let i = 0; i < 60; i++) {
    const sx = ((i * 137 + 50) % GAME_W + GAME_W - cameraX * 0.05 % GAME_W) % GAME_W;
    const sy = (i * 89 + 20) % (GAME_H * 0.5);
    const size = (i % 3 === 0) ? 2 : 1;
    ctx.globalAlpha = 0.3 + (Math.sin(frameCount * 0.02 + i) * 0.3);
    ctx.fillRect(sx, sy, size, size);
  }
  ctx.globalAlpha = 1;

  // Mountains (parallax)
  ctx.fillStyle = '#1a3328';
  for (let i = 0; i < 12; i++) {
    const mx = i * 200 - (cameraX * 0.15) % 2400;
    const mh = 80 + (i * 47) % 60;
    ctx.beginPath();
    ctx.moveTo(mx, GAME_H - 40);
    ctx.lineTo(mx + 100, GAME_H - 40 - mh);
    ctx.lineTo(mx + 200, GAME_H - 40);
    ctx.fill();
  }

  // Trees (parallax)
  ctx.fillStyle = '#0d2818';
  for (let i = 0; i < 20; i++) {
    const tx = i * 150 - (cameraX * 0.3) % 3000;
    const th = 40 + (i * 31) % 30;
    ctx.fillRect(tx + 15, GAME_H - 40 - th, 10, th);
    ctx.beginPath();
    ctx.moveTo(tx, GAME_H - 40 - th + 10);
    ctx.lineTo(tx + 20, GAME_H - 40 - th - 25);
    ctx.lineTo(tx + 40, GAME_H - 40 - th + 10);
    ctx.fill();
  }
}

function drawPlatforms() {
  for (const p of platforms) {
    const sx = p.x - cameraX;
    if (sx > GAME_W + 10 || sx + p.w < -10) continue;
    if (p.type === 'ground') {
      ctx.fillStyle = '#3e2723';
      ctx.fillRect(sx, p.y, p.w, p.h);
      ctx.fillStyle = '#4caf50';
      ctx.fillRect(sx, p.y, p.w, 6);
      ctx.fillStyle = '#2e7d32';
      ctx.fillRect(sx, p.y + 6, p.w, 4);
      // Dirt texture
      ctx.fillStyle = '#5d4037';
      for (let dx = 0; dx < p.w; dx += 20) {
        ctx.fillRect(sx + dx + 5, p.y + 15, 8, 4);
        ctx.fillRect(sx + dx + 2, p.y + 25, 6, 3);
      }
    } else if (p.type === 'platform') {
      ctx.fillStyle = '#78909c';
      ctx.fillRect(sx, p.y, p.w, p.h);
      ctx.fillStyle = '#90a4ae';
      ctx.fillRect(sx, p.y, p.w, 4);
      ctx.fillStyle = '#546e7a';
      ctx.fillRect(sx + 2, p.y + 6, p.w - 4, 4);
      // Rivets
      ctx.fillStyle = '#b0bec5';
      ctx.fillRect(sx + 4, p.y + 2, 3, 3);
      ctx.fillRect(sx + p.w - 7, p.y + 2, 3, 3);
    } else if (p.type === 'wall') {
      ctx.fillStyle = '#5d4037';
      ctx.fillRect(sx, p.y, p.w, p.h);
      ctx.fillStyle = '#6d4c41';
      for (let dy = 0; dy < p.h; dy += 12) {
        ctx.fillRect(sx, p.y + dy, p.w, 1);
        ctx.fillRect(sx + p.w/2, p.y + dy, 1, 12);
      }
    }
  }
}

function drawPowerups() {
  for (const p of powerups) {
    if (!p.alive) continue;
    const sx = p.x - cameraX;
    if (sx < -30 || sx > GAME_W + 30) continue;
    const bob = Math.sin((frameCount + p.bobTimer) * 0.05) * 4;
    const py = p.y + bob;
    // Wing container
    ctx.fillStyle = '#e53935';
    ctx.fillRect(sx - 2, py - 2, 28, 28);
    ctx.fillStyle = '#c62828';
    ctx.fillRect(sx, py, 24, 24);
    // Letter
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 14px monospace';
    const letters = { spread: 'S', rapid: 'R', laser: 'L', life: '1UP', shield: '&#128737;' };
    ctx.fillText(letters[p.type] || '?', sx + 4, py + 17);
    // Glow
    ctx.globalAlpha = 0.3 + Math.sin(frameCount * 0.1) * 0.2;
    ctx.fillStyle = '#ffeb3b';
    ctx.fillRect(sx - 4, py - 4, 32, 32);
    ctx.globalAlpha = 1;
  }
}

function drawBullets() {
  // Player bullets
  for (const b of playerBullets) {
    const sx = b.x - cameraX;
    if (sx < -10 || sx > GAME_W + 10) continue;
    if (b.type === 'laser') {
      ctx.fillStyle = '#00e5ff';
      ctx.fillRect(sx, b.y - 1, b.w, 3);
      ctx.fillStyle = '#b2ebf2';
      ctx.fillRect(sx, b.y, b.w, 1);
    } else if (b.type === 'spread') {
      ctx.fillStyle = '#ff9800';
      ctx.beginPath();
      ctx.arc(sx + b.w/2, b.y + b.h/2, 3, 0, Math.PI * 2);
      ctx.fill();
    } else {
      ctx.fillStyle = '#ffeb3b';
      ctx.fillRect(sx, b.y, b.w, b.h);
      ctx.fillStyle = '#fff';
      ctx.fillRect(sx + 1, b.y + 1, b.w - 2, b.h - 2);
    }
  }
  // Enemy bullets
  for (const b of enemyBullets) {
    const sx = b.x - cameraX;
    if (sx < -10 || sx > GAME_W + 10) continue;
    ctx.fillStyle = '#f44336';
    ctx.beginPath();
    ctx.arc(sx + 3, b.y + 3, 4, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#ff8a80';
    ctx.beginPath();
    ctx.arc(sx + 3, b.y + 3, 2, 0, Math.PI * 2);
    ctx.fill();
  }
}

function drawExplosions() {
  for (let i = explosions.length - 1; i >= 0; i--) {
    const e = explosions[i];
    const sx = e.x - cameraX;
    const progress = e.timer / e.maxTimer;
    const r = e.radius * (1 - progress * 0.5);
    ctx.globalAlpha = progress;
    ctx.fillStyle = '#ff6f00';
    ctx.beginPath(); ctx.arc(sx, e.y, r, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#ffeb3b';
    ctx.beginPath(); ctx.arc(sx, e.y, r * 0.6, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.beginPath(); ctx.arc(sx, e.y, r * 0.2, 0, Math.PI * 2); ctx.fill();
    ctx.globalAlpha = 1;
    e.timer--;
    if (e.timer <= 0) explosions.splice(i, 1);
  }
}

function drawParticles() {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    const sx = p.x - cameraX;
    ctx.globalAlpha = p.life / p.maxLife;
    ctx.fillStyle = p.color;
    ctx.fillRect(sx, p.y, p.size, p.size);
    p.x += p.vx; p.y += p.vy; p.vy += 0.1; p.life--;
    if (p.life <= 0) particles.splice(i, 1);
  }
  ctx.globalAlpha = 1;
}

function drawHUD() {
  // Score
  ctx.fillStyle = 'rgba(0,0,0,0.5)';
  ctx.fillRect(0, 0, GAME_W, 36);
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 16px monospace';
  ctx.fillText(`SCORE: ${score.toString().padStart(8, '0')}`, 10, 24);
  ctx.fillText(`HI: ${hiScore.toString().padStart(8, '0')}`, 220, 24);
  // Lives
  ctx.fillStyle = '#e53935';
  for (let i = 0; i < lives; i++) {
    ctx.fillRect(460 + i * 22, 10, 14, 16);
    ctx.fillStyle = '#ffcc80';
    ctx.fillRect(463 + i * 22, 6, 8, 8);
    ctx.fillStyle = '#e53935';
  }
  // Weapon
  ctx.fillStyle = '#ffeb3b';
  ctx.font = '12px monospace';
  const wNames = { normal: 'NORMAL', spread: 'SPREAD', rapid: 'RAPID', laser: 'LASER' };
  ctx.fillText(`WEAPON: ${wNames[player.weapon]}`, 580, 24);
  // Weapon timer
  if (player.weaponTimer > 0 && player.weapon !== 'normal') {
    ctx.fillStyle = '#333';
    ctx.fillRect(580, 28, 80, 4);
    ctx.fillStyle = '#ffeb3b';
    ctx.fillRect(580, 28, 80 * (player.weaponTimer / 600), 4);
  }
  // Progress bar
  ctx.fillStyle = '#333';
  ctx.fillRect(GAME_W - 160, 14, 150, 8);
  ctx.fillStyle = '#4caf50';
  ctx.fillRect(GAME_W - 160, 14, 150 * Math.min(1, cameraX / (LEVEL_W - GAME_W)), 8);
  ctx.fillStyle = '#fff';
  ctx.font = '8px monospace';
  ctx.fillText('PROGRESS', GAME_W - 145, 12);
}

// --- Title Screen ---
function drawTitle() {
  // Background
  ctx.fillStyle = '#0a0a0a';
  ctx.fillRect(0, 0, GAME_W, GAME_H);

  // Animated background lines
  ctx.strokeStyle = 'rgba(229, 57, 53, 0.1)';
  ctx.lineWidth = 1;
  for (let i = 0; i < 20; i++) {
    const y = (i * 30 + frameCount * 0.5) % GAME_H;
    ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(GAME_W, y); ctx.stroke();
  }

  // Title
  ctx.save();
  const titleY = 100 + Math.sin(frameCount * 0.03) * 8;
  ctx.fillStyle = '#e53935';
  ctx.font = 'bold 72px monospace';
  ctx.textAlign = 'center';
  ctx.fillText('CONTRA', GAME_W / 2, titleY);
  // Title shadow
  ctx.fillStyle = '#b71c1c';
  ctx.fillText('CONTRA', GAME_W / 2 + 3, titleY + 3);
  // Subtitle
  ctx.fillStyle = '#ffeb3b';
  ctx.font = 'bold 20px monospace';
  ctx.fillText('魂 斗 罗', GAME_W / 2, titleY + 40);

  // Blinking text
  if (frameCount % 60 < 40) {
    ctx.fillStyle = '#fff';
    ctx.font = '18px monospace';
    ctx.fillText('PRESS ENTER TO START', GAME_W / 2, 280);
  }

  // Controls
  ctx.fillStyle = '#90a4ae';
  ctx.font = '13px monospace';
  ctx.fillText('← → : MOVE    ↑ : AIM UP    ↓ : PRONE', GAME_W / 2, 340);
  ctx.fillText('SPACE : JUMP    Z : SHOOT    X : BOMB', GAME_W / 2, 365);

  // Hi-Score
  ctx.fillStyle = '#ffeb3b';
  ctx.font = '14px monospace';
  ctx.fillText(`HI-SCORE: ${hiScore.toString().padStart(8, '0')}`, GAME_W / 2, 420);

  ctx.restore();
}

function drawGameOver() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, GAME_W, GAME_H);
  ctx.save();
  ctx.textAlign = 'center';
  ctx.fillStyle = '#e53935';
  ctx.font = 'bold 56px monospace';
  ctx.fillText('GAME OVER', GAME_W / 2, GAME_H / 2 - 30);
  ctx.fillStyle = '#fff';
  ctx.font = '20px monospace';
  ctx.fillText(`FINAL SCORE: ${score.toString().padStart(8, '0')}`, GAME_W / 2, GAME_H / 2 + 20);
  if (frameCount % 60 < 40) {
    ctx.fillStyle = '#ffeb3b';
    ctx.font = '16px monospace';
    ctx.fillText('PRESS ENTER TO CONTINUE', GAME_W / 2, GAME_H / 2 + 70);
  }
  ctx.restore();
}

function drawWin() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, GAME_W, GAME_H);
  ctx.save();
  ctx.textAlign = 'center';
  ctx.fillStyle = '#4caf50';
  ctx.font = 'bold 48px monospace';
  ctx.fillText('MISSION COMPLETE!', GAME_W / 2, GAME_H / 2 - 40);
  ctx.fillStyle = '#ffeb3b';
  ctx.font = 'bold 24px monospace';
  ctx.fillText(`SCORE: ${score.toString().padStart(8, '0')}`, GAME_W / 2, GAME_H / 2 + 10);
  ctx.fillStyle = '#fff';
  ctx.font = '16px monospace';
  ctx.fillText('THE ALIEN CORE HAS BEEN DESTROYED!', GAME_W / 2, GAME_H / 2 + 50);
  if (frameCount % 60 < 40) {
    ctx.fillStyle = '#90a4ae';
    ctx.font = '14px monospace';
    ctx.fillText('PRESS ENTER TO PLAY AGAIN', GAME_W / 2, GAME_H / 2 + 90);
  }
  ctx.restore();
}

// --- Update Functions ---
function updatePlayer() {
  if (!player.alive) {
    player.respawnTimer--;
    if (player.respawnTimer <= 0) {
      if (lives > 0) {
        player.alive = true;
        player.x = cameraX + 100;
        player.y = 200;
        player.vx = 0; player.vy = 0;
        player.invincible = 120;
        player.weapon = 'normal';
      } else {
        gameState = 'gameover';
        if (score > hiScore) { hiScore = score; localStorage.setItem('contraHi', hiScore); }
      }
    }
    return;
  }

  // Movement
  const speed = 3;
  if (keys['ArrowLeft'] || keys['KeyA']) { player.vx = -speed; player.dir = -1; }
  else if (keys['ArrowRight'] || keys['KeyD']) { player.vx = speed; player.dir = 1; }
  else { player.vx = 0; }

  // Prone
  player.prone = (keys['ArrowDown'] || keys['KeyS']) && player.onGround;
  if (player.prone) { player.h = 20; player.vx *= 0.5; }
  else { player.h = 40; }

  // Jump
  if ((keys['Space'] || keys['ArrowUp'] || keys['KeyW']) && player.onGround) {
    player.vy = -10;
    player.jumping = true;
    player.onGround = false;
    playSound('jump');
  }

  // Gravity
  player.vy += GRAVITY;
  player.x += player.vx;
  player.y += player.vy;

  // Resolve collisions
  resolveCollision(player);

  // Bounds
  if (player.x < cameraX) player.x = cameraX;
  if (player.y > GAME_H + 50) {
    playerDie();
    return;
  }

  // Shooting
  if (player.shootTimer > 0) player.shootTimer--;
  const cooldown = player.weapon === 'rapid' ? 4 : player.shootCooldown;
  if (keys['KeyZ'] && player.shootTimer <= 0) {
    shoot();
    player.shootTimer = cooldown;
  }

  // Bomb (X key)
  if (keys['KeyX'] && !player._bombUsed) {
    player._bombUsed = true;
    // Screen clear bomb
    for (const e of enemies) {
      if (e.alive && Math.abs(e.x - player.x) < 400) {
        e.alive = false;
        score += 100;
        explosions.push({ x: e.x, y: e.y, radius: 30, timer: 20, maxTimer: 20 });
      }
    }
    screenShake = 15;
    playSound('explosion');
  }
  if (!keys['KeyX']) player._bombUsed = false;

  // Invincibility
  if (player.invincible > 0) player.invincible--;

  // Weapon timer
  if (player.weaponTimer > 0) {
    player.weaponTimer--;
    if (player.weaponTimer <= 0) player.weapon = 'normal';
  }

  // Animation
  if (Math.abs(player.vx) > 0) {
    player.animTimer++;
    if (player.animTimer > 6) { player.animFrame++; player.animTimer = 0; }
  }

  // Camera
  const targetCam = player.x - GAME_W * 0.35;
  cameraX += (targetCam - cameraX) * 0.08;
  cameraX = Math.max(0, Math.min(LEVEL_W - GAME_W, cameraX));

  // Boss trigger
  if (cameraX > LEVEL_W - GAME_W - 100 && !boss && !bossDefeated) {
    spawnBoss();
    playSound('boss');
  }
}

function shoot() {
  const bx = player.x + (player.dir > 0 ? player.w : -8);
  const by = player.y + (player.prone ? 26 : 14);
  let bvx = player.dir * 8;
  let bvy = 0;

  // Aim up
  if (keys['ArrowUp'] && !player.onGround) {
    bvy = -8;
    bvx = player.dir * 4;
  } else if (keys['ArrowUp'] && player.onGround && !player.prone) {
    bvy = -8;
    bvx = 0;
  }
  // Aim down (in air)
  if (keys['ArrowDown'] && !player.onGround) {
    bvy = 8;
    bvx = player.dir * 4;
  }

  if (player.weapon === 'spread') {
    playSound('spread');
    for (let a = -0.3; a <= 0.3; a += 0.15) {
      playerBullets.push({
        x: bx, y: by, w: 6, h: 6,
        vx: bvx * Math.cos(a) - bvy * Math.sin(a),
        vy: bvx * Math.sin(a) + bvy * Math.cos(a),
        type: 'spread', life: 60
      });
    }
  } else if (player.weapon === 'laser') {
    playSound('spread');
    playerBullets.push({
      x: bx, y: by, w: 30, h: 3,
      vx: bvx * 1.5, vy: bvy * 1.5,
      type: 'laser', life: 40
    });
  } else {
    playSound('shoot');
    playerBullets.push({
      x: bx, y: by, w: 8, h: 3,
      vx: bvx, vy: bvy,
      type: 'normal', life: 80
    });
  }
  player.shooting = true;
  setTimeout(() => player.shooting = false, 80);
}

function playerDie() {
  if (player.invincible > 0) return;
  player.alive = false;
  lives--;
  player.respawnTimer = 90;
  screenShake = 20;
  playSound('hit');
  explosions.push({ x: player.x, y: player.y + 20, radius: 25, timer: 25, maxTimer: 25 });
  for (let i = 0; i < 15; i++) {
    particles.push({
      x: player.x + 14, y: player.y + 20,
      vx: (Math.random() - 0.5) * 6, vy: (Math.random() - 0.5) * 6 - 3,
      size: 2 + Math.random() * 3, color: ['#e53935','#ffeb3b','#ff9800'][Math.floor(Math.random()*3)],
      life: 30 + Math.random() * 20, maxLife: 50
    });
  }
}

function updateBullets() {
  // Player bullets
  for (let i = playerBullets.length - 1; i >= 0; i--) {
    const b = playerBullets[i];
    b.x += b.vx; b.y += b.vy; b.life--;
    if (b.life <= 0 || b.x < cameraX - 50 || b.x > cameraX + GAME_W + 50 || b.y < -20 || b.y > GAME_H + 20) {
      playerBullets.splice(i, 1); continue;
    }
    // Hit enemies
    for (const e of enemies) {
      if (!e.alive) continue;
      if (rectCollide(b, e)) {
        e.hp--;
        if (e.hp <= 0) {
          e.alive = false;
          score += e.type === 'turret' ? 300 : e.type === 'runner' ? 200 : 100;
          explosions.push({ x: e.x + e.w/2, y: e.y + e.h/2, radius: 20, timer: 18, maxTimer: 18 });
          playSound('explosion');
          for (let j = 0; j < 8; j++) {
            particles.push({
              x: e.x + e.w/2, y: e.y + e.h/2,
              vx: (Math.random()-0.5)*5, vy: (Math.random()-0.5)*5-2,
              size: 2+Math.random()*2, color: ['#ff6f00','#ffeb3b','#fff'][Math.floor(Math.random()*3)],
              life: 20+Math.random()*15, maxLife: 35
            });
          }
        }
        playerBullets.splice(i, 1); break;
      }
    }
    // Hit boss
    if (boss && boss.alive && rectCollide(b, boss)) {
      boss.hp--;
      if (boss.hp <= 0) {
        boss.alive = false;
        bossDefeated = true;
        score += 5000;
        screenShake = 30;
        playSound('explosion');
        for (let k = 0; k < 30; k++) {
          explosions.push({
            x: boss.x + Math.random() * boss.w,
            y: boss.y + Math.random() * boss.h,
            radius: 15 + Math.random() * 20,
            timer: 20 + Math.random() * 20, maxTimer: 40
          });
        }
        setTimeout(() => { gameState = 'win'; if (score > hiScore) { hiScore = score; localStorage.setItem('contraHi', hiScore); } }, 2000);
      }
      playerBullets.splice(i, 1);
      playSound('hit');
    }
    // Hit platforms (walls only)
    for (const p of platforms) {
      if (p.type === 'wall' && rectCollide(b, p)) {
        playerBullets.splice(i, 1); break;
      }
    }
  }

  // Enemy bullets
  for (let i = enemyBullets.length - 1; i >= 0; i--) {
    const b = enemyBullets[i];
    b.x += b.vx; b.y += b.vy; b.life--;
    if (b.life <= 0 || b.x < cameraX - 100 || b.x > cameraX + GAME_W + 100) {
      enemyBullets.splice(i, 1); continue;
    }
    // Hit player
    if (player.alive && rectCollide({ x: b.x, y: b.y, w: 6, h: 6 }, player)) {
      playerDie();
      enemyBullets.splice(i, 1);
    }
  }
}

function updateEnemies() {
  for (const e of enemies) {
    if (!e.alive) continue;
    const dx = player.x - e.x;
    const dist = Math.abs(dx);

    if (e.type === 'soldier') {
      // AI
      if (dist < 350 && player.alive) {
        e.dir = dx > 0 ? 1 : -1;
        e.state = dist < 250 ? 'shoot' : 'chase';
      } else {
        e.state = 'patrol';
      }

      if (e.state === 'patrol') {
        e.vx = e.dir * 0.8;
        if (e.x <= e.patrol) e.dir = 1;
        if (e.x >= e.patrolMax) e.dir = -1;
      } else if (e.state === 'chase') {
        e.vx = e.dir * 1.5;
      } else {
        e.vx = 0;
        e.shootTimer--;
        if (e.shootTimer <= 0 && dist < 400) {
          e.shootTimer = 60 + Math.random() * 40;
          const angle = Math.atan2(player.y - e.y, player.x - e.x);
          enemyBullets.push({
            x: e.x + 12, y: e.y + 14,
            vx: Math.cos(angle) * 3, vy: Math.sin(angle) * 3,
            life: 120
          });
        }
      }

      e.vy += GRAVITY;
      e.x += e.vx;
      e.y += e.vy;
      resolveCollision(e);
      e.animFrame++;

      // Touch damage
      if (player.alive && rectCollide(player, e)) playerDie();

    } else if (e.type === 'turret') {
      if (player.alive) {
        e.angle = Math.atan2(player.y - e.y, player.x - e.x);
        e.dir = dx > 0 ? 1 : -1;
      }
      e.shootTimer--;
      if (e.shootTimer <= 0 && dist < 500) {
        e.shootTimer = 80;
        enemyBullets.push({
          x: e.x + 16 + Math.cos(e.angle) * 24,
          y: e.y + 14 + Math.sin(e.angle) * 24,
          vx: Math.cos(e.angle) * 4, vy: Math.sin(e.angle) * 4,
          life: 100
        });
      }
    } else if (e.type === 'runner') {
      if (dist < 500 && player.alive) {
        e.dir = dx > 0 ? 1 : -1;
        e.vx = e.dir * 3.5;
      }
      e.vy += GRAVITY;
      e.x += e.vx;
      e.y += e.vy;
      resolveCollision(e);
      e.animFrame++;

      if (player.alive && rectCollide(player, e)) playerDie();
    }
  }
}

function updateBoss() {
  if (!boss || !boss.alive) return;
  boss.shootTimer--;
  boss.moveTimer++;

  // Movement
  boss.y = GAME_H - 200 + Math.sin(boss.moveTimer * 0.02) * 40;

  // Attack patterns
  if (boss.shootTimer <= 0) {
    boss.phase = (boss.phase + 1) % 3;
    if (boss.phase === 0) {
      // Spread shot
      for (let a = -0.6; a <= 0.6; a += 0.2) {
        enemyBullets.push({
          x: boss.x + 20, y: boss.y + boss.h / 2,
          vx: Math.cos(a + Math.PI) * 4, vy: Math.sin(a + Math.PI) * 4,
          life: 150
        });
      }
      boss.shootTimer = 40;
    } else if (boss.phase === 1) {
      // Aimed shot
      const angle = Math.atan2(player.y - boss.y - boss.h/2, player.x - boss.x);
      for (let i = 0; i < 3; i++) {
        setTimeout(() => {
          if (boss && boss.alive) {
            enemyBullets.push({
              x: boss.x + 20, y: boss.y + boss.h / 2,
              vx: Math.cos(angle) * 5, vy: Math.sin(angle) * 5,
              life: 120
            });
          }
        }, i * 100);
      }
      boss.shootTimer = 60;
    } else {
      // Rain of bullets
      for (let i = 0; i < 5; i++) {
        enemyBullets.push({
          x: boss.x + Math.random() * boss.w,
          y: boss.y + boss.h,
          vx: (Math.random() - 0.5) * 3,
          vy: 2 + Math.random() * 3,
          life: 120
        });
      }
      boss.shootTimer = 50;
    }
    playSound('shoot');
  }

  // Touch damage
  if (player.alive && rectCollide(player, boss)) playerDie();
}

function updatePowerups() {
  for (const p of powerups) {
    if (!p.alive) continue;
    if (player.alive && rectCollide(player, p)) {
      p.alive = false;
      playSound('powerup');
      score += 500;
      switch (p.type) {
        case 'spread': player.weapon = 'spread'; player.weaponTimer = 600; break;
        case 'rapid': player.weapon = 'rapid'; player.weaponTimer = 600; break;
        case 'laser': player.weapon = 'laser'; player.weaponTimer = 600; break;
        case 'life': lives = Math.min(lives + 1, 9); break;
        case 'shield': player.invincible = 300; break;
      }
      for (let i = 0; i < 10; i++) {
        particles.push({
          x: p.x + 12, y: p.y + 12,
          vx: (Math.random()-0.5)*4, vy: (Math.random()-0.5)*4,
          size: 2+Math.random()*2, color: '#ffeb3b',
          life: 20+Math.random()*10, maxLife: 30
        });
      }
    }
  }
}

// --- Game Init ---
function initGame() {
  score = 0; lives = 3; cameraX = 0;
  bossDefeated = false; boss = null;
  playerBullets = []; enemyBullets = [];
  explosions = []; particles = [];
  player.x = 100; player.y = 300;
  player.vx = 0; player.vy = 0;
  player.alive = true; player.invincible = 60;
  player.weapon = 'normal'; player.weaponTimer = 0;
  player.dir = 1; player.prone = false;
  generateLevel();
  spawnEnemies();
}

// --- Main Loop ---
function gameLoop() {
  frameCount++;

  if (gameState === 'title') {
    drawTitle();
    if (keys['Enter'] || keys['Space']) {
      gameState = 'playing';
      initGame();
      ensureAudio();
    }
  } else if (gameState === 'playing') {
    // Screen shake
    ctx.save();
    if (screenShake > 0) {
      ctx.translate(
        (Math.random() - 0.5) * screenShake,
        (Math.random() - 0.5) * screenShake
      );
      screenShake--;
    }

    drawBackground();
    drawPlatforms();
    drawPowerups();
    updatePlayer();
    updateEnemies();
    updateBoss();
    updateBullets();
    updatePowerups();

    // Draw enemies
    for (const e of enemies) {
      if (!e.alive) continue;
      const sx = e.x - cameraX;
      if (sx < -50 || sx > GAME_W + 50) continue;
      if (e.type === 'soldier') drawSoldier(e);
      else if (e.type === 'turret') drawTurret(e);
      else if (e.type === 'runner') drawRunner(e);
    }

    drawBoss(boss);
    drawBullets();
    drawExplosions();
    drawParticles();

    // Draw player
    if (player.alive) {
      if (player.invincible > 0 && frameCount % 4 < 2) {
        // Flash when invincible
      } else {
        drawPixelChar(player.x, player.y, player.dir, player.animFrame, player.prone, player.shooting);
      }
    }

    drawHUD();
    ctx.restore();
  } else if (gameState === 'gameover') {
    drawBackground();
    drawPlatforms();
    drawGameOver();
    if (keys['Enter']) { gameState = 'title'; keys['Enter'] = false; }
  } else if (gameState === 'win') {
    drawBackground();
    drawPlatforms();
    drawWin();
    if (keys['Enter']) { gameState = 'title'; keys['Enter'] = false; }
  }

  requestAnimationFrame(gameLoop);
}

// --- Start ---
gameLoop();
</script>
</body>
</html>

quark-paste-af26e4a5-26c6-4257-a783-10d08e58f4de.jpg (53.22 KB, 下载次数: 3)

开始界面

开始界面

quark-paste-244f787b-c2b7-41e1-b38e-f8ae65099a18.jpg (71.41 KB, 下载次数: 2)

游戏界面

游戏界面

免费评分

参与人数 6吾爱币 +5 热心值 +5 收起 理由
王七岁 + 1 + 1 我很赞同!
shanzhanzhe + 1 + 1 谢谢@Thanks!
qjlfl + 1 + 1 我很赞同!
cd1688 + 1 谢谢@Thanks!
gugouo163 + 1 用心讨论,共获提升!
小众资源 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
kongxincai05 发表于 2026-7-2 17:42
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>魂斗罗 三关完整版|二段跳无限生命</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:#000;display:flex;align-items:center;justify-content:center}
canvas{display:block;image-rendering:pixelated;image-rendering:crisp-edges;background:#000}
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const GAME_W = 800, GAME_H = 480;
canvas.width = GAME_W;
canvas.height = GAME_H;

// 窗口自适应缩放
function resize(){
  let scale = Math.min(window.innerWidth / GAME_W, window.innerHeight / GAME_H);
  canvas.style.width = GAME_W * scale + 'px';
  canvas.style.height = GAME_H * scale + 'px';
}
window.addEventListener('resize', resize);
resize();

// 音频系统
const AudioCtx = window.AudioContext || window.webkitAudioContext;
let audioCtx;
function ensureAudio(){
  if(!audioCtx) audioCtx = new AudioCtx();
}
function playSound(type){
  ensureAudio();
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain);
  gain.connect(audioCtx.destination);
  let t = audioCtx.currentTime;
  switch(type){
    case 'shoot':
      osc.type='square';osc.frequency.setValueAtTime(800,t);osc.frequency.exponentialRampToValueAtTime(200,t+0.08);
      gain.gain.setValueAtTime(0.15,t);gain.gain.exponentialRampToValueAtTime(0.001,t+0.08);
      osc.start(t);osc.stop(t+0.08);break;
    case 'spread':
      osc.type='sawtooth';osc.frequency.setValueAtTime(600,t);osc.frequency.exponentialRampToValueAtTime(100,t+0.12);
      gain.gain.setValueAtTime(0.12,t);gain.gain.exponentialRampToValueAtTime(0.001,t+0.12);
      osc.start(t);osc.stop(t+0.12);break;
    case 'explosion':
      osc.type='sawtooth';osc.frequency.setValueAtTime(200,t);osc.frequency.exponentialRampToValueAtTime(30,t+0.3);
      gain.gain.setValueAtTime(0.2,t);gain.gain.exponentialRampToValueAtTime(0.001,t+0.3);
      osc.start(t);osc.stop(t+0.3);break;
    case 'powerup':
      osc.type='sine';osc.frequency.setValueAtTime(400,t);osc.frequency.exponentialRampToValueAtTime(1200,t+0.2);
      gain.gain.setValueAtTime(0.15,t);gain.gain.exponentialRampToValueAtTime(0.001,t+0.25);
      osc.start(t);osc.stop(t+0.25);break;
    case 'jump':
      osc.type='sine';osc.frequency.setValueAtTime(300,t);osc.frequency.exponentialRampToValueAtTime(600,t+0.1);
      gain.gain.setValueAtTime(0.1,t);gain.gain.exponentialRampToValueAtTime(0.001,t+0.1);
      osc.start(t);osc.stop(t+0.1);break;
    case 'hit':
      osc.type='square';osc.frequency.setValueAtTime(150,t);osc.frequency.exponentialRampToValueAtTime(50,t+0.2);
      gain.gain.setValueAtTime(0.2,t);gain.gain.exponentialRampToValueAtTime(0.001,t+0.2);
      osc.start(t);osc.stop(t+0.2);break;
    case 'boss':
      osc.type='sawtooth';osc.frequency.setValueAtTime(100,t);osc.frequency.exponentialRampToValueAtTime(40,t+0.5);
      gain.gain.setValueAtTime(0.25,t);gain.gain.exponentialRampToValueAtTime(0.001,t+0.5);
      osc.start(t);osc.stop(t+0.5);break;
  }
}

// 键盘输入
const keys = {};
window.addEventListener('keydown', e=>{keys[e.code]=true;e.preventDefault()});
window.addEventListener('keyup', e=>{keys[e.code]=false;e.preventDefault()});

// 关卡配置
const MAX_STAGE = 3;
let currentStage = 1;
const stageConfig = [
  {
    bgTop: '#0d1b2a', bgMid: '#1b2838', bgLow: '#2d4a3e', bgGround: '#1a3a2a',
    wallType: 'wall', enemyHpMult: 1, levelWidth: 6400, wallX: [800,1600,2400,3600,4800]
  },
  {
    bgTop: '#121212', bgMid: '#212121', bgLow: '#37474f', bgGround: '#263238',
    wallType: 'steelWall', enemyHpMult: 2, levelWidth: 6800, wallX: [600,1400,2200,3000,4000,5000]
  },
  {
    bgTop: '#0a192f', bgMid: '#112240', bgLow: '#233554', bgGround: '#1d3557',
    wallType: 'iceWall', enemyHpMult: 1, levelWidth: 7200, wallX: [750,1550,2350,3150,4150,5150]
  }
];

// 全局游戏变量
let gameState = 'title';
let score = 0, lives = 99, hiScore = parseInt(localStorage.getItem('contraHi') || '0');
let cameraX = 0;
let LEVEL_W = stageConfig[currentStage - 1].levelWidth;
const GRAVITY = 0.5;
let frameCount = 0;
let screenShake = 0;
let bossDefeated = false;
const platforms = [];

// 生成地图
function generateLevel(stageNum){
  platforms.length = 0;
  const cfg = stageConfig[stageNum - 1];
  LEVEL_W = cfg.levelWidth;
  // 地面
  for(let x = 0; x < LEVEL_W; x += 200){
    if((x>400&&x<600) || (x>1800&&x<2000) || (x>3200&&x<3400)) continue;
    platforms.push({x, y: GAME_H - 40, w:200, h:40, type:'ground'});
  }
  // 浮空平台
  let floatList;
  if(stageNum === 1){
    floatList = [
      {x:300,y:320,w:120},{x:500,y:280,w:100},{x:700,y:240,w:140},
      {x:950,y:300,w:100},{x:1100,y:220,w:120},{x:1300,y:280,w:100},
      {x:1500,y:200,w:140},{x:1700,y:320,w:100},{x:1900,y:260,w:120},
      {x:2100,y:180,w:100},{x:2300,y:300,w:140},{x:2500,y:240,w:100},
      {x:2700,y:320,w:120},{x:2900,y:200,w:100},{x:3100,y:280,w:140},
      {x:3300,y:160,w:100},{x:3500,y:300,w:120},{x:3700,y:240,w:100},
      {x:3900,y:320,w:140},{x:4100,y:200,w:100},{x:4300,y:280,w:120},
      {x:4500,y:160,w:100},{x:4700,y:300,w:140},{x:4900,y:240,w:100},
      {x:5100,y:320,w:120},{x:5300,y:200,w:100},{x:5500,y:280,w:140},
      {x:5700,y:160,w:120},{x:5900,y:300,w:100}
    ];
  }else if(stageNum === 2){
    floatList = [
      {x:200,y:340,w:100},{x:450,y:260,w:130},{x:680,y:200,w:90},
      {x:900,y:360,w:110},{x:1200,y:240,w:120},{x:1500,y:180,w:100},
      {x:1800,y:320,w:140},{x:2100,y:220,w:90},{x:2400,y:380,w:120},
      {x:2700,y:260,w:110},{x:3000,y:190,w:130},{x:3300,y:340,w:100},
      {x:3600,y:210,w:120},{x:3900,y:300,w:90},{x:4200,y:170,w:140},
      {x:4500,y:350,w:110},{x:4800,y:230,w:100},{x:5100,y:310,w:130},
      {x:5400,y:180,w:90},{x:5700,y:290,w:120}
    ];
  }else{
    floatList = [
      {x:250,y:360,w:80},{x:420,y:290,w:110},{x:630,y:210,w:90},{x:840,y:140,w:120},
      {x:1050,y:330,w:100},{x:1260,y:250,w:80},{x:1470,y:170,w:110},{x:1680,y:100,w:90},
      {x:1900,y:350,w:120},{x:2100,y:270,w:100},{x:2320,y:190,w:80},{x:2530,y:120,w:110},
      {x:2740,y:320,w:90},{x:2950,y:240,w:120},{x:3160,y:160,w:100},{x:3370,y:90,w:80},
      {x:3600,y:340,w:110},{x:3810,y:260,w:90},{x:4020,y:180,w:120},{x:4230,y:110,w:100},
      {x:4440,y:310,w:80},{x:4650,y:230,w:110},{x:4860,y:150,w:90},{x:5070,y:80,w:120},
      {x:5300,y:300,w:100},{x:5510,y:220,w:80},{x:5720,y:140,w:110},{x:5930,y:70,w:90}
    ];
  }
  floatList.forEach(f=>platforms.push({x:f.x,y:f.y,w:f.w,h:16,type:'platform'}));
  // 墙体
  cfg.wallX.forEach(x=>platforms.push({x, y:GAME_H-120, w:30, h:80, type:cfg.wallType}));
}

// 玩家对象
const player = {
  x:100,y:300,w:28,h:40,vx:0,vy:0,
  dir:1,onGround:false,jumping:false,
  shooting:false,shootTimer:0,shootCooldown:8,
  weapon:'normal',weaponTimer:0,invincible:0,
  prone:false,animFrame:0,alive:true,respawnTimer:0,_bombUsed:false,
  jumpCount:0
};
let playerBullets = [], enemyBullets = [];
let enemies = [], boss = null;
let powerups = [], explosions = [], particles = [];

// 生成怪物道具
function spawnEnemies(stageNum){
  enemies.length = 0; powerups.length = 0;
  const cfg = stageConfig[stageNum - 1];
  const hpMult = cfg.enemyHpMult;
  let soldierCount, runnerCount;
  if(stageNum === 1){soldierCount=40;runnerCount=15;}
  else if(stageNum === 2){soldierCount=45;runnerCount=20;}
  else{soldierCount=35;runnerCount=0;}
  // 步兵
  for(let i=0;i<soldierCount;i++){
    let ex = 600 + i*140 + Math.random()*60;
    enemies.push({
      type:'soldier',x:ex,y:GAME_H-80,w:24,h:36,hp:1*hpMult,dir:-1,vx:0,vy:0,onGround:false,
      shootTimer:Math.random()*120,alive:true,animFrame:0,patrol:ex-60,patrolMax:ex+60,state:'patrol'
    });
  }
  // 炮台
  let turretX;
  if(stageNum ===1) turretX = [1000,1800,2600,3400,4200,5000,5600];
  else if(stageNum===2) turretX = [900,1700,2500,3300,4100,4900,5500];
  else turretX = [1100,1900,2700,3500,4300,5100];
  turretX.forEach(tx=>enemies.push({
    type:'turret',x:tx,y:GAME_H-72,w:32,h:32,hp:3*hpMult,dir:-1,shootTimer:60,alive:true,angle:0
  }));
  // 冲刺兵
  for(let i=0;i<runnerCount;i++){
    let ex = 1200 + i*350 + Math.random()*100;
    enemies.push({
      type:'runner',x:ex,y:GAME_H-76,w:22,h:34,hp:1*hpMult,dir:-1,vx:-3,vy:0,onGround:false,alive:true,animFrame:0
    });
  }
  // 第三关飞行怪
  if(stageNum === 3){
    for(let i=0;i<22;i++){
      let ex = 800 + i*280 + Math.random()*90;
      let baseY = 120 + Math.random()*180;
      enemies.push({
        type:'flyer',x:ex,y:baseY,w:26,h:22,hp:1,dir:-1,vx:-2,vy:0,baseY,alive:true,animFrame:0
      });
    }
  }
  // 道具
  [700,1400,2200,3000,3800,4600,5400].forEach((px,i)=>{
    const types = ['spread','rapid','laser','life','shield'];
    powerups.push({x:px,y:180+Math.random()*80,w:24,h:24,type:types[i%types.length],alive:true,bobTimer:Math.random()*100});
  });
}
// 生成BOSS
function spawnBoss(){
  boss = {
    x:LEVEL_W-300,y:GAME_H-200,w:120,h:160,hp:50,maxHp:50,phase:0,
    shootTimer:0,moveTimer:0,alive:true,dir:-1,vy:0
  };
}
// 碰撞检测
function rectCollide(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;
}
// 地面碰撞修正
function resolveCollision(entity){
  entity.onGround = false;
  for(let p of platforms){
    if(rectCollide(entity,p)){
      let ox = Math.min(entity.x + entity.w - p.x, p.x + p.w - entity.x);
      let oy = Math.min(entity.y + entity.h - p.y, p.y + p.h - entity.y);
      if(oy < ox){
        if(entity.y + entity.h / 2 < p.y + p.h / 2){
          entity.y = p.y - entity.h;
          entity.vy = 0;
          entity.onGround = true;
          if(entity === player) player.jumpCount = 0;
        }else{
          entity.y = p.y + p.h;
          entity.vy = 0;
        }
      }else{
        if(entity.x + entity.w / 2 < p.x + p.w / 2) entity.x = p.x - entity.w;
        else entity.x = p.x + p.w;
        entity.vx = 0;
      }
    }
  }
}

// ========== 绘制函数 ==========
function drawPixelChar(x,y,dir,frame,prone,shooting){
  const sx = x - cameraX;
  if(sx < -50 || sx > GAME_W+50) return;
  ctx.save();
  ctx.translate(sx+14,y);
  ctx.scale(dir,1);
  if(prone){
    ctx.fillStyle='#1e88e5'; ctx.fillRect(-14,24,28,12);
    ctx.fillStyle='#ffcc80'; ctx.fillRect(10,22,10,10);
    ctx.fillStyle='#1565c0'; ctx.fillRect(-14,28,28,8);
    ctx.fillStyle='#757575'; ctx.fillRect(18,26,16,4);
    if(shooting) ctx.fillStyle='#ffeb3b',ctx.fillRect(34,24,6,8);
  }else{
    ctx.fillStyle='#ffcc80'; ctx.fillRect(-6,0,12,12);
    ctx.fillStyle='#e53935'; ctx.fillRect(-7,0,14,4); ctx.fillRect(-9,2,4,3);
    ctx.fillStyle='#000'; ctx.fillRect(2,5,3,3);
    ctx.fillStyle='#1e88e5'; ctx.fillRect(-8,12,16,14);
    ctx.fillStyle='#795548'; ctx.fillRect(-8,24,16,3);
    let legOff = Math.sin(frame*0.3)*4;
    ctx.fillStyle='#1565c0'; ctx.fillRect(-7,27,6,13+legOff); ctx.fillRect(1,27,6,13-legOff);
    ctx.fillStyle='#4e342e'; ctx.fillRect(-8,37,7,3); ctx.fillRect(1,37,7,3);
    ctx.fillStyle='#757575';
    if(shooting){ ctx.fillRect(8,14,18,4); ctx.fillStyle='#ffeb3b'; ctx.fillRect(26,12,6,8); }
    else ctx.fillRect(8,16,14,3);
    ctx.fillStyle='#ffcc80'; ctx.fillRect(6,14,4,6);
  }
  ctx.restore();
}
function drawSoldier(e){
  const sx = e.x - cameraX;
  if(sx < -50 || sx > GAME_W+50) return;
  ctx.save(); ctx.translate(sx+12,e.y); ctx.scale(e.dir,1);
  ctx.fillStyle='#4caf50'; ctx.fillRect(-5,0,10,10);
  ctx.fillStyle='#2e7d32'; ctx.fillRect(-6,-2,12,6);
  ctx.fillStyle='#f00'; ctx.fillRect(1,4,3,2);
  ctx.fillStyle='#388e3c'; ctx.fillRect(-7,10,14,14);
  let lo = Math.sin(e.animFrame*0.2)*3;
  ctx.fillStyle='#1b5e20'; ctx.fillRect(-6,24,5,12+lo); ctx.fillRect(1,24,5,12-lo);
  ctx.fillStyle='#616161'; ctx.fillRect(7,14,12,3);
  ctx.restore();
}
function drawTurret(e){
  const sx = e.x - cameraX;
  if(sx < -50 || sx > GAME_W+50) return;
  ctx.fillStyle='#616161'; ctx.fillRect(sx,e.y+16,32,16);
  ctx.fillStyle='#757575'; ctx.beginPath(); ctx.arc(sx+16,e.y+16,14,Math.PI,0); ctx.fill();
  ctx.save(); ctx.translate(sx+16,e.y+14); ctx.rotate(e.angle);
  ctx.fillStyle='#424242'; ctx.fillRect(0,-3,24,6); ctx.restore();
  ctx.fillStyle = frameCount%30<15 ? '#f44336' : '#ff8a80';
  ctx.fillRect(sx+13,e.y+6,6,4);
}
function drawRunner(e){
  const sx = e.x - cameraX;
  if(sx < -50 || sx > GAME_W+50) return;
  ctx.save(); ctx.translate(sx+11,e.y); ctx.scale(e.dir,1);
  ctx.fillStyle='#ff6f00'; ctx.fillRect(-5,0,10,10);
  ctx.fillStyle='#fff'; ctx.fillRect(1,3,4,4); ctx.fillStyle='#f00'; ctx.fillRect(2,4,2,2);
  ctx.fillStyle='#e65100'; ctx.fillRect(-6,10,12,12);
  let lo = Math.sin(e.animFrame*0.5)*6;
  ctx.fillStyle='#bf360c'; ctx.fillRect(-5,22,4,12+lo); ctx.fillRect(1,22,4,12-lo);
  ctx.fillStyle='#ff3d00'; ctx.fillRect(6,12,8,3);
  ctx.restore();
}
function drawFlyer(e){
  const sx = e.x - cameraX;
  if(sx < -50 || sx > GAME_W+50) return;
  ctx.save(); ctx.translate(sx+13,e.y); ctx.scale(e.dir,1);
  let wing = Math.sin(e.animFrame*0.4)*5;
  ctx.fillStyle='#78909c'; ctx.fillRect(-13+wing,8,10,6);
  ctx.fillRect(3-wing,8,10,6);
  ctx.fillStyle='#455a64'; ctx.fillRect(-8,2,16,14);
  ctx.fillStyle='#f44336'; ctx.fillRect(0,4,4,4);
  ctx.fillStyle='#263238'; ctx.fillRect(-6,14,12,6);
  ctx.restore();
}
function drawBoss(b){
  if(!b||!b.alive) return;
  const sx = b.x - cameraX;
  ctx.fillStyle='#455a64'; ctx.fillRect(sx,b.y,b.w,b.h);
  ctx.fillStyle='#37474f'; ctx.fillRect(sx+10,b.y+10,b.w-20,30);
  ctx.fillRect(sx+10,b.y+60,b.w-20,30); ctx.fillRect(sx+10,b.y+110,b.w-20,30);
  let coreGlow = Math.sin(frameCount*0.1)*0.3+0.7;
  ctx.fillStyle=`rgba(244,67,54,${coreGlow})`; ctx.beginPath(); ctx.arc(sx+b.w/2,b.y+b.h/2,20,0,Math.PI*2); ctx.fill();
  ctx.fillStyle='#ff8a80'; ctx.beginPath(); ctx.arc(sx+b.w/2,b.y+b.h/2,10,0,Math.PI*2); ctx.fill();
  ctx.fillStyle='#263238'; ctx.fillRect(sx-20,b.y+20,30,12); ctx.fillRect(sx-20,b.y+b.h-40,30,12);
  ctx.fillRect(sx+b.w-10,b.y+40,30,12); ctx.fillRect(sx+b.w-10,b.y+b.h-60,30,12);
  ctx.fillStyle='#f44336'; ctx.fillRect(sx+25,b.y+15,12,8); ctx.fillRect(sx+b.w-37,b.y+15,12,8);
  ctx.fillStyle='#333'; ctx.fillRect(sx,b.y-20,b.w,10);
  ctx.fillStyle = b.hp > b.maxHp*0.3 ? '#f44336' : '#ff1744';
  ctx.fillRect(sx+1,b.y-19,(b.w-2)*(b.hp/b.maxHp),8);
  ctx.fillStyle='#fff'; ctx.font='10px monospace'; ctx.fillText(`STAGE${currentStage} BOSS`,sx+10,b.y-24);
}
function drawBackground(){
  const cfg = stageConfig[currentStage-1];
  const grad = ctx.createLinearGradient(0,0,0,GAME_H);
  grad.addColorStop(0,cfg.bgTop); grad.addColorStop(0.4,cfg.bgMid); grad.addColorStop(0.7,cfg.bgLow); grad.addColorStop(1,cfg.bgGround);
  ctx.fillStyle=grad; ctx.fillRect(0,0,GAME_W,GAME_H);
  ctx.fillStyle='#fff';
  for(let i=0;i<60;i++){
    let sx = ((i*137+50)%GAME_W + GAME_W - cameraX*0.05%GAME_W)%GAME_W;
    let sy = (i*89+20)%(GAME_H*0.5);
    let s = i%3===0 ? 2 : 1;
    ctx.globalAlpha = 0.3 + Math.sin(frameCount*0.02+i)*0.3;
    ctx.fillRect(sx,sy,s,s);
  }
  ctx.globalAlpha=1;
  if(currentStage ===1){
    ctx.fillStyle='#1a3328';
    for(let i=0;i<12;i++){
      let mx = i*200 - (cameraX*0.15)%2400;
      let mh = 80 + (i*47)%60;
      ctx.beginPath(); ctx.moveTo(mx,GAME_H-40); ctx.lineTo(mx+100,GAME_H-40-mh); ctx.lineTo(mx+200,GAME_H-40); ctx.fill();
    }
    ctx.fillStyle='#0d2818';
    for(let i=0;i<20;i++){
      let tx = i*150 - (cameraX*0.3)%3000;
      let th = 40 + (i*31)%30;
      ctx.fillRect(tx+15,GAME_H-40-th,10,th);
      ctx.beginPath(); ctx.moveTo(tx,GAME_H-40-th+10); ctx.lineTo(tx+20,GAME_H-40-th-25); ctx.lineTo(tx+40,GAME_H-40-th+10); ctx.fill();
    }
  }else if(currentStage ===2){
    ctx.fillStyle='#263238';
    for(let i=0;i<15;i++){
      let mx = i*180 - (cameraX*0.15)%2700;
      let mh = 70 + (i*41)%50;
      ctx.fillRect(mx,GAME_H-40-mh,90,mh);
    }
  }else{
    ctx.fillStyle='#b3e5fc';
    for(let i=0;i<14;i++){
      let mx = i*190 - (cameraX*0.15)%2660;
      let mh = 90 + (i*53)%60;
      ctx.beginPath(); ctx.moveTo(mx,GAME_H-40); ctx.lineTo(mx+95,GAME_H-40-mh); ctx.lineTo(mx+190,GAME_H-40); ctx.fill();
    }
  }
}
function drawPlatforms(){
  for(let p of platforms){
    let sx = p.x - cameraX;
    if(sx > GAME_W+10 || sx+p.w < -10) continue;
    if(p.type === 'ground'){
      ctx.fillStyle='#3e2723'; ctx.fillRect(sx,p.y,p.w,p.h);
      ctx.fillStyle='#4caf50'; ctx.fillRect(sx,p.y,p.w,6); ctx.fillStyle='#2e7d32'; ctx.fillRect(sx,p.y+6,p.w,4);
      ctx.fillStyle='#5d4037'; for(let dx=0;dx<p.w;dx+=20){ ctx.fillRect(sx+dx+5,p.y+15,8,4); ctx.fillRect(sx+dx+2,p.y+25,6,3); }
    }else if(p.type === 'platform'){
      ctx.fillStyle='#78909c'; ctx.fillRect(sx,p.y,p.w,p.h);
      ctx.fillStyle='#90a4ae'; ctx.fillRect(sx,p.y,p.w,4); ctx.fillStyle='#546e7a'; ctx.fillRect(sx+2,p.y+6,p.w-4,4);
      ctx.fillStyle='#b0bec5'; ctx.fillRect(sx+4,p.y+2,3,3); ctx.fillRect(sx+p.w-7,p.y+2,3,3);
    }else if(p.type === 'wall'){
      ctx.fillStyle='#5d4037'; ctx.fillRect(sx,p.y,p.w,p.h);
      ctx.fillStyle='#6d4c41'; for(let dy=0;dy<p.h;dy+=12){ ctx.fillRect(sx,p.y+dy,p.w,1); ctx.fillRect(sx+p.w/2,p.y+dy,1,12); }
    }else if(p.type === 'steelWall'){
      ctx.fillStyle='#263238'; ctx.fillRect(sx,p.y,p.w,p.h);
      ctx.fillStyle='#455a64'; for(let dy=0;dy<p.h;dy+=15){ ctx.fillRect(sx,p.y+dy,p.w,2); ctx.fillRect(sx+p.w/2,p.y+dy,1,15); }
    }else if(p.type === 'iceWall'){
      ctx.fillStyle='#1565c0'; ctx.fillRect(sx,p.y,p.w,p.h);
      ctx.fillStyle='#64b5f6'; for(let dy=0;dy<p.h;dy+=14){ ctx.fillRect(sx,p.y+dy,p.w,2); ctx.fillRect(sx+p.w/2,p.y+dy,1,14); }
    }
  }
}
function drawPowerups(){
  for(let p of powerups){
    if(!p.alive) continue;
    let sx = p.x - cameraX;
    if(sx < -30 || sx > GAME_W+30) continue;
    let bob = Math.sin((frameCount+p.bobTimer)*0.05)*4;
    let py = p.y + bob;
    ctx.fillStyle='#e53935'; ctx.fillRect(sx-2,py-2,28,28);
    ctx.fillStyle='#c62828'; ctx.fillRect(sx,py,24,24);
    ctx.fillStyle='#fff'; ctx.font='bold 14px monospace';
    const letterMap = {spread:'S',rapid:'R',laser:'L',life:'1UP',shield:'SH'};
    ctx.fillText(letterMap[p.type]||'?',sx+4,py+17);
    ctx.globalAlpha = 0.3 + Math.sin(frameCount*0.1)*0.2;
    ctx.fillStyle='#ffeb3b'; ctx.fillRect(sx-4,py-4,32,32);
    ctx.globalAlpha=1;
  }
}
function drawBullets(){
  for(let b of playerBullets){
    let sx = b.x - cameraX;
    if(sx < -10 || sx > GAME_W+10) continue;
    if(b.type === 'laser'){
      ctx.fillStyle='#00e5ff'; ctx.fillRect(sx,b.y-1,b.w,3); ctx.fillStyle='#b2ebf2'; ctx.fillRect(sx,b.y,b.w,1);
    }else if(b.type === 'spread'){
      ctx.fillStyle='#ff9800'; ctx.beginPath(); ctx.arc(sx+b.w/2,b.y+b.h/2,3,0,Math.PI*2); ctx.fill();
    }else{
      ctx.fillStyle='#ffeb3b'; ctx.fillRect(sx,b.y,b.w,b.h); ctx.fillStyle='#fff'; ctx.fillRect(sx+1,b.y+1,b.w-2,b.h-2);
    }
  }
  for(let b of enemyBullets){
    let sx = b.x - cameraX;
    if(sx < -10 || sx > GAME_W+10) continue;
    ctx.fillStyle='#f44336'; ctx.beginPath(); ctx.arc(sx+3,b.y+3,4,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#ff8a80'; ctx.beginPath(); ctx.arc(sx+3,b.y+3,2,0,Math.PI*2); ctx.fill();
  }
}
function drawExplosions(){
  for(let i=explosions.length-1;i>=0;i--){
    let e = explosions[i];
    let sx = e.x - cameraX;
    let prog = e.timer / e.maxTimer;
    let r = e.radius * (1-prog*0.5);
    ctx.globalAlpha = prog;
    ctx.fillStyle='#ff6f00'; ctx.beginPath(); ctx.arc(sx,e.y,r,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#ffeb3b'; ctx.beginPath(); ctx.arc(sx,e.y,r*0.6,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(sx,e.y,r*0.2,0,Math.PI*2); ctx.fill();
    ctx.globalAlpha=1;
    e.timer--;
    if(e.timer <= 0) explosions.splice(i,1);
  }
}
function drawParticles(){
  for(let i=particles.length-1;i>=0;i--){
    let p = particles[i];
    let sx = p.x - cameraX;
    ctx.globalAlpha = p.life / p.maxLife;
    ctx.fillStyle = p.color; ctx.fillRect(sx,p.y,p.size,p.size);
    p.x += p.vx; p.y += p.vy; p.vy += 0.1; p.life--;
    if(p.life <= 0) particles.splice(i,1);
  }
  ctx.globalAlpha=1;
}
function drawHUD(){
  ctx.fillStyle='rgba(0,0,0,0.5)'; ctx.fillRect(0,0,GAME_W,36);
  ctx.fillStyle='#fff'; ctx.font='bold 16px monospace';
  ctx.fillText(`SCORE: ${score.toString().padStart(8,'0')}`,10,24);
  ctx.fillText(`HI: ${hiScore.toString().padStart(8,'0')}`,220,24);
  ctx.fillStyle='#4fc3f7'; ctx.fillText(`STAGE ${currentStage}/${MAX_STAGE}`,340,24);
  ctx.fillStyle='#e53935';
  for(let i=0;i<Math.min(lives,20);i++){
    ctx.fillRect(460+i*22,10,14,16); ctx.fillStyle='#ffcc80'; ctx.fillRect(463+i*22,6,8,8); ctx.fillStyle='#e53935';
  }
  ctx.fillStyle='#ffeb3b'; ctx.font='12px monospace';
  const weaponName = {normal:'NORMAL',spread:'SPREAD',rapid:'RAPID',laser:'LASER'};
  ctx.fillText(`WEAPON: ${weaponName[player.weapon]}`,580,24);
  if(player.weaponTimer>0 && player.weapon!=='normal'){
    ctx.fillStyle='#333'; ctx.fillRect(580,28,80,4);
    ctx.fillStyle='#ffeb3b'; ctx.fillRect(580,28,80*(player.weaponTimer/600),4);
  }
  ctx.fillStyle='#333'; ctx.fillRect(GAME_W-160,14,150,8);
  ctx.fillStyle='#4caf50'; ctx.fillRect(GAME_W-160,14,150*Math.min(1,cameraX/(LEVEL_W-GAME_W)),8);
  ctx.fillStyle='#fff'; ctx.font='8px monospace'; ctx.fillText('PROGRESS',GAME_W-145,12);
}
function drawTitle(){
  ctx.fillStyle='#0a0a0a'; ctx.fillRect(0,0,GAME_W,GAME_H);
  ctx.strokeStyle='rgba(229,57,53,0.1)'; ctx.lineWidth=1;
  for(let i=0;i<20;i++){ let y=(i*30+frameCount*0.5)%GAME_H; ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(GAME_W,y); ctx.stroke(); }
  ctx.save(); ctx.textAlign='center';
  let titleY = 100 + Math.sin(frameCount*0.03)*8;
  ctx.fillStyle='#e53935'; ctx.font='bold 72px monospace'; ctx.fillText('CONTRA',GAME_W/2,titleY);
  ctx.fillStyle='#b71c1c'; ctx.fillText('CONTRA',GAME_W/2+3,titleY+3);
  ctx.fillStyle='#ffeb3b'; ctx.font='bold 20px monospace'; ctx.fillText('魂 斗 罗 三关完整版',GAME_W/2,titleY+40);
  if(frameCount%60 < 40){ ctx.fillStyle='#fff'; ctx.font='18px monospace'; ctx.fillText('PRESS ENTER TO START',GAME_W/2,280); }
  ctx.fillStyle='#90a4ae'; ctx.font='13px monospace';
  ctx.fillText('← → / A D : MOVE    ↑ / W / SPACE : 二段跳    ↓ / S : PRONE',GAME_W/2,340);
  ctx.fillText('Z : SHOOT    X : BOMB(清屏炸弹)',GAME_W/2,365);
  ctx.fillStyle='#ffeb3b'; ctx.font='14px monospace'; ctx.fillText(`HI-SCORE: ${hiScore.toString().padStart(8,'0')}`,GAME_W/2,420);
  ctx.restore();
}
function drawGameOver(){
  ctx.fillStyle='rgba(0,0,0,0.7)'; ctx.fillRect(0,0,GAME_W,GAME_H);
  ctx.save(); ctx.textAlign='center';
  ctx.fillStyle='#e53935'; ctx.font='bold 56px monospace'; ctx.fillText('GAME OVER',GAME_W/2,GAME_H/2-30);
  ctx.fillStyle='#fff'; ctx.font='20px monospace'; ctx.fillText(`FINAL SCORE: ${score.toString().padStart(8,'0')}`,GAME_W/2,GAME_H/2+20);
  if(frameCount%60<40){ ctx.fillStyle='#ffeb3b'; ctx.font='16px monospace'; ctx.fillText('PRESS ENTER TO RETURN TITLE',GAME_W/2,GAME_H/2+70); }
  ctx.restore();
}
function drawWin(){
  ctx.fillStyle='rgba(0,0,0,0.7)'; ctx.fillRect(0,0,GAME_W,GAME_H);
  ctx.save(); ctx.textAlign='center';
  ctx.fillStyle='#4caf50'; ctx.font='bold 48px monospace'; ctx.fillText('ALL MISSIONS COMPLETE!',GAME_W/2,GAME_H/2-40);
  ctx.fillStyle='#ffeb3b'; ctx.font='bold 24px monospace'; ctx.fillText(`TOTAL SCORE: ${score.toString().padStart(8,'0')}`,GAME_W/2,GAME_H/2+10);
  ctx.fillStyle='#fff'; ctx.font='16px monospace'; ctx.fillText('ALL 3 STAGES CLEARED, ALIEN FORCES DEFEATED!',GAME_W/2,GAME_H/2+50);
  if(frameCount%60<40){ ctx.fillStyle='#90a4ae'; ctx.font='14px monospace'; ctx.fillText('PRESS ENTER TO PLAY AGAIN',GAME_W/2,GAME_H/2+90); }
  ctx.restore();
}

// 玩家死亡
function playerDie(){
  if(player.invincible > 0) return;
  player.alive = false;
  player.respawnTimer = 90;
  screenShake = 20;
  playSound('hit');
  explosions.push({x:player.x,y:player.y+20,radius:25,timer:25,maxTimer:25});
  for(let i=0;i<15;i++){
    particles.push({
      x:player.x+14,y:player.y+20,
      vx:(Math.random()-0.5)*6,vy:(Math.random()-0.5)*6-3,
      size:2+Math.random()*3,color:['#e53935','#ffeb3b','#ff9800'][Math.floor(Math.random()*3)],
      life:30+Math.random()*20,maxLife:50
    });
  }
}
// 玩家射击
function shoot(){
  const bx = player.x + (player.dir===1 ? player.w : -8);
  const by = player.y + (player.prone ? 26 : 14);
  let bvx = player.dir * 8;
  let bvy = 0;
  if(keys['ArrowUp'] || keys['KeyW']){ bvy = -6; bvx *= 0.6; }
  if(player.weapon === 'spread'){
    playSound('spread');
    for(let a=-0.3;a<=0.3;a+=0.15){
      playerBullets.push({
        x:bx,y:by,w:6,h:6,
        vx:bvx*Math.cos(a) - bvy*Math.sin(a),
        vy:bvx*Math.sin(a) + bvy*Math.cos(a),
        type:'spread',life:60
      });
    }
  }else if(player.weapon === 'laser'){
    playSound('spread');
    playerBullets.push({x:bx,y:by,w:30,h:3,vx:bvx*1.5,vy:bvy*1.5,type:'laser',life:40});
  }else{
    playSound('shoot');
    playerBullets.push({x:bx,y:by,w:8,h:3,vx:bvx,vy:bvy,type:'normal',life:80});
  }
  player.shooting = true;
  setTimeout(()=>player.shooting=false,80);
}

// 更新玩家逻辑
function updatePlayer(){
  if(!player.alive){
    player.respawnTimer--;
    if(player.respawnTimer <= 0){
      player.alive = true;
      player.x = cameraX + 100;
      player.y = 200;
      player.vx = 0; player.vy = 0;
      player.invincible = 120;
      player.weapon = 'normal';
    }
    return;
  }
  const spd = 3;
  if(keys['ArrowLeft']||keys['KeyA']){ player.vx=-spd; player.dir=-1; }
  else if(keys['ArrowRight']||keys['KeyD']){ player.vx=spd; player.dir=1; }
  else player.vx=0;
  player.prone = (keys['ArrowDown']||keys['KeyS']) && player.onGround;
  if(player.prone){ player.h=20; player.vx*=0.5; }else player.h=40;

  if(keys['Space'] || keys['ArrowUp'] || keys['KeyW']){
    if(player.onGround){
      player.vy = -10;
      player.jumpCount = 1;
      player.onGround = false;
      playSound('jump');
    }else if(player.jumpCount === 1){
      player.vy = -9;
      player.jumpCount = 2;
      playSound('jump');
    }
    keys['Space'] = false;
    keys['ArrowUp'] = false;
    keys['KeyW'] = false;
  }

  player.vy += GRAVITY;
  player.x += player.vx;
  player.y += player.vy;
  resolveCollision(player);
  if(player.x < cameraX) player.x = cameraX;
  if(player.y > GAME_H+50){ playerDie(); return; }

  if(player.shootTimer>0) player.shootTimer--;
  let cd = player.weapon === 'rapid' ? 4 : player.shootCooldown;
  if(keys['KeyZ'] && player.shootTimer <= 0){ shoot(); player.shootTimer = cd; }

  if(keys['KeyX'] && !player._bombUsed){
    player._bombUsed = true;
    for(let e of enemies){
      if(e.alive && Math.abs(e.x-player.x)<400){
        e.alive = false; score += 100;
        explosions.push({x:e.x+e.w/2,y:e.y+e.h/2,radius:30,timer:20,maxTimer:20});
      }
    }
    if(boss && boss.alive){
      boss.hp -= 10;
      if(boss.hp <= 0){
        boss.alive = false; bossDefeated = true; score += 5000; screenShake = 30; playSound('explosion');
        for(let k=0;k<30;k++){
          explosions.push({x:boss.x+Math.random()*boss.w,y:boss.y+Math.random()*boss.h,radius:15+Math.random()*20,timer:20+Math.random()*20,maxTimer:40});
        }
        setTimeout(()=>{
          if(currentStage < MAX_STAGE){
            currentStage++;
            bossDefeated = false;
            cameraX = 0;
            player.x = 100; player.y = 300;
            generateLevel(currentStage);
            spawnEnemies(currentStage);
            boss = null;
          }else{
            gameState='win';
            if(score>hiScore){ hiScore=score; localStorage.setItem('contraHi',hiScore); }
          }
        },2000);
      }
    }
    screenShake=15; playSound('explosion');
  }
  if(!keys['KeyX']) player._bombUsed = false;
  if(player.invincible>0) player.invincible--;
  if(player.weaponTimer>0){ player.weaponTimer--; if(player.weaponTimer<=0) player.weapon='normal'; }
  
  // 相机跟随与边界
  let targetCam = player.x - GAME_W/3;
  cameraX += (targetCam - cameraX)*0.08;
  if(cameraX < 0) cameraX = 0;
  if(cameraX > LEVEL_W - GAME_W) cameraX = LEVEL_W - GAME_W;
  
  // 到达关卡末尾生成BOSS
  if(cameraX > LEVEL_W - GAME_W - 200 && !boss && !bossDefeated){
    spawnBoss();
  }
  
  if(Math.abs(player.vx) > 0.1 && player.onGround) player.animFrame++;
}

// 更新子弹
function updateBullets(){
  for(let i=playerBullets.length-1;i>=0;i--){
    let b = playerBullets[i];
    b.x += b.vx; b.y += b.vy; b.life--;
    if(b.life <= 0 || b.x < cameraX-50 || b.x > cameraX+GAME_W+50){
      playerBullets.splice(i,1); continue;
    }
    // 击中敌人
    for(let e of enemies){
      if(e.alive && rectCollide(b,e)){
        e.hp--;
        playerBullets.splice(i,1);
        if(e.hp <= 0){
          e.alive = false; score += e.type==='turret'?200:100;
          explosions.push({x:e.x+e.w/2,y:e.y+e.h/2,radius:20,timer:15,maxTimer:15});
          playSound('explosion');
        }
        break;
      }
    }
    // 击中BOSS
    if(boss && boss.alive && rectCollide(b,boss)){
      boss.hp--;
      playerBullets.splice(i,1);
      if(boss.hp <= 0){
        boss.alive = false; bossDefeated = true; score += 5000; screenShake = 30; playSound('explosion');
        for(let k=0;k<30;k++){
          explosions.push({x:boss.x+Math.random()*boss.w,y:boss.y+Math.random()*boss.h,radius:15+Math.random()*20,timer:20+Math.random()*20,maxTimer:40});
        }
        setTimeout(()=>{
          if(currentStage < MAX_STAGE){
            currentStage++;
            bossDefeated = false;
            cameraX = 0;
            player.x = 100; player.y = 300;
            generateLevel(currentStage);
            spawnEnemies(currentStage);
            boss = null;
          }else{
            gameState='win';
            if(score>hiScore){ hiScore=score; localStorage.setItem('contraHi',hiScore); }
          }
        },2000);
      }
    }
  }
  for(let i=enemyBullets.length-1;i>=0;i--){
    let b = enemyBullets[i];
    b.x += b.vx; b.y += b.vy; b.life--;
    if(b.life <= 0 || b.x < cameraX-50 || b.x > cameraX+GAME_W+50){
      enemyBullets.splice(i,1); continue;
    }
    if(player.alive && rectCollide(b,player)){
      enemyBullets.splice(i,1);
      playerDie();
    }
  }
}

// 更新敌人
function updateEnemies(){
  for(let e of enemies){
    if(!e.alive) continue;
    if(e.x < cameraX - 200 || e.x > cameraX + GAME_W + 200) continue;
    e.animFrame++;
    
    if(e.type === 'soldier'){
      e.vy += GRAVITY;
      e.y += e.vy;
      resolveCollision(e);
      // 巡逻
      if(e.x <= e.patrol){ e.dir = 1; e.vx = 1; }
      else if(e.x >= e.patrolMax){ e.dir = -1; e.vx = -1; }
      e.x += e.vx;
      // 看到玩家射击
      if(Math.abs(e.x - player.x) < 400 && Math.abs(e.y - player.y) < 100){
        e.dir = player.x > e.x ? 1 : -1;
        e.shootTimer--;
        if(e.shootTimer <= 0){
          e.shootTimer = 90;
          enemyBullets.push({x:e.x+e.w/2,y:e.y+12,w:6,h:6,vx:e.dir*4,vy:0,life:120});
        }
      }
    }else if(e.type === 'turret'){
      // 炮台瞄准
      let dx = player.x - e.x;
      let dy = player.y - e.y;
      e.angle = Math.atan2(dy, dx);
      e.shootTimer--;
      if(e.shootTimer <= 0 && Math.abs(e.x - player.x) < 500){
        e.shootTimer = 80;
        let spd = 4;
        enemyBullets.push({
          x:e.x+16,y:e.y+14,w:6,h:6,
          vx:Math.cos(e.angle)*spd, vy:Math.sin(e.angle)*spd, life:150
        });
      }
    }else if(e.type === 'runner'){
      e.vy += GRAVITY;
      e.y += e.vy;
      resolveCollision(e);
      e.x += e.vx;
    }else if(e.type === 'flyer'){
      e.y = e.baseY + Math.sin(e.animFrame*0.05)*30;
      e.x += e.vx;
      // 追踪玩家
      if(Math.abs(e.x - player.x) < 350){
        e.vy = (player.y - e.y) * 0.02;
        e.y += e.vy;
      }
      e.shootTimer = (e.shootTimer||100) - 1;
      if(e.shootTimer <= 0 && Math.abs(e.x - player.x) < 300){
        e.shootTimer = 110;
        let dx = player.x - e.x, dy = player.y - e.y;
        let dist = Math.sqrt(dx*dx+dy*dy);
        enemyBullets.push({x:e.x+13,y:e.y+11,w:6,h:6,vx:(dx/dist)*3.5,vy:(dy/dist)*3.5,life:150});
      }
    }
  }
}

// 更新BOSS
function updateBoss(){
  if(!boss || !boss.alive) return;
  boss.moveTimer++;
  boss.shootTimer++;
  // 上下移动
  boss.y = GAME_H - 200 + Math.sin(boss.moveTimer*0.02)*40;
  // 射击
  if(boss.shootTimer > 60){
    boss.shootTimer = 0;
    // 扇形弹幕
    for(let a=-0.4;a<=0.4;a+=0.2){
      let dx = player.x - boss.x, dy = player.y - boss.y;
      let baseAngle = Math.atan2(dy, dx);
      let angle = baseAngle + a;
      enemyBullets.push({
        x:boss.x+boss.w/2, y:boss.y+boss.h/2, w:8,h:8,
        vx:Math.cos(angle)*3.5, vy:Math.sin(angle)*3.5, life:200
      });
    }
    playSound('boss');
  }
}

// 更新道具
function updatePowerups(){
  for(let p of powerups){
    if(!p.alive) continue;
    if(rectCollide(player,p)){
      p.alive = false;
      playSound('powerup');
      switch(p.type){
        case 'spread': player.weapon='spread'; player.weaponTimer=600; break;
        case 'rapid': player.weapon='rapid'; player.weaponTimer=600; break;
        case 'laser': player.weapon='laser'; player.weaponTimer=600; break;
        case 'life': lives = Math.min(lives+1, 99); break;
        case 'shield': player.invincible = 300; break;
      }
    }
  }
}

// 游戏初始化
function initGame(){
  score = 0; lives = 99;
  currentStage = 1;
  cameraX = 0;
  bossDefeated = false;
  player.x = 100; player.y = 300;
  player.vx = 0; player.vy = 0;
  player.weapon = 'normal'; player.weaponTimer = 0;
  player.alive = true; player.invincible = 0;
  player.jumpCount = 0;
  playerBullets.length = 0; enemyBullets.length = 0;
  explosions.length = 0; particles.length = 0;
  generateLevel(1);
  spawnEnemies(1);
  boss = null;
  gameState = 'playing';
}

// 主循环
function gameLoop(){
  frameCount++;
  if(screenShake > 0) screenShake--;
  
  ctx.save();
  if(screenShake > 0){
    ctx.translate((Math.random()-0.5)*screenShake, (Math.random()-0.5)*screenShake);
  }

  if(gameState === 'title'){
    drawTitle();
    if(keys['Enter']){
      keys['Enter'] = false;
      initGame();
    }
  }else if(gameState === 'playing'){
    updatePlayer();
    updateBullets();
    updateEnemies();
    updateBoss();
    updatePowerups();
    
    drawBackground();
    drawPlatforms();
    drawPowerups();
    drawBullets();
    for(let e of enemies){
      if(!e.alive) continue;
      if(e.type==='soldier') drawSoldier(e);
      else if(e.type==='turret') drawTurret(e);
      else if(e.type==='runner') drawRunner(e);
      else if(e.type==='flyer') drawFlyer(e);
    }
    drawBoss(boss);
    if(player.alive && player.invincible%4<2){
      drawPixelChar(player.x,player.y,player.dir,player.animFrame,player.prone,player.shooting);
    }
    drawExplosions();
    drawParticles();
    drawHUD();
  }else if(gameState === 'gameover'){
    drawBackground();
    drawPlatforms();
    drawGameOver();
    if(keys['Enter']){
      keys['Enter'] = false;
      gameState = 'title';
    }
  }else if(gameState === 'win'){
    drawBackground();
    drawWin();
    if(keys['Enter']){
      keys['Enter'] = false;
      gameState = 'title';
    }
  }
  
  ctx.restore();
  requestAnimationFrame(gameLoop);
}

// 启动游戏
gameLoop();
</script>
</body>
</html>



根据楼主的代码,找豆包新增了两关卡并加二段跳和无限生命。

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
lep52 + 2 + 1 用心讨论,共获提升!

查看全部评分

推荐
gy123621 发表于 2026-7-2 15:13
低配版魂斗罗跟超级玛丽合体,死人吃了S都跳不过去
沙发
27149 发表于 2026-7-2 15:02
看截图,低配版魂斗罗跟超级玛丽合体,哈哈,好好!
3#
 楼主| Summer000 发表于 2026-7-2 15:03 |楼主
27149 发表于 2026-7-2 15:02
看截图,低配版魂斗罗跟超级玛丽合体,哈哈,好好!

哈哈哈哈,网页像素级游戏,体验体验
4#
czlhw 发表于 2026-7-2 15:08
老了,兴致不高啊
5#
justfate 发表于 2026-7-2 15:09
好好,好像我的世界
6#
 楼主| Summer000 发表于 2026-7-2 15:10 |楼主
czlhw 发表于 2026-7-2 15:08
老了,兴致不高啊

老了才回味啊,现在就喜欢一些之前的老物件,感觉都是青春的痕迹。
8#
 楼主| Summer000 发表于 2026-7-2 15:17 |楼主
gy123621 发表于 2026-7-2 15:13
低配版魂斗罗跟超级玛丽合体,死人吃了S都跳不过去

哈哈哈,你多试试,我试了试,能过第一关
9#
sztoplon 发表于 2026-7-2 15:32
可以可以,梦回童年~
10#
oasis0222 发表于 2026-7-2 15:38
哈哈哈可以可以{:1_937:}{:1_937:}{:1_937:}{:1_937:}{:1_937:}{:1_937:}
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-3 06:34

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表