吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 733|回复: 10
上一主题 下一主题
收起左侧

[学习记录] 梦回童年时光--融合魂斗罗和超级玛丽等多款游戏元素,等你来挖掘和完善~~

[复制链接]
跳转到指定楼层
楼主
许愿南海 发表于 2026-7-7 14:57 回帖奖励
[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>
//============================================================
// 魂斗玛丽罗 (全面重制版)
// 保留全部玩法:5关 · 武器系统 · 防御 · 巨大蘑菇 · BOSS战
// 视觉/音频/UI 全面升级
//============================================================
const ca=document.getElementById('game'),ct=ca.getContext('2d');
const W=800,H=480;ca.width=W;ca.height=H;
function rs(){let s=Math.min(innerWidth/W,innerHeight/H);ca.style.width=W*s+'px';ca.style.height=H*s+'px'}
addEventListener('resize',rs);rs();
 
// ==================== 音频引擎 ====================
const AC=window.AudioContext||window.webkitAudioContext;
let ac,bgmI=null,bgmP=false;
function ea(){if(!ac)ac=new AC();if(ac.state==='suspended')ac.resume()}
function ps(t){
  ea();const o=ac.createOscillator(),g=ac.createGain();o.connect(g);g.connect(ac.destination);let n=ac.currentTime;
  const F=(f0,f1,d,gv)=>{o.type='square';o.frequency.setValueAtTime(f0,n);o.frequency.exponentialRampToValueAtTime(f1,n+d);g.gain.setValueAtTime(gv,n);g.gain.exponentialRampToValueAtTime(0.001,n+d);o.start(n);o.stop(n+d)};
  const S=(f0,f1,d,gv)=>{o.type='sawtooth';o.frequency.setValueAtTime(f0,n);o.frequency.exponentialRampToValueAtTime(f1,n+d);g.gain.setValueAtTime(gv,n);g.gain.exponentialRampToValueAtTime(0.001,n+d);o.start(n);o.stop(n+d)};
  const I=(f0,f1,d,gv)=>{o.type='sine';o.frequency.setValueAtTime(f0,n);o.frequency.exponentialRampToValueAtTime(f1,n+d);g.gain.setValueAtTime(gv,n);g.gain.exponentialRampToValueAtTime(0.001,n+d);o.start(n);o.stop(n+d)};
  const N2=(ns,ty,du,gv)=>{ns.forEach((fr,j)=>{let o2=ac.createOscillator(),g2=ac.createGain();o2.connect(g2);g2.connect(ac.destination);o2.type=ty;o2.frequency.setValueAtTime(fr,n+j*du);g2.gain.setValueAtTime(gv,n+j*du);g2.gain.exponentialRampToValueAtTime(0.001,n+j*du+0.3);o2.start(n+j*du);o2.stop(n+j*du+0.3)})};
  switch(t){
    case'shoot':F(800,200,0.08,0.12);break;case'spread':S(600,100,0.12,0.1);break;
    case'explosion':S(200,30,0.3,0.2);break;case'powerup':I(400,1200,0.2,0.12);break;
    case'jump':I(300,600,0.1,0.08);break;case'hit':F(150,50,0.2,0.2);break;
    case'boss_hit':S(100,40,0.5,0.25);break;case'shield':I(500,1000,0.15,0.1);break;
    case'bump':F(400,100,0.1,0.1);break;
    case'coin':I(988,1976,0.06,0.12);break;
    case'stage_clear':N2([523,659,784,1047],'sine',0.12,0.15);break;
    case'gameover':N2([400,350,300,200],'sawtooth',0.2,0.12);break;
  }
}
function bStart(){
  if(bgmP)return;ea();bgmP=true;
  const bn=[130.81,146.83,164.81,174.61,196.00,174.61,164.81,146.83];
  const mn=[261.63,329.63,293.66,349.23,329.63,293.66,261.63,246.94,293.66,349.23,392.00,440.00,392.00,349.23,329.63,293.66];
  let sp=0;
  const pp=()=>{
    if(!bgmP)return;try{ea();const t=ac.currentTime;
      const b=ac.createOscillator(),bg=ac.createGain();b.type='triangle';b.connect(bg);bg.connect(ac.destination);
      b.frequency.setValueAtTime(bn[sp%bn.length],t);bg.gain.setValueAtTime(0.06,t);bg.gain.exponentialRampToValueAtTime(0.001,t+0.45);b.start(t);b.stop(t+0.45);
      if(sp%2===0){const m=ac.createOscillator(),mg=ac.createGain();m.type='square';m.connect(mg);mg.connect(ac.destination);let i=Math.floor(sp/2)%mn.length;m.frequency.setValueAtTime(mn[i],t);mg.gain.setValueAtTime(0.04,t);mg.gain.exponentialRampToValueAtTime(0.001,t+0.35);m.start(t);m.stop(t+0.35)}
      sp=(sp+1)%16}catch(e){}
  };
  if(bgmI)clearInterval(bgmI);bgmI=setInterval(pp,200);
}
function bStop(){bgmP=false;if(bgmI){clearInterval(bgmI);bgmI=null}}
 
// ==================== 输入 ====================
const keys={};
addEventListener('keydown',e=>{keys[e.code]=true;e.preventDefault()});
addEventListener('keyup',e=>{keys[e.code]=false;e.preventDefault()});
 
// ==================== 关卡配置 ====================
const MAX_S=5;let curS=1;
const sCfg=[
  {bgT:'#0d1b2a',bgM:'#1b2838',bgL:'#2d4a3e',bgG:'#1a3a2a',wT:'wall',eM:1,lW:6400,wX:[800,1600,2400,3600,4800]},
  {bgT:'#121212',bgM:'#212121',bgL:'#37474f',bgG:'#263238',wT:'steelWall',eM:2,lW:6800,wX:[600,1400,2200,3000,4000,5000]},
  {bgT:'#0a192f',bgM:'#112240',bgL:'#233554',bgG:'#1d3557',wT:'iceWall',eM:1,lW:7200,wX:[750,1550,2350,3150,4150,5150]},
  {bgT:'#1a0a0a',bgM:'#331111',bgL:'#552222',bgG:'#442211',wT:'steelWall',eM:2,lW:7600,wX:[500,1300,2100,2900,3700,4500,5300,6100]},
  {bgT:'#0a001a',bgM:'#1a0033',bgL:'#2a0044',bgG:'#1a0033',wT:'iceWall',eM:3,lW:8000,wX:[400,1200,2000,2800,3600,4400,5200,6000,6800]}
];
let gs='title',sc=0,li=99,hi=parseInt(localStorage.getItem('contraHi')||'0');
let camX=0;
let LW=sCfg[curS-1].lW;const GR=0.5;let fc=0,shk=0,bD=false;
const plats=[];
 
const pl={x:100,y:300,bW:28,bH:40,w:28,h:40,vx:0,vy:0,dir:1,oG:false,jp:false,sh:false,sT:0,sC:8,wpn:'normal',wT:0,inv:0,pr:false,aF:0,al:true,rT:0,_b:false,jC:0,def:false,scl:1,sclT:0,melee:false,swT:0,swH:null};
let pB=[],eB=[],enm=[],bos=null,pup=[],exp=[],par=[],saws=[];let chest=null;
 
let sIntro={active:false,timer:0,stageNum:1};
let coins=0;
const coinAnims=[];
let combo=0,comboT=0;
 
// ==================== 地图生成 ====================
function genL(sn){
  plats.length=0;const c=sCfg[sn-1];LW=c.lW;
  for(let x=0;x<LW;x+=200){if((x>400&&x<600)||(x>1800&&x<2000)||(x>3200&&x<3400))continue;plats.push({x,y:H-40,w:200,h:40,t:'ground'})}
  const fls={
    1:[{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}],
    2:[{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}],
    3:[{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}],
    4:[{x:300,y:340,w:90},{x:550,y:260,w:100},{x:800,y:180,w:110},{x:1100,y:320,w:80},{x:1350,y:240,w:120},{x:1600,y:160,w:90},{x:1900,y:350,w:100},{x:2150,y:270,w:80},{x:2400,y:190,w:110},{x:2700,y:330,w:90},{x:2950,y:250,w:120},{x:3200,y:170,w:80},{x:3500,y:360,w:100},{x:3750,y:280,w:90},{x:4000,y:200,w:110},{x:4300,y:340,w:80},{x:4550,y:260,w:120},{x:4800,y:180,w:90},{x:5100,y:350,w:100},{x:5350,y:270,w:80},{x:5600,y:190,w:110},{x:5900,y:330,w:90},{x:6150,y:250,w:120},{x:6400,y:170,w:80},{x:6700,y:340,w:100},{x:6950,y:260,w:90},{x:7200,y:180,w:110}],
    5:[{x:200,y:360,w:70},{x:380,y:290,w:90},{x:560,y:210,w:80},{x:740,y:140,w:100},{x:930,y:330,w:70},{x:1120,y:250,w:90},{x:1310,y:170,w:80},{x:1500,y:100,w:100},{x:1700,y:350,w:70},{x:1890,y:270,w:90},{x:2080,y:190,w:80},{x:2270,y:120,w:100},{x:2470,y:320,w:70},{x:2660,y:240,w:90},{x:2850,y:160,w:80},{x:3040,y:90,w:100},{x:3240,y:340,w:70},{x:3430,y:260,w:90},{x:3620,y:180,w:80},{x:3810,y:110,w:100},{x:4010,y:310,w:70},{x:4200,y:230,w:90},{x:4390,y:150,w:80},{x:4580,y:80,w:100},{x:4780,y:300,w:70},{x:4970,y:220,w:90},{x:5160,y:140,w:80},{x:5350,y:70,w:100},{x:5550,y:330,w:70},{x:5740,y:250,w:90},{x:5930,y:170,w:80},{x:6120,y:100,w:100},{x:6320,y:360,w:70},{x:6510,y:290,w:90},{x:6700,y:210,w:80},{x:6890,y:140,w:100},{x:7090,y:350,w:70},{x:7280,y:270,w:90},{x:7470,y:190,w:80},{x:7660,y:120,w:100}]
  };
  (fls[sn]||[]).forEach(f=>plats.push({x:f.x,y:f.y,w:f.w,h:16,t:'platform'}));
  c.wX.forEach(x=>plats.push({x,y:H-120,w:30,h:80,t:c.wT}));
  for(let i=0;i<6+sn;i++){let px=400+i*180+Math.random()*60,py=200-Math.random()*100;if(!plats.some(p=>Math.abs(p.x-px)<40&&Math.abs(p.y-py)<40)&&px<LW-100)plats.push({x:px,y:py,w:32,h:32,t:'question',used:false})}
  for(let i=0;i<10+sn*2;i++){let px=300+i*130+Math.random()*80,py=200-Math.random()*120;if(!plats.some(p=>Math.abs(p.x-px)<30&&Math.abs(p.y-py)<30)&&px<LW-100)plats.push({x:px,y:py,w:32,h:32,t:'brick',broken:false})}
}
function spwnE(sn){
  enm.length=0;pup.length=0;const c=sCfg[sn-1],hm=c.eM;
  let nS=sn<3?40+sn*5:35+sn*3,nR=sn<3?15+sn*5:0,nF=0;
  if(sn===3)nF=22;else if(sn===4)nF=30;else if(sn===5)nF=40;
  for(let i=0;i<nS;i++){let ex=600+i*140+Math.random()*60;enm.push({t:'soldier',x:ex,y:H-80,w:24,h:36,hp:1*hm,dir:-1,vx:0,vy:0,oG:false,sT:Math.random()*120,al:true,aF:0,ptl:ex-60,ptm:ex+60,st:'patrol'})}
  const tXs={1:[1000,1800,2600,3400,4200,5000,5600],2:[900,1700,2500,3300,4100,4900,5500],3:[1100,1900,2700,3500,4300,5100],4:[800,1600,2400,3200,4000,4800,5600,6400],5:[600,1400,2200,3000,3800,4600,5400,6200,7000]};
  (tXs[sn]||[]).forEach(tx=>enm.push({t:'turret',x:tx,y:H-72,w:32,h:32,hp:3*hm,dir:-1,sT:60,al:true,an:0}));
  for(let i=0;i<nR;i++){let ex=1200+i*350+Math.random()*100;enm.push({t:'runner',x:ex,y:H-76,w:22,h:34,hp:1*hm,dir:-1,vx:-3,vy:0,oG:false,al:true,aF:0})}
  for(let i=0;i<nF;i++){let ex=500+i*(sn===5?190:280)+Math.random()*70,by=60+Math.random()*(sn===5?220:180);enm.push({t:'flyer',x:ex,y:by,w:26,h:22,hp:1,dir:-1,vx:-(1.5+sn*0.3),vy:0,baseY:by,al:true,aF:0})}
  saws.length=0;
  for(let i=0;i<3+sn;i++){
    let sx=500+i*800+Math.random()*400;
    if(sx>17000-500)break;
    saws.push({x:sx,y:200+Math.random()*150,w:30,h:30,an:0,al:true});
  }
  for(let i=0;i<8+sn*2;i++){
    let ex=600+i*180+Math.random()*80;if(ex>LW-400)continue;let r=Math.random();
    if(r<0.4)enm.push({t:'goomba',x:ex,y:H-40-28,w:24,h:28,hp:1,dir:-1,vx:-1.2,vy:0,oG:false,al:true,aF:0,ptl:ex-60,ptm:ex+60});
    else if(r<0.7)enm.push({t:'koopa',x:ex,y:H-40-30,w:26,h:30,hp:2,dir:-1,vx:-1,vy:0,oG:false,al:true,aF:0,ptl:ex-70,ptm:ex+70,shell:false});
    else{let pi=Math.floor(Math.random()*plats.length),p=plats[pi];if(p&&p.t==='platform')enm.push({t:'piranha',x:p.x+p.w/2-14,y:p.y-32,w:28,h:32,hp:3,dir:1,al:true,sT:80+Math.random()*60,baseY:p.y-32,extY:p.y-80,st:'idle'})}
  }
  [400,800,1200,1600,2000,2400,2800,3200,3600].forEach((px,i)=>{const tp=['blade','saber','spread','rapid','laser','life','shield','mega','spread'];pup.push({x:px,y:180+Math.random()*80,w:24,h:24,t:tp[i%tp.length],al:true,bT:Math.random()*100})});
}
function spawnB(){bos={x:LW-300,y:H-200,w:120,h:160,hp:50,maxHp:50,ph:0,sT:0,mT:0,al:true,dir:-1,vy:0}}
function rc(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 rC(e){
  e.oG=false;
  for(let p of plats){
    if(!rc(e,p))continue;
    let sp=p.t==='question'||p.t==='brick',fb=e.vy<0&&e.y+e.h>p.y&&e.y<p.y+p.h;
    if(sp&&fb&&e===pl){
      if(p.t==='question'&&!p.used){p.used=true;coins+=1;for(let k=0;k<3;k++)coinAnims.push({x:p.x+6+Math.random()*20,y:p.y-6,vy:-5-Math.random()*3,life:20+Math.random()*10,mL:30});ps('coin');let tp=['blade','saber','spread','rapid','laser','life','shield','mega','coin','coin','coin'],ch=tp[Math.floor(Math.random()*tp.length)];if(ch==='coin'){sc+=100;ps('coin')}else if(ch==='mega'){pup.push({x:p.x+p.w/2-12,y:p.y-40,w:24,h:24,t:ch,al:true,bT:0,vx:0,vy:-6,oG:false,ground:false});ps('powerup')}else{pup.push({x:p.x+p.w/2-12,y:p.y-30,w:24,h:24,t:ch,al:true,bT:0})}p.t='empty';sc+=50;e.vy=2;ps('bump')}
      else if(p.t==='brick'&&!p.broken){p.broken=true;sc+=10;coins+=1;for(let k=0;k<3;k++)coinAnims.push({x:p.x+6+Math.random()*20,y:p.y-6,vy:-5-Math.random()*3,life:20+Math.random()*10,mL:30});ps('coin');for(let i=0;i<12;i++)par.push({x:p.x+16,y:p.y+16,vx:(Math.random()-0.5)*6,vy:(Math.random()-0.5)*6-2,sz:3+Math.random()*4,col:['#8B4513','#A0522D','#D2691E'][Math.floor(Math.random()*3)],life:20+Math.random()*15,mL:35});p.w=0;p.h=0;e.vy=2}
      continue;
    }
    let ox=Math.min(e.x+e.w-p.x,p.x+p.w-e.x),oy=Math.min(e.y+e.h-p.y,p.y+p.h-e.y);
    if(oy<ox){
      if(e.y+e.h/2<p.y+p.h/2){e.y=p.y-e.h;e.vy=0;e.oG=true;if(e===pl)pl.jC=0}else{e.y=p.y+p.h;e.vy=0}
    }else{if(e.x+e.w/2<p.x+p.w/2)e.x=p.x-e.w;else e.x=p.x+p.w;e.vx=0}
  }
}
 
//============================================================
// ======================= 增强绘制引擎 =======================
//============================================================
 
// ---- 精致玩家角色 ----
function drawPl(xx,yy,d,fr,pr,sh,df,sc){
  const sx=xx-camX;if(sx<-60||sx>W+60)return;
  ct.save();ct.translate(sx+14*sc,yy);ct.scale(d,1);ct.scale(sc,sc);
  const gd=(c0,c1)=>{let g=ct.createLinearGradient(0,0,0,40);g.addColorStop(0,c0);g.addColorStop(1,c1);return g};
  if(pr){
    ct.fillStyle=gd('#1565c0','#0d47a1');ct.fillRect(-14,24,28,12);
    ct.fillStyle='#ffcc80';ct.fillRect(8,22,12,10);ct.fillStyle='#e53935';ct.fillRect(10,23,8,3);
    ct.fillStyle='#1565c0';ct.fillRect(-14,28,28,8);ct.fillStyle='#757575';ct.fillRect(18,26,16,4);
    if(sh){ct.fillStyle='#ffeb3b';ct.shadowColor='#ffeb3b';ct.shadowBlur=8;ct.fillRect(34,24,6,8);ct.shadowBlur=0}
  }else{
    ct.fillStyle=gd('#1565c0','#0d47a1');ct.fillRect(-11,-8,22,12);
    ct.fillStyle='#e53935';ct.fillRect(-8,-6,16,4);
    ct.fillStyle='#ffcc80';ct.fillRect(-6,0,12,10);
    ct.fillStyle='#1565c0';ct.fillRect(-10,0,4,6);ct.fillRect(6,0,4,6);
    ct.fillStyle='#fff';ct.fillRect(-4,2,3,3);ct.fillRect(1,2,3,3);
    ct.fillStyle='#000';ct.fillRect(-3,3,2,2);ct.fillRect(2,3,2,2);
    ct.fillStyle=gd('#0d47a1','#002171');ct.fillRect(-8,10,16,16);
    ct.fillStyle='#e53935';ct.fillRect(-6,12,12,2);ct.fillRect(-6,22,12,2);
    const arm=sh?8:Math.sin(fr*0.3)*3;
    ct.fillStyle='#1565c0';
    if(df){ct.fillRect(-14,10+arm,6,12)}else{ct.fillRect(8,10-arm,6,12)}
    const lOff=Math.sin(fr*0.3)*4;
    ct.fillStyle='#1a237e';ct.fillRect(-8,26,6,12-lOff);ct.fillRect(2,26,6,12+lOff);
    ct.fillStyle='#37474f';ct.fillRect(-9,34,7,4);ct.fillRect(2,34,7,4);
    ct.fillStyle='#757575';ct.fillRect(10,12,18,4);ct.fillStyle='#616161';ct.fillRect(10,12,4,4);
    if(sh){ct.fillStyle='#ffeb3b';ct.shadowColor='#ffeb3b';ct.shadowBlur=12;ct.fillRect(28,10,10,8);ct.shadowBlur=0}
    if(df){ct.strokeStyle='rgba(100,200,255,0.6)';ct.lineWidth=2;ct.shadowColor='#64c8ff';ct.shadowBlur=15;ct.strokeRect(-16,-12,32,52);ct.shadowBlur=0}
  }
  ct.restore();
}
 
// ---- 敌人绘制 ----
function drawSol(e){
  const sx=e.x-camX;if(sx<-50||sx>W+50)return;
  ct.save();ct.translate(sx+12,e.y);ct.scale(e.dir,1);
  ct.fillStyle='#424242';ct.fillRect(-8,-8,16,12);
  ct.fillStyle='#ffcc80';ct.fillRect(-6,0,12,10);
  ct.fillStyle='#c62828';ct.fillRect(-6,3,4,4);ct.fillRect(2,3,4,4);
  ct.fillStyle='#5d4037';ct.fillRect(-6,10,12,16);
  ct.fillStyle='#4e342e';ct.fillRect(-6,26,5,8);ct.fillRect(1,26,5,8);
  ct.fillStyle='#616161';ct.fillRect(-14,8,14,3);
  ct.restore();
}
function drawRun(e){
  const sx=e.x-camX;if(sx<-50||sx>W+50)return;
  ct.save();ct.translate(sx+11,e.y);ct.scale(e.dir,1);
  ct.fillStyle='#ff8f00';ct.fillRect(-7,-6,14,10);
  ct.fillStyle='#ffcc80';ct.fillRect(-5,2,10,8);
  ct.fillStyle='#e65100';ct.fillRect(-5,10,10,14);
  const lo=Math.sin(e.aF*0.3)*5;
  ct.fillStyle='#bf360c';ct.fillRect(-5,24,4,8+lo);ct.fillRect(1,24,4,8-lo);
  ct.restore();
}
function drawFly(e){
  const sx=e.x-camX;if(sx<-50||sx>W+50)return;
  ct.save();ct.translate(sx+13,e.y);ct.scale(e.dir,1);
  ct.fillStyle='#e53935';ct.beginPath();ct.arc(0,0,13,0,Math.PI*2);ct.fill();
  ct.fillStyle='#b71c1c';ct.beginPath();ct.arc(0,0,9,0,Math.PI*2);ct.fill();
  ct.fillStyle='#fff';ct.fillRect(-6,-8,3,3);ct.fillRect(3,-8,3,3);
  ct.fillStyle='#000';ct.fillRect(-5,-7,2,2);ct.fillRect(4,-7,2,2);
  const wf=Math.sin(fc*0.1)*8;
  ct.fillStyle='rgba(200,200,200,0.3)';ct.fillRect(-20,-3+wf,7,3);ct.fillRect(13,-3-wf,7,3);
  ct.restore();
}
function drawGoo(e){
  const sx=e.x-camX;if(sx<-50||sx>W+50)return;
  ct.save();ct.translate(sx+12,e.y);ct.scale(e.dir,1);
  ct.fillStyle='#6d4c41';ct.beginPath();ct.arc(0,0,14,0,Math.PI*2);ct.fill();
  ct.fillStyle='#5d4037';ct.beginPath();ct.arc(0,0,10,0,Math.PI*2);ct.fill();
  ct.fillStyle='#fff';ct.fillRect(-5,-4,4,4);ct.fillRect(1,-4,4,4);
  ct.fillStyle='#000';ct.fillRect(-4,-3,2,2);ct.fillRect(2,-3,2,2);
  const lo=Math.sin(e.aF*0.2)*3;
  ct.fillStyle='#4e342e';ct.fillRect(-8,10,6,8+lo);ct.fillRect(2,10,6,8-lo);
  ct.restore();
}
function drawKoo(e){
  const sx=e.x-camX;if(sx<-50||sx>W+50)return;
  ct.save();ct.translate(sx+13,e.y);ct.scale(e.dir,1);
  if(e.shell){
    ct.fillStyle='#2E7D32';ct.beginPath();ct.arc(0,14,14,0,Math.PI*2);ct.fill();
    ct.fillStyle='#4CAF50';ct.beginPath();ct.arc(0,14,10,0,Math.PI*2);ct.fill();
    ct.fillStyle='#FFD600';ct.beginPath();ct.arc(0,14,6,0,Math.PI*2);ct.fill();
    ct.fillStyle='rgba(255,255,255,0.15)';ct.beginPath();ct.arc(-3,10,4,0,Math.PI*2);ct.fill();
  }else{
    ct.fillStyle='#2E7D32';ct.beginPath();ct.arc(0,8,14,0,Math.PI*2);ct.fill();
    ct.fillStyle='#4CAF50';ct.beginPath();ct.arc(0,8,10,0,Math.PI*2);ct.fill();
    ct.fillStyle='#1B5E20';ct.beginPath();ct.arc(0,8,12,6,0,Math.PI*2);ct.fill();
    ct.fillStyle='#fff';ct.fillRect(-5,4,3,3);ct.fillRect(2,4,3,3);
    ct.fillStyle='#000';ct.fillRect(-4,5,2,2);ct.fillRect(3,5,2,2);
    const lo=Math.sin(e.aF*0.2)*3;
    ct.fillStyle='#FBC02D';ct.fillRect(-10,18,8,10+lo);ct.fillRect(2,18,8,10-lo);
    ct.fillStyle='#FFB300';ct.beginPath();ct.arc(0,2,6,0,Math.PI*2);ct.fill();
  }
  ct.restore();
}
function drawPir(e){
  const sx=e.x-camX;if(sx<-50||sx>W+50)return;
  ct.save();ct.translate(sx+14,e.y);ct.scale(e.dir,1);
  ct.fillStyle='#2E7D32';ct.fillRect(-4,16,8,16);
  const g=ct.createRadialGradient(0,8,2,0,8,14);
  g.addColorStop(0,'#ff5252');g.addColorStop(1,'#b71c1c');
  ct.fillStyle=g;ct.beginPath();ct.arc(0,8,14,0,Math.PI*2);ct.fill();
  ct.fillStyle='#fff';ct.fillRect(-5,6,4,4);ct.fillRect(1,6,4,4);
  ct.fillStyle='#000';ct.fillRect(-4,7,2,2);ct.fillRect(2,7,2,2);
  ct.fillStyle='#fff';for(let i=-3;i<=3;i+=6){ct.beginPath();ct.moveTo(i,12);ct.lineTo(i+3,16);ct.lineTo(i-3,16);ct.fill()}
  ct.restore();
}
function drawTur(e){
  const sx=e.x-camX;if(sx<-50||sx>W+50)return;
  ct.save();ct.translate(sx+16,e.y+16);
  e.an+=0.03;
  ct.fillStyle='#546e7a';ct.beginPath();ct.arc(0,0,16,0,Math.PI*2);ct.fill();
  ct.fillStyle='#78909c';ct.beginPath();ct.arc(0,0,12,0,Math.PI*2);ct.fill();
  ct.fillStyle='#455a64';ct.fillRect(-3,0,6,10);
  const ga=Math.sin(e.an)*0.5;
  ct.fillStyle='#263238';ct.fillRect(-3+ga*8,-2,8+Math.abs(ga*4),4);
  ct.fillStyle='#ff6f00';ct.beginPath();ct.arc(0,0,5,0,Math.PI*2);ct.fill();
  ct.restore();
}
 
// ---- 背景系统 ----
function drawBG(){
  const c=sCfg[curS-1];
  const g=ct.createLinearGradient(0,0,0,H);
  g.addColorStop(0,c.bgT);g.addColorStop(0.3,c.bgM);g.addColorStop(0.6,c.bgL);g.addColorStop(1,c.bgG);
  ct.fillStyle=g;ct.fillRect(0,0,W,H);
  // 星空
  ct.fillStyle='#fff';
  for(let i=0;i<80;i++){
    let sx=((i*137+50)%W+W-camX*0.03%W)%W;
    let sy=(i*89+20)%(H*0.6);
    let sz=i%5===0?2.5:1.5;
    ct.globalAlpha=0.2+Math.sin(fc*0.015+i)*0.3;
    ct.fillRect(sx,sy,sz,sz);
  }
  ct.globalAlpha=1;
  // 远山
  for(let i=0;i<16;i++){
    let mx=i*250-(camX*0.1)%4000,mh=60+(i*37)%70;
    const tc=[0,'#1a3a2a',1,'#37474f',2,'#1d3557',3,'#3e2723','#1a0033'];
    ct.fillStyle=tc[curS]||'#1a3a2a';
    ct.beginPath();ct.moveTo(mx,H-40);ct.lineTo(mx+125,H-40-mh);ct.lineTo(mx+250,H-40);ct.fill();
  }
  // 中景树
  for(let i=0;i<20;i++){
    let tx=i*300-(camX*0.25)%6000,th=40+(i*23)%50;
    const tc=[0,'#1b5e20',1,'#2e2e2e',2,'#1565c0',3,'#4e342e','#311b92'];
    ct.fillStyle=tc[curS]||'#1b5e20';
    ct.fillRect(tx+8,H-40-th-30,5,30);
    ct.beginPath();ct.arc(tx+10,H-40-th-35,18,0,Math.PI*2);ct.fill();
    ct.beginPath();ct.arc(tx-5,H-40-th-25,14,0,Math.PI*2);ct.fill();
    ct.beginPath();ct.arc(tx+25,H-40-th-25,14,0,Math.PI*2);ct.fill();
  }
  // 云
  for(let i=0;i<8;i++){
    let cx2=((i*350+100)%W+W-camX*0.5%W)%W,cy=30+(i*57)%120;
    ct.fillStyle='rgba(255,255,255,0.08)';
    ct.beginPath();ct.arc(cx2,cy,25+Math.sin(i)*8,0,Math.PI*2);ct.fill();
    ct.beginPath();ct.arc(cx2-25,cy+5,18,0,Math.PI*2);ct.fill();
    ct.beginPath();ct.arc(cx2+25,cy+3,20,0,Math.PI*2);ct.fill();
    ct.beginPath();ct.arc(cx2+8,cy-8,15,0,Math.PI*2);ct.fill();
  }
}
 
// ---- 平台绘制 ----
function drawPlats(){
  for(let p of plats){
    const sx=p.x-camX;if(sx<-40||sx>W+40)continue;
    if(p.t==='ground'){
      ct.fillStyle='#3e2723';ct.fillRect(sx,p.y,p.w,p.h);
      ct.fillStyle='#4e342e';ct.fillRect(sx,p.y,p.w,4);
      ct.fillStyle='#2d1a11';ct.fillRect(sx,p.y,p.w,40);
      ct.fillStyle='#388e3c';ct.fillRect(sx,p.y-2,p.w,4);
      ct.fillStyle='#43a047';for(let i=0;i<p.w;i+=8){ct.fillRect(sx+i,p.y-4,4,4)}
    }else if(p.t==='platform'){
      ct.fillStyle='#5d4037';ct.fillRect(sx,p.y,p.w,p.h);
      ct.fillStyle='#795548';ct.fillRect(sx,p.y,p.w,3);
      ct.fillStyle='rgba(255,255,255,0.1)';ct.fillRect(sx+2,p.y+2,p.w-4,2);
    }else if(p.t==='wall'||p.t==='steelWall'||p.t==='iceWall'){
      if(p.t==='wall'){ct.fillStyle='#5d4037';ct.fillRect(sx,p.y,p.w,p.h);ct.fillStyle='#795548';ct.fillRect(sx,p.y,4,p.h)}
      else if(p.t==='steelWall'){ct.fillStyle='#546e7a';ct.fillRect(sx,p.y,p.w,p.h);ct.fillStyle='#78909c';ct.fillRect(sx,p.y,4,p.h);ct.fillStyle='#90a4ae';ct.fillRect(sx+4,p.y+4,p.w-8,4);ct.fillRect(sx+4,p.y+p.h-8,p.w-8,4)}
      else{ct.fillStyle='#b3e5fc';ct.fillRect(sx,p.y,p.w,p.h);ct.fillStyle='#81d4fa';ct.fillRect(sx,p.y,4,p.h);ct.fillStyle='rgba(255,255,255,0.3)';ct.fillRect(sx+2,p.y+2,p.w-4,2)}
    }else if(p.t==='empty'){
      ct.fillStyle='#5d4037';ct.fillRect(sx,p.y,p.w,p.h);
      ct.fillStyle='#4e342e';ct.fillRect(sx+1,p.y+1,p.w-2,p.h-2);
    }else if(p.t==='question'){
      const bob=Math.sin(fc*0.08)*2;
      ct.fillStyle=p.used?'#795548':'#ff6f00';
      ct.fillRect(sx,p.y+bob,p.w,p.h);
      if(!p.used){
        ct.fillStyle='#ffab00';ct.fillRect(sx+2,p.y+2+bob,p.w-4,p.h-4);
        ct.fillStyle='#fff';ct.font='bold 16px monospace';ct.fillText('?',sx+10,p.y+22+bob);
        ct.shadowColor='#ff6f00';ct.shadowBlur=10+Math.sin(fc*0.1)*5;
        ct.strokeStyle='rgba(255,111,0,0.3)';ct.lineWidth=2;ct.strokeRect(sx,p.y+bob,p.w,p.h);
        ct.shadowBlur=0;
      }
    }else if(p.t==='brick'&&!p.broken){
      ct.fillStyle='#8d6e63';ct.fillRect(sx,p.y,p.w,p.h);
      ct.fillStyle='#6d4c41';ct.fillRect(sx,p.y,p.w,1);ct.fillRect(sx,p.y,1,p.h);
      ct.fillStyle='#a1887f';ct.fillRect(sx+1,p.y+1,15,15);ct.fillRect(sx+16,p.y+1,15,15);
      ct.fillStyle='#8d6e63';ct.fillRect(sx+1,p.y+16,15,15);ct.fillRect(sx+16,p.y+16,15,15);
    }
  }
}
 
// ---- 道具 ----
function drawPups(){
  for(let p of pup){
    if(!p.al)continue;
    const sx=p.x-camX;if(sx<-30||sx>W+30)continue;
    if(p.t==='mega'){
      ct.shadowColor='#8d6e63';ct.shadowBlur=10;
      ct.fillStyle='#f5deb3';ct.fillRect(sx+5,p.y+10,14,14);
      const mg=ct.createRadialGradient(sx+12,p.y+2,2,sx+12,p.y+2,16);
      mg.addColorStop(0,'#ff6b35');mg.addColorStop(1,'#c0392b');
      ct.fillStyle=mg;ct.beginPath();ct.ellipse(sx+12,p.y+4,14,10,0,0,Math.PI*2);ct.fill();
      ct.fillStyle='#fff';ct.beginPath();ct.arc(sx+6,p.y,4,0,Math.PI*2);ct.fill();
      ct.beginPath();ct.arc(sx+18,p.y,3,0,Math.PI*2);ct.fill();
      ct.fillStyle='#000';ct.beginPath();ct.arc(sx+8,p.y+8,2,0,Math.PI*2);ct.fill();
      ct.beginPath();ct.arc(sx+16,p.y+8,2,0,Math.PI*2);ct.fill();
      ct.shadowBlur=0;
    }else{
      const bob=Math.sin(fc*0.06+p.bT)*2;
      ct.shadowColor='#ffeb3b';ct.shadowBlur=15;
      ct.fillStyle='#c62828';ct.fillRect(sx,p.y+bob,24,24);
      ct.shadowBlur=0;
      ct.fillStyle='#fff';ct.font='bold 13px monospace';
      const lm={spread:'S',rapid:'R',laser:'L',life:'1UP',shield:'SH'};
      ct.fillText(lm[p.t]||'?',sx+4,p.y+bob+17);
      ct.globalAlpha=0.3+Math.sin(fc*0.1)*0.2;
      ct.fillStyle='#ffeb3b';ct.fillRect(sx-3,p.y+bob-3,30,30);
      ct.globalAlpha=1;
    }
  }
}
 
 
function drawChest(){
  if(!chest||!chest.al)return;
  const sx=chest.x-camX, sy=chest.y;
  if(sx<-60||sx>W+60)return;
  ct.save();
  ct.translate(sx+20, sy+chest.h);
  // Box body (wood)
  let grad=ct.createLinearGradient(0,-chest.h,0,0);
  grad.addColorStop(0,'#8d6e63');grad.addColorStop(1,'#5d4037');
  ct.fillStyle=grad;
  ct.fillRect(-chest.w/2, -chest.h, chest.w, chest.h);
  // Metal bands
  ct.fillStyle='#9e9e9e';
  ct.fillRect(-chest.w/2, -chest.h+4, chest.w, 3);
  ct.fillRect(-chest.w/2, -chest.h+chest.h-7, chest.w, 3);
  // Metal corners
  ct.fillStyle='#bdbdbd';
  ct.fillRect(-chest.w/2, -chest.h, 5, 5);
  ct.fillRect(chest.w/2-5, -chest.h, 5, 5);
  ct.fillRect(-chest.w/2, -7, 5, 7);
  ct.fillRect(chest.w/2-5, -7, 5, 7);
  // Lock/clasp
  if(!chest.open){
    ct.fillStyle='#ffd700';
    ct.beginPath();ct.arc(0, -chest.h/2+2, 5, 0, Math.PI*2);ct.fill();
    ct.fillStyle='#ff8f00';
    ct.beginPath();ct.arc(0, -chest.h/2+2, 2.5, 0, Math.PI*2);ct.fill();
  }
  // Lid
  if(chest.opening){
    // Animated lid opening
    let prog=1-chest.openT/30;
    ct.fillStyle='#8d6e63';
    ct.save();
    ct.translate(-chest.w/2, -chest.h);
    ct.rotate(-prog*1.2);
    ct.fillRect(0, -8, chest.w, 8);
    ct.fillStyle='#9e9e9e';ct.fillRect(0, -8, chest.w, 2);
    ct.restore();
  } else if(chest.open){
    // Lid fully open (tilted back)
    ct.fillStyle='#8d6e63';
    ct.save();
    ct.translate(-chest.w/2, -chest.h);
    ct.rotate(-1.2);
    ct.fillRect(0, -8, chest.w, 8);
    ct.fillStyle='#9e9e9e';ct.fillRect(0, -8, chest.w, 2);
    ct.restore();
    // Glow from inside
    ct.shadowColor='#ffd700';ct.shadowBlur=20+Math.sin(fc*0.1)*10;
    ct.fillStyle='rgba(255,215,0,0.3)';
    ct.fillRect(-chest.w/2+4, -chest.h+4, chest.w-8, chest.h-8);
    ct.shadowBlur=0;
  } else {
    // Closed lid
    ct.fillStyle='#8d6e63';
    ct.fillRect(-chest.w/2, -chest.h-8, chest.w, 8);
    ct.fillStyle='#9e9e9e';ct.fillRect(-chest.w/2, -chest.h-8, chest.w, 2);
  }
  // Outline
  ct.strokeStyle='#3e2723';ct.lineWidth=2;
  ct.strokeRect(-chest.w/2, -chest.h, chest.w, chest.h);
  ct.restore();
  // Floating indicator if closed
  if(!chest.open&&!chest.opening){
    let bob=Math.sin(fc*0.08)*4;
    ct.fillStyle='#ffd700';ct.font='bold 16px monospace';
    ct.fillText('?',sx+12,sy-10+bob);
  }
}
function drawSlash(){
  if(!pl.swH||pl.swH.life<=0)return;
  const sx=pl.swH.x-camX, sy=pl.swH.y;
  const prog=1-pl.swH.life/10;
  const len=40+prog*20;
  ct.save();
  ct.translate(sx+30,sy+30);
  ct.scale(pl.swH.dir,1);
  const col=pl.swH.t==='saber'?'#00e5ff':'#ff6f00';
  ct.shadowColor=col;ct.shadowBlur=30;
  ct.strokeStyle=col;ct.lineWidth=4+prog*4;
  ct.globalAlpha=1-prog*0.3;
  ct.beginPath();
  ct.arc(0,0,len, -0.8+prog*0.6, 0.8-prog*0.6);
  ct.stroke();
  ct.strokeStyle='#fff';ct.lineWidth=2;
  ct.beginPath();
  ct.arc(0,0,len-5, -0.6+prog*0.4, 0.6-prog*0.4);
  ct.stroke();
  ct.shadowBlur=0;ct.globalAlpha=1;
  ct.restore();
}
 
function updateSaws(){
  for(let s of saws){
    if(!s.al)continue;
    if(pl.al&&rc(pl,s)){
      for(let k=0;k<30;k++)par.push({x:pl.x+14,y:pl.y+20,vx:(Math.random()-0.5)*12,vy:(Math.random()-0.5)*12-3,sz:4+Math.random()*6,col:['#f44336','#ff1744','#b71c1c','#ffcc80'][Math.floor(Math.random()*4)],life:20+Math.random()*20,mL:40});
      for(let k=0;k<30;k++)par.push({x:pl.x+14,y:pl.y+20,vx:(Math.random()-0.5)*8,vy:(Math.random()-0.5)*8,sz:2+Math.random()*4,col:'#fff',life:10+Math.random()*10,mL:20});
      for(let k=0;k<5;k++)exp.push({x:pl.x+Math.random()*28,y:pl.y+Math.random()*40,rad:10+Math.random()*15,tm:15+Math.random()*15,mT:30});
      shk=30;li=0;gs='gameover';bStop();ps('gameover');
    }
  }
}
function drawSaws(){
  for(let s of saws){
    if(!s.al)continue;
    const sx=s.x-camX;if(sx<-40||sx>W+40)continue;
    s.an+=0.2;
    ct.save();ct.translate(sx+15,s.y+15);ct.rotate(s.an);
    ct.shadowColor='#f44336';ct.shadowBlur=20;
    ct.fillStyle='#bdbdbd';ct.beginPath();ct.arc(0,0,15,0,Math.PI*2);ct.fill();
    ct.fillStyle='#616161';ct.beginPath();ct.arc(0,0,12,0,Math.PI*2);ct.fill();
    ct.fillStyle='#f44336';ct.beginPath();ct.arc(0,0,5,0,Math.PI*2);ct.fill();
    ct.shadowBlur=0;
    ct.strokeStyle='#f44336';ct.lineWidth=3;
    for(let j=0;j<8;j++){
      let a=j*Math.PI/4;
      ct.beginPath();ct.moveTo(Math.cos(a)*10,Math.sin(a)*10);ct.lineTo(Math.cos(a)*16,Math.sin(a)*16);ct.stroke();
    }
    ct.fillStyle='#ff1744';ct.beginPath();ct.arc(0,0,2,0,Math.PI*2);ct.fill();
    ct.restore();
  }
}
function drawBuls(){
  for(let b of pB){
    const sx=b.x-camX;if(sx<-10||sx>W+10)continue;
    ct.shadowBlur=8;
    if(b.t==='laser'){ct.shadowColor='#00e5ff';ct.fillStyle='#00e5ff';ct.fillRect(sx,b.y-1,b.w,3);ct.fillStyle='#b2ebf2';ct.fillRect(sx,b.y,b.w,1)}
    else if(b.t==='spread'){ct.shadowColor='#ff9800';ct.fillStyle='#ff9800';ct.beginPath();ct.arc(sx+b.w/2,b.y+b.h/2,4,0,Math.PI*2);ct.fill()}
    else{ct.shadowColor='#ffeb3b';ct.fillStyle='#ffeb3b';ct.fillRect(sx,b.y,b.w,b.h);ct.fillStyle='#fff';ct.fillRect(sx+1,b.y+1,b.w-2,b.h-2)}
    ct.shadowBlur=0;
  }
  for(let b of eB){
    const sx=b.x-camX;if(sx<-10||sx>W+10)continue;
    ct.shadowColor='#f44336';ct.shadowBlur=10;
    ct.fillStyle='#f44336';ct.beginPath();ct.arc(sx+3,b.y+3,5,0,Math.PI*2);ct.fill();
    ct.fillStyle='#ff8a80';ct.beginPath();ct.arc(sx+3,b.y+3,2.5,0,Math.PI*2);ct.fill();
    ct.shadowBlur=0;
  }
}
function drawExps(){
  for(let i=exp.length-1;i>=0;i--){
    let e=exp[i];let sx=e.x-camX;let p=e.tm/e.mT;let r=e.rad*(1-p*0.5);
    ct.globalAlpha=p;
    ct.shadowColor='#ff6f00';ct.shadowBlur=20;
    ct.fillStyle='#ff6f00';ct.beginPath();ct.arc(sx,e.y,r,0,Math.PI*2);ct.fill();
    ct.fillStyle='#ffeb3b';ct.beginPath();ct.arc(sx,e.y,r*0.6,0,Math.PI*2);ct.fill();
    ct.fillStyle='#fff';ct.beginPath();ct.arc(sx,e.y,r*0.2,0,Math.PI*2);ct.fill();
    ct.shadowBlur=0;ct.globalAlpha=1;
    e.tm--;if(e.tm<=0)exp.splice(i,1);
  }
}
function drawPars(){
  for(let i=par.length-1;i>=0;i--){
    let p=par[i];let sx=p.x-camX;
    ct.globalAlpha=p.life/p.mL;
    ct.fillStyle=p.col;ct.fillRect(sx,p.y,p.sz,p.sz);
    p.x+=p.vx;p.y+=p.vy;p.vy+=0.1;p.life--;
    if(p.life<=0)par.splice(i,1);
  }
  ct.globalAlpha=1;
}
function drawCoins(){
  for(let i=coinAnims.length-1;i>=0;i--){
    let c=coinAnims[i];let sx=c.x-camX;
    ct.globalAlpha=c.life/c.mL;
    ct.shadowColor='#ffd700';ct.shadowBlur=15;
    ct.fillStyle='#ffd700';ct.beginPath();ct.arc(sx,c.y,6,0,Math.PI*2);ct.fill();
    ct.shadowBlur=0;
    ct.fillStyle='#ff8f00';ct.font='bold 7px monospace';ct.fillText('$',sx-2,c.y+3);
    c.y+=c.vy;c.vy+=0.4;c.life--;
    if(c.life<=0)coinAnims.splice(i,1);
  }
  ct.globalAlpha=1;
}
 
// ---- Boss 绘制 ----
function drawBoss(){
  if(!bos||!bos.al)return;
  const sx=bos.x-camX;
  if(sx<-60||sx>W+60)return;
  ct.save();ct.translate(sx+60,bos.y);ct.scale(bos.dir,1);
  const bg=ct.createRadialGradient(0,40,10,0,40,60);
  bg.addColorStop(0,'#c62828');bg.addColorStop(1,'#4a0000');
  ct.fillStyle=bg;ct.beginPath();ct.arc(0,50,60,0,Math.PI*2);ct.fill();
  ct.fillStyle='#5d0000';ct.fillRect(-35,40,70,70);
  ct.fillStyle='#37474f';ct.fillRect(-45,35,20,25);ct.fillRect(25,35,20,25);
  ct.fillStyle='#8d6e63';ct.beginPath();ct.arc(0,10,30,0,Math.PI*2);ct.fill();
  ct.fillStyle='#ffcc80';ct.beginPath();ct.arc(0,15,22,0,Math.PI*2);ct.fill();
  ct.fillStyle='#ff1744';ct.shadowColor='#ff1744';ct.shadowBlur=15;
  ct.beginPath();ct.arc(-10,10,6,0,Math.PI*2);ct.fill();
  ct.beginPath();ct.arc(10,10,6,0,Math.PI*2);ct.fill();
  ct.shadowBlur=0;
  ct.fillStyle='#000';ct.beginPath();ct.arc(-10,10,3,0,Math.PI*2);ct.fill();
  ct.beginPath();ct.arc(10,10,3,0,Math.PI*2);ct.fill();
  ct.fillStyle='#546e7a';ct.fillRect(-18,40,8,8);ct.fillRect(10,40,8,8);
  ct.fillRect(-18,55,36,4);
  const aa=Math.sin(fc*0.05)*0.3;
  ct.save();ct.rotate(aa);ct.fillStyle='#8d6e63';ct.fillRect(-50,10,15,40);ct.fillStyle='#616161';ct.fillRect(-52,40,19,8);ct.restore();
  ct.save();ct.rotate(-aa);ct.fillStyle='#8d6e63';ct.fillRect(35,10,15,40);ct.fillStyle='#616161';ct.fillRect(33,40,19,8);ct.restore();
  ct.fillStyle='#37474f';ct.fillRect(-25,90,20,30);ct.fillRect(5,90,20,30);
  const hpW=80,hpH=6;
  ct.fillStyle='rgba(0,0,0,0.7)';ct.fillRect(-hpW/2,-80,hpW,hpH);
  ct.fillStyle='#f44336';ct.fillRect(-hpW/2+1,-80+1,(hpW-2)*(bos.hp/bos.maxHp),hpH-2);
  ct.fillStyle='#fff';ct.font='8px monospace';ct.fillText(`${bos.hp}/${bos.maxHp}`,-10,-75);
  ct.restore();
}
 
// ---- HUD ----
function drawHUD(){
  ct.fillStyle='rgba(0,0,0,0.6)';ct.fillRect(0,0,W,38);
  ct.fillStyle='rgba(255,255,255,0.05)';ct.fillRect(0,0,W,1);
  ct.fillStyle='#ffd700';ct.font='bold 15px monospace';ct.fillText('SCORE',10,14);
  ct.fillStyle='#fff';ct.font='bold 14px monospace';ct.fillText(sc.toString().padStart(8,'0'),10,30);
  ct.fillStyle='#ff6f00';ct.font='bold 10px monospace';ct.fillText('HI',210,14);
  ct.fillStyle='#fff';ct.font='bold 14px monospace';ct.fillText(hi.toString().padStart(8,'0'),210,30);
  ct.fillStyle='#4fc3f7';ct.font='bold 13px monospace';ct.fillText('STAGE '+curS+'/'+MAX_S,330,24);
  ct.fillStyle='#e53935';ct.font='bold 14px monospace';ct.fillText('\u2764x'+Math.min(li,99),440,24);
  ct.fillStyle='#ffd700';ct.font='bold 13px monospace';ct.fillText('\u25cbx'+coins,500,24);
  const pX=540,pY=12,pW=120,pH=14;
  ct.fillStyle='rgba(255,255,255,0.1)';ct.fillRect(pX,pY,pW,pH);
  let pct=Math.min(1,camX/LW);
  const pg=ct.createLinearGradient(pX,0,pX+pW,0);
  pg.addColorStop(0,'#4fc3f7');pg.addColorStop(1,'#00e676');
  ct.fillStyle=pg;ct.fillRect(pX+2,pY+2,(pW-4)*pct,pH-4);
  ct.fillStyle='#fff';ct.font='8px monospace';ct.fillText(Math.floor(pct*100)+'%',pX+pW/2-8,pY+11);
  const wn={normal:'NORMAL',spread:'SPREAD',rapid:'RAPID',laser:'LASER',blade:'剪刀',saber:'光剑'};
  const wc={normal:'#fff',spread:'#ff9800',rapid:'#4fc3f7',laser:'#00e5ff',blade:'#ff6f00',saber:'#00e5ff'};
  ct.fillStyle=wc[pl.wpn]||'#fff';ct.font='bold 13px monospace';
  ct.fillText('\u2694'+wn[pl.wpn],680,24);
  if(pl.wpn!=='normalmal'&&pl.wT>0){
    ct.fillStyle='rgba(255,255,255,0.2)';ct.fillRect(680,28,80,4);
    ct.fillStyle=wc[pl.w]||'#fff';ct.fillRect(680,28,80*(pl.wT/600),4);
  }
  if(combo>1&&comboT>0){
    ct.fillStyle='#ffd700';ct.font='bold 20px monospace';
    ct.shadowColor='#ffd700';ct.shadowBlur=12;
    ct.fillText(combo+'x COMBO!',W/2-60,80);
    ct.shadowBlur=0;
  }
  if(gs==='playing'&&fc<180){
    ct.globalAlpha=Math.max(0,1-fc/180);
    ct.fillStyle='rgba(255,255,255,0.4)';ct.font='12px monospace';
    ct.fillText('Z:射击  X:全屏炸弹  \u2191:跳跃  \u2193:趴下  \u2190\u2192:移动',W/2-240,420);
    ct.globalAlpha=1;
  }
}
 
// ---- 标题画面 ----
function drawTitle(){
  for(let i=0;i<120;i++){
    let sx=((i*157+50)%W),sy=(i*97+20)%H,sz=1+((i*3)%3);
    ct.fillStyle='#fff';
    ct.globalAlpha=0.15+Math.sin(fc*0.02+i)*0.25;
    ct.fillRect(sx,sy,sz,sz);
  }
  ct.globalAlpha=1;
  for(let i=0;i<20;i++){let mx=i*200,mh=80+(i*47)%80;ct.fillStyle='rgba(13,27,42,0.6)';ct.beginPath();ct.moveTo(mx,H);ct.lineTo(mx+100,H-mh);ct.lineTo(mx+200,H);ct.fill()}
  // 标题
  ct.shadowColor='#f44336';ct.shadowBlur=30;
  ct.fillStyle='#ff1744';ct.font='bold 72px monospace';ct.fillText('\u9b42\u6597\u739b\u4e3d\u7f57',W/2-188-4,202);
  ct.shadowBlur=20;
  ct.fillStyle='#fff';ct.font='bold 70px monospace';ct.fillText('\u9b42\u6597\u739b\u4e3d\u7f57',W/2-186,200);
  ct.shadowBlur=0;
  ct.fillStyle='#4fc3f7';ct.font='bold 22px monospace';ct.shadowColor='#4fc3f7';ct.shadowBlur=15;
  ct.fillText('\u2764 ULTIMATE EDITION \u2764',W/2-158,240);
  ct.shadowBlur=0;
  // 闪烁提示
  ct.fillStyle='rgba(255,255,255,'+(0.5+Math.sin(fc*0.06)*0.5)+')';ct.font='bold 20px monospace';ct.fillText('\u6309 ENTER \u5f00\u59cb\u6e38\u620f',W/2-136,310);
  ct.fillStyle='rgba(255,255,255,0.3)';ct.font='12px monospace';
  ct.fillText('Z \u5c04\u51fb | X \u5168\u5c4f\u70b8\u5f39 | \u2191 \u8df3\u8dc3 | \u2193 \u8db4\u4e0b | \u2190\u2192 \u79fb\u52a8',W/2-186,360);
  ct.fillText('\u51fb\u8d25\u654c\u4eba\u83b7\u53d6\u6b66\u5668\u5347\u7ea7 \u00b7 \u6536\u96c6 1UP \u589e\u52a0\u751f\u547d \u00b7 \u62b5\u5fa1 5 \u5173 BOSS',W/2-222,380);
  if(hi>0){ct.fillStyle='#ffd700';ct.font='bold 14px monospace';ct.fillText('\u6700\u9ad8\u5206: '+hi,W/2-48,420)}
  for(let i=0;i<15;i++){let px=(i*97+fc*2)%W,py=(H/2-100+Math.sin(fc*0.03+i*1.5)*40+50);ct.fillStyle='hsla('+((i*40+fc)%360)+',80%,60%,0.3)';ct.fillRect(px,py,3,3)}
}
 
// ---- 关卡过渡 / 游戏结束 / 胜利 ----
function drawStageIntro(){
  if(!sIntro.active)return;
  const p=Math.min(1,sIntro.timer/60);
  ct.fillStyle='rgba(0,0,0,'+(0.6*p)+')';ct.fillRect(0,0,W,H);
  ct.textAlign='center';
  const sc2=1+Math.max(0,1-p)*0.3;
  ct.save();ct.translate(W/2,H/2);ct.scale(sc2,sc2);
  ct.shadowColor='#4fc3f7';ct.shadowBlur=30*p;
  ct.fillStyle='#4fc3f7';ct.font='bold 60px monospace';
  ct.globalAlpha=p;ct.fillText('STAGE '+sIntro.stageNum,0,-20);
  ct.shadowBlur=0;
  ct.fillStyle='#fff';ct.font='bold 20px monospace';ct.fillText('\u51c6\u5907\u6218\u6597',0,30);
  ct.globalAlpha=1;ct.restore();ct.textAlign='start';
}
function drawGO(){
  ct.fillStyle='rgba(0,0,0,0.7)';ct.fillRect(0,0,W,H);
  ct.shadowColor='#f44336';ct.shadowBlur=40;
  ct.fillStyle='#f44336';ct.font='bold 56px monospace';
  ct.fillText('GAME OVER',W/2-190,H/2-10);ct.shadowBlur=0;
  ct.fillStyle='#fff';ct.font='bold 18px monospace';
  ct.fillText('\u5f97\u5206: '+sc,W/2-48,H/2+50);
  ct.fillText('\u6309 ENTER \u91cd\u65b0\u5f00\u59cb',W/2-104,H/2+90);
}
function drawWin(){
  ct.fillStyle='rgba(0,0,0,0.8)';ct.fillRect(0,0,W,H);
  for(let i=0;i<30;i++){let px=(i*73+fc*3)%W,py=(i*59+fc*2)%H;ct.fillStyle='hsla('+((i*50+fc*2)%360)+',80%,60%,0.5)';ct.fillRect(px,py,4,4)}
  ct.shadowColor='#ffd700';ct.shadowBlur=50;
  ct.fillStyle='#ffd700';ct.font='bold 56px monospace';ct.fillText('\u80dc \u5229!',W/2-140,H/2-20);ct.shadowBlur=0;
  ct.fillStyle='#fff';ct.font='bold 22px monospace';ct.fillText('\u6700\u7ec8\u5f97\u5206: '+sc,W/2-88,H/2+40);
  if(sc>=hi){ct.fillStyle='#4fc3f7';ct.font='bold 16px monospace';ct.fillText('\uD83C\uDFC6 \u65b0\u7eaa\u5f55!',W/2-60,H/2+75)}
  ct.fillStyle='#fff';ct.font='bold 16px monospace';ct.fillText('\u6309 ENTER \u91cd\u65b0\u5f00\u59cb',W/2-92,H/2+110);
}
 
//============================================================
// ======================= 游戏逻辑 =======================
//============================================================
function shoot(){
  if(!pl.al)return;
  // 近战攻击
  if(pl.melee){
    if(pl.swT>0)return;
    pl.swT=18;ps('shield');
    const hx=pl.x+14+pl.dir*40, hy=pl.y+20;
    pl.swH={x:hx-30,y:hy-30,w:60,h:60,life:10,dir:pl.dir,t:pl.wpn};
    for(let i=0;i<12;i++){
      let ang=Math.atan2(1,pl.dir)+Math.PI*0.8+Math.random()*0.4;
      par.push({x:pl.x+14+pl.dir*30,y:pl.y+20,vx:Math.cos(ang)*(3+Math.random()*4),vy:Math.sin(ang)*(3+Math.random()*4),sz:3+Math.random()*4,col:pl.wpn==='saber'?'#00e5ff':'#ff6f00',life:10+Math.random()*8,mL:18});
    }
    return;
  }
  let st=pl.wpn==='rapid'?4:8;
  if(pl.sT>0||pl.sC<st)return;
  pl.sT=pl.sC;ps('shoot');
  let dx=0,dy=0;
  if(keys['ArrowLeft']||keys['KeyA'])dx=-1;else if(keys['ArrowRight']||keys['KeyD'])dx=1;
  if(keys['ArrowUp']||keys['KeyW'])dy=-1;else if(keys['ArrowDown']||keys['KeyS'])dy=1;
  if(dx===0&&dy===0)dx=pl.dir;
  let len=Math.sqrt(dx*dx+dy*dy),nx=dx/len,ny=dy/len;
  let ax=pl.x+14+nx*20,ay=pl.y+20+ny*20;
  if(pl.wpn==='spread'){
    let a=Math.PI/12;
    pB.push({x:ax,y:ay,w:8,h:2,vx:nx*6,vy:ny*6,life:25,t:'spread'});
    pB.push({x:ax-nx*3,y:ay-ny*3,w:8,h:2,vx:Math.cos(Math.atan2(ny,nx)-a)*6,vy:Math.sin(Math.atan2(ny,nx)-a)*6,life:25,t:'spread'});
    pB.push({x:ax-nx*3,y:ay-ny*3,w:8,h:2,vx:Math.cos(Math.atan2(ny,nx)+a)*6,vy:Math.sin(Math.atan2(ny,nx)+a)*6,life:25,t:'spread'});
    ps('spread');
  }else if(pl.wpn==='laser'){
    pB.push({x:ax,y:ay,w:24,h:3,vx:nx*12,vy:ny*12,life:20,t:'laser'});
  }else if(pl.wpn==='rapid'){
    pB.push({x:ax,y:ay,w:10,h:3,vx:nx*10,vy:ny*10,life:22,t:'rapid'});
  }else{
    pB.push({x:ax,y:ay,w:8,h:4,vx:nx*8,vy:ny*8,life:28,t:'normal'});
  }
  for(let i2=0;i2<5;i2++)par.push({x:ax,y:ay,vx:nx*(1+Math.random()*3),vy:ny*(1+Math.random()*3)+((Math.random()-0.5)*2),sz:2+Math.random()*3,col:'#ffeb3b',life:8+Math.random()*5,mL:12});
}function plDie(){
  if(pl.inv>0||pl.def)return;
  if(pl.scl>1){pl.scl=1;pl.w=pl.bW;pl.h=pl.bH;pl.sclT=0}
  li--;if(li<0){gs='gameover';bStop();ps('gameover');return}
  pl.inv=120;pl.al=true;pl.x-=50;pl.y=300;pl.vx=0;pl.vy=0;pl.w=pl.bW;pl.h=pl.bH;
  for(let i=0;i<20;i++)exp.push({x:pl.x+14,y:pl.y+20,rad:5+Math.random()*10,tm:15+Math.random()*15,mT:30});
  ps('hit');shk=15;
}
 
function updatePlayer(){
  if(!pl.al||gs!=='playing')return;
  let speed=4;
  if(keys['ArrowLeft']||keys['KeyA']){pl.vx=-speed;pl.dir=-1}
  else if(keys['ArrowRight']||keys['KeyD']){pl.vx=speed;pl.dir=1}
  else pl.vx*=0.8;
  if(keys['ArrowDown']||keys['KeyS']){pl.pr=pl.oG;if(pl.pr)pl.h=24}else{pl.pr=false;pl.h=pl.bH}
  var jk=keys['ArrowUp']||keys['KeyW']||keys['Space'];if(jk&&!pl._jp&&pl.jC<2){pl._jp=true
    pl.vy=-10;pl.jC++;pl.oG=false;ps('jump');
    for(let i=0;i<6;i++)par.push({x:pl.x+7,y:pl.y+pl.h,vx:(Math.random()-0.5)*3,vy:Math.random()*2,sz:3,col:'#9e9e9e',life:12+Math.random()*8,mL:20});
  }
  if(keys['ArrowDown']&&!pl.oG)pl.vy+=1;
    if(!jk)pl._jp=false;
    pl.def=!!keys['KeyF'];
    pl.vy+=GR;pl.x+=pl.vx;pl.y+=pl.vy;
    rC(pl);
  if(pl.x<camX)pl.x=camX;
  if(pl.y>H+50){plDie();return}
  if(pl.sT>0)pl.sT--;
  let cd=pl.wpn==='rapid'?4:pl.sC;
  if(keys['KeyZ']&&pl.sT<=0){shoot();pl.sT=cd}
  if((keys['KeyX']||keys['KeyC'])&&!pl._b){
    pl._b=true;
    for(let e of enm){if(e.al&&Math.abs(e.x-pl.x)<400){e.al=false;sc+=100;exp.push({x:e.x+e.w/2,y:e.y+e.h/2,rad:30,tm:20,mT:20})}}
    if(bos&&bos.al){
      bos.hp-=10;
      if(bos.hp<=0){
        bos.al=false;bD=true;sc+=5000;shk=30;ps('stage_clear');
        chest={x:bos.x+bos.w/2-20,y:H-80,w:40,h:36,open:false,opening:false,openT:0,al:true};
        for(let k=0;k<30;k++)exp.push({x:bos.x+Math.random()*bos.w,y:bos.y+Math.random()*bos.h,rad:15+Math.random()*20,tm:20+Math.random()*20,mT:40});
                // Wait for chest to be opened before next level
      }
    }
    shk=15;ps('explosion');
  }
  if(!keys['KeyX']&&!keys['KeyC'])pl._b=false;
  if(pl.inv>0)pl.inv--;
  if(pl.wT>0){pl.wT--;if(pl.wT<=0){pl.wpn='normal';pl.scl=1;pl.w=pl.bW;pl.h=pl.bH}}
  if(pl.sclT>0)pl.sclT--;
  let tC=pl.x-W/3;
  camX+=(tC-camX)*0.08;
  if(camX<0)camX=0;
  if(camX>LW-W)camX=LW-W;
  if(camX>LW-W-200&&!bos&&!bD)spawnB();
  if(Math.abs(pl.vx)>0.1&&pl.oG)pl.aF++;
}
 
function updateBuls(){
  for(let i=pB.length-1;i>=0;i--){
    let b=pB[i];b.x+=b.vx;b.y+=b.vy;b.life--;
    if(b.life<=0||b.x<camX-50||b.x>camX+W+50){pB.splice(i,1);continue}
    for(let e of enm){
      if(e.al&&rc(b,e)){
        if(e.t==='koopa'&&!e.shell){e.shell=true;e.hp=1;pB.splice(i,1);exp.push({x:e.x+e.w/2,y:e.y+e.h/2,rad:15,tm:10,mT:10});break}
        if(e.t==='koopa'&&e.shell)e.hp=0;
        e.hp--;pB.splice(i,1);
        if(e.hp<=0){e.al=false;sc+=e.t==='turret'?200:100;combo++;comboT=60;exp.push({x:e.x+e.w/2,y:e.y+e.h/2,rad:20,tm:15,mT:15});ps('explosion')}
        break;
      }
    }
    if(bos&&bos.al&&rc(b,bos)){
      bos.hp-=b.t==='laser'?3:1;bos.sT=Math.max(0,bos.sT-5);pB.splice(i,1);
      exp.push({x:bos.x+bos.w/2,y:bos.y+Math.random()*bos.h,rad:12,tm:10,mT:10});ps('boss_hit');
      if(bos.hp<=0){
        bos.al=false;bD=true;sc+=5000;shk=30;ps('stage_clear');
        chest={x:bos.x+bos.w/2-20,y:H-80,w:40,h:36,open:false,opening:false,openT:0,al:true};
        for(let k=0;k<30;k++)exp.push({x:bos.x+Math.random()*bos.w,y:bos.y+Math.random()*bos.h,rad:15+Math.random()*20,tm:20+Math.random()*20,mT:40});
        setTimeout(()=>{
          if(curS<MAX_S){curS++;bD=false;camX=0;pl.x=100;pl.y=300;genL(curS);spwnE(curS);bos=null;combo=0;bStop();sIntro={active:true,timer:0,stageNum:curS}}
          else{gs='win';bStop();ps('stage_clear');if(sc>hi){hi=sc;localStorage.setItem('contraHi',hi)}}
        },2000);
      }
    }
  }
  for(let i=eB.length-1;i>=0;i--){
    let b=eB[i];b.x+=b.vx;b.y+=b.vy;b.life--;
    if(b.life<=0||b.x<camX-50||b.x>camX+W+50){eB.splice(i,1);continue}
    if(pl.al&&rc(b,pl)){plDie();eB.splice(i,1)}
  }
}
 
function updateEnemies(){
  for(let e of enm){
    if(!e.al)continue;
    if(Math.abs(e.x-camX)>W+100)continue;
    if(e.t==='soldier'){
      if(e.st==='patrol'){e.x+=e.dir*0.8;if(e.x<e.ptl||e.x>e.ptm)e.dir*=-1;e.sT--;if(e.sT<=0&&Math.abs(e.x-pl.x)<300){e.sT=80+Math.random()*60;e.st='attack'}}
      else{e.dir=pl.x<e.x?-1:1;e.sT--;if(e.sT<=0){e.sT=80+Math.random()*60;e.st='patrol'}}
      rC(e);
      if(e.st==='attack'&&e.sT%30===0&&Math.abs(e.x-pl.x)<300){const dx=pl.x-e.x,dy=pl.y-e.y,d=Math.sqrt(dx*dx+dy*dy);eB.push({x:e.x+12,y:e.y+18,w:6,h:6,vx:(dx/d)*3,vy:(dy/d)*3,life:60})}
    }else if(e.t==='runner'){e.x+=e.vx;e.aF++;rC(e)}
    else if(e.t==='flyer'){e.x+=e.vx;e.y=e.baseY+Math.sin(fc*0.05+e.x*0.01)*30;e.aF++;if(Math.abs(e.x-pl.x)<250&&fc%40===0)eB.push({x:e.x+13,y:e.y+11,w:6,h:6,vx:pl.x<e.x?-2.5:2.5,vy:0,life:60})}
    else if(e.t==='turret'){if(Math.abs(e.x-pl.x)<400&&fc%50===0){const dx=pl.x-e.x,dy=pl.y-e.y,d=Math.sqrt(dx*dx+dy*dy);eB.push({x:e.x+16,y:e.y+16,w:6,h:6,vx:(dx/d)*2.5,vy:(dy/d)*2.5,life:60})}}
    else if(e.t==='goomba'){
      e.x+=e.vx;e.aF++;rC(e);e.vy+=GR;e.y+=e.vy;
      if(e.x<e.ptl||e.x>e.ptm)e.vx*=-1;
      if(pl.al&&rc(pl,e)){if(pl.vy>0&&pl.y+pl.h<e.y+e.h/2+5){e.al=false;sc+=100;pl.vy=-8;exp.push({x:e.x+e.w/2,y:e.y+e.h/2,rad:15,tm:10,mT:10});ps('bump');combo++;comboT=60}else if(pl.inv<=0)plDie()}
    }else if(e.t==='koopa'){
      if(!e.shell){e.x+=e.vx;e.aF++;rC(e);e.vy+=GR;e.y+=e.vy;if(e.x<e.ptl||e.x>e.ptm)e.vx*=-1}
      if(pl.al&&rc(pl,e)){if(e.shell){e.vx=pl.x<e.x?8:-8;if(Math.abs(pl.vy)>0&&pl.y+pl.h<e.y+e.h/2){e.al=false;sc+=150;exp.push({x:e.x+e.w/2,y:e.y+e.h/2,rad:20,tm:15,mT:15})}else if(pl.inv<=0)plDie()}else{if(pl.vy>0&&pl.y+pl.h<e.y+e.h/2){e.shell=true;e.hp=1;pl.vy=-8;ps('bump');combo++;comboT=60}else if(pl.inv<=0)plDie()}}
    }else if(e.t==='piranha'){
      const dist=Math.abs(pl.x-e.x);
      if(dist<200)e.y=Math.max(e.baseY,e.y-1.5);else e.y=Math.min(e.baseY+10,e.y+0.5);
      if(dist<250&&fc%60===0)eB.push({x:e.x+14,y:e.y,w:6,h:6,vx:(pl.x<e.x?-2:2),vy:-3,life:50});
    }
  }
  enm=enm.filter(e=>e.al||Math.abs(e.x-camX)<W);
}
 
function updateBoss(){
  if(!bos||!bos.al)return;
  bos.dir=pl.x<bos.x?-1:1;
  bos.sT++;
  if(bos.sT>60){
    const dx=pl.x-(bos.x+bos.w/2),dy=pl.y-(bos.y+bos.h/2),d=Math.sqrt(dx*dx+dy*dy);
    eB.push({x:bos.x+bos.w/2+(bos.dir*-20),y:bos.y+40,w:8,h:8,vx:(dx/d)*3.5+(Math.random()-0.5),vy:(dy/d)*3.5+(Math.random()-0.5),life:50});
    if(bos.hp<bos.maxHp*0.5)eB.push({x:bos.x+bos.w/2+(bos.dir*-20),y:bos.y+60,w:8,h:8,vx:(dx/d)*4+(Math.random()-0.5),vy:(dy/d)*4+(Math.random()-0.5),life:50});
    bos.sT=0;
  }
  bos.mT++;
  if(bos.mT>120){bos.vy=(Math.random()-0.5)*8;bos.mT=0}
  bos.vy+=0.3;bos.y+=bos.vy;
  if(bos.y<80)bos.y=80;if(bos.y>H-200)bos.y=H-200;
  if(pl.al&&rc(pl,bos)&&pl.inv<=0)plDie();
}
 
 
 
function updateChest(){
  if(!chest||!chest.al)return;
  // Player opens chest by walking into it
  if(!chest.open&&!chest.opening&&pl.al&&rc(pl,chest)){
    chest.opening=true;chest.openT=30;ps('powerup');
  }
  // Opening animation
  if(chest.opening){
    chest.openT--;
    if(chest.openT<=0){
      chest.opening=false;chest.open=true;chest.weaponReady=true;
      // Spawn random melee weapon
      let wt=Math.random()<0.5?'blade':'saber';
      pup.push({x:chest.x+chest.w/2-12,y:chest.y-30,w:24,h:24,t:wt,al:true,bT:0,_chestW:true});
      // Sparkle particles
      for(let i=0;i<15;i++){
        par.push({x:chest.x+chest.w/2,y:chest.y,vx:(Math.random()-0.5)*6,vy:-2-Math.random()*5,sz:3+Math.random()*4,col:wt==='blade'?'#ff6f00':'#00e5ff',life:20+Math.random()*10,mL:30});
      }
      sc+=500;
    }
  }
  // After weapon is picked up (melee weapon active), go to next level
  if(chest.open&&chest.weaponReady&&chest.pickedUp&&bD){
    chest.weaponReady=false;
    setTimeout(function(){
      if(curS<MAX_S){curS++;bD=false;camX=0;pl.x=100;pl.y=300;let _pw=pl.wpn,_mt=pl.melee,_wt=pl.wT;genL(curS);spwnE(curS);bos=null;chest=null;combo=0;pl.wpn=_pw;pl.melee=_mt;pl.wT=_wt;bStop();sIntro={active:true,timer:0,stageNum:curS}}
      else{gs='win';bStop();ps('stage_clear');if(sc>hi){hi=sc;localStorage.setItem('contraHi',hi)}}
    },5000);
  }
}
function updateMelee(){
  if(pl.swH&&pl.swH.life>0){
    pl.swH.life--;
    for(let e of enm){
      if(!e.al)continue;
      if(rc(pl.swH,e)){
        e.al=false;sc+=e.t==='turret'?300:150;combo++;comboT=60;
        exp.push({x:e.x+e.w/2,y:e.y+e.h/2,rad:25,tm:15,mT:15});ps('explosion');
        for(let k=0;k<8;k++)par.push({x:e.x+e.w/2,y:e.y+e.h/2,vx:(Math.random()-0.5)*8,vy:(Math.random()-0.5)*8,sz:3+Math.random()*4,col:pl.wpn==='saber'?'#00e5ff':'#ff6f00',life:15+Math.random()*5,mL:20});
      }
    }
    if(bos&&bos.al&&rc(pl.swH,bos)){
      bos.hp-=5;bos.sT=Math.max(0,bos.sT-10);
      exp.push({x:bos.x+bos.w/2,y:bos.y+Math.random()*bos.h,rad:15,tm:8,mT:8});ps('boss_hit');
      if(bos.hp<=0){
        bos.al=false;bD=true;sc+=5000;shk=30;ps('stage_clear');
        chest={x:bos.x+bos.w/2-20,y:H-80,w:40,h:36,open:false,opening:false,openT:0,al:true};
        for(let k=0;k<30;k++)exp.push({x:bos.x+Math.random()*bos.w,y:bos.y+Math.random()*bos.h,rad:15+Math.random()*20,tm:20+Math.random()*20,mT:40});
        setTimeout(function(){
          if(curS<MAX_S){curS++;bD=false;camX=0;pl.x=100;pl.y=300;genL(curS);spwnE(curS);bos=null;combo=0;bStop();sIntro={active:true,timer:0,stageNum:curS}}
          else{gs='win';bStop();ps('stage_clear');if(sc>hi){hi=sc;localStorage.setItem('contraHi',hi)}}
        },2000);
      }
    }
  }else pl.swH=null;
  if(pl.swT>0)pl.swT--;
  if(!keys['KeyX']&&pl.melee&&pl.wT<99990){pl.melee=false;pl.wT=0;if(pl.wpn==='blade'||pl.wpn==='saber')pl.wpn='normal'}
}
function updatePowerups(){
  // 蘑菇物理
  for(let p of pup){
    if(!p.al||p.t!=="mega"||p.vy===undefined)continue;
    p.vy+=GR*0.8;p.y+=p.vy;p.x+=p.vx;
    for(let pl2 of plats){
      if(rc(p,pl2)){
        let oy2=Math.min(p.y+p.h-pl2.y,pl2.y+pl2.h-p.y);
        if(oy2<p.w){
          if(p.y+p.h/2<pl2.y+pl2.h/2){p.y=pl2.y-p.h;p.vy=0;p.oG=true;if(!p.ground){p.ground=true;p.vx=-2}}
          else{p.y=pl2.y+pl2.h;p.vy=0}
        }
      }
    }
    if(p.x<camX-60||p.x>camX+W+60||p.y>H+100)p.al=false;
  }
  for(let i=pup.length-1;i>=0;i--){
    let p=pup[i];if(!p.al)continue;p.bT++;
    const pb=Math.sin(fc*0.06+p.bT)*2;
    const py=p.t==="mega"&&p.vy!==undefined?p.y:p.y+pb;
    if(pl.al&&rc(pl,{x:p.x,y:py,w:24,h:24})){
      p.al=false;ps("powerup");
      if(p.t==="spread"){pl.wpn="spread";pl.wT=600}
      else if(p.t==="rapid"){pl.wpn="rapid";pl.wT=600}
      else if(p.t==="laser"){pl.wpn="laser";pl.wT=600}
      else if(p.t==="life"){li++;sc+=200}
      else if(p.t==="shield"){pl.inv=300;sc+=100}
      else if(p.t==="mega"){pl.scl=2;pl.w=56;pl.h=80;pl.sclT=600;sc+=300}
      else if(p.t==="blade"||p.t==="saber"){pl.wpn=p.t;pl.wT=99999;pl.melee=true;sc+=200;if(p._chestW&&chest)chest.pickedUp=true}
      for(let k=0;k<10;k++)par.push({x:p.x+12,y:py+6,vx:(Math.random()-0.5)*8,vy:(Math.random()-0.5)*8-2,sz:3+Math.random()*3,col:["#ffeb3b","#4fc3f7","#ff6f00"][Math.floor(Math.random()*3)],life:20+Math.random()*10,mL:30});
    }
  }
  pup=pup.filter(p=>p.al);
}
function updateCombo(){if(comboT>0){comboT--;if(comboT<=0)combo=0}}
 
//============================================================
// ======================= 主渲染 =======================
//============================================================
function render(){
  fc++;
  if(shk>0)shk--;
  ct.save();
  if(shk>0)ct.translate((Math.random()-0.5)*shk*2,(Math.random()-0.5)*shk*2);
  if(gs==='title'){drawTitle()}
  else if(gs==='playing'||gs==='gameover'||gs==='win'){
    drawBG();drawPlats();drawPups();drawBuls();
    for(let e of enm){
      if(!e.al||Math.abs(e.x-camX)>W+80)continue;
      const ed={soldier:drawSol,runner:drawRun,flyer:drawFly,goomba:drawGoo,koopa:drawKoo,piranha:drawPir,turret:drawTur};
      if(ed[e.t])ed[e.t](e);
    }
    drawChest();drawSaws();drawBoss();drawExps();drawPars();drawCoins();drawSlash();
    if(pl.al&&pl.inv%6<3)drawPl(pl.x,pl.y,pl.dir,pl.aF,pl.pr,pl.sh,pl.def,pl.scl);
    drawHUD();drawStageIntro();
    if(gs==='gameover')drawGO();
    if(gs==='win')drawWin();
  }
  ct.restore();
}
 
function update(){
  if(gs==='playing'){
    if(typeof introInit==='undefined'||!introInit){spwnE(curS);introInit=true;sIntro={active:true,timer:0,stageNum:curS};bStart()}
    if(sIntro.active){sIntro.timer++;if(sIntro.timer>90)sIntro.active=false;return}
    updatePlayer();updateChest();updateMelee();updateSaws();updateBuls();updateEnemies();updateBoss();updatePowerups();updateCombo();
  }
}
function gameLoop(){update();render();requestAnimationFrame(gameLoop)}
 
addEventListener('keydown',e=>{
  if(e.code==='Enter'||e.code==='Space'){
    if(gs==='title'){
      gs='playing';curS=1;LW=sCfg[curS-1].lW;pl.x=100;pl.y=300;pl.al=true;pl.w=pl.bW;pl.h=pl.bH;pl.scl=1;pl.wpn='normal';
      pl.inv=0;pl.wT=0;pl._b=false;sc=0;li=99;coins=0;pl.melee=false;pl.swT=0;pl.swH=null;bos=null;chest=null;camX=0;bD=false;genL(curS);introInit=false;combo=0;exp.length=0;par.length=0;eB.length=0;coinAnims.length=0;saws.length=0;bStop();
    }
    if(gs==='gameover'||gs==='win'){gs='title';bStop()}
  }
  if(e.code==='KeyM'&&gs==='playing'){if(bgmP)bStop();else bStart()}
});
 
genL(1);
gameLoop();
</script>
</body></html>


测试了,目前可以玩了,欢迎爱好者,提出批评的意见和宝贵的建议。
复制代码到记事本另存为htmll格式即可试玩哦~~

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

沙发
 楼主| 许愿南海 发表于 2026-7-7 16:22 |楼主
测试了,可以正常打开。

a2317796593f56ef3f0b513e2c8fd7f7.png (85.03 KB, 下载次数: 0)

a2317796593f56ef3f0b513e2c8fd7f7.png

c82c14cc5b9889be53386bd78d365973.png (73.19 KB, 下载次数: 0)

c82c14cc5b9889be53386bd78d365973.png
3#
深爱成瘾 发表于 2026-7-7 17:26
没必要这样写啊,可以直接调用游戏的nes文件,运行原版魂斗罗啊,也有改版的魂斗罗,而且nes文件也可以二次编辑,想要什么样子都可以自定义。
4#
gy123621 发表于 2026-7-7 17:30
5#
linweite 发表于 2026-7-7 18:28
梦回童年时光
6#
freshegg 发表于 2026-7-7 18:44
大佬神勇
7#
robyyang 发表于 2026-7-7 19:27
额,还真别说,有点难,比如我魂斗罗和超级玛丽难。。。
8#
dex5242 发表于 2026-7-7 19:35
求个自定义按键,P1 P2联机和P1 P2对战 哈哈哈
9#
 楼主| 许愿南海 发表于 2026-7-7 19:54 |楼主
dex5242 发表于 2026-7-7 19:35
求个自定义按键,P1 P2联机和P1 P2对战 哈哈哈

我尝试下
10#
 楼主| 许愿南海 发表于 2026-7-7 19:54 |楼主
深爱成瘾 发表于 2026-7-7 17:26
没必要这样写啊,可以直接调用游戏的nes文件,运行原版魂斗罗啊,也有改版的魂斗罗,而且nes文件也可以二次 ...

我试试,谢谢宝贵建议
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-7-14 15:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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