taizo-hori 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +179 -0
  2. package/assets/sprites/dig_dug_title.png +0 -0
  3. package/assets/sprites/flower_large_1.png +0 -0
  4. package/assets/sprites/flower_large_2.png +0 -0
  5. package/assets/sprites/flower_small.png +0 -0
  6. package/assets/sprites/fygar_fire_1.png +0 -0
  7. package/assets/sprites/fygar_fire_2.png +0 -0
  8. package/assets/sprites/fygar_fire_3.png +0 -0
  9. package/assets/sprites/fygar_ghosting_1.png +0 -0
  10. package/assets/sprites/fygar_ghosting_2.png +0 -0
  11. package/assets/sprites/fygar_inflating_1.png +0 -0
  12. package/assets/sprites/fygar_inflating_2.png +0 -0
  13. package/assets/sprites/fygar_inflating_3.png +0 -0
  14. package/assets/sprites/fygar_popped.png +0 -0
  15. package/assets/sprites/fygar_smooshed.png +0 -0
  16. package/assets/sprites/fygar_walking_1.png +0 -0
  17. package/assets/sprites/fygar_walking_2.png +0 -0
  18. package/assets/sprites/hose_line_horizontal.png +0 -0
  19. package/assets/sprites/hose_line_vertical.png +0 -0
  20. package/assets/sprites/hose_nozzle_horizontal.png +0 -0
  21. package/assets/sprites/hose_nozzle_vertical.png +0 -0
  22. package/assets/sprites/player_digging_horizontal_1.png +0 -0
  23. package/assets/sprites/player_digging_horizontal_2.png +0 -0
  24. package/assets/sprites/player_digging_vertical_1.png +0 -0
  25. package/assets/sprites/player_digging_vertical_2.png +0 -0
  26. package/assets/sprites/player_dying_horizontal_1.png +0 -0
  27. package/assets/sprites/player_dying_horizontal_2.png +0 -0
  28. package/assets/sprites/player_dying_horizontal_3.png +0 -0
  29. package/assets/sprites/player_dying_horizontal_4.png +0 -0
  30. package/assets/sprites/player_dying_horizontal_5.png +0 -0
  31. package/assets/sprites/player_dying_vertical_1.png +0 -0
  32. package/assets/sprites/player_dying_vertical_2.png +0 -0
  33. package/assets/sprites/player_dying_vertical_3.png +0 -0
  34. package/assets/sprites/player_dying_vertical_4.png +0 -0
  35. package/assets/sprites/player_dying_vertical_5.png +0 -0
  36. package/assets/sprites/player_pumping_horizontal_1.png +0 -0
  37. package/assets/sprites/player_pumping_horizontal_2.png +0 -0
  38. package/assets/sprites/player_pumping_vertical_1.png +0 -0
  39. package/assets/sprites/player_pumping_vertical_2.png +0 -0
  40. package/assets/sprites/player_shooting_horizontal.png +0 -0
  41. package/assets/sprites/player_shooting_vertical.png +0 -0
  42. package/assets/sprites/player_smooshed_horizontal.png +0 -0
  43. package/assets/sprites/player_smooshed_vertical.png +0 -0
  44. package/assets/sprites/player_walking_horizontal_1.png +0 -0
  45. package/assets/sprites/player_walking_horizontal_2.png +0 -0
  46. package/assets/sprites/player_walking_vertical_1.png +0 -0
  47. package/assets/sprites/player_walking_vertical_2.png +0 -0
  48. package/assets/sprites/pooka_ghosting_1.png +0 -0
  49. package/assets/sprites/pooka_ghosting_2.png +0 -0
  50. package/assets/sprites/pooka_inflating_1.png +0 -0
  51. package/assets/sprites/pooka_inflating_2.png +0 -0
  52. package/assets/sprites/pooka_inflating_3.png +0 -0
  53. package/assets/sprites/pooka_popped.png +0 -0
  54. package/assets/sprites/pooka_smooshed.png +0 -0
  55. package/assets/sprites/pooka_walking_1.png +0 -0
  56. package/assets/sprites/pooka_walking_2.png +0 -0
  57. package/assets/sprites/prize_1.png +0 -0
  58. package/assets/sprites/prize_10.png +0 -0
  59. package/assets/sprites/prize_11.png +0 -0
  60. package/assets/sprites/prize_2.png +0 -0
  61. package/assets/sprites/prize_3.png +0 -0
  62. package/assets/sprites/prize_4.png +0 -0
  63. package/assets/sprites/prize_5.png +0 -0
  64. package/assets/sprites/prize_6.png +0 -0
  65. package/assets/sprites/prize_7.png +0 -0
  66. package/assets/sprites/prize_8.png +0 -0
  67. package/assets/sprites/prize_9.png +0 -0
  68. package/assets/sprites/rock_1.png +0 -0
  69. package/assets/sprites/rock_2.png +0 -0
  70. package/assets/sprites/rock_crumbling_1.png +0 -0
  71. package/assets/sprites/rock_crumbling_2.png +0 -0
  72. package/assets/sprites/score_sheet.png +0 -0
  73. package/dist/assets/style.css +1 -0
  74. package/dist/digdug.es.js +3197 -0
  75. package/dist/digdug.es.js.map +1 -0
  76. package/dist/digdug.umd.js +2 -0
  77. package/dist/digdug.umd.js.map +1 -0
  78. package/package.json +61 -0
@@ -0,0 +1,3197 @@
1
+ const N = "digdug_highscore", m = {
2
+ MENU: "menu",
3
+ INTRO: "intro",
4
+ // Starting animation before gameplay
5
+ PLAYING: "playing",
6
+ PAUSED: "paused",
7
+ DYING: "dying",
8
+ RESPAWNING: "respawning",
9
+ LEVEL_COMPLETE: "level_complete",
10
+ GAME_OVER: "game_over"
11
+ }, R = {
12
+ EMPTY: 0,
13
+ DIRT: 1,
14
+ ROCK: 2
15
+ }, h = {
16
+ UP: "up",
17
+ DOWN: "down",
18
+ LEFT: "left",
19
+ RIGHT: "right"
20
+ }, b = {
21
+ SPEED: 1.2,
22
+ // Pixels per frame - player should feel responsive
23
+ START_LIVES: 3,
24
+ PUMP_RANGE: 16 * 3
25
+ }, G = {
26
+ POOKA: "pooka",
27
+ FYGAR: "fygar"
28
+ }, M = {
29
+ MIN_GHOST_DURATION: 1200,
30
+ // Must ghost for at least 1.2 seconds
31
+ POOKA: {
32
+ SPEED: 0.7,
33
+ // Pixels per frame in tunnels
34
+ POINTS: 200,
35
+ GHOST_SPEED: 0.5,
36
+ // Speed when moving through dirt
37
+ GHOST_MODE_DELAY: () => 5e3 + Math.floor(Math.random() * 3) * 2500
38
+ // 5, 7.5, or 10 seconds before entering ghost mode
39
+ },
40
+ FYGAR: {
41
+ SPEED: 0.6,
42
+ // Pixels per frame in tunnels
43
+ GHOST_SPEED: 0.4,
44
+ GHOST_MODE_DELAY: 1e4,
45
+ // 10 seconds before entering ghost mode
46
+ FIRE_RANGE: 16 * 4,
47
+ FIRE_COOLDOWN: 2500,
48
+ // 2.5 seconds between fire breaths
49
+ FIRE_CHARGE_TIME: 300,
50
+ // ms pause before breathing fire
51
+ FIRE_DURATION: 450
52
+ // 450ms fire stays visible (150ms per tile extension)
53
+ }
54
+ }, A = {
55
+ DIG_TILE: 10,
56
+ // Depth-based pump kill points
57
+ // Dirt area: rows 2-17 (16 rows), sky: rows 0-1
58
+ // Quarters of DIRT ONLY: rows 2-5 (Q1), 6-9 (Q2), 10-13 (Q3), 14-17 (Q4)
59
+ PUMP_KILL: {
60
+ POOKA: [200, 300, 400, 500],
61
+ FYGAR: [200, 300, 400, 500],
62
+ FYGAR_HORIZONTAL: [400, 600, 800, 1e3]
63
+ },
64
+ // Rock kills by enemy count (index = count, 8+ capped at index 8)
65
+ ROCK_KILL: [0, 1e3, 2500, 4e3, 6e3, 8e3, 1e4, 12e3, 15e3],
66
+ // Prize values (prize_1 through prize_11)
67
+ BONUS_ITEMS: [
68
+ 400,
69
+ 600,
70
+ 800,
71
+ 1e3,
72
+ 2e3,
73
+ 3e3,
74
+ 4e3,
75
+ 5e3,
76
+ 6e3,
77
+ 7e3,
78
+ 8e3
79
+ ]
80
+ }, S = {
81
+ BACKGROUND: "#000000",
82
+ SKY: "rgb(0, 0, 145)",
83
+ // Blue sky for top rows
84
+ TEXT_WHITE: "#ffffff",
85
+ TEXT_RED: "#e33122"
86
+ }, C = {
87
+ FALL_DELAY: 300,
88
+ // 400ms delay before rock falls after player triggers it
89
+ FALL_SPEED: 3,
90
+ // Pixels per frame when falling
91
+ SHAKE_DURATION: 200
92
+ // ms rock shakes before falling (triggered by player from below)
93
+ }, F = {
94
+ START_ENEMIES: 4,
95
+ MAX_ENEMIES: 8,
96
+ ENEMY_INCREMENT: 1,
97
+ // Additional enemies per level
98
+ ROCKS_PER_LEVEL: 3
99
+ }, v = {
100
+ ANIMATION_DURATION: 1500,
101
+ // 1.5 second death animation
102
+ RESPAWN_DELAY: 3e3,
103
+ // 3 seconds "Player 1 Ready" display
104
+ INVINCIBILITY_TIME: 2e3
105
+ // 2 seconds invincibility after respawn
106
+ }, Y = (L) => new Promise((t, i) => {
107
+ const e = new Image();
108
+ e.onload = () => t(e), e.onerror = (s) => i(s), e.src = L;
109
+ }), W = [
110
+ {
111
+ DIRT_TOP: "rgb(244, 187, 64)",
112
+ DIRT_MID: "rgb(207, 111, 41)",
113
+ DIRT_LOW: "rgb(169, 49, 24)",
114
+ DIRT_LOWEST: "rgb(138, 26, 16)"
115
+ },
116
+ {
117
+ DIRT_TOP: "rgb(128, 128, 128)",
118
+ DIRT_MID: "rgb(162, 130, 74)",
119
+ DIRT_LOW: "rgb(222, 171, 58)",
120
+ DIRT_LOWEST: "rgb(187, 102, 37)"
121
+ },
122
+ {
123
+ DIRT_TOP: "rgb(128, 128, 128)",
124
+ DIRT_MID: "rgb(104, 104, 69)",
125
+ DIRT_LOW: "rgb(73, 103, 68)",
126
+ DIRT_LOWEST: "rgb(64, 64, 64)"
127
+ }
128
+ ];
129
+ function H(L) {
130
+ const [t, i, e] = L.match(/\d+/g).map(Number), s = t / 255, r = i / 255, n = e / 255, o = Math.max(s, r, n), a = Math.min(s, r, n);
131
+ let l, d;
132
+ const c = (o + a) / 2;
133
+ if (o === a)
134
+ l = d = 0;
135
+ else {
136
+ const f = o - a;
137
+ switch (d = c > 0.5 ? f / (2 - o - a) : f / (o + a), o) {
138
+ case s:
139
+ l = (r - n) / f + (r < n ? 6 : 0);
140
+ break;
141
+ case r:
142
+ l = (n - s) / f + 2;
143
+ break;
144
+ case n:
145
+ l = (s - r) / f + 4;
146
+ break;
147
+ }
148
+ l /= 6;
149
+ }
150
+ return {
151
+ h: Math.round(l * 360),
152
+ s: Math.round(d * 100),
153
+ l: Math.round(c * 100)
154
+ };
155
+ }
156
+ function V(L) {
157
+ const i = Math.floor((L - 1) / 4) % W.length, { DIRT_TOP: e, DIRT_MID: s, DIRT_LOW: r, DIRT_LOWEST: n } = W[i];
158
+ return [
159
+ { stop: 0, color: H(e) },
160
+ { stop: 0.33, color: H(s) },
161
+ { stop: 0.66, color: H(r) },
162
+ { stop: 1, color: H(n) }
163
+ ];
164
+ }
165
+ class K {
166
+ constructor(t) {
167
+ this.config = t, this.canvas = document.createElement("canvas"), this.ctx = this.canvas.getContext("2d"), this.canvas.width = t.width, this.canvas.height = t.height, t.scale && t.scale !== 1 && (this.canvas.style.width = `${t.width * t.scale}px`, this.canvas.style.height = `${t.height * t.scale}px`), this.ctx.imageSmoothingEnabled = !1, this.sprites = {}, this.spritesLoaded = !1, this.loadSprites(), this.scoreSheet = null, this.scoreSheetLoaded = !1, this.loadScoreSheet(), this.scoreSpriteMap = {
168
+ 200: { x: 23, y: 0, w: 15, h: 7 },
169
+ 300: { x: 42, y: 0, w: 15, h: 7 },
170
+ 400: { x: 61, y: 0, w: 15, h: 7 },
171
+ 500: { x: 81, y: 0, w: 15, h: 7 },
172
+ 600: { x: 100, y: 0, w: 15, h: 7 },
173
+ 800: { x: 119, y: 0, w: 15, h: 7 },
174
+ 1e3: { x: 0, y: 15, w: 17, h: 7 },
175
+ 2e3: { x: 21, y: 15, w: 20, h: 7 },
176
+ 2500: { x: 45, y: 15, w: 20, h: 7 },
177
+ 3e3: { x: 69, y: 15, w: 20, h: 7 },
178
+ 4e3: { x: 93, y: 15, w: 20, h: 7 },
179
+ 5e3: { x: 117, y: 15, w: 20, h: 7 },
180
+ 6e3: { x: 141, y: 15, w: 20, h: 7 },
181
+ 7e3: { x: 13, y: 30, w: 20, h: 7 },
182
+ 8e3: { x: 40, y: 30, w: 20, h: 7 },
183
+ 1e4: { x: 66, y: 30, w: 22, h: 7 },
184
+ 12e3: { x: 95, y: 30, w: 22, h: 7 },
185
+ 15e3: { x: 124, y: 30, w: 22, h: 7 }
186
+ }, this.backgroundCanvas = document.createElement("canvas"), this.backgroundCanvas.width = t.width, this.backgroundCanvas.height = t.height, this.backgroundCtx = this.backgroundCanvas.getContext("2d"), this.backgroundCtx.imageSmoothingEnabled = !1, this.backgroundDirty = !0, this.lastGridState = null;
187
+ }
188
+ /**
189
+ * Load sprite images
190
+ */
191
+ loadSprites() {
192
+ const t = [
193
+ "player_walking_horizontal_1.png",
194
+ "player_walking_horizontal_2.png",
195
+ "player_walking_vertical_1.png",
196
+ "player_walking_vertical_2.png",
197
+ "player_digging_horizontal_1.png",
198
+ "player_digging_horizontal_2.png",
199
+ "player_digging_vertical_1.png",
200
+ "player_digging_vertical_2.png",
201
+ "player_shooting_horizontal.png",
202
+ "player_shooting_vertical.png",
203
+ "player_pumping_horizontal_1.png",
204
+ "player_pumping_horizontal_2.png",
205
+ "player_pumping_vertical_1.png",
206
+ "player_pumping_vertical_2.png",
207
+ "player_smooshed_horizontal.png",
208
+ "player_smooshed_vertical.png",
209
+ "player_dying_horizontal_1.png",
210
+ "player_dying_horizontal_2.png",
211
+ "player_dying_horizontal_3.png",
212
+ "player_dying_horizontal_4.png",
213
+ "player_dying_horizontal_5.png",
214
+ "player_dying_vertical_1.png",
215
+ "player_dying_vertical_2.png",
216
+ "player_dying_vertical_3.png",
217
+ "player_dying_vertical_4.png",
218
+ "player_dying_vertical_5.png",
219
+ "hose_line_horizontal.png",
220
+ "hose_line_vertical.png",
221
+ "hose_nozzle_horizontal.png",
222
+ "hose_nozzle_vertical.png",
223
+ "pooka_walking_1.png",
224
+ "pooka_walking_2.png",
225
+ "pooka_ghosting_1.png",
226
+ "pooka_ghosting_2.png",
227
+ "fygar_walking_1.png",
228
+ "fygar_walking_2.png",
229
+ "fygar_ghosting_1.png",
230
+ "fygar_ghosting_2.png",
231
+ "fygar_fire_1.png",
232
+ "fygar_fire_2.png",
233
+ "fygar_fire_3.png",
234
+ "pooka_inflating_1.png",
235
+ "pooka_inflating_2.png",
236
+ "pooka_inflating_3.png",
237
+ "pooka_popped.png",
238
+ "pooka_smooshed.png",
239
+ "fygar_inflating_1.png",
240
+ "fygar_inflating_2.png",
241
+ "fygar_inflating_3.png",
242
+ "fygar_popped.png",
243
+ "fygar_smooshed.png",
244
+ "rock_1.png",
245
+ "rock_2.png",
246
+ "rock_crumbling_1.png",
247
+ "rock_crumbling_2.png",
248
+ "flower_small.png",
249
+ "prize_1.png",
250
+ "prize_2.png",
251
+ "prize_3.png",
252
+ "prize_4.png",
253
+ "prize_5.png",
254
+ "prize_6.png",
255
+ "prize_7.png",
256
+ "prize_8.png",
257
+ "prize_9.png",
258
+ "prize_10.png",
259
+ "prize_11.png"
260
+ ];
261
+ let i = 0;
262
+ const e = t.length;
263
+ t.forEach((s) => {
264
+ const r = new Image();
265
+ r.onload = () => {
266
+ i++, i === e && (this.spritesLoaded = !0);
267
+ }, r.onerror = () => {
268
+ console.warn(`Failed to load sprite: ${s}`), i++, i === e && (this.spritesLoaded = !0);
269
+ };
270
+ const n = s.replace(".png", "");
271
+ r.src = `/assets/sprites/${s}`, this.sprites[n] = r;
272
+ });
273
+ }
274
+ /**
275
+ * Load the score sprite sheet
276
+ */
277
+ async loadScoreSheet() {
278
+ const t = await Y("/assets/sprites/score_sheet.png");
279
+ this.scoreSheet = t, this.scoreSheetLoaded = !0;
280
+ }
281
+ /**
282
+ * Attach canvas to DOM container
283
+ */
284
+ attachTo(t) {
285
+ t.appendChild(this.canvas);
286
+ }
287
+ async drawMenu() {
288
+ const t = await Y("/assets/sprites/dig_dug_title.png"), i = t.naturalWidth * 1.5;
289
+ this.ctx.drawImage(
290
+ t,
291
+ 432 / 2 - i / 2,
292
+ 16 * 2,
293
+ i,
294
+ t.naturalHeight * 1.5
295
+ ), this.drawText(
296
+ "▶ 1 PLAYER",
297
+ 432 / 2,
298
+ 288 / 2 + 16 * 6,
299
+ {
300
+ size: 8,
301
+ color: S.TEXT_WHITE,
302
+ align: "center"
303
+ }
304
+ );
305
+ }
306
+ /**
307
+ * Clear the canvas
308
+ * Note: With background caching, we don't need to clear to black
309
+ * since drawGrid() will overwrite with the cached background
310
+ */
311
+ clear() {
312
+ (this.backgroundDirty || !this.lastGridState) && (this.ctx.fillStyle = S.BACKGROUND, this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height));
313
+ }
314
+ /**
315
+ * Force clear the canvas to black (for menus, game over screens, etc.)
316
+ */
317
+ forceClear() {
318
+ this.ctx.fillStyle = S.BACKGROUND, this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
319
+ }
320
+ /**
321
+ * Mark the background as needing redraw (call when grid changes)
322
+ */
323
+ markBackgroundDirty() {
324
+ this.backgroundDirty = !0;
325
+ }
326
+ /**
327
+ * Draw the grid using cached background for performance
328
+ * Only redraws when grid actually changes
329
+ */
330
+ drawGrid(t, i) {
331
+ const e = t.getStateHash ? t.getStateHash() : null;
332
+ e !== this.lastGridState && (this.backgroundDirty = !0, this.lastGridState = e), this.backgroundDirty && (this.renderBackgroundToCache(t, i), this.backgroundDirty = !1), this.ctx.drawImage(this.backgroundCanvas, 0, 0);
333
+ }
334
+ getDirtColorHSL(t, i) {
335
+ const e = Math.max(0, Math.min(1, t)), s = V(i);
336
+ let r = s[0], n = s[s.length - 1];
337
+ for (let l = 0; l < s.length - 1; l++)
338
+ if (e >= s[l].stop && e <= s[l + 1].stop) {
339
+ r = s[l], n = s[l + 1];
340
+ break;
341
+ }
342
+ const o = n.stop - r.stop, a = (e - r.stop) / o;
343
+ return o === 0 ? n.color : {
344
+ h: Math.round(
345
+ r.color.h + (n.color.h - r.color.h) * a
346
+ ),
347
+ s: Math.round(
348
+ r.color.s + (n.color.s - r.color.s) * a
349
+ ),
350
+ l: Math.round(
351
+ r.color.l + (n.color.l - r.color.l) * a
352
+ )
353
+ };
354
+ }
355
+ /**
356
+ * Render the full background to the cache canvas
357
+ * Called only when grid changes
358
+ */
359
+ renderBackgroundToCache(t, i) {
360
+ const e = this.backgroundCtx;
361
+ e.fillStyle = S.BACKGROUND, e.fillRect(
362
+ 0,
363
+ 0,
364
+ this.backgroundCanvas.width,
365
+ this.backgroundCanvas.height
366
+ );
367
+ for (let s = 0; s < t.height; s++)
368
+ for (let r = 0; r < t.width; r++) {
369
+ const n = t.getTile(r, s), o = r * 16, a = s * 16;
370
+ if (s < 2) {
371
+ e.fillStyle = S.SKY, e.fillRect(o, a, 16, 16);
372
+ continue;
373
+ }
374
+ if (n === R.DIRT || n === R.ROCK) {
375
+ const l = (s - 2) / (t.height - 2), { h: d, s: c, l: f } = this.getDirtColorHSL(
376
+ l,
377
+ i
378
+ );
379
+ e.fillStyle = `hsl(${d}, ${c}%, ${f}%)`, e.fillRect(o, a, 16, 16);
380
+ const u = `hsl(${d}, ${Math.min(100, c + 20)}%, ${Math.max(0, f - 15)}%)`, g = `hsl(${d}, ${Math.min(100, c + 10)}%, ${Math.min(100, f + 10)}%)`, T = 4, I = 2;
381
+ e.fillStyle = u;
382
+ for (let p = 0; p < 16; p += T)
383
+ for (let E = 0; E < 16; E += T) {
384
+ const x = r * 16 + E, D = s * 16 + p;
385
+ Math.abs(
386
+ Math.sin(x * 12.989 + D * 78.233) * 43758.545
387
+ ) % 1 < 0.15 && e.fillRect(
388
+ o + E + 1,
389
+ a + p + 1,
390
+ I,
391
+ I
392
+ );
393
+ }
394
+ e.fillStyle = g;
395
+ for (let p = 0; p < 16; p += T)
396
+ for (let E = 0; E < 16; E += T) {
397
+ const x = r * 16 + E, D = s * 16 + p;
398
+ Math.abs(
399
+ Math.sin(x * 90.123 + D * 11.456) * 12345.678
400
+ ) % 1 < 0.08 && e.fillRect(
401
+ o + E + 1,
402
+ a + p + 1,
403
+ I,
404
+ I
405
+ );
406
+ }
407
+ }
408
+ }
409
+ }
410
+ /**
411
+ * Get the player's orientation for sprite rendering
412
+ */
413
+ getPlayerOrientation(t) {
414
+ return t.direction === h.LEFT || t.direction === h.RIGHT ? "horizontal" : "vertical";
415
+ }
416
+ /**
417
+ * Draw the player with walking sprites
418
+ */
419
+ drawPlayer(t) {
420
+ if (t.isDying) {
421
+ this.drawPlayerDeath(t);
422
+ return;
423
+ }
424
+ if (t.isInvincible && !(Math.floor(t.invincibilityTimer / 100) % 2 === 0))
425
+ return;
426
+ t.pumpLength > 0 && this.drawPumpLine(t);
427
+ const i = t.x, e = t.y;
428
+ if (this.spritesLoaded) {
429
+ let s = "walking";
430
+ t.isDigging ? s = "digging" : t.isPumping && (s = "pumping");
431
+ let r = t.animationFrame === 0 ? "_1" : "_2";
432
+ const n = this.getPlayerOrientation(t);
433
+ t.isShooting && (s = "shooting", r = "");
434
+ const o = this.sprites[`player_${s}_${n}${r}`];
435
+ if (o && o.complete)
436
+ if (t.spriteFlipH || t.spriteFlipV) {
437
+ const l = i + 8, d = e + 16 / 2;
438
+ this.drawFlippedSprite(
439
+ l,
440
+ d,
441
+ t.spriteFlipH,
442
+ t.spriteFlipV,
443
+ o
444
+ );
445
+ } else
446
+ this.ctx.drawImage(o, i, e, 16, 16);
447
+ }
448
+ }
449
+ /**
450
+ * Draw a horizontally or vertically flipped sprite
451
+ * at TILE_SIZE width and height
452
+ */
453
+ drawFlippedSprite(t, i, e, s, r) {
454
+ this.ctx.save(), this.ctx.translate(t, i), this.ctx.scale(e ? -1 : 1, s ? -1 : 1), this.ctx.drawImage(
455
+ r,
456
+ -16 / 2,
457
+ -16 / 2,
458
+ 16,
459
+ 16
460
+ ), this.ctx.restore();
461
+ }
462
+ /**
463
+ * Draw player death animation
464
+ */
465
+ drawPlayerDeath(t) {
466
+ var a;
467
+ const i = t.x, e = t.y, s = this.getPlayerOrientation(t);
468
+ if (t.isSmooshed && ((a = t.attachedToRock) != null && a.isFalling || t.smooshedDelayTimer < t.SMOOSHED_DELAY)) {
469
+ const l = `player_smooshed_${s}`;
470
+ if (this.spritesLoaded) {
471
+ const d = this.sprites[l];
472
+ if (d && d.complete) {
473
+ if (t.spriteFlipH) {
474
+ const c = i + 8, f = e + 16 / 2;
475
+ this.drawFlippedSprite(
476
+ c,
477
+ f,
478
+ !0,
479
+ !1,
480
+ d
481
+ );
482
+ } else
483
+ this.ctx.drawImage(
484
+ d,
485
+ i,
486
+ e,
487
+ 16,
488
+ 16
489
+ );
490
+ return;
491
+ }
492
+ }
493
+ }
494
+ const r = t.deathTimer / v.ANIMATION_DURATION, n = Math.min(5, Math.floor(r * 5) + 1), o = `player_dying_${s}_${n}`;
495
+ if (this.spritesLoaded) {
496
+ const l = this.sprites[o];
497
+ if (l && l.complete) {
498
+ if (t.spriteFlipH) {
499
+ const d = i + 8, c = e + 16 / 2;
500
+ this.drawFlippedSprite(
501
+ d,
502
+ c,
503
+ !0,
504
+ !1,
505
+ l
506
+ );
507
+ } else
508
+ this.ctx.drawImage(l, i, e, 16, 16);
509
+ return;
510
+ }
511
+ }
512
+ }
513
+ /**
514
+ * Draw pump line extending from player
515
+ */
516
+ drawPumpLine(t) {
517
+ if (!this.spritesLoaded) return;
518
+ const i = 16, e = i / 2, s = t.x, r = t.y, n = t.getPumpEndPoint(), o = this.ctx, a = this.getPlayerOrientation(t), l = this.sprites[`hose_nozzle_${a}`], d = this.sprites[`hose_line_${a}`];
519
+ if (!l || !l.complete || !d || !d.complete)
520
+ return;
521
+ const c = Math.round((s + e - n.x) / i), f = Math.round((r + e - n.y) / i), u = t.spriteFlipH, g = t.spriteFlipV;
522
+ if (f < 0) {
523
+ let T = Math.abs(f) - 1;
524
+ if (u) {
525
+ const I = s + e;
526
+ let p = n.y - i * 0.25;
527
+ for (this.drawFlippedSprite(I, p, u, g, l); T > 0; )
528
+ p -= i, this.drawFlippedSprite(I, p, u, g, d), T--;
529
+ } else {
530
+ const I = s;
531
+ let p = n.y - i * 0.75;
532
+ for (o.drawImage(l, I, p, i, i); T > 0; )
533
+ p -= i, o.drawImage(d, I, p, i, i), T--;
534
+ }
535
+ return;
536
+ }
537
+ if (f > 0) {
538
+ let T = Math.abs(f) - 1;
539
+ const I = s + e;
540
+ let p = n.y + i * 0.25;
541
+ for (this.drawFlippedSprite(I, p, u, g, l); T > 0; )
542
+ p += i, this.drawFlippedSprite(I, p, u, g, d), T--;
543
+ return;
544
+ }
545
+ if (c < 0) {
546
+ let T = Math.abs(c) - 1, I = n.x - i * 0.25;
547
+ const p = r + e;
548
+ for (this.drawFlippedSprite(I, p, u, g, l); T > 0; )
549
+ I -= i, this.drawFlippedSprite(I, p, u, g, d), T--;
550
+ return;
551
+ }
552
+ if (c > 0) {
553
+ let T = Math.abs(c) - 1, I = n.x - i * 0.25;
554
+ for (o.drawImage(l, I, r, i, i); T > 0; )
555
+ I += i, o.drawImage(d, I, r, i, i), T--;
556
+ }
557
+ }
558
+ /**
559
+ * Draw an enemy with inflation, popped, and smooshed states
560
+ */
561
+ drawEnemy(t) {
562
+ const i = t.x, e = t.y, s = i + 16 / 2, r = e + 16 / 2;
563
+ if (t.isSmooshed) {
564
+ this.drawEnemySmooshed(t, s, r);
565
+ return;
566
+ }
567
+ if (t.isPopped) {
568
+ this.drawEnemyPopped(t, s, r);
569
+ return;
570
+ }
571
+ if (t.inflateLevel > 1) {
572
+ this.drawEnemyInflating(t, s, r);
573
+ return;
574
+ }
575
+ if (this.spritesLoaded) {
576
+ const n = t.animationFrame === 0 ? "1" : "2", o = t.isGhosting ? "ghosting" : "walking", a = this.sprites[`${t.type}_${o}_${n}`];
577
+ this.drawEnemySprite(a, s, r, t.spriteFlipH);
578
+ }
579
+ t.type === G.FYGAR && (t.isCharging && t.isCharging() ? this.drawFygarCharging(t) : t.isFireActive && t.isFireActive() && this.drawFygarFire(t));
580
+ }
581
+ /**
582
+ * Draw enemy in inflating state using inflation sprites
583
+ * Sprites: inflating_1 (first third), inflating_2 (second third), inflating_3 (final third)
584
+ * Each sprite has its own size (16x16, 20x20, 21x21) - render at natural size
585
+ */
586
+ drawEnemyInflating(t, i, e) {
587
+ const s = (t.inflateLevel - 1) / 1;
588
+ let r;
589
+ s < 0.33 ? r = 1 : s < 0.66 ? r = 2 : r = 3;
590
+ const n = `${t.type}_inflating_${r}`, o = this.sprites[n];
591
+ this.drawEnemySprite(o, i, e, t.spriteFlipH);
592
+ }
593
+ /**
594
+ * Draw enemy in popped state (after full inflation)
595
+ */
596
+ drawEnemyPopped(t, i, e) {
597
+ const s = this.sprites[`${t.type}_popped`];
598
+ this.drawEnemySprite(s, i, e, t.spriteFlipH);
599
+ }
600
+ /**
601
+ * Helper to draw enemy sprite with flipping
602
+ * and renders at natural size
603
+ */
604
+ drawEnemySprite(t, i, e, s) {
605
+ if (t && t.complete) {
606
+ const r = t.naturalWidth, n = t.naturalHeight;
607
+ this.ctx.save(), this.ctx.translate(i, e), s && this.ctx.scale(-1, 1), this.ctx.drawImage(
608
+ t,
609
+ -r / 2,
610
+ -n / 2,
611
+ r,
612
+ n
613
+ ), this.ctx.restore();
614
+ }
615
+ }
616
+ /**
617
+ * Draw enemy in smooshed state (crushed by rock)
618
+ */
619
+ drawEnemySmooshed(t, i, e) {
620
+ const s = this.sprites[`${t.type}_smooshed`];
621
+ this.drawEnemySprite(s, i, e, t.spriteFlipH);
622
+ }
623
+ /**
624
+ * Draw Fygar charging animation (pulsing/flashing before fire)
625
+ */
626
+ drawFygarCharging(t) {
627
+ const i = t.x + 8, e = t.y + 16 / 2, s = t.getFireDirection(), r = Math.floor(Date.now() / 100) % 2;
628
+ this.ctx.fillStyle = r === 0 ? "#ff6600" : "#ff3300";
629
+ const n = s === h.RIGHT ? 16 / 2 : -16 / 2;
630
+ this.ctx.beginPath(), this.ctx.arc(i + n, e, 3, 0, Math.PI * 2), this.ctx.fill();
631
+ }
632
+ /**
633
+ * Draw Fygar fire breath using sprites
634
+ * Fire extends progressively: 1 tile (fire_1) -> 2 tiles (fire_2) -> 3 tiles (fire_3)
635
+ * Each sprite is already the correct width (1, 2, or 3 tiles), so draw once
636
+ */
637
+ drawFygarFire(t) {
638
+ if (!t.getFireHitbox()) return;
639
+ const s = t.getFireDirection() === h.LEFT, r = t.getFireTileCount ? t.getFireTileCount() : 3, n = this.sprites[`fygar_fire_${r}`];
640
+ if (n && n.complete) {
641
+ this.ctx.save();
642
+ const o = r * 16;
643
+ let a;
644
+ s ? a = t.x - o : a = t.x + 16;
645
+ const l = t.y, d = a + o / 2, c = l + 16 / 2;
646
+ this.ctx.translate(d, c), s || this.ctx.scale(-1, 1), this.ctx.drawImage(
647
+ n,
648
+ -o / 2,
649
+ -16 / 2,
650
+ o,
651
+ 16
652
+ ), this.ctx.restore();
653
+ }
654
+ }
655
+ /**
656
+ * Draw a rock with shaking and crumbling animations
657
+ */
658
+ drawRock(t) {
659
+ const i = t.x, e = t.y;
660
+ if (t.isSpawning) {
661
+ const r = t.spawnTimer / t.SPAWN_DURATION, n = r, o = 8 + r * 12, a = Math.sin(
662
+ t.spawnTimer * o * 0.01
663
+ ), l = (1 - r) * 0.4, d = Math.min(1, n + a * l);
664
+ if (this.ctx.save(), this.ctx.globalAlpha = Math.max(0, d), this.spritesLoaded) {
665
+ const c = this.sprites.rock_1;
666
+ c && c.complete && this.ctx.drawImage(c, i, e, 16, 16);
667
+ }
668
+ this.ctx.restore();
669
+ return;
670
+ }
671
+ if (t.isCrumbling) {
672
+ const n = t.crumbleTimer / t.CRUMBLE_DURATION < 0.5 ? "rock_crumbling_1" : "rock_crumbling_2";
673
+ if (this.spritesLoaded) {
674
+ const o = this.sprites[n];
675
+ if (o && o.complete) {
676
+ this.ctx.drawImage(o, i, e, 16, 16);
677
+ return;
678
+ }
679
+ }
680
+ }
681
+ let s = "rock_1";
682
+ if (t.isShaking && (s = Math.floor(t.shakeTimer / 200) % 2 === 0 ? "rock_1" : "rock_2"), this.spritesLoaded) {
683
+ const r = this.sprites[s];
684
+ r && r.complete && this.ctx.drawImage(r, i, e, 16, 16);
685
+ }
686
+ }
687
+ /**
688
+ * Draw bonus item with level-based prize sprites
689
+ * Prizes unlock every 20 levels, spawning in order (0, 1, 2 within available range)
690
+ * Item flashes after 3 seconds and disappears after 5 seconds total
691
+ */
692
+ drawBonusItem(t, i = 1) {
693
+ const e = t.x, s = t.y;
694
+ if (t.isFlashing && t.isFlashing() && !(Math.floor(t.elapsedTime / 100) % 2 === 0))
695
+ return;
696
+ const r = Math.min(Math.floor((i - 1) / 20), 8), n = (t.bonusIndex || 0) % 3, a = `prize_${Math.min(r + n + 1, 11)}`;
697
+ if (this.spritesLoaded) {
698
+ const l = this.sprites[a];
699
+ l && l.complete && this.ctx.drawImage(l, e, s, 16, 16);
700
+ }
701
+ }
702
+ /**
703
+ * Draw floating score displays
704
+ */
705
+ drawFloatingScores(t) {
706
+ !this.scoreSheetLoaded || !this.scoreSheet || t.forEach((i) => {
707
+ const e = this.scoreSpriteMap[i.points];
708
+ if (!e) return;
709
+ const s = Math.round(i.x + (16 - e.w) / 2), r = Math.round(i.y);
710
+ this.ctx.drawImage(
711
+ this.scoreSheet,
712
+ e.x,
713
+ e.y,
714
+ e.w,
715
+ e.h,
716
+ s,
717
+ r,
718
+ e.w,
719
+ e.h
720
+ );
721
+ });
722
+ }
723
+ /**
724
+ * Draw UI elements (score, lives, level) with Press Start 2P font
725
+ */
726
+ drawUI(t, i) {
727
+ if (this.drawText("1UP", 4, 10, {
728
+ size: 6,
729
+ color: S.TEXT_RED,
730
+ align: "left"
731
+ }), this.drawText(`${t.score}`.padStart(2, "0"), 4, 20, {
732
+ size: 8,
733
+ color: S.TEXT_WHITE,
734
+ align: "left"
735
+ }), this.spritesLoaded) {
736
+ const e = this.sprites.player_digging_horizontal_1;
737
+ if (e && e.complete)
738
+ for (let s = 1; s < t.lives; s++) {
739
+ const r = 28 + (s - 1) * 16;
740
+ this.ctx.drawImage(e, r, 0, 16, 16);
741
+ }
742
+ }
743
+ if (this.drawText("HI-SCORE", 432 / 2, 10, {
744
+ size: 6,
745
+ color: S.TEXT_RED,
746
+ align: "center"
747
+ }), this.drawText(
748
+ `${t.highScore}`.padStart(2, "0"),
749
+ 432 / 2,
750
+ 20,
751
+ {
752
+ size: 8,
753
+ color: S.TEXT_WHITE,
754
+ align: "center"
755
+ }
756
+ ), this.drawText(
757
+ `ROUND ${i.currentLevel}`,
758
+ 428,
759
+ 10,
760
+ {
761
+ size: 6,
762
+ color: S.TEXT_WHITE,
763
+ align: "right"
764
+ }
765
+ ), this.spritesLoaded) {
766
+ const e = this.sprites.flower_small;
767
+ if (e && e.complete)
768
+ for (let s = 1; s <= i.currentLevel; s++)
769
+ this.ctx.drawImage(
770
+ e,
771
+ 432 - 16 * s,
772
+ 16,
773
+ 16,
774
+ 16
775
+ );
776
+ }
777
+ }
778
+ /**
779
+ * Draw text with Press Start 2P font
780
+ */
781
+ drawText(t, i, e, s = {}) {
782
+ const r = s.size || 16, n = s.color || S.TEXT_WHITE, o = s.align || "left";
783
+ this.ctx.font = `${r}px "Press Start 2P", "Courier New", monospace`, this.ctx.fillStyle = n, this.ctx.textAlign = o, this.ctx.fillText(t, i, e);
784
+ }
785
+ /**
786
+ * Render respawning state with "Player 1 Ready" overlay
787
+ */
788
+ renderRespawning() {
789
+ this.drawText("PLAYER 1", 432 / 2, 288 / 2 - 3, {
790
+ size: 10,
791
+ color: S.TEXT_WHITE,
792
+ align: "center"
793
+ }), this.drawText(
794
+ "READY!",
795
+ 432 / 2,
796
+ 288 / 2 + 16 + 15,
797
+ {
798
+ size: 10,
799
+ color: S.TEXT_WHITE,
800
+ align: "center"
801
+ }
802
+ );
803
+ }
804
+ /**
805
+ * Draw debug information
806
+ */
807
+ drawDebugInfo(t, i) {
808
+ this.ctx.strokeStyle = "rgba(255, 255, 255, 0.1)", this.ctx.lineWidth = 1;
809
+ for (let e = 0; e <= 432; e += 16)
810
+ this.ctx.beginPath(), this.ctx.moveTo(e, 0), this.ctx.lineTo(e, 288), this.ctx.stroke();
811
+ for (let e = 0; e <= 288; e += 16)
812
+ this.ctx.beginPath(), this.ctx.moveTo(0, e), this.ctx.lineTo(432, e), this.ctx.stroke();
813
+ t && (this.ctx.strokeStyle = "rgba(0, 255, 0, 0.5)", this.ctx.strokeRect(t.x, t.y, 16, 16)), i.forEach((e) => {
814
+ this.ctx.strokeStyle = "rgba(255, 0, 0, 0.5)", this.ctx.strokeRect(e.x, e.y, 16, 16);
815
+ });
816
+ }
817
+ }
818
+ class z {
819
+ constructor() {
820
+ this.width = 27, this.height = 18, this.tiles = [], this.stateVersion = 0, this.init();
821
+ }
822
+ /**
823
+ * Initialize grid with all tiles as dirt
824
+ */
825
+ init() {
826
+ this.tiles = [];
827
+ for (let t = 0; t < this.height; t++) {
828
+ this.tiles[t] = [];
829
+ for (let i = 0; i < this.width; i++)
830
+ t <= 1 ? this.tiles[t][i] = R.EMPTY : this.tiles[t][i] = R.DIRT;
831
+ }
832
+ this.stateVersion++;
833
+ }
834
+ /**
835
+ * Get tile type at grid position
836
+ */
837
+ getTile(t, i) {
838
+ return t < 0 || t >= this.width || i < 0 || i >= this.height ? R.DIRT : this.tiles[i][t];
839
+ }
840
+ /**
841
+ * Set tile type at grid position
842
+ */
843
+ setTile(t, i, e) {
844
+ t >= 0 && t < this.width && i >= 0 && i < this.height && this.tiles[i][t] !== e && (this.tiles[i][t] = e, this.stateVersion++);
845
+ }
846
+ /**
847
+ * Get a simple version number that changes when grid changes
848
+ * Used by renderer for cache invalidation
849
+ */
850
+ getStateHash() {
851
+ return this.stateVersion;
852
+ }
853
+ /**
854
+ * Check if tile is empty (tunnel)
855
+ */
856
+ isEmpty(t, i) {
857
+ return this.getTile(t, i) === R.EMPTY;
858
+ }
859
+ /**
860
+ * Check if tile is dirt
861
+ */
862
+ isDirt(t, i) {
863
+ return this.getTile(t, i) === R.DIRT;
864
+ }
865
+ /**
866
+ * Check if tile is a rock
867
+ */
868
+ isRock(t, i) {
869
+ return this.getTile(t, i) === R.ROCK;
870
+ }
871
+ /**
872
+ * Dig (remove dirt) at grid position
873
+ */
874
+ dig(t, i) {
875
+ return this.isDirt(t, i) ? (this.setTile(t, i, R.EMPTY), !0) : !1;
876
+ }
877
+ /**
878
+ * Count the number of dirt tiles
879
+ */
880
+ countDirt() {
881
+ let t = 0;
882
+ for (let i = 0; i < this.height; i++)
883
+ for (let e = 0; e < this.width; e++)
884
+ this.isDirt(e, i) && t++;
885
+ return t;
886
+ }
887
+ /**
888
+ * Convert pixel coordinates to grid coordinates
889
+ */
890
+ pixelToGrid(t, i) {
891
+ return {
892
+ x: Math.floor(t / 16),
893
+ y: Math.floor(i / 16)
894
+ };
895
+ }
896
+ /**
897
+ * Convert grid coordinates to pixel coordinates (top-left of tile)
898
+ */
899
+ gridToPixel(t, i) {
900
+ return {
901
+ x: t * 16,
902
+ y: i * 16
903
+ };
904
+ }
905
+ /**
906
+ * Get center pixel position of a grid tile
907
+ */
908
+ gridToPixelCenter(t, i) {
909
+ return {
910
+ x: t * 16 + 16 / 2,
911
+ y: i * 16 + 16 / 2
912
+ };
913
+ }
914
+ /**
915
+ * Check if position (in pixels) is walkable
916
+ */
917
+ isWalkable(t, i) {
918
+ const { x: e, y: s } = this.pixelToGrid(t, i);
919
+ return this.isEmpty(e, s);
920
+ }
921
+ /**
922
+ * Check if entity can move in a direction
923
+ */
924
+ canMove(t, i, e, s = 16) {
925
+ let r = t, n = i;
926
+ switch (e) {
927
+ case "up":
928
+ n -= 1;
929
+ break;
930
+ case "down":
931
+ n += 1;
932
+ break;
933
+ case "left":
934
+ r -= 1;
935
+ break;
936
+ case "right":
937
+ r += 1;
938
+ break;
939
+ }
940
+ return [
941
+ { x: r, y: n },
942
+ { x: r + s - 1, y: n },
943
+ { x: r, y: n + s - 1 },
944
+ { x: r + s - 1, y: n + s - 1 }
945
+ ].every((a) => {
946
+ const { x: l, y: d } = this.pixelToGrid(a.x, a.y);
947
+ return l >= 0 && l < this.width && d >= 0 && d < this.height;
948
+ });
949
+ }
950
+ /**
951
+ * Clear a horizontal tunnel
952
+ */
953
+ clearHorizontalTunnel(t, i, e) {
954
+ const s = Math.min(t, i), r = Math.max(t, i);
955
+ for (let n = s; n <= r; n++)
956
+ this.setTile(n, e, R.EMPTY);
957
+ }
958
+ /**
959
+ * Clear a vertical tunnel
960
+ */
961
+ clearVerticalTunnel(t, i, e) {
962
+ const s = Math.min(i, e), r = Math.max(i, e);
963
+ for (let n = s; n <= r; n++)
964
+ this.setTile(t, n, R.EMPTY);
965
+ }
966
+ /**
967
+ * Place a rock at grid position
968
+ */
969
+ placeRock(t, i) {
970
+ this.setTile(t, i, R.ROCK);
971
+ }
972
+ /**
973
+ * Remove rock at grid position
974
+ */
975
+ removeRock(t, i) {
976
+ this.isRock(t, i) && this.setTile(t, i, R.EMPTY);
977
+ }
978
+ /**
979
+ * Get all rock positions
980
+ */
981
+ getRockPositions() {
982
+ const t = [];
983
+ for (let i = 0; i < this.height; i++)
984
+ for (let e = 0; e < this.width; e++)
985
+ this.isRock(e, i) && t.push({ x: e, y: i });
986
+ return t;
987
+ }
988
+ /**
989
+ * Check if there's dirt below a position
990
+ */
991
+ hasDirtBelow(t, i) {
992
+ return this.isDirt(t, i + 1);
993
+ }
994
+ /**
995
+ * Reset the grid
996
+ */
997
+ reset() {
998
+ this.init();
999
+ }
1000
+ }
1001
+ class B {
1002
+ constructor() {
1003
+ this.keys = /* @__PURE__ */ new Set(), this.keysPressed = /* @__PURE__ */ new Set(), this.handleKeyDown = this.handleKeyDown.bind(this), this.handleKeyUp = this.handleKeyUp.bind(this);
1004
+ }
1005
+ /**
1006
+ * Initialize input listeners
1007
+ */
1008
+ init() {
1009
+ document.addEventListener("keydown", this.handleKeyDown), document.addEventListener("keyup", this.handleKeyUp);
1010
+ }
1011
+ /**
1012
+ * Handle key down event
1013
+ */
1014
+ handleKeyDown(t) {
1015
+ this.keys.has(t.code) || this.keysPressed.add(t.code), this.keys.add(t.code), [
1016
+ "ArrowUp",
1017
+ "ArrowDown",
1018
+ "ArrowLeft",
1019
+ "ArrowRight",
1020
+ "Space"
1021
+ ].includes(t.code) && t.preventDefault();
1022
+ }
1023
+ /**
1024
+ * Handle key up event
1025
+ */
1026
+ handleKeyUp(t) {
1027
+ this.keys.delete(t.code), this.keysPressed.delete(t.code);
1028
+ }
1029
+ /**
1030
+ * Check if key is currently held down
1031
+ */
1032
+ isKeyDown(t) {
1033
+ return this.keys.has(t);
1034
+ }
1035
+ /**
1036
+ * Check if key was just pressed (single press detection)
1037
+ */
1038
+ isKeyPressed(t) {
1039
+ const i = this.keysPressed.has(t);
1040
+ return i && this.keysPressed.delete(t), i;
1041
+ }
1042
+ /**
1043
+ * Check if up arrow or W is pressed
1044
+ */
1045
+ isUpPressed() {
1046
+ return this.isKeyDown("ArrowUp") || this.isKeyDown("KeyW");
1047
+ }
1048
+ /**
1049
+ * Check if down arrow or S is pressed
1050
+ */
1051
+ isDownPressed() {
1052
+ return this.isKeyDown("ArrowDown") || this.isKeyDown("KeyS");
1053
+ }
1054
+ /**
1055
+ * Check if left arrow or A is pressed
1056
+ */
1057
+ isLeftPressed() {
1058
+ return this.isKeyDown("ArrowLeft") || this.isKeyDown("KeyA");
1059
+ }
1060
+ /**
1061
+ * Check if right arrow or D is pressed
1062
+ */
1063
+ isRightPressed() {
1064
+ return this.isKeyDown("ArrowRight") || this.isKeyDown("KeyD");
1065
+ }
1066
+ /**
1067
+ * Check if space bar is pressed
1068
+ */
1069
+ isSpacePressed() {
1070
+ return this.isKeyDown("Space");
1071
+ }
1072
+ /**
1073
+ * Get movement direction from input
1074
+ */
1075
+ getDirection() {
1076
+ return this.isUpPressed() ? "up" : this.isDownPressed() ? "down" : this.isLeftPressed() ? "left" : this.isRightPressed() ? "right" : null;
1077
+ }
1078
+ /**
1079
+ * Clean up listeners
1080
+ */
1081
+ destroy() {
1082
+ document.removeEventListener("keydown", this.handleKeyDown), document.removeEventListener("keyup", this.handleKeyUp), this.keys.clear(), this.keysPressed.clear();
1083
+ }
1084
+ }
1085
+ class $ {
1086
+ constructor(t) {
1087
+ this.grid = t;
1088
+ }
1089
+ /**
1090
+ * Check if two rectangles overlap (AABB collision)
1091
+ */
1092
+ checkAABB(t, i, e, s, r, n, o, a) {
1093
+ return t < r + o && t + e > r && i < n + a && i + s > n;
1094
+ }
1095
+ /**
1096
+ * Check collision between player and enemy
1097
+ */
1098
+ checkPlayerEnemyCollision(t, i) {
1099
+ return this.checkAABB(
1100
+ t.x,
1101
+ t.y,
1102
+ 10,
1103
+ 10,
1104
+ i.x,
1105
+ i.y,
1106
+ 10,
1107
+ 10
1108
+ );
1109
+ }
1110
+ /**
1111
+ * Check collision between rock and entity
1112
+ */
1113
+ checkRockEntityCollision(t, i) {
1114
+ return this.checkAABB(
1115
+ t.x,
1116
+ t.y,
1117
+ 16,
1118
+ 16,
1119
+ i.x,
1120
+ i.y,
1121
+ 16,
1122
+ 16
1123
+ );
1124
+ }
1125
+ /**
1126
+ * Check collision between player and bonus item
1127
+ */
1128
+ checkPlayerBonusCollision(t, i) {
1129
+ return this.checkAABB(
1130
+ t.x,
1131
+ t.y,
1132
+ 16,
1133
+ 16,
1134
+ i.x,
1135
+ i.y,
1136
+ 16,
1137
+ 16
1138
+ );
1139
+ }
1140
+ /**
1141
+ * Check if pump is in range of enemy
1142
+ */
1143
+ checkPumpRange(t, i, e) {
1144
+ const s = t.x + 8 - (i.x + 8), r = t.y + 16 / 2 - (i.y + 16 / 2);
1145
+ return Math.sqrt(s * s + r * r) <= e;
1146
+ }
1147
+ /**
1148
+ * Check if entity is facing direction towards target
1149
+ */
1150
+ isFacing(t, i, e) {
1151
+ const s = i.x - t.x, r = i.y - t.y;
1152
+ switch (e) {
1153
+ case h.UP:
1154
+ return r < 0 && Math.abs(r) > Math.abs(s);
1155
+ case h.DOWN:
1156
+ return r > 0 && Math.abs(r) > Math.abs(s);
1157
+ case h.LEFT:
1158
+ return s < 0 && Math.abs(s) > Math.abs(r);
1159
+ case h.RIGHT:
1160
+ return s > 0 && Math.abs(s) > Math.abs(r);
1161
+ default:
1162
+ return !1;
1163
+ }
1164
+ }
1165
+ /**
1166
+ * Get distance between two points
1167
+ */
1168
+ getDistance(t, i, e, s) {
1169
+ const r = e - t, n = s - i;
1170
+ return Math.sqrt(r * r + n * n);
1171
+ }
1172
+ /**
1173
+ * Check line of sight between two entities
1174
+ */
1175
+ hasLineOfSight(t, i) {
1176
+ const { x: e, y: s } = this.grid.pixelToGrid(t.x, t.y), { x: r, y: n } = this.grid.pixelToGrid(i.x, i.y), o = Math.abs(r - e), a = Math.abs(n - s), l = e < r ? 1 : -1, d = s < n ? 1 : -1;
1177
+ let c = o - a, f = e, u = s;
1178
+ for (; f !== r || u !== n; ) {
1179
+ if (this.grid.isDirt(f, u))
1180
+ return !1;
1181
+ const g = 2 * c;
1182
+ g > -a && (c -= a, f += l), g < o && (c += o, u += d);
1183
+ }
1184
+ return !0;
1185
+ }
1186
+ }
1187
+ class U {
1188
+ constructor(t, i, e, s, r = 1) {
1189
+ this.x = t, this.y = i, this.type = e, this.level = r, this.baseSpeed = s, this.speed = s + (r - 1) * 0.1, this.direction = h.DOWN, this.isMoving = !0, this.directionInitialized = !1, this.state = "roaming", this.stateTimer = 0, this.directionChangeTimer = 0, this.lastGridX = -1, this.lastGridY = -1, this.prevGridX = -1, this.prevGridY = -1, this.tilesTraveledInDirection = 0, this.minTilesBeforeReverse = 2, this.animationFrame = 0, this.animationTimer = 0, this.spriteFlipH = !1, this.isInflating = !1, this.inflateLevel = 1, this.inflateTimer = 0, this.INFLATE_DURATION = 1200, this.deflateTimer = 0, this.isPopped = !1, this.poppedTimer = 0, this.POPPED_DURATION = 400, this.isSmooshed = !1, this.smooshedTimer = 0, this.SMOOSHED_DURATION = 400, this.isDestroyed = !1, this.distanceFromPlayer = 0, this.ghostModeTimer = 0, this.canGhostMode = !1, this.isGhosting = !1, this.ghostingDuration = 0, this.GHOST_MODE_DELAY = e === G.POOKA ? M.POOKA.GHOST_MODE_DELAY() : M.FYGAR.GHOST_MODE_DELAY, this.MIN_GHOST_DURATION = M.MIN_GHOST_DURATION, this.wasInDirt = !1, this.isLastEnemy = !1, this.isEscaping = !1, this.hasEscaped = !1;
1190
+ }
1191
+ /**
1192
+ * Update enemy state
1193
+ */
1194
+ update(t, i, e) {
1195
+ if (this.isPopped) {
1196
+ this.updatePoppedState(t);
1197
+ return;
1198
+ }
1199
+ if (this.isSmooshed) {
1200
+ this.updateSmooshedState(t);
1201
+ return;
1202
+ }
1203
+ if (!this.directionInitialized) {
1204
+ this.initializeDirection(e), this.directionInitialized = !0;
1205
+ const { x: s, y: r } = e.pixelToGrid(
1206
+ this.x + 16 / 2,
1207
+ this.y + 16 / 2
1208
+ );
1209
+ this.lastGridX = s, this.lastGridY = r;
1210
+ }
1211
+ if ((this.isInflating || this.inflateLevel > 1) && this.updateInflation(t), i && this.updateAI(t, i), this.updateGhostMode(t, i, e), this.isMoving && !this.isInflating && this.move(e, i), this.isEscaping && !this.hasEscaped && this.x + 16 <= 0 && (this.hasEscaped = !0), this.updateAnimation(t), i) {
1212
+ const s = this.x - i.x, r = this.y - i.y;
1213
+ this.distanceFromPlayer = Math.sqrt(s * s + r * r);
1214
+ }
1215
+ }
1216
+ /**
1217
+ * Update ghost mode state - timer, activation, and deactivation
1218
+ */
1219
+ updateGhostMode(t, i, e) {
1220
+ const { x: s, y: r } = e.pixelToGrid(
1221
+ this.x + 8,
1222
+ this.y + 8
1223
+ ), n = e.isEmpty(s, r), o = !n && !e.isRock(s, r);
1224
+ if (this.isGhosting && (this.ghostingDuration += t), this.isGhosting && n && this.ghostingDuration >= this.MIN_GHOST_DURATION && (this.ghostModeTimer = 0, this.canGhostMode = !1, this.isGhosting = !1, this.ghostingDuration = 0), this.isGhosting && !n && this.ghostingDuration >= this.MIN_GHOST_DURATION) {
1225
+ const c = this.findAdjacentTunnel(e, s, r);
1226
+ c && (this.x = c.x * 16, this.y = c.y * 16, this.lastGridX = c.x, this.lastGridY = c.y, this.ghostModeTimer = 0, this.canGhostMode = !1, this.isGhosting = !1, this.ghostingDuration = 0);
1227
+ }
1228
+ this.wasInDirt = o, this.isGhosting || (this.ghostModeTimer += t, this.ghostModeTimer >= this.GHOST_MODE_DELAY && (this.canGhostMode = !0));
1229
+ const a = r <= 1, l = this.isEscaping && this.state === "escaping" && a;
1230
+ this.canGhostMode && !this.isGhosting && i && !l && (this.isGhosting = !0, this.ghostingDuration = 0);
1231
+ }
1232
+ /**
1233
+ * Update AI behavior
1234
+ */
1235
+ updateAI(t, i) {
1236
+ if (this.isLastEnemy && this.state !== "chasing" && (this.isEscaping || (this.isEscaping = !0, this.state = "escaping", this.stateTimer = 0)), this.isEscaping) {
1237
+ const a = this.x - i.x, l = this.y - i.y, d = Math.sqrt(a * a + l * l), c = 16 * 4;
1238
+ d <= c ? this.state = "chasing" : this.state = "escaping", this.stateTimer += t, this.directionChangeTimer += t;
1239
+ return;
1240
+ }
1241
+ const e = this.x - i.x, s = this.y - i.y, r = Math.sqrt(e * e + s * s), n = 16 * 8, o = 16 * 10;
1242
+ r <= n && this.state !== "chasing" ? (this.state = "chasing", this.stateTimer = 0) : r > o && this.state !== "roaming" && (this.state = "roaming", this.stateTimer = 0), this.stateTimer += t, this.directionChangeTimer += t;
1243
+ }
1244
+ /**
1245
+ * Move enemy through tunnels (or through dirt when ghosting)
1246
+ */
1247
+ move(t, i = null) {
1248
+ if (this.isGhosting && i) {
1249
+ this.moveGhost(t, i);
1250
+ return;
1251
+ }
1252
+ const e = this.x + 16 / 2, s = this.y + 16 / 2, { x: r, y: n } = t.pixelToGrid(e, s);
1253
+ r !== this.lastGridX || n !== this.lastGridY ? (this.prevGridX = this.lastGridX, this.prevGridY = this.lastGridY, this.lastGridX = r, this.lastGridY = n, this.tilesTraveledInDirection++, i && this.state === "chasing" ? this.decideDirectionAtTile(i, t, r, n) : this.state === "escaping" ? this.escapeAtTile(t, r, n) : this.state === "roaming" && this.roamAtTile(t, r, n)) : i && this.state === "chasing" ? this.checkForBetterDirection(i, t, r, n) : this.state === "escaping" && this.checkForBetterEscapeDirection(t, r, n);
1254
+ let a = this.x, l = this.y;
1255
+ switch (this.direction) {
1256
+ case h.UP:
1257
+ l -= this.speed;
1258
+ break;
1259
+ case h.DOWN:
1260
+ l += this.speed;
1261
+ break;
1262
+ case h.LEFT:
1263
+ a -= this.speed, this.spriteFlipH && (this.spriteFlipH = !1);
1264
+ break;
1265
+ case h.RIGHT:
1266
+ a += this.speed, this.spriteFlipH || (this.spriteFlipH = !0);
1267
+ break;
1268
+ }
1269
+ this.canMoveInDirection(
1270
+ a,
1271
+ l,
1272
+ this.direction,
1273
+ t
1274
+ ) ? (this.x = a, this.y = l, this.applyGridSnapping(t)) : (this.snapToGrid(t), this.pickValidDirection(t, i, r, n), this.tilesTraveledInDirection = 0);
1275
+ }
1276
+ /**
1277
+ * Ghost mode movement - move directly toward target through dirt
1278
+ * When escaping, moves toward exit point; otherwise toward player
1279
+ * Uses subclass ghostSpeed if defined, otherwise 80% of base speed
1280
+ */
1281
+ moveGhost(t, i) {
1282
+ const e = this.ghostSpeed || this.speed * 0.8;
1283
+ let s, r;
1284
+ this.isEscaping && this.state === "escaping" ? (s = this.x, r = -16) : (s = i.x, r = i.y);
1285
+ const n = s - this.x, o = r - this.y, a = Math.sqrt(n * n + o * o);
1286
+ if (a < 2)
1287
+ return;
1288
+ const l = n / a, d = o / a, c = l * e, f = d * e, u = this.x + c, g = this.y + f;
1289
+ let T;
1290
+ Math.abs(n) > Math.abs(o) ? T = n > 0 ? h.RIGHT : h.LEFT : T = o > 0 ? h.DOWN : h.UP;
1291
+ const I = this.canGhostMoveToPosition(
1292
+ this.x + c,
1293
+ this.y,
1294
+ t
1295
+ ), p = this.canGhostMoveToPosition(
1296
+ this.x,
1297
+ this.y + f,
1298
+ t
1299
+ );
1300
+ if (I && p) {
1301
+ this.x = u, this.y = g, this.direction = T;
1302
+ return;
1303
+ }
1304
+ if (I) {
1305
+ this.x = this.x + c, this.direction = n > 0 ? h.RIGHT : h.LEFT;
1306
+ return;
1307
+ }
1308
+ if (p) {
1309
+ this.y = this.y + f, this.direction = o > 0 ? h.DOWN : h.UP;
1310
+ return;
1311
+ }
1312
+ let E = [];
1313
+ Math.abs(n) > Math.abs(o) ? o > 0 ? E = [h.DOWN, h.UP] : E = [h.UP, h.DOWN] : n > 0 ? E = [h.RIGHT, h.LEFT] : E = [h.LEFT, h.RIGHT];
1314
+ for (const D of E) {
1315
+ const y = this.getNewPosition(this.x, this.y, D, e);
1316
+ if (this.canGhostMoveToPosition(y.x, y.y, t)) {
1317
+ this.x = y.x, this.y = y.y, this.direction = D;
1318
+ return;
1319
+ }
1320
+ }
1321
+ const x = [
1322
+ h.UP,
1323
+ h.DOWN,
1324
+ h.LEFT,
1325
+ h.RIGHT
1326
+ ];
1327
+ for (const D of x) {
1328
+ const y = this.getNewPosition(this.x, this.y, D, e);
1329
+ if (this.canGhostMoveToPosition(y.x, y.y, t)) {
1330
+ this.x = y.x, this.y = y.y, this.direction = D;
1331
+ return;
1332
+ }
1333
+ }
1334
+ }
1335
+ /**
1336
+ * Check if ghost can move to position (only sky blocks)
1337
+ */
1338
+ canGhostMoveToPosition(t, i, e) {
1339
+ const { y: s } = e.pixelToGrid(
1340
+ t + 8,
1341
+ i + 8
1342
+ );
1343
+ return s !== 0;
1344
+ }
1345
+ /**
1346
+ * Get perpendicular direction for rock avoidance
1347
+ */
1348
+ getPerpendicularDirection(t, i) {
1349
+ if (i)
1350
+ switch (t) {
1351
+ case h.UP:
1352
+ return h.RIGHT;
1353
+ case h.RIGHT:
1354
+ return h.DOWN;
1355
+ case h.DOWN:
1356
+ return h.LEFT;
1357
+ case h.LEFT:
1358
+ return h.UP;
1359
+ }
1360
+ else
1361
+ switch (t) {
1362
+ case h.UP:
1363
+ return h.LEFT;
1364
+ case h.LEFT:
1365
+ return h.DOWN;
1366
+ case h.DOWN:
1367
+ return h.RIGHT;
1368
+ case h.RIGHT:
1369
+ return h.UP;
1370
+ }
1371
+ return t;
1372
+ }
1373
+ /**
1374
+ * Calculate new position given direction and speed
1375
+ */
1376
+ getNewPosition(t, i, e, s) {
1377
+ let r = t, n = i;
1378
+ switch (e) {
1379
+ case h.UP:
1380
+ n -= s;
1381
+ break;
1382
+ case h.DOWN:
1383
+ n += s;
1384
+ break;
1385
+ case h.LEFT:
1386
+ r -= s;
1387
+ break;
1388
+ case h.RIGHT:
1389
+ r += s;
1390
+ break;
1391
+ }
1392
+ return { x: r, y: n };
1393
+ }
1394
+ /**
1395
+ * Check if there's a better direction toward the player while passing through a tile
1396
+ * Only triggers at intersections (3+ valid directions)
1397
+ */
1398
+ checkForBetterDirection(t, i, e, s) {
1399
+ const r = this.getValidDirectionsFromTile(i, e, s);
1400
+ if (r.length < 3 || !r.includes(this.direction))
1401
+ return;
1402
+ const n = t.x - this.x, o = t.y - this.y;
1403
+ let a;
1404
+ if (Math.abs(n) > Math.abs(o) ? a = n > 0 ? h.RIGHT : h.LEFT : a = o > 0 ? h.DOWN : h.UP, this.direction === a || !r.includes(a))
1405
+ return;
1406
+ const l = this.direction === h.LEFT || this.direction === h.RIGHT, d = a === h.LEFT || a === h.RIGHT, c = l !== d, f = e * 16, u = s * 16;
1407
+ if (c) {
1408
+ let g;
1409
+ l ? g = Math.abs(this.x - f) : g = Math.abs(this.y - u);
1410
+ const T = this.speed;
1411
+ g <= T && (this.direction = a, this.tilesTraveledInDirection = 0, this.x = f, this.y = u);
1412
+ } else
1413
+ this.tilesTraveledInDirection >= this.minTilesBeforeReverse && (this.direction = a, this.tilesTraveledInDirection = 0);
1414
+ }
1415
+ /**
1416
+ * Decide which direction to go when entering a new tile (chasing mode)
1417
+ */
1418
+ decideDirectionAtTile(t, i, e, s) {
1419
+ const r = this.getValidDirectionsFromTile(i, e, s);
1420
+ if (r.length === 0 || r.length === 1)
1421
+ return;
1422
+ const n = r.includes(this.direction), o = this.getOppositeDirection(this.direction);
1423
+ if (r.length === 2) {
1424
+ if (!n) {
1425
+ const E = r.find(
1426
+ (x) => x !== o
1427
+ );
1428
+ E && (this.direction = E, this.tilesTraveledInDirection = 0);
1429
+ return;
1430
+ }
1431
+ if (!r.includes(o)) {
1432
+ const E = r.find(
1433
+ (x) => x !== this.direction
1434
+ );
1435
+ E && this.shouldTurnToward(t, E) && (this.direction = E, this.tilesTraveledInDirection = 0);
1436
+ }
1437
+ return;
1438
+ }
1439
+ const a = r.filter(
1440
+ (p) => this.isViableDirection(i, e, s, p)
1441
+ ), l = this.getDirectionToTile(
1442
+ e,
1443
+ s,
1444
+ this.prevGridX,
1445
+ this.prevGridY
1446
+ ), d = a.filter(
1447
+ (p) => p !== l
1448
+ ), c = d.length > 0 ? d : a.length > 0 ? a : r, f = t.x - this.x, u = t.y - this.y;
1449
+ let g, T;
1450
+ if (Math.abs(f) > Math.abs(u) ? (g = f > 0 ? h.RIGHT : h.LEFT, T = u > 0 ? h.DOWN : h.UP) : (g = u > 0 ? h.DOWN : h.UP, T = f > 0 ? h.RIGHT : h.LEFT), c.includes(g)) {
1451
+ this.direction = g;
1452
+ return;
1453
+ }
1454
+ if (c.includes(T)) {
1455
+ this.direction = T;
1456
+ return;
1457
+ }
1458
+ if (c.includes(this.direction))
1459
+ return;
1460
+ const I = c.filter(
1461
+ (p) => p !== o
1462
+ );
1463
+ I.length > 0 ? this.direction = I[0] : c.length > 0 && (this.direction = c[0]);
1464
+ }
1465
+ /**
1466
+ * Get the direction from one tile to an adjacent tile
1467
+ * Returns null if tiles are not adjacent or are the same
1468
+ */
1469
+ getDirectionToTile(t, i, e, s) {
1470
+ const r = e - t, n = s - i;
1471
+ return r === 1 && n === 0 ? h.RIGHT : r === -1 && n === 0 ? h.LEFT : r === 0 && n === 1 ? h.DOWN : r === 0 && n === -1 ? h.UP : null;
1472
+ }
1473
+ /**
1474
+ * Get the opposite direction
1475
+ */
1476
+ getOppositeDirection(t) {
1477
+ switch (t) {
1478
+ case h.UP:
1479
+ return h.DOWN;
1480
+ case h.DOWN:
1481
+ return h.UP;
1482
+ case h.LEFT:
1483
+ return h.RIGHT;
1484
+ case h.RIGHT:
1485
+ return h.LEFT;
1486
+ default:
1487
+ return t;
1488
+ }
1489
+ }
1490
+ /**
1491
+ * Check if turning to a direction would move us closer to player
1492
+ */
1493
+ shouldTurnToward(t, i) {
1494
+ const e = t.x - this.x, s = t.y - this.y;
1495
+ switch (i) {
1496
+ case h.RIGHT:
1497
+ return e > 16;
1498
+ case h.LEFT:
1499
+ return e < -16;
1500
+ case h.DOWN:
1501
+ return s > 16;
1502
+ case h.UP:
1503
+ return s < -16;
1504
+ }
1505
+ return !1;
1506
+ }
1507
+ /**
1508
+ * Find an adjacent tunnel tile if enemy is close enough to snap to it
1509
+ * Used to exit ghost mode when near a tunnel
1510
+ */
1511
+ findAdjacentTunnel(t, i, e) {
1512
+ const r = [
1513
+ { x: i - 1, y: e },
1514
+ // Left
1515
+ { x: i + 1, y: e },
1516
+ // Right
1517
+ { x: i, y: e - 1 },
1518
+ // Up
1519
+ { x: i, y: e + 1 }
1520
+ // Down
1521
+ ];
1522
+ for (const n of r)
1523
+ if (t.isEmpty(n.x, n.y) && !t.isRock(n.x, n.y)) {
1524
+ const o = Math.abs(this.x - n.x * 16), a = Math.abs(this.y - n.y * 16);
1525
+ if (o < 9.6 && a < 9.6)
1526
+ return n;
1527
+ }
1528
+ return null;
1529
+ }
1530
+ /**
1531
+ * Check if a direction leads to a dead end within 5 tiles
1532
+ * Returns true if direction is viable (leads somewhere useful)
1533
+ */
1534
+ isViableDirection(t, i, e, s) {
1535
+ let n = i, o = e, a = s;
1536
+ for (let l = 0; l < 5; l++) {
1537
+ let d = n, c = o;
1538
+ switch (a) {
1539
+ case h.UP:
1540
+ c--;
1541
+ break;
1542
+ case h.DOWN:
1543
+ c++;
1544
+ break;
1545
+ case h.LEFT:
1546
+ d--;
1547
+ break;
1548
+ case h.RIGHT:
1549
+ d++;
1550
+ break;
1551
+ }
1552
+ if (!t.isEmpty(d, c) || t.isRock(d, c))
1553
+ return !1;
1554
+ const f = this.getOppositeDirection(a), g = this.getValidDirectionsFromTile(
1555
+ t,
1556
+ d,
1557
+ c
1558
+ ).filter((T) => T !== f);
1559
+ if (g.length > 1)
1560
+ return !0;
1561
+ if (g.length === 1) {
1562
+ n = d, o = c, a = g[0];
1563
+ continue;
1564
+ }
1565
+ return !1;
1566
+ }
1567
+ return !0;
1568
+ }
1569
+ /**
1570
+ * Roam behavior when entering a new tile
1571
+ */
1572
+ roamAtTile(t, i, e) {
1573
+ const s = this.getValidDirectionsFromTile(t, i, e);
1574
+ if (s.length === 0 || s.length === 1)
1575
+ return;
1576
+ const r = this.getDirectionToTile(
1577
+ i,
1578
+ e,
1579
+ this.prevGridX,
1580
+ this.prevGridY
1581
+ ), n = s.filter(
1582
+ (l) => l !== r
1583
+ ), o = n.length > 0 ? n : s;
1584
+ o.includes(this.direction) && this.directionChangeTimer < 1e3 + Math.random() * 1e3 || (this.directionChangeTimer = 0, o.length > 0 && (this.direction = o[Math.floor(Math.random() * o.length)]));
1585
+ }
1586
+ /**
1587
+ * Escape behavior when entering a new tile
1588
+ * Try to head up and left to exit at row 1
1589
+ * Avoids dead ends to prevent getting stuck
1590
+ */
1591
+ escapeAtTile(t, i, e) {
1592
+ if (e === 1 && i <= 1) {
1593
+ this.direction = h.LEFT;
1594
+ return;
1595
+ }
1596
+ const s = this.getValidDirectionsFromTile(t, i, e);
1597
+ if (s.length === 0) return;
1598
+ if (s.length === 1) {
1599
+ this.direction = s[0];
1600
+ return;
1601
+ }
1602
+ const r = this.getDirectionToTile(
1603
+ i,
1604
+ e,
1605
+ this.prevGridX,
1606
+ this.prevGridY
1607
+ ), n = s.filter(
1608
+ (c) => this.isViableDirection(t, i, e, c)
1609
+ ), o = n.filter(
1610
+ (c) => c !== r
1611
+ );
1612
+ let l = o.length > 0 ? o : n;
1613
+ if (l.length === 0) {
1614
+ const c = s.filter(
1615
+ (f) => f !== r
1616
+ );
1617
+ l = c.length > 0 ? c : s;
1618
+ }
1619
+ const d = [
1620
+ h.UP,
1621
+ h.LEFT,
1622
+ h.DOWN,
1623
+ h.RIGHT
1624
+ ];
1625
+ e <= 1 && (d[0] = h.LEFT, d[1] = h.UP);
1626
+ for (const c of d)
1627
+ if (l.includes(c)) {
1628
+ this.direction = c;
1629
+ return;
1630
+ }
1631
+ this.direction = s[0];
1632
+ }
1633
+ /**
1634
+ * Check for better escape direction mid-tile (at intersections)
1635
+ */
1636
+ checkForBetterEscapeDirection(t, i, e) {
1637
+ const s = this.getValidDirectionsFromTile(t, i, e);
1638
+ if (s.length < 3 || !s.includes(this.direction))
1639
+ return;
1640
+ let r;
1641
+ if (e <= 1 ? r = h.LEFT : r = h.UP, this.direction === r || !s.includes(r))
1642
+ return;
1643
+ const n = this.direction === h.LEFT || this.direction === h.RIGHT, o = r === h.LEFT || r === h.RIGHT, a = n !== o, l = i * 16, d = e * 16;
1644
+ if (a) {
1645
+ let c;
1646
+ n ? c = Math.abs(this.x - l) : c = Math.abs(this.y - d);
1647
+ const f = this.speed;
1648
+ c <= f && (this.direction = r, this.tilesTraveledInDirection = 0, this.x = l, this.y = d);
1649
+ } else
1650
+ this.direction = r, this.tilesTraveledInDirection = 0;
1651
+ }
1652
+ /**
1653
+ * Get valid directions (tunnel tiles) from a specific grid position
1654
+ */
1655
+ getValidDirectionsFromTile(t, i, e) {
1656
+ const s = [];
1657
+ return t.isEmpty(i, e - 1) && !t.isRock(i, e - 1) && s.push(h.UP), t.isEmpty(i, e + 1) && !t.isRock(i, e + 1) && s.push(h.DOWN), t.isEmpty(i - 1, e) && !t.isRock(i - 1, e) && s.push(h.LEFT), t.isEmpty(i + 1, e) && !t.isRock(i + 1, e) && s.push(h.RIGHT), s;
1658
+ }
1659
+ /**
1660
+ * Check if enemy can move in a direction (leading edge collision check)
1661
+ */
1662
+ canMoveInDirection(t, i, e, s) {
1663
+ let r, n;
1664
+ switch (e) {
1665
+ case h.UP:
1666
+ r = t + 16 / 2, n = i;
1667
+ break;
1668
+ case h.DOWN:
1669
+ r = t + 16 / 2, n = i + 16 - 1;
1670
+ break;
1671
+ case h.LEFT:
1672
+ r = t, n = i + 16 / 2;
1673
+ break;
1674
+ case h.RIGHT:
1675
+ r = t + 16 - 1, n = i + 16 / 2;
1676
+ break;
1677
+ default:
1678
+ r = t + 16 / 2, n = i + 16 / 2;
1679
+ }
1680
+ const { x: o, y: a } = s.pixelToGrid(r, n);
1681
+ return this.isEscaping && this.state === "escaping" && e === h.LEFT && a === 1 ? !0 : a === 0 ? !1 : this.isGhosting ? !0 : s.isRock(o, a) ? !1 : this.isGhosting ? !0 : !!s.isEmpty(o, a);
1682
+ }
1683
+ /**
1684
+ * Snap position to align with grid
1685
+ */
1686
+ snapToGrid(t) {
1687
+ const i = this.x + 8, e = this.y + 16 / 2, { x: s, y: r } = t.pixelToGrid(i, e);
1688
+ this.x = s * 16, this.y = r * 16;
1689
+ }
1690
+ /**
1691
+ * Pick a valid direction when hitting a wall
1692
+ */
1693
+ pickValidDirection(t, i, e, s) {
1694
+ const r = this.getValidDirectionsFromTile(t, e, s);
1695
+ if (r.length !== 0) {
1696
+ if (this.isEscaping && this.state === "escaping") {
1697
+ this.pickEscapeDirection(t, e, s, r);
1698
+ return;
1699
+ }
1700
+ if (i && this.state === "chasing") {
1701
+ const n = i.x - this.x, o = i.y - this.y;
1702
+ let a, l;
1703
+ if (Math.abs(n) > Math.abs(o) ? (a = n > 0 ? h.RIGHT : h.LEFT, l = o > 0 ? h.DOWN : h.UP) : (a = o > 0 ? h.DOWN : h.UP, l = n > 0 ? h.RIGHT : h.LEFT), r.includes(a)) {
1704
+ this.direction = a;
1705
+ return;
1706
+ }
1707
+ if (r.includes(l)) {
1708
+ this.direction = l;
1709
+ return;
1710
+ }
1711
+ }
1712
+ this.direction = r[Math.floor(Math.random() * r.length)];
1713
+ }
1714
+ }
1715
+ /**
1716
+ * Pick direction for escaping enemy - navigate tunnels toward exit
1717
+ * Avoids dead ends and prefers directions that lead toward row 1, left side
1718
+ */
1719
+ pickEscapeDirection(t, i, e, s) {
1720
+ if (e === 1 && i <= 1) {
1721
+ this.direction = h.LEFT;
1722
+ return;
1723
+ }
1724
+ const r = this.getOppositeDirection(this.direction), n = s.filter(
1725
+ (c) => this.isViableDirection(t, i, e, c)
1726
+ ), o = n.filter(
1727
+ (c) => c !== r
1728
+ );
1729
+ let l = o.length > 0 ? o : n;
1730
+ if (l.length === 0) {
1731
+ const c = s.filter(
1732
+ (f) => f !== r
1733
+ );
1734
+ l = c.length > 0 ? c : s;
1735
+ }
1736
+ const d = e <= 1 ? [
1737
+ h.LEFT,
1738
+ h.UP,
1739
+ h.DOWN,
1740
+ h.RIGHT
1741
+ ] : [
1742
+ h.UP,
1743
+ h.LEFT,
1744
+ h.DOWN,
1745
+ h.RIGHT
1746
+ ];
1747
+ for (const c of d)
1748
+ if (l.includes(c)) {
1749
+ this.direction = c;
1750
+ return;
1751
+ }
1752
+ this.direction = s[0];
1753
+ }
1754
+ /**
1755
+ * Apply grid snapping to keep enemy centered in tunnels
1756
+ */
1757
+ applyGridSnapping(t) {
1758
+ const { x: i, y: e } = t.pixelToGrid(
1759
+ this.x + 8,
1760
+ this.y + 8
1761
+ );
1762
+ if (t.isEmpty(i, e)) {
1763
+ if (this.direction === h.LEFT || this.direction === h.RIGHT) {
1764
+ const r = e * 16 - this.y;
1765
+ if (Math.abs(r) > 0) {
1766
+ const n = Math.sign(r) * Math.min(Math.abs(r), this.speed);
1767
+ this.y += n;
1768
+ }
1769
+ }
1770
+ if (this.direction === h.UP || this.direction === h.DOWN) {
1771
+ const r = i * 16 - this.x;
1772
+ if (Math.abs(r) > 0) {
1773
+ const n = Math.sign(r) * Math.min(Math.abs(r), this.speed);
1774
+ this.x += n;
1775
+ }
1776
+ }
1777
+ }
1778
+ }
1779
+ /**
1780
+ * Initialize direction based on tunnel orientation
1781
+ */
1782
+ initializeDirection(t) {
1783
+ const { x: i, y: e } = t.pixelToGrid(
1784
+ this.x + 8,
1785
+ this.y + 8
1786
+ ), s = this.getValidDirectionsFromTile(t, i, e);
1787
+ s.includes(h.RIGHT) ? this.spriteFlipH = !0 : s.includes(h.LEFT) ? this.direction = h.LEFT : s.includes(h.DOWN) ? this.direction = h.DOWN : s.includes(h.UP) && (this.direction = h.UP);
1788
+ }
1789
+ /**
1790
+ * Start or continue inflation (when pumped)
1791
+ */
1792
+ startInflation() {
1793
+ this.isInflating = !0, this.isMoving = !1, this.deflateTimer = 0;
1794
+ }
1795
+ /**
1796
+ * Stop being pumped - will start deflating
1797
+ */
1798
+ stopInflation() {
1799
+ }
1800
+ /**
1801
+ * Update inflation state
1802
+ * Called every frame - inflates while being pumped, deflates otherwise
1803
+ */
1804
+ updateInflation(t) {
1805
+ this.isInflating ? (this.inflateTimer += t, this.inflateLevel = 1 + this.inflateTimer / this.INFLATE_DURATION, this.inflateLevel >= 2 && this.pop()) : this.inflateLevel > 1 && (this.deflateTimer += t, this.deflateTimer > 300 && (this.inflateTimer = Math.max(
1806
+ 0,
1807
+ this.inflateTimer - t * 0.5
1808
+ ), this.inflateLevel = 1 + this.inflateTimer / this.INFLATE_DURATION, this.inflateLevel <= 1 && (this.inflateLevel = 1, this.inflateTimer = 0, this.deflateTimer = 0, this.isMoving = !0))), this.isInflating = !1;
1809
+ }
1810
+ /**
1811
+ * Enemy pops (dies from inflation)
1812
+ * Shows popped sprite before being destroyed
1813
+ */
1814
+ pop() {
1815
+ this.isPopped = !0, this.poppedTimer = 0, this.isMoving = !1;
1816
+ }
1817
+ /**
1818
+ * Enemy gets smooshed (crushed by rock)
1819
+ * Shows smooshed sprite and falls with rock
1820
+ */
1821
+ smoosh() {
1822
+ this.isSmooshed = !0, this.smooshedTimer = 0, this.isMoving = !1;
1823
+ }
1824
+ /**
1825
+ * Update popped state timer
1826
+ */
1827
+ updatePoppedState(t) {
1828
+ this.isPopped && (this.poppedTimer += t, this.poppedTimer >= this.POPPED_DURATION && (this.isDestroyed = !0));
1829
+ }
1830
+ /**
1831
+ * Update smooshed state timer
1832
+ * Only counts down after the rock has stopped falling
1833
+ */
1834
+ updateSmooshedState(t) {
1835
+ this.isSmooshed && (this.attachedToRock && this.attachedToRock.isFalling || (this.smooshedTimer += t, this.smooshedTimer >= this.SMOOSHED_DURATION && (this.isDestroyed = !0)));
1836
+ }
1837
+ /**
1838
+ * Update animation
1839
+ */
1840
+ updateAnimation(t) {
1841
+ this.animationTimer += t, this.animationTimer > 400 && (this.animationFrame = (this.animationFrame + 1) % 2, this.animationTimer = 0);
1842
+ }
1843
+ /**
1844
+ * Get center position
1845
+ */
1846
+ getCenter() {
1847
+ return {
1848
+ x: this.x + 16 / 2,
1849
+ y: this.y + 16 / 2
1850
+ };
1851
+ }
1852
+ /**
1853
+ * Reset all timers - called when game starts or respawns
1854
+ */
1855
+ resetTimers() {
1856
+ this.ghostModeTimer = 0, this.canGhostMode = !1, this.isGhosting = !1, this.ghostingDuration = 0, this.stateTimer = 0, this.directionChangeTimer = 0, this.inflateTimer = 0, this.inflateLevel = 1, this.isInflating = !1, this.deflateTimer = 0, this.isMoving = !0, this.isLastEnemy = !1, this.isEscaping = !1, this.hasEscaped = !1, this.state = "roaming";
1857
+ }
1858
+ }
1859
+ class q extends U {
1860
+ constructor(t, i, e = 1) {
1861
+ super(t, i, G.POOKA, M.POOKA.SPEED, e), this.ghostSpeed = M.POOKA.GHOST_SPEED;
1862
+ }
1863
+ // Pooka uses base Enemy movement with ghost mode from timer
1864
+ // No override needed - ghost mode is handled by Enemy.updateGhostMode()
1865
+ }
1866
+ class j extends U {
1867
+ constructor(t, i, e = 1) {
1868
+ super(t, i, G.FYGAR, M.FYGAR.SPEED, e), this.ghostSpeed = M.FYGAR.GHOST_SPEED, this.fireState = "ready", this.fireStateTimer = 0, this.fireCooldownTimer = 0, this.fireDirection = null, this.fireHitbox = null;
1869
+ }
1870
+ /**
1871
+ * Update Fygar-specific behavior
1872
+ */
1873
+ update(t, i, e) {
1874
+ super.update(t, i, e), i && !this.isInflating && !this.isGhosting && this.updateFireBreath(t, i, e), (this.isInflating || this.inflateLevel > 1) && (this.fireState === "charging" || this.fireState === "firing") && this.cancelFire(), this.isGhosting && (this.fireState === "charging" || this.fireState === "firing") && this.cancelFire();
1875
+ }
1876
+ /**
1877
+ * Override ghost mode update to pause timer while charging/firing
1878
+ * Fygar should not accumulate ghost mode time while breathing fire
1879
+ */
1880
+ updateGhostMode(t, i, e) {
1881
+ this.fireState === "charging" || this.fireState === "firing" || super.updateGhostMode(t, i, e);
1882
+ }
1883
+ /**
1884
+ * Update fire breath state machine
1885
+ */
1886
+ updateFireBreath(t, i, e) {
1887
+ if (this.fireState === "cooldown") {
1888
+ this.fireCooldownTimer += t, this.fireCooldownTimer >= M.FYGAR.FIRE_COOLDOWN && (this.fireState = "ready", this.fireCooldownTimer = 0);
1889
+ return;
1890
+ }
1891
+ if (this.fireState === "ready") {
1892
+ if (this.direction !== h.LEFT && this.direction !== h.RIGHT)
1893
+ return;
1894
+ (this.direction === h.RIGHT && this.x < i.x || this.direction === h.LEFT && this.x > i.x) && this.isPlayerInFireRange(i, e) && this.startCharging();
1895
+ return;
1896
+ }
1897
+ if (this.fireState === "charging") {
1898
+ this.fireStateTimer += t, this.fireStateTimer >= M.FYGAR.FIRE_CHARGE_TIME && this.startFiring();
1899
+ return;
1900
+ }
1901
+ if (this.fireState === "firing") {
1902
+ this.fireStateTimer += t, this.calculateFireHitbox(), this.fireStateTimer >= M.FYGAR.FIRE_DURATION && this.stopFiring();
1903
+ return;
1904
+ }
1905
+ }
1906
+ /**
1907
+ * Check if player is in fire range (horizontally aligned and in front)
1908
+ * Also checks that no dirt blocks the fire path
1909
+ */
1910
+ isPlayerInFireRange(t, i) {
1911
+ const e = t.x - this.x, s = t.y - this.y;
1912
+ if (Math.abs(s) > 16 * 0.75 || !(this.direction === h.RIGHT && e > 0 && e < M.FYGAR.FIRE_RANGE || this.direction === h.LEFT && e < 0 && e > -64))
1913
+ return !1;
1914
+ const n = Math.floor((this.x + 16 / 2) / 16), o = Math.floor((t.x + 16 / 2) / 16), a = Math.floor((this.y + 16 / 2) / 16), l = Math.min(n, o), d = Math.max(n, o);
1915
+ for (let c = l + 1; c < d; c++)
1916
+ if (i.isDirt(c, a))
1917
+ return !1;
1918
+ return !0;
1919
+ }
1920
+ /**
1921
+ * Start charging phase (pause before fire)
1922
+ */
1923
+ startCharging() {
1924
+ this.fireState = "charging", this.fireStateTimer = 0, this.fireDirection = this.direction, this.isMoving = !1;
1925
+ const t = Math.floor((this.x + 16 / 2) / 16), i = Math.floor((this.y + 16 / 2) / 16);
1926
+ this.x = t * 16, this.y = i * 16, this.lastGridX = t, this.lastGridY = i;
1927
+ }
1928
+ /**
1929
+ * Start firing phase (fire is now active and can damage player)
1930
+ */
1931
+ startFiring() {
1932
+ this.fireState = "firing", this.fireStateTimer = 0, this.calculateFireHitbox();
1933
+ }
1934
+ /**
1935
+ * Calculate the fire hitbox based on current position, direction, and tile count
1936
+ * Hitbox grows as fire extends: 1 tile -> 2 tiles -> 3 tiles
1937
+ */
1938
+ calculateFireHitbox() {
1939
+ const t = this.getFireTileCount();
1940
+ if (t === 0) {
1941
+ this.fireHitbox = null;
1942
+ return;
1943
+ }
1944
+ const i = t * 16, e = this.y + 16 / 2;
1945
+ this.fireDirection === h.RIGHT ? this.fireHitbox = {
1946
+ x: this.x + 16,
1947
+ // Start from right edge of Fygar
1948
+ y: e - 16 / 4,
1949
+ // Vertically centered with some height
1950
+ width: i,
1951
+ height: 16 / 2
1952
+ } : this.fireHitbox = {
1953
+ x: this.x - i,
1954
+ // Start from current fire length to the left
1955
+ y: e - 16 / 4,
1956
+ width: i,
1957
+ height: 16 / 2
1958
+ };
1959
+ }
1960
+ /**
1961
+ * Stop firing and enter cooldown
1962
+ */
1963
+ stopFiring() {
1964
+ this.fireState = "cooldown", this.fireStateTimer = 0, this.fireCooldownTimer = 0, this.fireHitbox = null, this.fireDirection = null, this.isMoving = !0;
1965
+ const t = Math.floor((this.x + 16 / 2) / 16), i = Math.floor((this.y + 16 / 2) / 16);
1966
+ this.lastGridX = t, this.lastGridY = i;
1967
+ }
1968
+ /**
1969
+ * Cancel fire (when pumped during charge/fire)
1970
+ */
1971
+ cancelFire() {
1972
+ this.fireState = "cooldown", this.fireStateTimer = 0, this.fireCooldownTimer = M.FYGAR.FIRE_COOLDOWN * 0.5, this.fireHitbox = null, this.fireDirection = null;
1973
+ }
1974
+ /**
1975
+ * Check if fire is currently active and dangerous
1976
+ */
1977
+ isFireActive() {
1978
+ return this.fireState === "firing" && this.fireHitbox !== null;
1979
+ }
1980
+ /**
1981
+ * Check if currently charging (for visual feedback)
1982
+ */
1983
+ isCharging() {
1984
+ return this.fireState === "charging";
1985
+ }
1986
+ /**
1987
+ * Get fire hitbox for collision detection
1988
+ */
1989
+ getFireHitbox() {
1990
+ return this.fireHitbox;
1991
+ }
1992
+ /**
1993
+ * Get fire direction for rendering
1994
+ */
1995
+ getFireDirection() {
1996
+ return this.fireDirection;
1997
+ }
1998
+ /**
1999
+ * Get current fire length in tiles (1-3) based on fire timer progress
2000
+ * Fire extends over the duration: 1 tile -> 2 tiles -> 3 tiles
2001
+ */
2002
+ getFireTileCount() {
2003
+ if (this.fireState !== "firing")
2004
+ return 0;
2005
+ const t = this.fireStateTimer / M.FYGAR.FIRE_DURATION;
2006
+ return t < 0.33 ? 1 : t < 0.66 ? 2 : 3;
2007
+ }
2008
+ /**
2009
+ * Reset timers - called on respawn
2010
+ */
2011
+ resetTimers() {
2012
+ super.resetTimers(), this.fireState = "ready", this.fireStateTimer = 0, this.fireCooldownTimer = 0, this.fireDirection = null, this.fireHitbox = null;
2013
+ }
2014
+ }
2015
+ class X {
2016
+ constructor(t, i, e) {
2017
+ this.x = t, this.y = i, this.grid = e, this.isFalling = !1, this.isShaking = !1, this.shakeTimer = 0, this.fallDelay = C.FALL_DELAY, this.fallSpeed = C.FALL_SPEED, this.isCrumbling = !1, this.crumbleTimer = 0, this.CRUMBLE_DURATION = 800, this.waitingToCrumble = !1, this.crumbleDelayTimer = 0, this.CRUMBLE_DELAY = 400, this.crushedEnemy = !1, this.enemiesKilled = 0, this.killPositions = [], this.playerStillBelow = !1, this.playerLastX = 0, this.playerLastY = 0, this.fallDelayTimer = 0, this.waitingToFall = !1, this.gridX = Math.floor(t / 16), this.gridY = Math.floor(i / 16), this.isSpawning = !1, this.spawnTimer = 0, this.SPAWN_DURATION = 2e3;
2018
+ }
2019
+ /**
2020
+ * Update rock state
2021
+ */
2022
+ update(t, i, e) {
2023
+ if (this.isSpawning) {
2024
+ this.spawnTimer += t, this.spawnTimer >= this.SPAWN_DURATION && (this.isSpawning = !1);
2025
+ return;
2026
+ }
2027
+ if (this.waitingToCrumble) {
2028
+ this.crumbleDelayTimer += t, this.crumbleDelayTimer >= this.CRUMBLE_DELAY && (this.waitingToCrumble = !1, this.isCrumbling = !0, this.crumbleTimer = 0);
2029
+ return;
2030
+ }
2031
+ if (this.isCrumbling) {
2032
+ this.crumbleTimer += t, this.crumbleTimer > this.CRUMBLE_DURATION && (this.isDestroyed = !0);
2033
+ return;
2034
+ }
2035
+ this.isShaking && (this.shakeTimer += t, this.shakeTimer > C.SHAKE_DURATION && (e && this.playerStillBelow ? this.hasPlayerCleared(e) && (this.playerStillBelow = !1, this.waitingToFall = !0, this.fallDelayTimer = 0) : this.waitingToFall || this.startFalling(i))), this.waitingToFall && (this.fallDelayTimer += t, this.fallDelayTimer >= this.fallDelay && (this.waitingToFall = !1, this.startFalling(i))), this.isFalling && this.fall(i), !this.isFalling && !this.isShaking && e && this.checkPlayerTrigger(e, i);
2036
+ }
2037
+ /**
2038
+ * Check if player is touching rock from below
2039
+ */
2040
+ checkPlayerTrigger(t, i) {
2041
+ const e = i.isDirt(this.gridX, this.gridY + 1), s = i.isRock(this.gridX, this.gridY + 1);
2042
+ if (e || s || this.gridY >= i.height - 1)
2043
+ return;
2044
+ const r = Math.floor(t.x / 16), o = Math.floor(t.y / 16) === this.gridY + 1 && Math.abs(r - this.gridX) <= 1, a = t.y, l = this.y + 16, d = Math.abs(a - l) < 4, c = Math.abs(t.x - this.x) < 16;
2045
+ (o && d && c || d && c && t.y < this.y) && (this.playerStillBelow = !0, this.playerLastX = t.x, this.playerLastY = t.y, this.startShaking());
2046
+ }
2047
+ /**
2048
+ * Check if player has fully cleared from underneath the rock
2049
+ */
2050
+ hasPlayerCleared(t) {
2051
+ const i = t.x, e = t.x + 16, s = this.x, r = this.x + 16, n = e <= s || i >= r, a = Math.floor(t.y / 16) !== this.gridY + 1;
2052
+ return n || a;
2053
+ }
2054
+ /**
2055
+ * Start spawn animation (for respawned rocks)
2056
+ */
2057
+ startSpawnAnimation() {
2058
+ this.isSpawning = !0, this.spawnTimer = 0;
2059
+ }
2060
+ /**
2061
+ * Start shaking animation
2062
+ */
2063
+ startShaking() {
2064
+ this.isShaking = !0, this.shakeTimer = 0;
2065
+ }
2066
+ /**
2067
+ * Start falling
2068
+ */
2069
+ startFalling(t) {
2070
+ this.isFalling = !0, this.isShaking = !1, t.removeRock(this.gridX, this.gridY);
2071
+ }
2072
+ /**
2073
+ * Fall downward
2074
+ */
2075
+ fall(t) {
2076
+ this.y += this.fallSpeed;
2077
+ const i = Math.floor(this.y / 16);
2078
+ if (i !== this.gridY) {
2079
+ this.gridY = i;
2080
+ const e = t.isDirt(this.gridX, this.gridY + 1), s = t.isRock(this.gridX, this.gridY + 1), r = this.gridY >= t.height - 1;
2081
+ (e || s || r) && this.stopFalling();
2082
+ }
2083
+ }
2084
+ /**
2085
+ * Stop falling - crumble after landing
2086
+ */
2087
+ stopFalling() {
2088
+ this.isFalling = !1, this.y = this.gridY * 16, this.waitingToCrumble = !0, this.crumbleDelayTimer = 0;
2089
+ }
2090
+ /**
2091
+ * Mark that this rock crushed an enemy
2092
+ */
2093
+ markEnemyCrushed() {
2094
+ this.crushedEnemy = !0;
2095
+ }
2096
+ /**
2097
+ * Increment the kill count and store position for score display
2098
+ */
2099
+ incrementKillCount(t, i) {
2100
+ return this.enemiesKilled++, this.killPositions.push({ x: t, y: i }), this.enemiesKilled;
2101
+ }
2102
+ /**
2103
+ * Get the last kill position for floating score display
2104
+ */
2105
+ getLastKillPosition() {
2106
+ return this.killPositions.length > 0 ? this.killPositions[this.killPositions.length - 1] : { x: this.x, y: this.y };
2107
+ }
2108
+ /**
2109
+ * Get center position
2110
+ */
2111
+ getCenter() {
2112
+ return {
2113
+ x: this.x + 16 / 2,
2114
+ y: this.y + 16 / 2
2115
+ };
2116
+ }
2117
+ /**
2118
+ * Reset rock state (called on player respawn)
2119
+ */
2120
+ reset() {
2121
+ this.isShaking = !1, this.shakeTimer = 0, this.playerStillBelow = !1, this.waitingToFall = !1, this.fallDelayTimer = 0, this.waitingToCrumble = !1, this.crumbleDelayTimer = 0;
2122
+ }
2123
+ }
2124
+ class Q {
2125
+ constructor(t) {
2126
+ this.grid = t, this.currentLevel = 0, this.rocks = [];
2127
+ }
2128
+ /**
2129
+ * Generate a procedural level
2130
+ */
2131
+ generateLevel(t) {
2132
+ this.currentLevel = t, this.grid.reset(), this.rocks = [], this.currentEnemies = [], this.generateTunnels();
2133
+ }
2134
+ /**
2135
+ * Generate tunnel network
2136
+ */
2137
+ generateTunnels() {
2138
+ const t = Math.floor(13.5), i = Math.floor(18 / 2);
2139
+ this.grid.clearHorizontalTunnel(t - 1, t + 1, i), this.currentLevel > 1 && this.grid.clearVerticalTunnel(t, 2, i), this.playerStartTunnel = { x: t, y: i };
2140
+ }
2141
+ /**
2142
+ * Place rocks strategically in the level (away from paths and enemies)
2143
+ * Called after enemies are spawned
2144
+ */
2145
+ placeRocksAfterEnemies(t, i) {
2146
+ const e = Math.floor(t / 3), s = Math.min(F.ROCKS_PER_LEVEL + e, 6), r = 3, n = 27, o = 18, a = n / 2, l = a - 2, d = a + 2, c = i.map((p) => ({
2147
+ x: Math.floor(p.x / 16),
2148
+ y: Math.floor(p.y / 16)
2149
+ }));
2150
+ this.rocks = [];
2151
+ const f = [], u = 4, g = 5;
2152
+ for (let p = g; p < o - 5; p++)
2153
+ for (let E = u; E < n - 8; E++)
2154
+ E >= l && E <= d || this.grid.isDirt(E, p) && f.push({ x: E, y: p });
2155
+ for (let p = f.length - 1; p > 0; p--) {
2156
+ const E = Math.floor(Math.random() * (p + 1));
2157
+ [f[p], f[E]] = [f[E], f[p]];
2158
+ }
2159
+ const T = (p, E, x, D) => {
2160
+ const { x: y, y: k } = p;
2161
+ for (let _ = 0; _ < this.rocks.length; _++) {
2162
+ const w = this.rocks[_], P = Math.abs(w.gridX - y), Z = Math.abs(w.gridY - k);
2163
+ if (P < D && Z < D) return !1;
2164
+ }
2165
+ if (x > 0)
2166
+ for (let _ = 0; _ < c.length; _++) {
2167
+ const w = c[_], P = Math.abs(w.x - y), Z = Math.abs(w.y - k);
2168
+ if (P < x && Z < x) return !1;
2169
+ }
2170
+ for (let _ = -E; _ <= E; _++)
2171
+ for (let w = -E; w <= E; w++)
2172
+ if (!(w === 0 && _ === 0) && this.grid.isEmpty(y + w, k + _))
2173
+ return !1;
2174
+ return !0;
2175
+ }, I = [
2176
+ // Pass 1: Strict (Ideal placement)
2177
+ // rock: 10 ensures huge gaps
2178
+ {
2179
+ target: s,
2180
+ path: 3,
2181
+ enemy: 6,
2182
+ rock: 10
2183
+ },
2184
+ // Pass 2: Relaxed (Compromise)
2185
+ // rock: 4 ensures reasonable spacing
2186
+ {
2187
+ target: s,
2188
+ path: 2,
2189
+ enemy: 4,
2190
+ rock: 4
2191
+ },
2192
+ // Pass 3: Desperation (Force Minimum)
2193
+ // rock: 2 means dx must be >= 2. (0 and 1 are disallowed).
2194
+ // This guarantees rocks never touch, even in desperation mode.
2195
+ {
2196
+ target: r,
2197
+ path: 1,
2198
+ enemy: 0,
2199
+ rock: 2
2200
+ }
2201
+ ];
2202
+ for (const p of I)
2203
+ if (!(this.rocks.length >= p.target))
2204
+ for (const E of f) {
2205
+ if (this.rocks.length >= p.target) break;
2206
+ this.grid.isRock(E.x, E.y) || T(E, p.path, p.enemy, p.rock) && (this.grid.placeRock(E.x, E.y), this.rocks.push(
2207
+ new X(
2208
+ E.x * 16,
2209
+ E.y * 16,
2210
+ this.grid
2211
+ )
2212
+ ));
2213
+ }
2214
+ }
2215
+ /**
2216
+ * Spawn enemies for the level
2217
+ */
2218
+ spawnEnemies(t) {
2219
+ const i = [], e = Math.min(
2220
+ F.START_ENEMIES + (t - 1) * F.ENEMY_INCREMENT,
2221
+ F.MAX_ENEMIES
2222
+ ), s = Math.min(0.5, 0.2 + t * 0.05), r = Math.floor(e * s), n = e - r;
2223
+ this.enemyTunnels = [], this.createEnemyTunnels(e);
2224
+ for (let o = 0; o < n; o++) {
2225
+ const a = this.getEnemySpawnPosition(o);
2226
+ i.push(new q(a.x, a.y, t));
2227
+ }
2228
+ for (let o = 0; o < r; o++) {
2229
+ const a = this.getEnemySpawnPosition(n + o);
2230
+ i.push(new j(a.x, a.y, t));
2231
+ }
2232
+ return i;
2233
+ }
2234
+ /**
2235
+ * Create tunnels for enemies to spawn in
2236
+ */
2237
+ createEnemyTunnels(t) {
2238
+ this.enemyTunnels = [];
2239
+ const i = 27, e = 18, s = Math.floor(i / 2), r = Math.floor(e / 2), n = 100, o = 36, a = [], l = 4, d = 4;
2240
+ for (let g = d; g < e - d; g++)
2241
+ for (let T = l; T < i - l; T++) {
2242
+ const I = T - s, p = g - r;
2243
+ I * I + p * p < o || a.push({ x: T, y: g });
2244
+ }
2245
+ for (let g = a.length - 1; g > 0; g--) {
2246
+ const T = Math.floor(Math.random() * (g + 1));
2247
+ [a[g], a[T]] = [a[T], a[g]];
2248
+ }
2249
+ const c = (g, T, I, p) => {
2250
+ if (I) {
2251
+ const E = Math.min(p, i - 2 - g);
2252
+ return { minX: g, maxX: g + E, minY: T, maxY: T };
2253
+ } else {
2254
+ const E = Math.min(p, e - 2 - T);
2255
+ return { minX: g, maxX: g, minY: T, maxY: T + E };
2256
+ }
2257
+ }, f = (g, T, I) => g.minX <= T.maxX + I && g.maxX >= T.minX - I && g.minY <= T.maxY + I && g.maxY >= T.minY - I, u = (g, T) => {
2258
+ for (let I = 0; I < a.length; I++) {
2259
+ if (this.enemyTunnels.length >= t) return;
2260
+ const { x: p, y: E } = a[I], x = p - s, D = E - r;
2261
+ if (x * x + D * D < T) continue;
2262
+ const y = Math.random() > 0.5, k = Math.floor(Math.random() * 2) + 2, _ = c(p, E, y, k);
2263
+ let w = !0;
2264
+ for (const P of this.enemyTunnels)
2265
+ if (f(_, P.bounds, g)) {
2266
+ w = !1;
2267
+ break;
2268
+ }
2269
+ w && (y ? this.grid.clearHorizontalTunnel(
2270
+ _.minX,
2271
+ _.maxX,
2272
+ _.minY
2273
+ ) : this.grid.clearVerticalTunnel(
2274
+ _.minX,
2275
+ _.minY,
2276
+ _.maxY
2277
+ ), this.enemyTunnels.push({
2278
+ x: p,
2279
+ y: E,
2280
+ horizontal: y,
2281
+ length: k,
2282
+ bounds: _
2283
+ }));
2284
+ }
2285
+ };
2286
+ u(6, n), this.enemyTunnels.length < t && u(2, o);
2287
+ }
2288
+ /**
2289
+ * Get a valid spawn position for an enemy in their pre-created tunnel
2290
+ */
2291
+ getEnemySpawnPosition(t) {
2292
+ if (t < this.enemyTunnels.length) {
2293
+ const i = this.enemyTunnels[t], e = i.horizontal ? Math.floor(Math.random() * i.length) : 0, s = i.horizontal ? 0 : Math.floor(Math.random() * i.length), r = i.x + e, n = i.y + s;
2294
+ if (this.grid.isEmpty(r, n))
2295
+ return {
2296
+ x: r * 16,
2297
+ y: n * 16
2298
+ };
2299
+ }
2300
+ for (let i = 0; i < this.grid.height; i++)
2301
+ for (let e = 0; e < this.grid.width; e++)
2302
+ if (this.grid.isEmpty(e, i) && i > 2)
2303
+ return { x: e * 16, y: i * 16 };
2304
+ return { x: 16 * 5, y: 16 * 5 };
2305
+ }
2306
+ /**
2307
+ * Spawn bonus item in center of screen
2308
+ * @param {number} bonusIndex - Sequential index for determining which prize to show (0, 1, 2, ...)
2309
+ */
2310
+ spawnBonusItem(t = 0) {
2311
+ const i = Math.floor(13.5) * 16, e = Math.floor(18 / 2) * 16;
2312
+ return {
2313
+ x: i,
2314
+ y: e,
2315
+ bonusIndex: t,
2316
+ // Store the index for sequential prize selection
2317
+ FLASH_START: 3e3,
2318
+ // Start flashing after 3 seconds
2319
+ FLASH_DURATION: 2e3,
2320
+ // Flash for 2 seconds then disappear
2321
+ elapsedTime: 0,
2322
+ update: function(s) {
2323
+ return this.elapsedTime += s, !(this.elapsedTime > this.FLASH_START + this.FLASH_DURATION);
2324
+ },
2325
+ isFlashing: function() {
2326
+ return this.elapsedTime >= this.FLASH_START;
2327
+ }
2328
+ };
2329
+ }
2330
+ /**
2331
+ * Spawn a single rock in a dirt tile
2332
+ * @returns {Rock|null} The spawned rock, or null if no valid position found
2333
+ */
2334
+ spawnSingleRock() {
2335
+ const t = [];
2336
+ for (let s = 4; s < 16; s++)
2337
+ for (let r = 2; r < 25; r++) {
2338
+ if (!this.grid.isDirt(r, s) || r >= 27 / 2 - 2 && r <= 27 / 2 + 2)
2339
+ continue;
2340
+ let n = !1;
2341
+ for (const o of this.rocks) {
2342
+ const a = Math.floor(o.x / 16), l = Math.floor(o.y / 16);
2343
+ if (Math.abs(r - a) + Math.abs(s - l) < 4) {
2344
+ n = !0;
2345
+ break;
2346
+ }
2347
+ }
2348
+ n || t.push({ x: r, y: s });
2349
+ }
2350
+ if (t.length === 0)
2351
+ return null;
2352
+ const i = t[Math.floor(Math.random() * t.length)];
2353
+ return this.grid.placeRock(i.x, i.y), new X(i.x * 16, i.y * 16, this.grid);
2354
+ }
2355
+ /**
2356
+ * Get rocks array
2357
+ */
2358
+ getRocks() {
2359
+ return this.rocks;
2360
+ }
2361
+ }
2362
+ class J {
2363
+ constructor() {
2364
+ this.score = 0, this.lives = b.START_LIVES, this.highScore = this.loadHighScore(), this.nextExtraLifeThreshold = 2e4;
2365
+ }
2366
+ /**
2367
+ * Reset score and lives for new game
2368
+ */
2369
+ reset() {
2370
+ this.score = 0, this.lives = b.START_LIVES, this.nextExtraLifeThreshold = 2e4;
2371
+ }
2372
+ /**
2373
+ * Add points for digging a tile
2374
+ */
2375
+ addDigScore() {
2376
+ const t = A.DIG_TILE;
2377
+ return this.addScore(t), t;
2378
+ }
2379
+ /**
2380
+ * Calculate depth quarter based on Y position
2381
+ * Sky is rows 0-1, dirt is rows 2-17 (16 rows total)
2382
+ * Quarters of dirt: 2-5 (Q0), 6-9 (Q1), 10-13 (Q2), 14-17 (Q3)
2383
+ */
2384
+ getDepthQuarter(t) {
2385
+ const i = Math.floor(t / 16), e = 2, r = 16 / 4, n = Math.max(0, i - e);
2386
+ return Math.min(3, Math.floor(n / r));
2387
+ }
2388
+ /**
2389
+ * Add points for enemy kill by pumping
2390
+ * Points based on depth and whether Fygar was killed horizontally
2391
+ */
2392
+ addEnemyKill(t, i, e = !1) {
2393
+ const s = this.getDepthQuarter(i);
2394
+ let r;
2395
+ return t === G.FYGAR && e ? r = A.PUMP_KILL.FYGAR_HORIZONTAL[s] : t === G.FYGAR ? r = A.PUMP_KILL.FYGAR[s] : r = A.PUMP_KILL.POOKA[s], this.addScore(r), r;
2396
+ }
2397
+ /**
2398
+ * Add points for rock kill based on number of enemies killed
2399
+ */
2400
+ addRockKill(t) {
2401
+ const i = Math.min(t, 8), e = A.ROCK_KILL[i];
2402
+ return this.addScore(e), e;
2403
+ }
2404
+ /**
2405
+ * Add points for bonus item based on prize index
2406
+ */
2407
+ addBonusItem(t) {
2408
+ const i = Math.min(t, A.BONUS_ITEMS.length - 1), e = A.BONUS_ITEMS[i];
2409
+ return this.addScore(e), e;
2410
+ }
2411
+ /**
2412
+ * Add points to score and check for extra life
2413
+ * Returns true if an extra life was awarded
2414
+ */
2415
+ addScore(t) {
2416
+ return this.score += t, this.checkExtraLife();
2417
+ }
2418
+ /**
2419
+ * Check and award extra life at thresholds:
2420
+ * 20,000, 50,000, then every 50,000 (100,000, 150,000, ...)
2421
+ */
2422
+ checkExtraLife() {
2423
+ return this.score >= this.nextExtraLifeThreshold ? (this.gainLife(), this.nextExtraLifeThreshold === 2e4 ? this.nextExtraLifeThreshold = 5e4 : this.nextExtraLifeThreshold += 5e4, !0) : !1;
2424
+ }
2425
+ /**
2426
+ * Lose a life
2427
+ */
2428
+ loseLife() {
2429
+ this.lives--;
2430
+ }
2431
+ /**
2432
+ * Gain a life
2433
+ */
2434
+ gainLife() {
2435
+ this.lives++;
2436
+ }
2437
+ /**
2438
+ * Load high score from localStorage
2439
+ */
2440
+ loadHighScore() {
2441
+ try {
2442
+ const t = localStorage.getItem(N);
2443
+ return t ? parseInt(t, 10) : 0;
2444
+ } catch {
2445
+ return 0;
2446
+ }
2447
+ }
2448
+ /**
2449
+ * Save high score to localStorage
2450
+ */
2451
+ saveHighScore() {
2452
+ try {
2453
+ localStorage.setItem(N, this.highScore.toString());
2454
+ } catch {
2455
+ }
2456
+ }
2457
+ }
2458
+ class O {
2459
+ constructor(t) {
2460
+ this.grid = t;
2461
+ const i = Math.floor(t.width / 2), e = Math.floor(t.height / 2);
2462
+ this.x = 16 * i, this.y = 16 * e, this.speed = b.SPEED, this.direction = h.LEFT, this.previousDirection = h.LEFT, this.isMoving = !1, this.isShooting = !1, this.isPumping = !1, this.pumpTarget = null, this.pumpLength = 0, this.pumpMaxLength = b.PUMP_RANGE, this.pumpExtendSpeed = 4, this.pumpRetractSpeed = 8, this.shouldAutoRetract = !1, this.pumpUsed = !1, this.fullLengthTimer = 0, this.fullLengthDelay = 200, this.animationFrame = 0, this.animationTimer = 0, this.spriteFlipH = !1, this.spriteFlipV = !1, this.isDigging = !1, this.isDying = !1, this.deathTimer = 0, this.deathType = null, this.isSmooshed = !1, this.attachedToRock = null, this.smooshedDelayTimer = 0, this.SMOOSHED_DELAY = 400, this.isInvincible = !1, this.invincibilityTimer = 0, this.pumpCooldown = 800, this.pumpCooldownTimer = 0;
2463
+ }
2464
+ /**
2465
+ * Start death animation
2466
+ */
2467
+ startDeath(t) {
2468
+ this.isDying = !0, this.deathTimer = 0, this.deathType = t, this.isMoving = !1;
2469
+ }
2470
+ /**
2471
+ * Player gets smooshed (crushed by rock)
2472
+ * Shows smooshed sprite and falls with rock
2473
+ */
2474
+ smoosh(t) {
2475
+ this.isSmooshed = !0, this.attachedToRock = t, this.startDeath("rock");
2476
+ }
2477
+ /**
2478
+ * Update player state
2479
+ */
2480
+ update(t, i, e) {
2481
+ if (this.isDying) {
2482
+ if (this.isSmooshed && this.attachedToRock && this.attachedToRock.isFalling)
2483
+ return;
2484
+ if (this.isSmooshed && this.smooshedDelayTimer < this.SMOOSHED_DELAY) {
2485
+ this.smooshedDelayTimer += t;
2486
+ return;
2487
+ }
2488
+ this.deathTimer += t;
2489
+ return;
2490
+ }
2491
+ this.isInvincible && (this.invincibilityTimer += t, this.invincibilityTimer > v.INVINCIBILITY_TIME && (this.isInvincible = !1)), this.pumpCooldownTimer < this.pumpCooldown && (this.pumpCooldownTimer += t);
2492
+ const s = this.pumpCooldownTimer >= this.pumpCooldown, r = i.isSpacePressed();
2493
+ r || (this.pumpUsed = !1), r && s && !this.shouldAutoRetract && !this.pumpUsed ? (this.isShooting || this.startPump(), this.extendPump(t)) : (this.isShooting || this.pumpLength > 0) && this.retractPump();
2494
+ const n = i.getDirection();
2495
+ n && !this.isShooting && !this.isPumping ? (n !== this.direction && (this.previousDirection = this.direction, this.updateSpriteOrientation(n)), this.direction = n, this.isMoving = !0, this.move(n, e)) : this.isMoving = !1, (this.isMoving || this.isPumping) && (this.animationTimer += t, this.animationTimer > (this.isMoving ? 100 : 200) && (this.animationFrame = (this.animationFrame + 1) % 2, this.animationTimer = 0)), this.checkDiggingState(e), this.dig(e);
2496
+ }
2497
+ /**
2498
+ * Move player in direction
2499
+ */
2500
+ move(t, i) {
2501
+ let e = this.x, s = this.y;
2502
+ switch (t) {
2503
+ case h.UP:
2504
+ s -= this.speed;
2505
+ break;
2506
+ case h.DOWN:
2507
+ s += this.speed;
2508
+ break;
2509
+ case h.LEFT:
2510
+ e -= this.speed;
2511
+ break;
2512
+ case h.RIGHT:
2513
+ e += this.speed;
2514
+ break;
2515
+ }
2516
+ e < 0 && (e = 0), e > i.width * 16 - 16 && (e = i.width * 16 - 16), s < 0 && (s = 0), s > i.height * 16 - 16 && (s = i.height * 16 - 16);
2517
+ const r = e + 16 / 2, n = s + 16 / 2, o = i.pixelToGrid(r, n);
2518
+ if (i.dig(o.x, o.y), this.canMoveToPosition(e, s, i)) {
2519
+ if (this.x = e, this.y = s, t === h.LEFT || t === h.RIGHT) {
2520
+ const l = this.y + 8, f = Math.floor(l / 16) * 16 - this.y;
2521
+ Math.abs(f) > 0 && (this.y += Math.sign(f) * Math.min(Math.abs(f), this.speed));
2522
+ }
2523
+ if (t === h.UP || t === h.DOWN) {
2524
+ const l = this.x + 8, f = Math.floor(l / 16) * 16 - this.x;
2525
+ Math.abs(f) > 0 && (this.x += Math.sign(f) * Math.min(Math.abs(f), this.speed));
2526
+ }
2527
+ }
2528
+ }
2529
+ /**
2530
+ * Check if player can move to position
2531
+ */
2532
+ canMoveToPosition(t, i, e) {
2533
+ return [
2534
+ { x: t, y: i },
2535
+ { x: t + 16 - 1, y: i },
2536
+ { x: t, y: i + 16 - 1 },
2537
+ { x: t + 16 - 1, y: i + 16 - 1 }
2538
+ ].every((r) => {
2539
+ const { x: n, y: o } = e.pixelToGrid(r.x, r.y);
2540
+ return o === 0 ? !1 : !e.isRock(n, o);
2541
+ });
2542
+ }
2543
+ /**
2544
+ * Check if player is in contact with dirt (digging state)
2545
+ */
2546
+ checkDiggingState(t) {
2547
+ const i = this.x + 8, e = this.y + 16 / 2, { x: s, y: r } = t.pixelToGrid(i, e);
2548
+ let n = !1;
2549
+ switch (this.direction) {
2550
+ case h.LEFT:
2551
+ n = t.isDirt(s - 1, r);
2552
+ break;
2553
+ case h.RIGHT:
2554
+ n = t.isDirt(s + 1, r);
2555
+ break;
2556
+ case h.UP:
2557
+ n = t.isDirt(s, r - 1);
2558
+ break;
2559
+ case h.DOWN:
2560
+ n = t.isDirt(s, r + 1);
2561
+ break;
2562
+ }
2563
+ this.isDigging = n;
2564
+ }
2565
+ /**
2566
+ * Dig dirt at current position
2567
+ */
2568
+ dig(t) {
2569
+ const i = this.x + 8, e = this.y + 16 / 2, { x: s, y: r } = t.pixelToGrid(i, e);
2570
+ t.dig(s, r);
2571
+ }
2572
+ /**
2573
+ * Update sprite orientation based on direction change
2574
+ */
2575
+ updateSpriteOrientation(t) {
2576
+ t === h.LEFT || t === h.RIGHT ? t === h.RIGHT ? (this.spriteFlipH = !0, this.spriteFlipV = !1) : (this.spriteFlipH = !1, this.spriteFlipV = !1) : t === h.DOWN ? this.previousDirection === h.RIGHT ? (this.spriteFlipH = !0, this.spriteFlipV = !1) : this.previousDirection === h.UP ? this.spriteFlipV = !this.spriteFlipV : this.previousDirection === h.LEFT && (this.spriteFlipH = !1, this.spriteFlipV = !1) : t === h.UP && (this.previousDirection === h.LEFT ? (this.spriteFlipH = !0, this.spriteFlipV = !0) : this.previousDirection === h.RIGHT ? (this.spriteFlipH = !1, this.spriteFlipV = !0) : this.previousDirection === h.DOWN && (this.spriteFlipV = !this.spriteFlipV));
2577
+ }
2578
+ /**
2579
+ * Start pumping attack
2580
+ */
2581
+ startPump() {
2582
+ this.isShooting = !0, this.isMoving = !1;
2583
+ }
2584
+ /**
2585
+ * Extend pump line while Space is held
2586
+ */
2587
+ extendPump(t) {
2588
+ if (this.pumpTarget) {
2589
+ this.isShooting = !1, this.isPumping = !0, this.pumpTarget.isDestroyed && (this.pumpLength = 0, this.stopPump());
2590
+ return;
2591
+ }
2592
+ if (this.pumpLength < this.pumpMaxLength) {
2593
+ const i = Math.min(
2594
+ this.pumpLength + this.pumpExtendSpeed,
2595
+ this.pumpMaxLength
2596
+ );
2597
+ this.canPumpExtendTo(i) ? (this.pumpLength = i, this.fullLengthTimer = 0) : (this.fullLengthTimer += t, this.fullLengthTimer >= this.fullLengthDelay && (this.shouldAutoRetract = !0));
2598
+ } else
2599
+ this.fullLengthTimer += t, this.fullLengthTimer >= this.fullLengthDelay && (this.shouldAutoRetract = !0);
2600
+ }
2601
+ /**
2602
+ * Check if pump can extend to a given length (not blocked by dirt or rocks)
2603
+ */
2604
+ canPumpExtendTo(t) {
2605
+ const i = this.x + 8, e = this.y + 16 / 2;
2606
+ let s = i, r = e;
2607
+ switch (this.direction) {
2608
+ case h.UP:
2609
+ r = e - t;
2610
+ break;
2611
+ case h.DOWN:
2612
+ r = e + t;
2613
+ break;
2614
+ case h.LEFT:
2615
+ s = i - t;
2616
+ break;
2617
+ case h.RIGHT:
2618
+ s = i + t;
2619
+ break;
2620
+ }
2621
+ const { x: n, y: o } = this.grid.pixelToGrid(s, r);
2622
+ return !this.grid.isDirt(n, o) && !this.grid.isRock(n, o);
2623
+ }
2624
+ /**
2625
+ * Retract pump line when Space is released
2626
+ */
2627
+ retractPump() {
2628
+ if (this.pumpTarget && this.pumpTarget.isDestroyed) {
2629
+ this.pumpLength = 0, this.stopPump();
2630
+ return;
2631
+ }
2632
+ this.pumpLength = Math.max(0, this.pumpLength - this.pumpRetractSpeed), this.pumpLength === 0 && this.stopPump();
2633
+ }
2634
+ /**
2635
+ * Stop pumping
2636
+ */
2637
+ stopPump() {
2638
+ this.isShooting = !1, this.isPumping = !1, this.pumpTarget = null, this.pumpLength = 0, this.shouldAutoRetract = !1, this.pumpUsed = !0, this.fullLengthTimer = 0;
2639
+ }
2640
+ /**
2641
+ * Get the end point of the pump line
2642
+ */
2643
+ getPumpEndPoint() {
2644
+ const t = this.x + 8, i = this.y + 16 / 2;
2645
+ switch (this.direction) {
2646
+ case h.UP:
2647
+ return { x: t, y: i - this.pumpLength };
2648
+ case h.DOWN:
2649
+ return { x: t, y: i + this.pumpLength };
2650
+ case h.LEFT:
2651
+ return { x: t - this.pumpLength, y: i };
2652
+ case h.RIGHT:
2653
+ return { x: t + this.pumpLength, y: i };
2654
+ default:
2655
+ return { x: t, y: i };
2656
+ }
2657
+ }
2658
+ /**
2659
+ * Get grid position
2660
+ */
2661
+ getGridPosition() {
2662
+ return this.grid.pixelToGrid(this.x, this.y);
2663
+ }
2664
+ /**
2665
+ * Get center position
2666
+ */
2667
+ getCenter() {
2668
+ return {
2669
+ x: this.x + 16 / 2,
2670
+ y: this.y + 16 / 2
2671
+ };
2672
+ }
2673
+ /**
2674
+ * Reset all timers - called when game starts or respawns
2675
+ */
2676
+ resetTimers() {
2677
+ this.pumpCooldownTimer = 0, this.invincibilityTimer = 0, this.animationTimer = 0, this.fullLengthTimer = 0, this.pumpLength = 0, this.isShooting = !1, this.isPumping = !1, this.pumpTarget = null, this.shouldAutoRetract = !1, this.pumpUsed = !1, this.isSmooshed = !1, this.attachedToRock = null, this.smooshedDelayTimer = 0;
2678
+ }
2679
+ }
2680
+ class tt {
2681
+ constructor(t = {}) {
2682
+ this.config = {
2683
+ container: t.container || document.body,
2684
+ width: t.width || 432,
2685
+ height: t.height || 288,
2686
+ scale: t.scale || 1,
2687
+ debug: t.debug || !1,
2688
+ onGameOver: t.onGameOver || (() => {
2689
+ }),
2690
+ onLevelComplete: t.onLevelComplete || (() => {
2691
+ }),
2692
+ onScoreChange: t.onScoreChange || (() => {
2693
+ })
2694
+ }, this.state = m.MENU, this.lastTime = 0, this.animationFrameId = null, this.renderer = new K(this.config), this.grid = new z(), this.inputManager = new B(), this.collisionSystem = new $(this.grid), this.levelManager = new Q(this.grid), this.scoreManager = new J(), this.player = null, this.enemies = [], this.rocks = [], this.bonusItems = [], this.rockRespawnTimer = 0, this.ROCK_RESPAWN_DELAY = 5e3, this.needsRockRespawn = !1, this.bonusSpawnCount = 0, this.floatingScores = [], this.lastDirtCount = 0, this.gameLoop = this.gameLoop.bind(this);
2695
+ }
2696
+ /**
2697
+ * Initialize the game
2698
+ */
2699
+ init() {
2700
+ this.renderer.attachTo(this.config.container), this.inputManager.init(), this.showMenu();
2701
+ }
2702
+ /**
2703
+ * Show the main menu
2704
+ */
2705
+ showMenu() {
2706
+ if (this.state = m.MENU, this.renderer.clear(), this.renderer.drawMenu(), !this.menuListenerAdded) {
2707
+ this.menuListenerAdded = !0;
2708
+ const t = (i) => {
2709
+ i.code === "Space" && this.state === m.MENU && (document.removeEventListener("keydown", t), this.menuListenerAdded = !1, this.startGame());
2710
+ };
2711
+ document.addEventListener("keydown", t);
2712
+ }
2713
+ }
2714
+ /**
2715
+ * Start a new game
2716
+ */
2717
+ startGame() {
2718
+ this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null), this.scoreManager.reset(), this.lastTime = 0, this.startIntro(), this.gameLoop(0);
2719
+ }
2720
+ /**
2721
+ * Start the intro animation sequence
2722
+ */
2723
+ startIntro() {
2724
+ this.state = m.INTRO, this.levelManager.generateLevel(1), this.enemies = this.config.debug ? [] : this.levelManager.spawnEnemies(1), this.levelManager.placeRocksAfterEnemies(1, this.enemies), this.rocks = this.levelManager.getRocks(), this.bonusItems = [], this.droppedRocksCount = 0, this.bonusSpawnCount = 0, this.rockRespawnTimer = 0, this.needsRockRespawn = !1, this.introCenterX = Math.floor(this.grid.width / 2), this.introCenterY = Math.floor(this.grid.height / 2), this.introTargetX = this.introCenterX * 16, this.introTargetY = this.introCenterY * 16, this.player = new O(this.grid), this.player.x = this.grid.width * 16, this.player.y = 16, this.player.direction = h.LEFT, this.player.spriteFlipH = !1, this.player.isMoving = !0, this.introPhase = "walk_left", this.introTimer = 0, this.introReadyDelay = 1e3;
2725
+ }
2726
+ /**
2727
+ * Reset all game timers - called when game starts
2728
+ */
2729
+ resetAllTimers() {
2730
+ this.enemies.forEach((t) => {
2731
+ t.resetTimers();
2732
+ }), this.player && this.player.resetTimers();
2733
+ }
2734
+ /**
2735
+ * Start a new level
2736
+ */
2737
+ startLevel(t) {
2738
+ this.levelManager.generateLevel(t), this.player = new O(this.grid), this.enemies = this.levelManager.spawnEnemies(t), this.levelManager.placeRocksAfterEnemies(t, this.enemies), this.rocks = this.levelManager.getRocks(), this.bonusItems = [], this.droppedRocksCount = 0, this.rockRespawnTimer = 0, this.needsRockRespawn = !1, this.floatingScores = [];
2739
+ }
2740
+ /**
2741
+ * Main game loop
2742
+ */
2743
+ gameLoop(t) {
2744
+ const i = t - this.lastTime, e = Math.min(i, 100);
2745
+ switch (this.lastTime = t, this.state) {
2746
+ case m.INTRO:
2747
+ this.updateIntro(e), this.renderIntro();
2748
+ break;
2749
+ case m.PLAYING:
2750
+ this.update(e), this.render();
2751
+ break;
2752
+ case m.DYING:
2753
+ this.updateDeath(e), this.render();
2754
+ break;
2755
+ case m.RESPAWNING:
2756
+ this.updateRespawn(e), this.renderRespawning();
2757
+ break;
2758
+ case m.PAUSED:
2759
+ this.renderPaused();
2760
+ break;
2761
+ case m.LEVEL_COMPLETE:
2762
+ this.renderLevelComplete();
2763
+ break;
2764
+ case m.GAME_OVER:
2765
+ this.renderGameOver();
2766
+ break;
2767
+ }
2768
+ this.animationFrameId = requestAnimationFrame(this.gameLoop);
2769
+ }
2770
+ /**
2771
+ * Update intro animation
2772
+ */
2773
+ updateIntro(t) {
2774
+ const i = this.config.debug ? 3 : 1.6;
2775
+ switch (this.introPhase !== "ready" && (this.player.animationTimer += t, this.player.animationTimer > 100 && (this.player.animationFrame = (this.player.animationFrame + 1) % 2, this.player.animationTimer = 0)), this.introPhase) {
2776
+ case "walk_left":
2777
+ this.player.x -= i, this.player.isMoving = !0, this.player.direction = h.LEFT, this.player.spriteFlipH = !1, this.player.x <= this.introTargetX && (this.player.x = this.introTargetX, this.introPhase = "dig_down", this.player.direction = h.DOWN, this.player.spriteFlipH = !1, this.player.spriteFlipV = !1);
2778
+ break;
2779
+ case "dig_down": {
2780
+ this.player.y += i, this.player.isMoving = !0, this.player.isDigging = !0, this.player.direction = h.DOWN;
2781
+ const e = this.grid.pixelToGrid(
2782
+ this.player.x + 16 / 2,
2783
+ this.player.y + 16 / 2
2784
+ );
2785
+ this.grid.dig(e.x, e.y), this.player.y >= this.introTargetY && (this.player.y = this.introTargetY, this.introPhase = "ready", this.introTimer = 0, this.player.isMoving = !1, this.player.isDigging = !1, this.player.direction = h.RIGHT, this.player.spriteFlipH = !0);
2786
+ break;
2787
+ }
2788
+ case "ready":
2789
+ this.player.animationFrame = 0, this.introTimer += t, this.introTimer >= this.introReadyDelay && (this.introPhase = "done", this.finishIntro());
2790
+ break;
2791
+ }
2792
+ }
2793
+ /**
2794
+ * Finish intro and start actual gameplay
2795
+ */
2796
+ finishIntro() {
2797
+ this.player.resetTimers(), this.resetAllTimers(), this.state = m.PLAYING;
2798
+ }
2799
+ /**
2800
+ * Render intro animation
2801
+ */
2802
+ renderIntro() {
2803
+ this.renderer.clear(), this.renderer.drawGrid(this.grid, this.levelManager.currentLevel), this.rocks.forEach((t) => {
2804
+ this.renderer.drawRock(t);
2805
+ }), this.player && this.renderer.drawPlayer(this.player), this.enemies.forEach((t) => {
2806
+ this.renderer.drawEnemy(t);
2807
+ }), this.renderer.drawUI(this.scoreManager, this.levelManager), this.renderer.renderRespawning();
2808
+ }
2809
+ /**
2810
+ * Update game state
2811
+ */
2812
+ update(t) {
2813
+ if (this.inputManager.isKeyPressed("Escape")) {
2814
+ this.pause();
2815
+ return;
2816
+ }
2817
+ if (this.player) {
2818
+ const i = this.countDirtTiles();
2819
+ this.player.update(t, this.inputManager, this.grid);
2820
+ const e = this.countDirtTiles(), s = i - e;
2821
+ for (let r = 0; r < s; r++)
2822
+ this.scoreManager.addDigScore();
2823
+ s > 0 && this.config.onScoreChange(this.scoreManager.score);
2824
+ }
2825
+ if (this.enemies.length === 1 && (this.enemies[0].isLastEnemy = !0), this.enemies.forEach((i) => {
2826
+ i.update(t, this.player, this.grid);
2827
+ }), this.rocks.forEach((i) => {
2828
+ i.update(t, this.grid, this.player);
2829
+ }), this.needsRockRespawn && this.rocks.length === 0 && (this.rockRespawnTimer += t, this.rockRespawnTimer >= this.ROCK_RESPAWN_DELAY)) {
2830
+ const i = this.levelManager.spawnSingleRock();
2831
+ i ? (i.startSpawnAnimation(), this.rocks.push(i), this.needsRockRespawn = !1, this.rockRespawnTimer = 0) : this.needsRockRespawn = !1;
2832
+ }
2833
+ this.bonusItems.forEach((i) => {
2834
+ i.update(t);
2835
+ }), this.updateFloatingScores(t), this.checkCollisions(), !this.config.debug && this.enemies.length === 0 && this.levelComplete();
2836
+ }
2837
+ /**
2838
+ * Check all collisions
2839
+ */
2840
+ checkCollisions() {
2841
+ this.player.pumpLength > 0 && this.checkPumpCollisions(), this.checkFireCollisions(), this.enemies.forEach((i) => {
2842
+ i.deflateTimer === 0 && this.collisionSystem.checkPlayerEnemyCollision(
2843
+ this.player,
2844
+ i
2845
+ ) && this.playerHit("enemy");
2846
+ }), this.rocks.forEach((i) => {
2847
+ i.isFalling && (this.enemies.forEach((e) => {
2848
+ e.isSmooshed || this.collisionSystem.checkRockEntityCollision(
2849
+ i,
2850
+ e
2851
+ ) && (i.markEnemyCrushed(), i.incrementKillCount(e.x, e.y), this.droppedRocksCount++, this.checkBonusSpawn(), e.smoosh(), e.attachedToRock = i, e.x = i.x, e.y = i.y);
2852
+ }), !this.player.isSmooshed && this.collisionSystem.checkRockEntityCollision(
2853
+ i,
2854
+ this.player
2855
+ ) && (this.player.smoosh(i), this.player.x = i.x, this.player.y = i.y, this.state = m.DYING, this.deathStartTime = Date.now())), this.enemies.forEach((e) => {
2856
+ e.isSmooshed && e.attachedToRock === i && (e.x = i.x, e.y = i.y);
2857
+ }), this.player.isSmooshed && this.player.attachedToRock === i && (this.player.x = i.x, this.player.y = i.y);
2858
+ });
2859
+ const t = this.rocks.length;
2860
+ this.rocks = this.rocks.filter((i) => {
2861
+ if (i.isDestroyed) {
2862
+ if (i.enemiesKilled > 0) {
2863
+ const e = this.scoreManager.addRockKill(
2864
+ i.enemiesKilled
2865
+ );
2866
+ this.config.onScoreChange(this.scoreManager.score), this.spawnFloatingScore(e, i.x, i.y);
2867
+ }
2868
+ return !1;
2869
+ }
2870
+ return !0;
2871
+ }), this.rocks.length === 0 && t > 0 && (this.needsRockRespawn = !0, this.rockRespawnTimer = 0), this.enemies = this.enemies.filter((i) => !i.hasEscaped), this.enemies = this.enemies.filter((i) => {
2872
+ if (i.isDestroyed) {
2873
+ if (!i.isSmooshed) {
2874
+ const e = this.player.direction === h.LEFT || this.player.direction === h.RIGHT, s = this.scoreManager.addEnemyKill(
2875
+ i.type,
2876
+ i.y,
2877
+ e
2878
+ );
2879
+ this.config.onScoreChange(this.scoreManager.score), this.spawnFloatingScore(s, i.x, i.y);
2880
+ }
2881
+ return !1;
2882
+ }
2883
+ return !0;
2884
+ }), this.bonusItems = this.bonusItems.filter((i) => {
2885
+ if (this.collisionSystem.checkPlayerBonusCollision(
2886
+ this.player,
2887
+ i
2888
+ )) {
2889
+ const e = this.scoreManager.addBonusItem(i.bonusIndex);
2890
+ return this.config.onScoreChange(this.scoreManager.score), this.spawnFloatingScore(e, i.x, i.y), !1;
2891
+ }
2892
+ return !0;
2893
+ });
2894
+ }
2895
+ /**
2896
+ * Check if pump line hits any enemies (only the closest one)
2897
+ */
2898
+ checkPumpCollisions() {
2899
+ if (this.player.pumpTarget && !this.player.pumpTarget.isDestroyed) {
2900
+ this.player.pumpTarget.startInflation();
2901
+ return;
2902
+ }
2903
+ const t = this.player.getCenter(), i = this.player.getPumpEndPoint(), e = this.player.pumpLength;
2904
+ let s = null, r = 1 / 0;
2905
+ this.enemies.forEach((n) => {
2906
+ if (n.isDestroyed) return;
2907
+ const o = n.getCenter(), a = this.pointToLineDistance(
2908
+ o.x,
2909
+ o.y,
2910
+ t.x,
2911
+ t.y,
2912
+ i.x,
2913
+ i.y
2914
+ ), l = Math.sqrt(
2915
+ Math.pow(o.x - t.x, 2) + Math.pow(o.y - t.y, 2)
2916
+ ), d = 16 * 0.6;
2917
+ a < d && l <= e + 16 / 2 && this.isInPumpDirection(t, i, o) && l < r && (r = l, s = n);
2918
+ }), s && (s.startInflation(), this.player.pumpTarget = s);
2919
+ }
2920
+ /**
2921
+ * Calculate perpendicular distance from point to line segment
2922
+ */
2923
+ pointToLineDistance(t, i, e, s, r, n) {
2924
+ const o = r - e, a = n - s, l = o * o + a * a;
2925
+ if (l === 0)
2926
+ return Math.sqrt((t - e) ** 2 + (i - s) ** 2);
2927
+ let d = ((t - e) * o + (i - s) * a) / l;
2928
+ d = Math.max(0, Math.min(1, d));
2929
+ const c = e + d * o, f = s + d * a;
2930
+ return Math.sqrt((t - c) ** 2 + (i - f) ** 2);
2931
+ }
2932
+ /**
2933
+ * Check if enemy is in the direction the pump is pointing
2934
+ */
2935
+ isInPumpDirection(t, i, e) {
2936
+ const s = i.x - t.x, r = i.y - t.y, n = e.x - t.x, o = e.y - t.y;
2937
+ return s * n + r * o > 0;
2938
+ }
2939
+ /**
2940
+ * Check if Fygar fire hits the player
2941
+ */
2942
+ checkFireCollisions() {
2943
+ this.enemies.forEach((t) => {
2944
+ if (t.type === G.POOKA || !t.isFireActive())
2945
+ return;
2946
+ const i = t.getFireHitbox();
2947
+ if (!i) return;
2948
+ this.collisionSystem.checkAABB(
2949
+ i.x,
2950
+ i.y,
2951
+ i.width,
2952
+ i.height,
2953
+ this.player.x,
2954
+ this.player.y,
2955
+ 16,
2956
+ 16
2957
+ ) && this.playerHit("enemy");
2958
+ });
2959
+ }
2960
+ /**
2961
+ * Check if bonus item should spawn
2962
+ */
2963
+ checkBonusSpawn() {
2964
+ if (this.droppedRocksCount === 2 && this.bonusItems.length === 0) {
2965
+ const t = this.levelManager.spawnBonusItem(
2966
+ this.bonusSpawnCount
2967
+ );
2968
+ t && (this.bonusItems.push(t), this.bonusSpawnCount++);
2969
+ }
2970
+ }
2971
+ /**
2972
+ * Spawn a floating score display at the given position
2973
+ */
2974
+ spawnFloatingScore(t, i, e) {
2975
+ this.floatingScores.push({
2976
+ points: t,
2977
+ x: i,
2978
+ y: e,
2979
+ timer: 0,
2980
+ duration: 1e3
2981
+ // 1 second display
2982
+ });
2983
+ }
2984
+ /**
2985
+ * Update floating scores
2986
+ */
2987
+ updateFloatingScores(t) {
2988
+ this.floatingScores = this.floatingScores.filter((i) => (i.timer += t, i.timer < i.duration));
2989
+ }
2990
+ /**
2991
+ * Count dirt tiles in the grid
2992
+ */
2993
+ countDirtTiles() {
2994
+ return this.grid.countDirt();
2995
+ }
2996
+ /**
2997
+ * Handle player getting hit
2998
+ */
2999
+ playerHit(t = "enemy") {
3000
+ this.player.isInvincible || (this.player.startDeath(t), this.state = m.DYING, this.deathStartTime = Date.now());
3001
+ }
3002
+ /**
3003
+ * Update death animation state
3004
+ */
3005
+ updateDeath(t) {
3006
+ this.player.update(t, null, this.grid), this.player.isSmooshed && (this.rocks.forEach((i) => {
3007
+ i.update(t, this.grid, null);
3008
+ }), this.player.attachedToRock && (this.player.x = this.player.attachedToRock.x, this.player.y = this.player.attachedToRock.y)), this.player.deathTimer >= v.ANIMATION_DURATION && (this.scoreManager.loseLife(), this.scoreManager.lives <= 0 ? this.gameOver() : this.startRespawn());
3009
+ }
3010
+ /**
3011
+ * Start respawn sequence
3012
+ */
3013
+ startRespawn() {
3014
+ this.state = m.RESPAWNING, this.respawnStartTime = Date.now(), this.player = new O(this.grid), this.player.isInvincible = !0, this.enemies.forEach((t, i) => {
3015
+ const e = this.levelManager.getEnemySpawnPosition(i);
3016
+ t.x = e.x, t.y = e.y, t.resetTimers(), t.inTunnel = !0, t.state = "roaming", t.isLastEnemy = !1, t.isEscaping = !1, t.hasEscaped = !1;
3017
+ }), this.rocks.forEach((t) => {
3018
+ t.reset();
3019
+ });
3020
+ }
3021
+ /**
3022
+ * Update respawn state
3023
+ */
3024
+ updateRespawn(t) {
3025
+ Date.now() - this.respawnStartTime >= v.RESPAWN_DELAY && (this.state = m.PLAYING, this.resetAllTimers());
3026
+ }
3027
+ /**
3028
+ * Level complete
3029
+ */
3030
+ levelComplete() {
3031
+ this.state = m.LEVEL_COMPLETE;
3032
+ const t = this.levelManager.currentLevel + 1;
3033
+ this.config.onLevelComplete(t - 1), setTimeout(() => {
3034
+ this.startLevel(t), this.state = m.PLAYING;
3035
+ }, 2e3);
3036
+ }
3037
+ /**
3038
+ * Game over
3039
+ */
3040
+ gameOver() {
3041
+ this.state = m.GAME_OVER, this.config.onGameOver(this.scoreManager.score), this.scoreManager.score > this.scoreManager.highScore && (this.scoreManager.highScore = this.scoreManager.score, this.scoreManager.saveHighScore());
3042
+ }
3043
+ /**
3044
+ * Pause the game
3045
+ */
3046
+ pause() {
3047
+ this.state = m.PAUSED;
3048
+ }
3049
+ /**
3050
+ * Resume the game
3051
+ */
3052
+ resume() {
3053
+ this.state = m.PLAYING;
3054
+ }
3055
+ /**
3056
+ * Render the game
3057
+ */
3058
+ render() {
3059
+ this.renderer.clear(), this.renderer.drawGrid(this.grid, this.levelManager.currentLevel), this.rocks.forEach((t) => {
3060
+ this.renderer.drawRock(t);
3061
+ }), this.bonusItems.forEach((t) => {
3062
+ this.renderer.drawBonusItem(t, this.levelManager.currentLevel);
3063
+ }), this.player && this.renderer.drawPlayer(this.player), this.enemies.forEach((t) => {
3064
+ this.renderer.drawEnemy(t);
3065
+ }), this.renderer.drawFloatingScores(this.floatingScores), this.renderer.drawUI(this.scoreManager, this.levelManager), this.config.debug && this.renderer.drawDebugInfo(this.player, this.enemies);
3066
+ }
3067
+ /**
3068
+ * Render paused state
3069
+ */
3070
+ renderPaused() {
3071
+ if (this.inputManager.isKeyPressed("Escape")) {
3072
+ this.resume();
3073
+ return;
3074
+ }
3075
+ this.render(), this.renderer.drawText("PAUSED", 432 / 2, 288 / 2, {
3076
+ size: 16,
3077
+ color: S.TEXT_WHITE,
3078
+ align: "center"
3079
+ });
3080
+ }
3081
+ /**
3082
+ * Render respawning state
3083
+ */
3084
+ renderRespawning() {
3085
+ this.render(), this.renderer.renderRespawning();
3086
+ }
3087
+ /**
3088
+ * Render level complete
3089
+ */
3090
+ renderLevelComplete() {
3091
+ this.render(), this.renderer.drawText(
3092
+ `LEVEL ${this.levelManager.currentLevel} `,
3093
+ 432 / 2,
3094
+ 288 / 2 - 10,
3095
+ {
3096
+ size: 12,
3097
+ color: S.TEXT_WHITE,
3098
+ align: "center"
3099
+ }
3100
+ ), this.renderer.drawText(
3101
+ "COMPLETE",
3102
+ 432 / 2,
3103
+ 288 / 2 + 10,
3104
+ {
3105
+ size: 12,
3106
+ color: S.TEXT_WHITE,
3107
+ align: "center"
3108
+ }
3109
+ );
3110
+ }
3111
+ /**
3112
+ * Render game over
3113
+ */
3114
+ renderGameOver() {
3115
+ if (this.renderer.forceClear(), this.renderer.drawText(
3116
+ "GAME OVER",
3117
+ 432 / 2,
3118
+ 288 / 2 - 40,
3119
+ {
3120
+ size: 12,
3121
+ color: S.TEXT_RED,
3122
+ align: "center"
3123
+ }
3124
+ ), this.renderer.drawText("SCORE", 432 / 2, 288 / 2, {
3125
+ size: 8,
3126
+ color: S.TEXT_WHITE,
3127
+ align: "center"
3128
+ }), this.renderer.drawText(
3129
+ `${this.scoreManager.score}`,
3130
+ 432 / 2,
3131
+ 288 / 2 + 20,
3132
+ {
3133
+ size: 10,
3134
+ color: S.TEXT_RED,
3135
+ align: "center"
3136
+ }
3137
+ ), this.renderer.drawText(
3138
+ "PRESS SPACE",
3139
+ 432 / 2,
3140
+ 288 / 2 + 50,
3141
+ {
3142
+ size: 6,
3143
+ color: S.TEXT_WHITE,
3144
+ align: "center"
3145
+ }
3146
+ ), this.renderer.drawText(
3147
+ "TO RESTART",
3148
+ 432 / 2,
3149
+ 288 / 2 + 65,
3150
+ {
3151
+ size: 6,
3152
+ color: S.TEXT_WHITE,
3153
+ align: "center"
3154
+ }
3155
+ ), !this.restartListenerAdded) {
3156
+ this.restartListenerAdded = !0;
3157
+ const t = (i) => {
3158
+ i.code === "Space" && this.state === m.GAME_OVER && (document.removeEventListener("keydown", t), this.restartListenerAdded = !1, this.startGame());
3159
+ };
3160
+ document.addEventListener("keydown", t);
3161
+ }
3162
+ }
3163
+ /**
3164
+ * Start the game
3165
+ */
3166
+ start() {
3167
+ this.init();
3168
+ }
3169
+ /**
3170
+ * Stop the game
3171
+ */
3172
+ stop() {
3173
+ this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null), this.inputManager.destroy();
3174
+ }
3175
+ }
3176
+ if (typeof window < "u") {
3177
+ const L = document.getElementById("game");
3178
+ if (L) {
3179
+ const t = new tt({
3180
+ container: L,
3181
+ scale: window.devicePixelRatio,
3182
+ debug: !1,
3183
+ onGameOver: (i) => {
3184
+ },
3185
+ onLevelComplete: (i) => {
3186
+ },
3187
+ onScoreChange: (i) => {
3188
+ }
3189
+ });
3190
+ t.start(), window.digdugGame = t;
3191
+ }
3192
+ }
3193
+ export {
3194
+ tt as Game,
3195
+ tt as default
3196
+ };
3197
+ //# sourceMappingURL=digdug.es.js.map