- 19 TypeScript modules under src/ (constants, entities, room, game, render) - Canvas 2D rendering with dark fantasy palette - Room-based navigation, random 7x7 map generation - Two combat modes: ranged (pistol) and melee (knife) - Wall collision with door opening support - Minimap, HP bar, enemy AI - Bun build pipeline: src/main.ts -> dist/main.js + dist/index.html
4 lines
17 KiB
JavaScript
4 lines
17 KiB
JavaScript
var H={};function l(){window.addEventListener("keydown",(q)=>{if(H[q.key]=!0,["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"," "].includes(q.key))q.preventDefault()}),window.addEventListener("keyup",(q)=>{H[q.key]=!1}),window.addEventListener("blur",()=>{for(let q in H)delete H[q]})}var N=880,b=660,j=44,K=15,M=11,s=660,D=484,u=110,v=80,Y=0,m=1,W=2,P=0,a=1,k={up:[0,-1],down:[0,1],left:[-1,0],right:[1,0]},g={up:"bottom",down:"top",left:"right",right:"left"},B={up:{cols:[6,7,8],row:0,cx:7,cy:0},down:{cols:[6,7,8],row:10,cx:7,cy:10},left:{col:0,rows:[4,5,6],cx:0,cy:5},right:{col:14,rows:[4,5,6],cx:14,cy:5}},O=3,r=8,o=4;function E(q){let J=[];for(let Q=0;Q<M;Q++){J[Q]=[];for(let V=0;V<K;V++)J[Q][V]=Q===0||Q===M-1||V===0||V===K-1?Y:m}if(q)V0(J,q);return J}function V0(q,J){if(J.up)for(let Q of B.up.cols)q[B.up.row][Q]=W;if(J.down)for(let Q of B.down.cols)q[B.down.row][Q]=W;if(J.left)for(let Q of B.left.rows)q[Q][B.left.col]=W;if(J.right)for(let Q of B.right.rows)q[Q][B.right.col]=W}function e(q){for(let J=q.length-1;J>0;J--){let Q=Math.random()*J|0;[q[J],q[Q]]=[q[Q],q[J]]}return q}function $0(q,J){return Math.random()*(J-q)+q}function S(q,J){return Math.floor($0(q,J+1))}function x(q,J){return q.x<J.x+J.w&&q.x+q.w>J.x&&q.y<J.y+J.h&&q.y+q.h>J.y}class f{c;r;type;doors={up:!1,down:!1,left:!1,right:!1};visited=!1;cleared=!1;enemies=[];tears=[];tiles;constructor(q,J,Q){this.c=q,this.r=J,this.type=Q,this.tiles=E()}buildTiles(){this.tiles=E(this.cleared||this.type==="spawn"?this.doors:void 0)}}class I{rooms=new Map;key(q,J){return q+","+J}get(q,J){return this.rooms.get(this.key(q,J))}has(q,J){return this.rooms.has(this.key(q,J))}add(q,J,Q){let V=new f(q,J,Q);return this.rooms.set(this.key(q,J),V),V}hasBoss(){for(let q of this.rooms.values())if(q.type==="boss")return!0;return!1}generate(){this.add(0,0,"spawn");let q=[[0,0]],J=1,Q=r+S(0,o),V=[["up",0,-1],["down",0,1],["left",-1,0],["right",1,0]];while(q.length>0&&J<Q){let $=S(0,q.length-1),[z,F]=q[$];e(V);let U=!1;for(let[X,Z,_]of V){if(J>=Q)break;let G=z+Z,A=F+_;if(Math.abs(G)>O||Math.abs(A)>O)continue;if(this.has(G,A))continue;let L="normal";if(!this.hasBoss()&&(J===Q-1||Math.random()<0.2&&J>=3))L="boss";else if(Math.random()<0.12&&J>=2)L="treasure";this.add(G,A,L);let p=X;this.get(z,F).doors[p]=!0,this.get(G,A).doors[g[p]]=!0,q.push([G,A]),J++,U=!0}if(!U)q.splice($,1)}if(!this.hasBoss()){let $=[...this.rooms.values()].filter((z)=>z.type==="normal");if($.length>0)$[S(0,$.length-1)].type="boss"}}}class T{x=0;y=0;w=26;h=26;speed=3.2;hp=6;maxHp=6;mode=P;facing="up";moveDir="up";atkCD=0;invTimer=0;transCD=0;get box(){return{x:this.x-this.w/2,y:this.y-this.h/2,w:this.w,h:this.h}}}var z0={normal:{w:32,hp:3,speed:1.15,damage:1},fast:{w:26,hp:2,speed:1.9,damage:1},boss:{w:46,hp:10,speed:0.9,damage:2}};class i{x;y;type;w;h;hp;maxHp;speed;damage;knx=0;kny=0;hitTimer=0;atkTimer=0;constructor(q,J,Q){this.x=q,this.y=J,this.type=Q;let V=z0[Q];this.w=V.w,this.h=V.w,this.hp=V.hp,this.maxHp=V.hp,this.speed=V.speed,this.damage=V.damage}get box(){return{x:this.x-this.w/2,y:this.y-this.h/2,w:this.w,h:this.h}}get alive(){return this.hp>0}}class d{x;y;dx;dy;r=5;speed=7;damage=1;life=80;constructor(q,J,Q,V){this.x=q,this.y=J,this.dx=Q,this.dy=V}get alive(){return this.life>0}}class n{dir;life=10;damage=2;kb=10;box;constructor(q,J,Q){this.dir=Q;let V=22,$=50,[z,F]=k[Q];this.box={x:q+(z>0?V:z<0?-V-$:-$/2),y:J+(F>0?V:F<0?-V-$:-$/2),w:$,h:$}}get alive(){return this.life>0}}function j0(q,J,Q){if(Q<0&&q.doors.up&&B.up.cols.includes(J))return!1;if(Q>=M&&q.doors.down&&B.down.cols.includes(J))return!1;if(J<0&&q.doors.left&&B.left.rows.includes(Q))return!1;if(J>=K&&q.doors.right&&B.right.rows.includes(Q))return!1;if(Q<0||Q>=M||J<0||J>=K)return!0;return q.tiles[Q][J]===Y}function C(q,J,Q,V){let $=Math.floor((q.x-Q)/w),z=Math.floor((q.x+q.w-Q)/w),F=Math.floor((q.y-V)/w),U=Math.floor((q.y+q.h-V)/w);for(let X=F;X<=U;X++)for(let Z=$;Z<=z;Z++)if(j0(J,Z,X))return!0;return!1}var w=44;var F0={up:["w","W","ArrowUp"],down:["s","S","ArrowDown"],left:["a","A","ArrowLeft"],right:["d","D","ArrowRight"]};function R(q){for(let J of F0[q])if(H[J])return!0;return!1}function c(q){if(q.gameOver||q.won)return;if(q.player.transCD>0)return;let{player:J,curRoom:Q}=q;if(!Q.cleared)return;let V=Math.floor((J.x-u)/j),$=Math.floor((J.y-v)/j);if($===0&&Q.doors.up&&B.up.cols.includes(V)&&R("up")){if(q.roomMap.has(q.cc,q.cr-1)){q.cr--,q.enterRoom("down");return}}if($===M-1&&Q.doors.down&&B.down.cols.includes(V)&&R("down")){if(q.roomMap.has(q.cc,q.cr+1)){q.cr++,q.enterRoom("up");return}}if(V===0&&Q.doors.left&&B.left.rows.includes($)&&R("left")){if(q.roomMap.has(q.cc-1,q.cr)){q.cc--,q.enterRoom("right");return}}if(V===K-1&&Q.doors.right&&B.right.rows.includes($)&&R("right")){if(q.roomMap.has(q.cc+1,q.cr)){q.cc++,q.enterRoom("left");return}}}function t(q,J){for(let Q=0;Q<M;Q++)for(let V=0;V<K;V++){let $=u+V*j,z=v+Q*j,F=J.tiles[Q][V];if(F===Y)q.fillStyle="#1a1a24",q.fillRect($,z,j,j),q.fillStyle="#242436",q.fillRect($+2,z+2,j-4,j-4),q.fillStyle="#1e1e2c",q.fillRect($+4,z+4,j-8,j-8),q.strokeStyle="#161620",q.lineWidth=1,q.beginPath(),q.moveTo($,z+j/2),q.lineTo($+j,z+j/2),q.stroke(),q.beginPath(),q.moveTo($+j/2,z),q.lineTo($+j/2,z+j/2),q.stroke();else if(F===W)q.fillStyle="#0d0d14",q.fillRect($,z,j,j),q.fillStyle="#2a1e0e",q.fillRect($+6,z+6,j-12,j-12),q.fillStyle="#3a2e14",q.fillRect($+10,z+10,j-20,j-20);else{let U=(Q+V)%2===0;q.fillStyle=U?"#2e2e24":"#353528",q.fillRect($,z,j,j)}}q.strokeStyle="rgba(0,0,0,0.3)",q.lineWidth=2,q.strokeRect(u,v,s,D)}function q0(q,J,Q,V){for(let $ of J.enemies){if(!$.alive)continue;let z=$.hitTimer>0&&$.hitTimer%4<2;if(q.save(),q.fillStyle="rgba(0,0,0,0.3)",q.beginPath(),q.ellipse($.x+2,$.y+$.h/4,$.w/3,4,0,0,Math.PI*2),q.fill(),$.type==="boss")U0(q,$,z);else if($.type==="fast")Z0(q,$,z);else B0(q,$,z);q.restore()}X0(q,Q);for(let $ of J.tears){if(!$.alive)continue;q.save(),q.fillStyle="#6699cc",q.beginPath(),q.arc($.x,$.y,$.r,0,Math.PI*2),q.fill(),q.fillStyle="#99bbee",q.beginPath(),q.arc($.x-1.5,$.y-1.5,$.r-2,0,Math.PI*2),q.fill(),q.restore()}if(V&&V.alive)v0(q,V)}function U0(q,J,Q){if(q.fillStyle=Q?"#ddd":"#5a0a0a",q.beginPath(),q.arc(J.x,J.y,J.w/2,0,Math.PI*2),q.fill(),q.fillStyle="#4a0808",q.beginPath(),q.arc(J.x-3,J.y-3,J.w/2-4,0,Math.PI*2),q.fill(),q.fillStyle=Q?"#000":"#ff3333",q.beginPath(),q.arc(J.x-8,J.y-8,5,0,Math.PI*2),q.fill(),q.beginPath(),q.arc(J.x+8,J.y-8,5,0,Math.PI*2),q.fill(),q.fillStyle="#000",q.beginPath(),q.arc(J.x-8,J.y-8,2.5,0,Math.PI*2),q.fill(),q.beginPath(),q.arc(J.x+8,J.y-8,2.5,0,Math.PI*2),q.fill(),q.fillStyle=Q?"#bbb":"#3a0505",q.beginPath(),q.moveTo(J.x-16,J.y-J.w/2+4),q.lineTo(J.x-8,J.y-J.w/2-16),q.lineTo(J.x,J.y-J.w/2+4),q.fill(),q.beginPath(),q.moveTo(J.x-4,J.y-J.w/2+4),q.lineTo(J.x+4,J.y-J.w/2-16),q.lineTo(J.x+12,J.y-J.w/2+4),q.fill(),J.hp<J.maxHp)q.fillStyle="#222",q.fillRect(J.x-22,J.y-J.h/2-14,44,4),q.fillStyle="#c33",q.fillRect(J.x-22,J.y-J.h/2-14,44*(J.hp/J.maxHp),4)}function Z0(q,J,Q){q.fillStyle=Q?"#ddd":"#992222",q.beginPath(),q.arc(J.x,J.y,J.w/2,0,Math.PI*2),q.fill(),q.fillStyle="#771111",q.beginPath(),q.arc(J.x-1,J.y-1,J.w/2-3,0,Math.PI*2),q.fill(),q.fillStyle="#ff4444",q.beginPath(),q.arc(J.x-5,J.y-4,3,0,Math.PI*2),q.fill(),q.beginPath(),q.arc(J.x+5,J.y-4,3,0,Math.PI*2),q.fill(),q.fillStyle="#000",q.beginPath(),q.arc(J.x-5,J.y-5,1.5,0,Math.PI*2),q.fill(),q.beginPath(),q.arc(J.x+5,J.y-5,1.5,0,Math.PI*2),q.fill()}function B0(q,J,Q){q.fillStyle=Q?"#ccc":"#5a4a2e",q.fillRect(J.x-J.w/2,J.y-J.h/2,J.w,J.h),q.fillStyle="#4a3a1e",q.fillRect(J.x-J.w/2+3,J.y-J.h/2+3,J.w-6,J.h-6),q.fillStyle="#332816",q.fillRect(J.x-J.w/2+6,J.y-J.h/2+6,J.w-12,J.h-12),q.fillStyle="#ffcc66",q.fillRect(J.x-7,J.y-5,5,5),q.fillRect(J.x+2,J.y-5,5,5),q.fillStyle="#000",q.fillRect(J.x-6,J.y-4,3,3),q.fillRect(J.x+3,J.y-4,3,3)}function X0(q,J){q.save();let Q=J.invTimer>0&&J.invTimer%6<3,V=J.mode===0?"#2a6a9a":"#9a3a2a";q.fillStyle=Q?"#ddd":V,q.fillRect(J.x-J.w/2,J.y-J.h/2,J.w,J.h),q.fillStyle=Q?"#ccc":"rgba(0,0,0,0.3)",q.fillRect(J.x-J.w/2+3,J.y-J.h/2+3,J.w-6,J.h-6);let[$,z]=k[J.facing],F=J.x+$*(J.w/2+4),U=J.y+z*(J.h/2+4);if(J.mode===0)H0(q,J,F,U,$,z,Q);else N0(q,F,U,$,z,Q);q.fillStyle="#fff";let X=J.x+$*5,Z=J.y+z*5;q.fillRect(X-5,Z-4,4,5),q.fillRect(X+1,Z-4,4,5),q.fillStyle="#111",q.fillRect(X-4+$,Z-3+z,2,3),q.fillRect(X+2+$,Z-3+z,2,3),q.restore()}function H0(q,J,Q,V,$,z,F){q.strokeStyle=F?"#999":"#555",q.lineWidth=3,q.lineCap="round",q.beginPath(),q.moveTo(Q,V),q.lineTo(Q+$*14+z*2,V+z*14+$*2),q.stroke(),q.fillStyle=F?"#aaa":"#444",q.save();let U=z!==0?Math.PI/2*(z<0?-1:1):$<0?Math.PI:0;if(q.translate(J.x+$*8,J.y+z*8),q.rotate(U),q.fillRect(-7,-4,14,8),q.restore(),J.atkCD>8&&J.mode===0)q.fillStyle="rgba(255,200,50,0.6)",q.beginPath(),q.arc(Q+$*16,V+z*16,6,0,Math.PI*2),q.fill(),q.fillStyle="rgba(255,255,200,0.4)",q.beginPath(),q.arc(Q+$*18,V+z*18,8,0,Math.PI*2),q.fill()}function N0(q,J,Q,V,$,z){q.strokeStyle=z?"#bbb":"#ccc",q.lineWidth=2;let F=J+V*6,U=Q+$*6;q.beginPath(),q.moveTo(F,U),q.lineTo(F+V*16-$*6,U+$*16+V*6),q.lineTo(F+V*16+$*6,U+$*16-V*6),q.closePath(),q.fillStyle=z?"#ddd":"#d4d4d4",q.fill(),q.stroke(),q.fillStyle=z?"#a99":"#5a3a1a",q.fillRect(F-V*3-$*3,U-$*3-V*3,8,8),q.fillStyle=z?"#bbb":"#888",q.fillRect(F-V*2-$*5,U-$*2-V*5,5,12)}function v0(q,J){let Q=J.life/10;q.save(),q.globalAlpha=Q*0.35,q.fillStyle="#cc8844",q.fillRect(J.box.x,J.box.y,J.box.w,J.box.h),q.globalAlpha=Q,q.strokeStyle="#ddbb88",q.lineWidth=2,q.strokeRect(J.box.x,J.box.y,J.box.w,J.box.h),q.globalAlpha=Q*0.8,q.strokeStyle="#ffcc88",q.lineWidth=3;let[V,$]=k[J.dir];q.beginPath(),q.moveTo(J.box.x+J.box.w/2-V*18,J.box.y+J.box.h/2-$*18),q.lineTo(J.box.x+J.box.w/2+V*18,J.box.y+J.box.h/2+$*18),q.stroke(),q.restore()}function J0(q,J,Q){G0(q,J),u0(q,J),K0(q,Q),M0(q,Q)}function G0(q,J){q.fillStyle="#111",q.fillRect(20,20,140,14),q.fillStyle="#2a0a0a",q.fillRect(22,22,136,10);let F=Math.max(0,J.hp/J.maxHp),U=F>0.5?"#993333":F>0.25?"#994422":"#663322";q.fillStyle=U,q.fillRect(22,22,136*F,10),q.strokeStyle="#333",q.lineWidth=1,q.strokeRect(20,20,140,14),q.fillStyle="#bbb",q.font="10px monospace",q.textAlign="center",q.fillText(`HP ${J.hp}/${J.maxHp}`,90,31)}function u0(q,J){let Q=b-46;q.textAlign="center";let V=J.mode===P?"RANGED":"MELEE",$=J.mode===P?"#4488cc":"#cc6644";if(q.fillStyle="#0d0d0d",q.fillRect(N/2-95,Q-18,190,34),q.strokeStyle=$,q.lineWidth=2,q.strokeRect(N/2-95,Q-18,190,34),q.fillStyle=$,q.font="bold 17px monospace",q.fillText(`[ ${V} ]`,N/2,Q+8),q.fillStyle="#555",q.font="11px monospace",q.fillText("[Tab/Q] switch",N/2,Q-26),J.mode===P)q.strokeStyle="#88bbdd",q.lineWidth=2,q.beginPath(),q.moveTo(N/2-82,Q-4),q.lineTo(N/2-72,Q-4),q.stroke(),q.fillStyle="#88bbdd",q.fillRect(N/2-82,Q-8,10,8);else q.fillStyle="#ddbb88",q.beginPath(),q.moveTo(N/2-82,Q-10),q.lineTo(N/2-74,Q-2),q.lineTo(N/2-82,Q+4),q.fill()}function K0(q,J){let Q=J.enemies.filter((V)=>V.alive).length;if(q.textAlign="left",Q>0)q.fillStyle="#aa4444",q.font="13px monospace",q.fillText(`▶ ${Q}`,20,b-18);else if(!J.cleared&&J.type!=="spawn")q.fillStyle="#886633",q.font="13px monospace",q.fillText("Clear the room",20,b-18)}function M0(q,J){if(!J.visited)return;q.textAlign="right";let V={spawn:"START",normal:"",treasure:"TREASURE",boss:"BOSS"}[J.type];if(V)q.fillStyle="#555",q.font="11px monospace",q.fillText(V,N-20,v+D+30)}var h=14,A0=2;function Q0(q,J,Q,V){let $=h+A0,z=N-180,F=12;q.fillStyle="rgba(0,0,0,0.75)",q.fillRect(z-8,4,$*7+16,$*7+16),q.strokeStyle="#333",q.lineWidth=1,q.strokeRect(z-8,4,$*7+16,$*7+16);for(let U=-3;U<=3;U++)for(let X=-3;X<=3;X++){let Z=J.get(Q+X,V+U);if(!Z)continue;let _=z+(X+3)*$,G=12+(U+3)*$,A="#141414";if(Z.visited)A=Z.type==="spawn"?"#2a5a2a":Z.type==="boss"?"#5a1a1a":Z.type==="treasure"?"#5a5a1a":"#555";if(q.fillStyle=A,q.fillRect(_,G,h,h),Z.visited){if(q.strokeStyle="rgba(255,255,255,0.12)",q.lineWidth=1,Z.doors.up)q.fillRect(_+$/2-2,G-2,4,3);if(Z.doors.down)q.fillRect(_+$/2-2,G+h-1,4,3);if(Z.doors.left)q.fillRect(_-2,G+$/2-2,3,4);if(Z.doors.right)q.fillRect(_+h-1,G+$/2-2,3,4)}if(X===0&&U===0)q.strokeStyle="#ddd",q.lineWidth=2,q.strokeRect(_-1.5,G-1.5,h+3,h+3)}}class y{canvas;ctx;roomMap=new I;player=new T;cc=0;cr=0;meleeSwing=null;gameOver=!1;won=!1;constructor(q){this.canvas=q,this.ctx=q.getContext("2d"),this.enterRoom("up"),this.loop()}get curRoom(){return this.roomMap.get(this.cc,this.cr)}toggleMode(){this.player.mode=this.player.mode===P?a:P}restart(){this.gameOver=!1,this.won=!1,this.roomMap=new I,this.player=new T,this.cc=0,this.cr=0,this.meleeSwing=null,this.enterRoom("up")}enterRoom(q){let J=this.curRoom;J.visited=!0;let Q=B[q],[V,$]=k[q];if(this.player.x=u+Q.cx*j+j/2-V*j,this.player.y=v+Q.cy*j+j/2-$*j,this.player.facing=q,this.player.invTimer=20,this.player.transCD=15,this.meleeSwing=null,J.buildTiles(),J.enemies=[],J.tears=[],!J.cleared&&J.type!=="spawn")this.spawnEnemies(J,q);else J.cleared=!0,J.buildTiles()}spawnEnemies(q,J){let Q=q.type==="boss"?1:q.type==="treasure"?0:2+Math.floor(Math.random()*3);for(let V=0;V<Q;V++){let $=0,z,F,U,X=q.type==="boss"?"boss":Math.random()<0.3?"fast":"normal";do{z=u+2*j+Math.random()*(K-4)*j,F=v+2*j+Math.random()*(M-4)*j,U=!0;let Z=B[J],_=u+Z.cx*j+j/2,G=v+Z.cy*j+j/2;if(Math.hypot(z-_,F-G)<180)U=!1;for(let A of q.enemies)if(Math.hypot(z-A.x,F-A.y)<60){U=!1;break}if(Math.hypot(z-this.player.x,F-this.player.y)<150)U=!1;$++}while(!U&&$<100);q.enemies.push(new i(z,F,X))}}loop(){if(!this.gameOver&&!this.won)this.tick();this.render(),requestAnimationFrame(()=>this.loop())}tick(){let q=this.curRoom,J=this.player;if(J.invTimer>0)J.invTimer--;if(J.atkCD>0)J.atkCD--;if(J.transCD>0)J.transCD--;this.processMovement(J),this.processAttack(q,J),this.updateMelee(q),this.updateTears(q);let Q=this.updateEnemies(q,J);if(q.enemies.length>0&&Q===0&&!q.cleared)q.cleared=!0,q.buildTiles();if(c(this),!this.gameOver){if([...this.roomMap.rooms.values()].find(($)=>$.type==="boss")?.cleared)this.won=!0}}processMovement(q){let J=0,Q=0;if(H.w||H.W)Q=-1;if(H.s||H.S)Q=1;if(H.a||H.A)J=-1;if(H.d||H.D)J=1;if(J!==0||Q!==0){let V=Math.hypot(J,Q);if(J/=V,Q/=V,Q<0)q.moveDir="up";else if(Q>0)q.moveDir="down";if(J<0)q.moveDir="left";else if(J>0)q.moveDir="right";let $=J*q.speed,z=Q*q.speed;if(q.x+=$,C(q.box,this.curRoom,u,v))q.x-=$;if(q.y+=z,C(q.box,this.curRoom,u,v))q.y-=z}}processAttack(q,J){let Q=0,V=0;if(H.ArrowUp)Q=0,V=-1;else if(H.ArrowDown)Q=0,V=1;else if(H.ArrowLeft)Q=-1,V=0;else if(H.ArrowRight)Q=1,V=0;else if(H[" "]||H.Space)[Q,V]=k[J.moveDir];if((Q!==0||V!==0)&&J.atkCD<=0){let $=Math.hypot(Q,V);Q/=$,V/=$;let z=V<0?"up":V>0?"down":Q<0?"left":"right";if(J.facing=z,J.atkCD=J.mode===P?10:22,J.mode===P)q.tears.push(new d(J.x,J.y,Q,V));else this.meleeSwing=new n(J.x,J.y,z)}}updateMelee(q){if(this.meleeSwing&&!this.meleeSwing.alive)this.meleeSwing=null;if(!this.meleeSwing)return;this.meleeSwing.life--;for(let J of q.enemies){if(!J.alive||J.hitTimer>0)continue;if(x(J.box,this.meleeSwing.box)){J.hp-=this.meleeSwing.damage,J.hitTimer=10;let[Q,V]=k[this.meleeSwing.dir];J.knx=Q*this.meleeSwing.kb,J.kny=V*this.meleeSwing.kb}}}updateTears(q){for(let J of q.tears){if(!J.alive)continue;J.x+=J.dx*J.speed,J.y+=J.dy*J.speed,J.life--;let Q=Math.floor((J.x-u)/j),V=Math.floor((J.y-v)/j);if(Q<0||Q>=K||V<0||V>=M||J.life<=0){J.life=0;continue}if(q.tiles[V][Q]===Y){J.life=0;continue}for(let $ of q.enemies){if(!$.alive)continue;if(Math.hypot(J.x-$.x,J.y-$.y)<$.w/2+J.r){$.hp-=J.damage,$.hitTimer=8,J.life=0;break}}}q.tears=q.tears.filter((J)=>J.alive)}updateEnemies(q,J){let Q=0;for(let V of q.enemies){if(!V.alive)continue;if(Q++,V.hitTimer>0)V.hitTimer--;if(Math.abs(V.knx)>0.1||Math.abs(V.kny)>0.1){V.x+=V.knx*3,V.y+=V.kny*3,V.knx*=0.85,V.kny*=0.85;continue}V.knx=0,V.kny=0;let $=J.x-V.x,z=J.y-V.y,F=Math.hypot($,z);if(F>0&&F<500){let U=V.speed,X=$/F*U,Z=z/F*U;if(V.x+=X,C(V.box,q,u,v))V.x-=X;if(V.y+=Z,C(V.box,q,u,v))V.y-=Z}if(V.atkTimer>0)V.atkTimer--;if(Math.hypot(V.x-J.x,V.y-J.y)<(V.w+J.w)/2&&J.invTimer<=0&&V.atkTimer<=0){if(J.hp-=V.damage,J.invTimer=60,V.atkTimer=30,J.hp<=0)return this.gameOver=!0,Q}}return Q}render(){let q=this.ctx;if(q.fillStyle="#0a0a0f",q.fillRect(0,0,N,b),t(q,this.curRoom),q0(q,this.curRoom,this.player,this.meleeSwing),J0(q,this.player,this.curRoom),Q0(q,this.roomMap,this.cc,this.cr),this.gameOver)this.drawOverlay("#c33","GAME OVER");else if(this.won)this.drawOverlay("#3c3","VICTORY")}drawOverlay(q,J){let Q=this.ctx;Q.fillStyle="rgba(0,0,0,0.8)",Q.fillRect(0,0,N,b),Q.fillStyle=q,Q.font="bold 56px monospace",Q.textAlign="center",Q.fillText(J,N/2,b/2-20),Q.fillStyle="#888",Q.font="18px monospace",Q.fillText("[R] restart",N/2,b/2+40)}}l();window.addEventListener("keydown",(q)=>{let J=window.__game;if(!J)return;if((q.key==="Tab"||q.key==="q"||q.key==="Q")&&!J.gameOver&&!J.won)q.preventDefault(),J.toggleMode();if(q.key==="r"||q.key==="R"){if(J.gameOver||J.won)J.restart()}});window.addEventListener("load",()=>{let q=document.getElementById("game");if(!q){document.body.innerHTML='<p style="color:red">Error: canvas element not found</p>';return}let J=new y(q);window.__game=J});
|
|
|
|
//# debugId=8155D39DF19FF28B64756E2164756E21
|