Added kidney_labe and Cyste_kid

This commit is contained in:
verboomp
2026-04-16 08:14:20 +02:00
parent aa66c030f8
commit 9cc8ac8cad
40 changed files with 6762 additions and 0 deletions

266
Cyst_Kid/js/game.js Normal file
View File

@@ -0,0 +1,266 @@
/* ========================================
CYST-KID — Game Core
======================================== */
class Game{
constructor(){
this.cv=document.getElementById('gameCanvas');
this.cx=this.cv.getContext('2d');
this.snd=new Snd();
this.cv.width=CW;this.cv.height=CH;
this.state='start';
this.lv=1;this.lives=3;this.sc=0;this.lsc=0;
this.lt=0;this.ld=0;this.tt=0;this.td=0;
this.dots=[];this.nDots=0;this.eDots=0;
this.pl=null;this.pains=[];this.cysts=[];this.bios=[];
this.ateSt=false;this.ateBi=false;this.combD=0;this.combN=1;
this.sup=false;this.supT=0;
this.fx=[];this.fr=0;this.lastT=0;this.acc=0;
// Input
this.ks={up:false,down:false,left:false,right:false,shift:false};
this.swD=null;
this.rank=JSON.parse(localStorage.getItem('ckr3')||'[]');
this._inp();this._ui();this._rsz();
window.addEventListener('resize',()=>this._rsz());
this._rf=this._loop.bind(this);requestAnimationFrame(this._rf);
}
_rsz(){
const s=Math.min(window.innerWidth*.95/CW,window.innerHeight*.95/CH,2.5);
this.cv.style.width=CW*s+'px';this.cv.style.height=CH*s+'px';
}
// ---------- INPUT ----------
_inp(){
window.addEventListener('keydown',e=>{
let h=true;
switch(e.code){
case'ArrowUp':case'KeyW':this.ks.up=true;break;
case'ArrowDown':case'KeyS':this.ks.down=true;break;
case'ArrowLeft':case'KeyA':this.ks.left=true;break;
case'ArrowRight':case'KeyD':this.ks.right=true;break;
case'ShiftLeft':case'ShiftRight':this.ks.shift=true;break;
case'Escape':this._esc();break;
case'Enter':case'NumpadEnter':this._ent();break;
default:h=false;
}
if(h)e.preventDefault();
});
window.addEventListener('keyup',e=>{
switch(e.code){
case'ArrowUp':case'KeyW':this.ks.up=false;break;
case'ArrowDown':case'KeyS':this.ks.down=false;break;
case'ArrowLeft':case'KeyA':this.ks.left=false;break;
case'ArrowRight':case'KeyD':this.ks.right=false;break;
case'ShiftLeft':case'ShiftRight':this.ks.shift=false;break;
}
});
let tx=0,ty=0;
this.cv.addEventListener('touchstart',e=>{e.preventDefault();tx=e.touches[0].clientX;ty=e.touches[0].clientY},{passive:false});
this.cv.addEventListener('touchmove',e=>{e.preventDefault();const dx=e.touches[0].clientX-tx,dy=e.touches[0].clientY-ty;if(Math.abs(dx)+Math.abs(dy)<12)return;this.swD=Math.abs(dx)>Math.abs(dy)?(dx>0?'right':'left'):(dy>0?'down':'up');tx=e.touches[0].clientX;ty=e.touches[0].clientY},{passive:false});
this._gpi=null;
window.addEventListener('gamepadconnected',e=>{this._gpi=e.gamepad.index});
window.addEventListener('gamepaddisconnected',e=>{if(this._gpi===e.gamepad.index)this._gpi=null});
// Virtual joystick — global touch tracking so dragging outside the circle keeps working
const base=document.getElementById('joystick-base');
const knob=document.getElementById('joystick-knob');
if(base&&knob){
const MAX=39; // max knob travel px
const DEAD=10; // deadzone px
let joyId=null, cx=0, cy=0; // active touch id and fixed center coords
const moveKnob=(dx,dy)=>{
const dist=Math.hypot(dx,dy);
const scale=Math.min(dist,MAX)/(dist||1);
knob.style.transform=`translate(calc(-50% + ${dx*scale}px), calc(-50% + ${dy*scale}px))`;
};
const applyDir=(dx,dy)=>{
this.ks.up=this.ks.down=this.ks.left=this.ks.right=false;
if(Math.hypot(dx,dy)<DEAD)return;
if(Math.abs(dx)>Math.abs(dy)){this.ks[dx>0?'right':'left']=true}
else{this.ks[dy>0?'down':'up']=true}
};
const reset=()=>{
joyId=null;
this.ks.up=this.ks.down=this.ks.left=this.ks.right=false;
knob.style.transform='translate(-50%, -50%)';
};
// touchstart only on the base — anchors the joystick center
base.addEventListener('touchstart',e=>{
e.preventDefault();
if(joyId!==null)return; // ignore second finger
const t=e.changedTouches[0];
joyId=t.identifier;
const r=base.getBoundingClientRect();
cx=r.left+r.width/2; cy=r.top+r.height/2;
const dx=t.clientX-cx, dy=t.clientY-cy;
moveKnob(dx,dy); applyDir(dx,dy);
},{passive:false});
// move and end tracked globally so dragging outside the element keeps working
document.addEventListener('touchmove',e=>{
if(joyId===null)return;
const t=Array.from(e.changedTouches).find(x=>x.identifier===joyId);
if(!t)return;
e.preventDefault();
const dx=t.clientX-cx, dy=t.clientY-cy;
moveKnob(dx,dy); applyDir(dx,dy);
},{passive:false});
document.addEventListener('touchend',e=>{
if(joyId===null)return;
if(Array.from(e.changedTouches).some(x=>x.identifier===joyId))reset();
});
document.addEventListener('touchcancel',e=>{
if(joyId===null)return;
if(Array.from(e.changedTouches).some(x=>x.identifier===joyId))reset();
});
}
}
_gp(){if(this._gpi===null)return{};const g=navigator.getGamepads()[this._gpi];if(!g)return{};let d=null;const z=.3;if(Math.abs(g.axes[0])>Math.abs(g.axes[1])){if(g.axes[0]<-z)d='left';else if(g.axes[0]>z)d='right'}else{if(g.axes[1]<-z)d='up';else if(g.axes[1]>z)d='down'}if(g.buttons[0]?.pressed)this._ent();if(g.buttons[8]?.pressed)this._esc();return{dir:d,sprint:g.buttons[5]?.pressed||g.buttons[7]?.pressed}}
// Which direction key is currently held?
_dir(){
let d=null;
if(this.ks.up)d='up';
if(this.ks.down)d='down';
if(this.ks.left)d='left';
if(this.ks.right)d='right';
if(this.swD){d=this.swD;this.swD=null}
const gp=this._gp();
if(gp.dir)d=gp.dir;
return d;
}
// ---------- UI BUTTON BINDINGS ----------
_ui(){
document.getElementById('startBtn').onclick=()=>{this.snd.init();this._start()};
document.getElementById('nextLevelBtn').onclick=()=>this._ent();
document.getElementById('restartBtn').onclick=()=>{this.sc=0;this._start()};
document.getElementById('submitScoreBtn').onclick=()=>this._sub();
document.getElementById('playAgainBtn').onclick=()=>this._ov('start');
}
_esc(){if(this.state==='playing'){this.sc=0;this.lsc=0;this.state='start';this._ov('start')}}
_ent(){
if(this.state==='lvlDone'){this.lv++;this._init();this.state='playing';this._ov(null)}
else if(this.state==='over'){this.sc=0;this._start()}
else if(this.state==='start'){this.snd.init();this._start()}
}
// ---------- OVERLAY / SCREEN MANAGER ----------
_ov(n){
['start-screen','level-complete-screen','game-over-screen','win-screen'].forEach(id=>document.getElementById(id).style.display='none');
if(n==='start') this._showStart();
if(n==='lvlDone') this._showLvlDone();
if(n==='over') this._showGameOver();
if(n==='win') this._showWin();
}
// ---------- LIFECYCLE ----------
_init(){
this.dots=[];this.nDots=0;this.eDots=0;
for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++)if(MAP[r][c]===0){this.dots.push({r,c,e:false});this.nDots++}
this.lsc=0;this.lt=0;this.ld=0;this.sup=false;this.supT=0;
this.ateSt=false;this.ateBi=false;this.combD=0;this.combN=this.lv;
this.fx=[];this.lives=3;this.qDir=null;
this.pl={
x:PL0.c*TILE, y:PL0.r*TILE+HUD_TOP,
gc:PL0.c, gr:PL0.r,
dir:'right', moving:false,
spd:BASE_SPD, mouth:0, mouthD:1,
lastFace:'right'
};
this.pains=[];
const st=['chase','ambush','patrol','random'];
PH.forEach((h,i)=>this.pains.push({
x:h.c*TILE, y:h.r*TILE+HUD_TOP,
gc:h.c, gr:h.r,
dir:'up', moving:false,
spd:1.2+this.lv*0.2,
strat:st[i], scared:false, eaten:false,
inHouse:true, leaving:false,
relT:i*80+50,
pulse:Math.random()*6.28
}));
this.cysts=[];
this._rp(this.lv,1,8).forEach(p=>this.cysts.push({r:p.r,c:p.c,e:false,v:true}));
this.bios=[];
}
_rp(n,mr,xr){
const cs=[];for(let r=mr;r<=xr;r++)for(let c=1;c<COLS-1;c++)if(isPath(r,c))cs.push({r,c});
for(let i=cs.length-1;i>0;i--){const j=0|Math.random()*(i+1);[cs[i],cs[j]]=[cs[j],cs[i]]}
const o=[];for(const p of cs){if(o.length>=n)break;if(o.every(q=>Math.abs(q.r-p.r)+Math.abs(q.c-p.c)>=4))o.push(p)}return o;
}
// -------- MAIN LOOP --------
_loop(ts){
if(!this.lastT)this.lastT=ts;
const dt=ts-this.lastT;this.lastT=ts;
if(this.state==='playing'){this.acc+=dt;let s=0;while(this.acc>=TICK&&s<5){this._upd();this.acc-=TICK;s++}if(this.acc>TICK*5)this.acc=0}
this._draw();requestAnimationFrame(this._rf);
}
// -------- UPDATE --------
_upd(){
this.fr++;this.lt++;
const liveDir=this._dir();
if(liveDir) this.qDir=liveDir;
const wantDir=this.qDir;
const base=BASE_SPD+(this.lv-1)*0.2;
const gp=this._gp();
const sprint=this.ks.shift||(gp.sprint||false);
this.pl.spd=this.sup?base*2:(sprint?base*2:base);
this._movePl(wantDir);
this._movePains();
this._coll();
if(this.sup){this.supT--;if(this.supT<=0)this._esup()}
this.fx=this.fx.filter(f=>{f.t--;return f.t>0});
this._chkDone();
}
// -------- COLLISIONS --------
_coll(){
for(const p of this.pains){
if(p.eaten||p.inHouse||p.leaving)continue;
if(Math.abs(this.pl.x-p.x)+Math.abs(this.pl.y-p.y)<TILE*.8){
if(this.sup&&p.scared){p.eaten=true;this.lsc+=5;this.snd.bite();this.fx.push({x:p.x+TILE/2,y:p.y+TILE/2,co:'#00ff88',t:20,mx:20,tp:'burst'})}
else if(!this.sup){this._die();return}
}
}
}
_die(){
this.lives--;this.snd.boom();
this.fx.push({x:this.pl.x+TILE/2,y:this.pl.y+TILE/2,co:'#FF4444',t:40,mx:40,tp:'exp'});
if(this.lives<=0){this.lsc=0;this.state='over';this.snd.over();setTimeout(()=>this._ov('over'),600)}
else{
this.pl.x=PL0.c*TILE;this.pl.y=PL0.r*TILE+HUD_TOP;this.pl.gc=PL0.c;this.pl.gr=PL0.r;this.pl.dir='right';this.pl.moving=false;
this.qDir=null;this.swD=null;this.ks.up=this.ks.down=this.ks.left=this.ks.right=false;
PH.forEach((h,i)=>{const p=this.pains[i];p.x=h.c*TILE;p.y=h.r*TILE+HUD_TOP;p.gc=h.c;p.gr=h.r;p.inHouse=true;p.leaving=false;p.eaten=false;p.relT=i*90+60;p.moving=false});
}
}
_chkDone(){
if(this.eDots>=this.nDots&&this.cysts.every(c=>c.e)&&this.combD>=this.combN){
this.sc+=this.lsc;this.tt+=this.lt;this.td+=this.ld;this.snd.lvlUp();
if(this.lv>=3){this.state='win';setTimeout(()=>this._ov('win'),800)}
else{this.state='lvlDone';setTimeout(()=>this._ov('lvlDone'),800)}
}
}
}
window.addEventListener('load',()=>new Game());