[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>