vibedino 0.1.0

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1156 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ startGame: () => startGame
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/engine/GameLoop.ts
28
+ var GameLoop = class {
29
+ constructor(onUpdate, onRender, fps = 30) {
30
+ this.onUpdate = onUpdate;
31
+ this.onRender = onRender;
32
+ this.tickMs = Math.floor(1e3 / fps);
33
+ }
34
+ onUpdate;
35
+ onRender;
36
+ intervalId = null;
37
+ running = false;
38
+ tickMs;
39
+ start() {
40
+ if (this.running) return;
41
+ this.running = true;
42
+ this.intervalId = setInterval(() => {
43
+ if (!this.running) return;
44
+ this.onUpdate(1);
45
+ this.onRender();
46
+ }, this.tickMs);
47
+ }
48
+ stop() {
49
+ this.running = false;
50
+ if (this.intervalId) {
51
+ clearInterval(this.intervalId);
52
+ this.intervalId = null;
53
+ }
54
+ }
55
+ isRunning() {
56
+ return this.running;
57
+ }
58
+ };
59
+
60
+ // src/engine/Renderer.ts
61
+ var ESC = "\x1B";
62
+ var CSI = `${ESC}[`;
63
+ var Color = {
64
+ reset: `${CSI}0m`,
65
+ // Foreground
66
+ fg: (n) => `${CSI}38;5;${n}m`,
67
+ // Background
68
+ bg: (n) => `${CSI}48;5;${n}m`,
69
+ // RGB foreground
70
+ fgRgb: (r, g, b) => `${CSI}38;2;${r};${g};${b}m`,
71
+ // RGB background
72
+ bgRgb: (r, g, b) => `${CSI}48;2;${r};${g};${b}m`,
73
+ bold: `${CSI}1m`,
74
+ dim: `${CSI}2m`
75
+ };
76
+ var Renderer = class {
77
+ buffer = [];
78
+ width = 0;
79
+ height = 0;
80
+ defaultBg = "";
81
+ defaultFg = "";
82
+ constructor() {
83
+ this.width = process.stdout.columns || 80;
84
+ this.height = process.stdout.rows || 24;
85
+ this.clearBuffer();
86
+ process.stdout.write(`${CSI}?1049h${CSI}2J${CSI}?25l`);
87
+ process.stdout.on("resize", () => {
88
+ this.width = process.stdout.columns || 80;
89
+ this.height = process.stdout.rows || 24;
90
+ this.clearBuffer();
91
+ });
92
+ }
93
+ getWidth() {
94
+ return this.width;
95
+ }
96
+ getHeight() {
97
+ return this.height;
98
+ }
99
+ setDefaultColors(fg, bg) {
100
+ this.defaultFg = fg;
101
+ this.defaultBg = bg;
102
+ }
103
+ clearBuffer() {
104
+ this.buffer = [];
105
+ for (let y = 0; y < this.height; y++) {
106
+ this.buffer[y] = [];
107
+ for (let x = 0; x < this.width; x++) {
108
+ this.buffer[y][x] = { char: " ", fg: this.defaultFg, bg: this.defaultBg };
109
+ }
110
+ }
111
+ }
112
+ drawChar(x, y, ch, fg = "", bg = "") {
113
+ const rx = Math.round(x);
114
+ const ry = Math.round(y);
115
+ if (ry >= 0 && ry < this.height && rx >= 0 && rx < this.width) {
116
+ this.buffer[ry][rx] = { char: ch, fg: fg || this.defaultFg, bg: bg || this.defaultBg };
117
+ }
118
+ }
119
+ drawSprite(x, y, sprite, fg = "", bg = "") {
120
+ for (let row = 0; row < sprite.length; row++) {
121
+ const line = sprite[row];
122
+ for (let col = 0; col < line.length; col++) {
123
+ const ch = line[col];
124
+ if (ch !== " ") {
125
+ this.drawChar(x + col, y + row, ch, fg, bg);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ drawText(x, y, text, fg = "", bg = "") {
131
+ for (let i = 0; i < text.length; i++) {
132
+ this.drawChar(x + i, y, text[i], fg, bg);
133
+ }
134
+ }
135
+ // Fill a rectangular area with a background color
136
+ fillRect(x, y, w, h, bg) {
137
+ for (let row = 0; row < h; row++) {
138
+ for (let col = 0; col < w; col++) {
139
+ const rx = Math.round(x + col);
140
+ const ry = Math.round(y + row);
141
+ if (ry >= 0 && ry < this.height && rx >= 0 && rx < this.width) {
142
+ this.buffer[ry][rx].bg = bg;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ // Fill entire row with background color
148
+ fillRow(y, bg) {
149
+ if (y >= 0 && y < this.height) {
150
+ for (let x = 0; x < this.width; x++) {
151
+ this.buffer[y][x].bg = bg;
152
+ }
153
+ }
154
+ }
155
+ present() {
156
+ let out = "";
157
+ for (let y = 0; y < this.height; y++) {
158
+ out += `${CSI}${y + 1};1H`;
159
+ let lastFg = "";
160
+ let lastBg = "";
161
+ for (let x = 0; x < this.width; x++) {
162
+ const cell = this.buffer[y][x];
163
+ if (cell.fg !== lastFg || cell.bg !== lastBg) {
164
+ out += Color.reset;
165
+ if (cell.bg) out += cell.bg;
166
+ if (cell.fg) out += cell.fg;
167
+ lastFg = cell.fg;
168
+ lastBg = cell.bg;
169
+ }
170
+ out += cell.char;
171
+ }
172
+ out += Color.reset;
173
+ }
174
+ process.stdout.write(out);
175
+ }
176
+ destroy() {
177
+ process.stdout.write(`${Color.reset}${CSI}?25h${CSI}?1049l`);
178
+ }
179
+ };
180
+
181
+ // src/engine/Input.ts
182
+ var Input = class {
183
+ actionQueue = [];
184
+ ducking = false;
185
+ rl = null;
186
+ bind() {
187
+ if (process.stdin.isTTY) {
188
+ process.stdin.setRawMode(true);
189
+ }
190
+ process.stdin.resume();
191
+ process.stdin.setEncoding("utf8");
192
+ process.stdin.on("data", (key) => {
193
+ if (key === "") {
194
+ this.actionQueue.push(4 /* QUIT */);
195
+ return;
196
+ }
197
+ if (key === "\x1B[A" || key === " ") {
198
+ if (this.ducking) {
199
+ this.ducking = false;
200
+ this.actionQueue.push(2 /* DUCK_RELEASE */);
201
+ }
202
+ this.actionQueue.push(0 /* JUMP */);
203
+ return;
204
+ }
205
+ if (key === "\x1B[B") {
206
+ if (!this.ducking) {
207
+ this.ducking = true;
208
+ this.actionQueue.push(1 /* DUCK */);
209
+ }
210
+ return;
211
+ }
212
+ if (key === "\x1B" || key === "q" || key === "Q") {
213
+ this.actionQueue.push(4 /* QUIT */);
214
+ return;
215
+ }
216
+ if (key === "r" || key === "R") {
217
+ this.actionQueue.push(3 /* RESTART */);
218
+ return;
219
+ }
220
+ this.actionQueue.push(5 /* ANY_KEY */);
221
+ });
222
+ }
223
+ poll() {
224
+ const actions = [...this.actionQueue];
225
+ this.actionQueue = [];
226
+ return actions;
227
+ }
228
+ releaseDuck() {
229
+ this.ducking = false;
230
+ }
231
+ clear() {
232
+ this.actionQueue = [];
233
+ }
234
+ destroy() {
235
+ if (process.stdin.isTTY) {
236
+ process.stdin.setRawMode(false);
237
+ }
238
+ process.stdin.pause();
239
+ }
240
+ };
241
+
242
+ // src/scenes/SceneManager.ts
243
+ var SceneManager = class {
244
+ current = null;
245
+ set(scene) {
246
+ this.current = scene;
247
+ }
248
+ update(dt) {
249
+ this.current?.update(dt);
250
+ }
251
+ render(renderer) {
252
+ this.current?.render(renderer);
253
+ }
254
+ handleInput(actions) {
255
+ this.current?.handleInput(actions);
256
+ }
257
+ };
258
+
259
+ // src/assets/sprites.ts
260
+ var DINO_RUN_1 = [
261
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
262
+ " \u2588\u2588\u2591\u2588 \u2588\u2588",
263
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
264
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
265
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
266
+ " \u2588\u2580\u2580\u2584 "
267
+ ];
268
+ var DINO_RUN_2 = [
269
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
270
+ " \u2588\u2588\u2591\u2588 \u2588\u2588",
271
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
272
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
273
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
274
+ " \u2584\u2580 \u2588\u2580 "
275
+ ];
276
+ var DINO_JUMP = [
277
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
278
+ " \u2588\u2588\u2591\u2588 \u2588\u2588",
279
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
280
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
281
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
282
+ " \u2588\u2580 \u2588\u2580 "
283
+ ];
284
+ var DINO_DUCK_1 = [
285
+ " ",
286
+ " ",
287
+ " ",
288
+ " \u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2584\u2588\u2588",
289
+ " \u2588\u2588\u2588\u2588\u2588\u2591\u2588\u2588\u2588\u2588",
290
+ " \u2580\u2580 \u2580\u2580 "
291
+ ];
292
+ var DINO_DUCK_2 = [
293
+ " ",
294
+ " ",
295
+ " ",
296
+ " \u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2584\u2588\u2588",
297
+ " \u2588\u2588\u2588\u2588\u2588\u2591\u2588\u2588\u2588\u2588",
298
+ " \u2580\u2580\u2580\u2580 "
299
+ ];
300
+ var DINO_DEAD = [
301
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
302
+ " \u2588\u2588X\u2588X\u2588\u2588",
303
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
304
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
305
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
306
+ " \u2588\u2580 \u2588\u2580 "
307
+ ];
308
+ var CACTUS_SMALL = [
309
+ " \u2584\u2588\u2584 ",
310
+ "\u2580\u2588\u2588\u2588\u2580",
311
+ " \u2588 ",
312
+ " \u2588 "
313
+ ];
314
+ var CACTUS_LARGE = [
315
+ " \u2588 \u2584 ",
316
+ " \u2584\u2588\u2584\u2588\u2588 ",
317
+ "\u2580\u2588\u2588\u2588\u2588\u2580 ",
318
+ " \u2588\u2580 ",
319
+ " \u2588 "
320
+ ];
321
+ var CACTUS_GROUP = [
322
+ " \u2584\u2588\u2584 \u2584\u2588\u2584 ",
323
+ "\u2580\u2588\u2588\u2588\u2580\u2588\u2588\u2588\u2588\u2580",
324
+ " \u2588 \u2588 ",
325
+ " \u2588 \u2588 "
326
+ ];
327
+ var NODE_MODULES = [
328
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
329
+ "\u2551node_mods/\u2551",
330
+ "\u2551 \u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593 \u2551",
331
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
332
+ ];
333
+ var NODE_MODULES_SMALL = [
334
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
335
+ "\u2551n_mod/\u2551",
336
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
337
+ ];
338
+ var TODO_BIRD_1 = [
339
+ " \u2584\u2580\u2580\u2584//TODO",
340
+ "\u2580\u2584\u2584\u2580\u2580 "
341
+ ];
342
+ var RATE_LIMIT = [
343
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2557",
344
+ "\u2551 429 \u2551",
345
+ "\u2551LIMIT\u2551",
346
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u255D"
347
+ ];
348
+ var MERGE_CONFLICT = [
349
+ "\u25C4\u25C4\u25C4HEAD",
350
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
351
+ "\u25BA\u25BA\u25BAmain"
352
+ ];
353
+ var GROUND_CHARS = "\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581";
354
+ var CLOUD_1 = [
355
+ " \u2584\u2584\u2584\u2584 ",
356
+ "\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580 "
357
+ ];
358
+ var CLOUD_2 = [
359
+ " \u2584\u2584\u2584 ",
360
+ "\u2580\u2580\u2580\u2580\u2580 "
361
+ ];
362
+ var STAR_CHARS = ["\xB7", "\u2219", "\u2022", "\xB0"];
363
+ var GAME_OVER_TEXT = [
364
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
365
+ "\u2551 G A M E O V E R \u2551",
366
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
367
+ ];
368
+ var TITLE_DINO = [
369
+ " \u2584\u2584\u2588\u2588\u2588\u2588\u2584\u2584 ",
370
+ " \u2584\u2588\u2588\u2591\u2591\u2588\u2588\u2584\u2588\u2588\u2588\u2588 ",
371
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 ",
372
+ " \u2584\u2584 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580\u2580 ",
373
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580 ",
374
+ " \u2580\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580\u2580 ",
375
+ " \u2588\u2588\u2580\u2580\u2588\u2588 ",
376
+ " \u2588 \u2588 "
377
+ ];
378
+
379
+ // src/ui/Messages.ts
380
+ var TITLE_MESSAGES = {
381
+ claude: [
382
+ "Claude ran out of tokens!",
383
+ "Token limit reached. Time to play!",
384
+ "Rate limited by Claude. Quick, run!"
385
+ ],
386
+ copilot: [
387
+ "Copilot hit its limit!",
388
+ "Copilot needs a break. You play!",
389
+ "Rate limited. Copilot is resting."
390
+ ],
391
+ default: [
392
+ "No tokens? No problem!",
393
+ "Token drought detected!",
394
+ "AI is resting. Dino is not."
395
+ ]
396
+ };
397
+ var GAME_OVER_QUIPS = [
398
+ "Tokens still regenerating...",
399
+ "Your AI will be back soon.",
400
+ "That was fun! Press R to retry.",
401
+ "node_modules got you!",
402
+ "Even dinos get merge conflicts.",
403
+ "HTTP 429: Too Many Jumps"
404
+ ];
405
+ function getTitleMessage(source) {
406
+ const pool = TITLE_MESSAGES[source ?? "default"] ?? TITLE_MESSAGES.default;
407
+ return pool[Math.floor(Math.random() * pool.length)];
408
+ }
409
+ function getGameOverQuip() {
410
+ return GAME_OVER_QUIPS[Math.floor(Math.random() * GAME_OVER_QUIPS.length)];
411
+ }
412
+
413
+ // src/assets/theme.ts
414
+ var SKY = {
415
+ top: Color.bgRgb(25, 25, 50),
416
+ // dark blue-black
417
+ mid: Color.bgRgb(35, 35, 65),
418
+ // slightly lighter
419
+ bottom: Color.bgRgb(50, 45, 75)
420
+ // dusk purple
421
+ };
422
+ var GROUND_BG = Color.bgRgb(60, 50, 30);
423
+ var GROUND_DEEP = Color.bgRgb(45, 38, 22);
424
+ var DINO_FG = Color.fgRgb(80, 200, 80);
425
+ var DINO_EYE = Color.fgRgb(255, 255, 255);
426
+ var CACTUS_FG = Color.fgRgb(50, 160, 50);
427
+ var OBSTACLE_FG = Color.fgRgb(200, 160, 60);
428
+ var BIRD_FG = Color.fgRgb(180, 100, 200);
429
+ var CLOUD_FG = Color.fgRgb(120, 120, 160);
430
+ var GROUND_FG = Color.fgRgb(140, 110, 60);
431
+ var SCORE_FG = Color.fgRgb(255, 255, 200);
432
+ var TITLE_FG = Color.fgRgb(100, 220, 100);
433
+ var GAMEOVER_FG = Color.fgRgb(255, 80, 80);
434
+ var PROMPT_FG = Color.fgRgb(255, 255, 100);
435
+ var HI_SCORE_FG = Color.fgRgb(255, 200, 50);
436
+ var QUIP_FG = Color.fgRgb(150, 150, 170);
437
+ function getObstacleColor(type) {
438
+ switch (type) {
439
+ case "cactus_small":
440
+ case "cactus_large":
441
+ case "cactus_group":
442
+ return CACTUS_FG;
443
+ case "todo_bird":
444
+ return BIRD_FG;
445
+ case "node_modules":
446
+ case "node_modules_small":
447
+ case "rate_limit":
448
+ case "merge_conflict":
449
+ return OBSTACLE_FG;
450
+ default:
451
+ return "";
452
+ }
453
+ }
454
+ function getSkyBg(y, height) {
455
+ const t = y / height;
456
+ const r = Math.round(25 + t * 35);
457
+ const g = Math.round(25 + t * 25);
458
+ const b = Math.round(50 + t * 30);
459
+ return Color.bgRgb(r, g, b);
460
+ }
461
+
462
+ // src/scenes/TitleScene.ts
463
+ var TitleScene = class {
464
+ message;
465
+ progressTick = 0;
466
+ progressTotal = 75;
467
+ ready = false;
468
+ onStart;
469
+ blinkTick = 0;
470
+ constructor(source, onStart) {
471
+ this.message = getTitleMessage(source);
472
+ this.onStart = onStart;
473
+ }
474
+ update(_dt) {
475
+ this.blinkTick++;
476
+ if (!this.ready) {
477
+ this.progressTick++;
478
+ if (this.progressTick >= this.progressTotal) {
479
+ this.ready = true;
480
+ }
481
+ }
482
+ }
483
+ render(renderer) {
484
+ renderer.clearBuffer();
485
+ const w = renderer.getWidth();
486
+ const h = renderer.getHeight();
487
+ for (let y = 0; y < h; y++) {
488
+ renderer.fillRow(y, getSkyBg(y, h));
489
+ }
490
+ const titleY = Math.floor(h * 0.12);
491
+ const titleStr = "V I B E D I N O";
492
+ renderer.drawText(Math.floor(w / 2) - Math.floor(titleStr.length / 2), titleY, titleStr, TITLE_FG);
493
+ const sub = "Token Exhaustion Entertainment";
494
+ renderer.drawText(Math.floor(w / 2) - Math.floor(sub.length / 2), titleY + 2, sub, QUIP_FG);
495
+ const dinoY = titleY + 5;
496
+ const dinoX = Math.floor(w / 2) - Math.floor(TITLE_DINO[0].length / 2);
497
+ renderer.drawSprite(dinoX, dinoY, TITLE_DINO, DINO_FG);
498
+ const groundY = dinoY + TITLE_DINO.length;
499
+ renderer.fillRow(groundY, GROUND_BG);
500
+ renderer.drawText(0, groundY, GROUND_CHARS.slice(0, w), GROUND_FG, GROUND_BG);
501
+ const msgY = groundY + 2;
502
+ renderer.drawText(Math.floor(w / 2) - Math.floor(this.message.length / 2), msgY, this.message, SCORE_FG);
503
+ const barY = msgY + 2;
504
+ if (!this.ready) {
505
+ const barWidth = 32;
506
+ const filled = Math.floor(this.progressTick / this.progressTotal * barWidth);
507
+ const barFull = "\u2588".repeat(filled);
508
+ const barEmpty = "\u2591".repeat(barWidth - filled);
509
+ const pct = Math.floor(this.progressTick / this.progressTotal * 100);
510
+ renderer.drawText(Math.floor(w / 2) - Math.floor(barWidth / 2) - 1, barY, "\u2551", QUIP_FG);
511
+ renderer.drawText(Math.floor(w / 2) - Math.floor(barWidth / 2), barY, barFull, Color.fgRgb(80, 220, 80));
512
+ renderer.drawText(Math.floor(w / 2) - Math.floor(barWidth / 2) + filled, barY, barEmpty, QUIP_FG);
513
+ renderer.drawText(Math.floor(w / 2) + Math.floor(barWidth / 2), barY, "\u2551", QUIP_FG);
514
+ const label = `Regenerating tokens... ${pct}%`;
515
+ renderer.drawText(Math.floor(w / 2) - Math.floor(label.length / 2), barY + 1, label, QUIP_FG);
516
+ } else {
517
+ if (Math.floor(this.blinkTick / 18) % 2 === 0) {
518
+ const prompt = ">>> Press SPACE to start <<<";
519
+ renderer.drawText(Math.floor(w / 2) - Math.floor(prompt.length / 2), barY, prompt, PROMPT_FG);
520
+ }
521
+ }
522
+ const footer = "SPACE/UP: Jump | DOWN: Duck | Q: Quit";
523
+ renderer.drawText(Math.floor(w / 2) - Math.floor(footer.length / 2), h - 2, footer, QUIP_FG);
524
+ renderer.present();
525
+ }
526
+ handleInput(actions) {
527
+ if (!this.ready) return;
528
+ for (const action of actions) {
529
+ if (action === 4 /* QUIT */) {
530
+ process.exit(0);
531
+ }
532
+ if (action === 0 /* JUMP */ || action === 5 /* ANY_KEY */) {
533
+ this.onStart();
534
+ return;
535
+ }
536
+ }
537
+ }
538
+ };
539
+
540
+ // src/entities/Entity.ts
541
+ var Entity = class {
542
+ x;
543
+ y;
544
+ vx = 0;
545
+ vy = 0;
546
+ grounded = false;
547
+ active = true;
548
+ sprite = [];
549
+ hitboxDef = { offsetX: 0, offsetY: 0, width: 0, height: 0 };
550
+ constructor(x, y) {
551
+ this.x = x;
552
+ this.y = y;
553
+ }
554
+ getHitbox() {
555
+ return {
556
+ x: this.x + this.hitboxDef.offsetX,
557
+ y: this.y + this.hitboxDef.offsetY,
558
+ width: this.hitboxDef.width,
559
+ height: this.hitboxDef.height
560
+ };
561
+ }
562
+ update(_dt) {
563
+ }
564
+ };
565
+
566
+ // src/entities/Dino.ts
567
+ var Dino = class extends Entity {
568
+ state = 0 /* IDLE */;
569
+ animFrame = 0;
570
+ animTick = 0;
571
+ animSpeed = 4;
572
+ // ticks per frame swap
573
+ constructor(x, groundY) {
574
+ super(x, groundY);
575
+ this.grounded = true;
576
+ this.updateSprite();
577
+ this.updateHitbox();
578
+ }
579
+ setState(state) {
580
+ if (this.state === state) return;
581
+ this.state = state;
582
+ this.animFrame = 0;
583
+ this.animTick = 0;
584
+ this.updateSprite();
585
+ this.updateHitbox();
586
+ }
587
+ update(_dt) {
588
+ this.animTick++;
589
+ if (this.animTick >= this.animSpeed) {
590
+ this.animTick = 0;
591
+ this.animFrame = (this.animFrame + 1) % 2;
592
+ this.updateSprite();
593
+ }
594
+ if (this.state === 2 /* JUMPING */ && this.grounded) {
595
+ this.setState(1 /* RUNNING */);
596
+ }
597
+ }
598
+ updateSprite() {
599
+ switch (this.state) {
600
+ case 0 /* IDLE */:
601
+ case 1 /* RUNNING */:
602
+ this.sprite = this.animFrame === 0 ? DINO_RUN_1 : DINO_RUN_2;
603
+ break;
604
+ case 2 /* JUMPING */:
605
+ this.sprite = DINO_JUMP;
606
+ break;
607
+ case 3 /* DUCKING */:
608
+ this.sprite = this.animFrame === 0 ? DINO_DUCK_1 : DINO_DUCK_2;
609
+ break;
610
+ case 4 /* DEAD */:
611
+ this.sprite = DINO_DEAD;
612
+ break;
613
+ }
614
+ }
615
+ updateHitbox() {
616
+ if (this.state === 3 /* DUCKING */) {
617
+ this.hitboxDef = { offsetX: 2, offsetY: 3, width: 9, height: 3 };
618
+ } else {
619
+ this.hitboxDef = { offsetX: 3, offsetY: 0, width: 8, height: 5 };
620
+ }
621
+ }
622
+ };
623
+
624
+ // src/entities/Ground.ts
625
+ var Ground = class {
626
+ offset = 0;
627
+ pattern;
628
+ constructor() {
629
+ this.pattern = GROUND_CHARS + GROUND_CHARS;
630
+ }
631
+ update(speed) {
632
+ this.offset = (this.offset + speed) % GROUND_CHARS.length;
633
+ }
634
+ getOffset() {
635
+ return this.offset;
636
+ }
637
+ getLine(width) {
638
+ const start = Math.floor(this.offset);
639
+ return this.pattern.slice(start, start + width);
640
+ }
641
+ };
642
+
643
+ // src/entities/Cloud.ts
644
+ var Cloud = class extends Entity {
645
+ constructor(x, y) {
646
+ super(x, y);
647
+ this.sprite = Math.random() > 0.5 ? CLOUD_1 : CLOUD_2;
648
+ this.vx = -(0.2 + Math.random() * 0.3);
649
+ }
650
+ update(_dt) {
651
+ this.x += this.vx;
652
+ }
653
+ };
654
+
655
+ // src/entities/obstacles/Obstacle.ts
656
+ var Obstacle = class extends Entity {
657
+ type;
658
+ isFlying;
659
+ constructor(x, y, type, sprite, isFlying = false) {
660
+ super(x, y);
661
+ this.type = type;
662
+ this.sprite = sprite;
663
+ this.isFlying = isFlying;
664
+ this.grounded = !isFlying;
665
+ const spriteWidth = Math.max(...sprite.map((l) => l.length));
666
+ const spriteHeight = sprite.length;
667
+ this.hitboxDef = {
668
+ offsetX: 1,
669
+ offsetY: 1,
670
+ width: Math.max(1, spriteWidth - 2),
671
+ height: Math.max(1, spriteHeight - 1)
672
+ };
673
+ }
674
+ update(_dt) {
675
+ this.x += this.vx;
676
+ }
677
+ };
678
+
679
+ // src/systems/SpawnSystem.ts
680
+ var OBSTACLE_DEFS = [
681
+ { type: "cactus_small", sprite: CACTUS_SMALL, isFlying: false, minDifficulty: 0 },
682
+ { type: "cactus_large", sprite: CACTUS_LARGE, isFlying: false, minDifficulty: 0.05 },
683
+ { type: "cactus_group", sprite: CACTUS_GROUP, isFlying: false, minDifficulty: 0.1 },
684
+ { type: "node_modules_small", sprite: NODE_MODULES_SMALL, isFlying: false, minDifficulty: 0.15 },
685
+ { type: "node_modules", sprite: NODE_MODULES, isFlying: false, minDifficulty: 0.25 },
686
+ { type: "merge_conflict", sprite: MERGE_CONFLICT, isFlying: false, minDifficulty: 0.3 },
687
+ { type: "rate_limit", sprite: RATE_LIMIT, isFlying: false, minDifficulty: 0.35 },
688
+ { type: "todo_bird", sprite: TODO_BIRD_1, isFlying: true, minDifficulty: 0.4 }
689
+ ];
690
+ var SpawnSystem = class {
691
+ ticksSinceSpawn = 0;
692
+ minGapTicks = 45;
693
+ maxGapTicks = 90;
694
+ nextSpawnAt;
695
+ constructor() {
696
+ this.nextSpawnAt = this.randomGap();
697
+ }
698
+ update(obstacles, screenWidth, groundY, speed, difficulty) {
699
+ this.ticksSinceSpawn++;
700
+ this.minGapTicks = Math.max(25, 45 - difficulty * 20);
701
+ this.maxGapTicks = Math.max(50, 90 - difficulty * 30);
702
+ if (this.ticksSinceSpawn >= this.nextSpawnAt) {
703
+ const available = OBSTACLE_DEFS.filter((d) => d.minDifficulty <= difficulty);
704
+ const def = available[Math.floor(Math.random() * available.length)];
705
+ const spriteH = def.sprite.length;
706
+ const yPos = def.isFlying ? groundY - spriteH - 3 - Math.floor(Math.random() * 3) : groundY - spriteH;
707
+ const obs = new Obstacle(screenWidth + 2, yPos, def.type, def.sprite, def.isFlying);
708
+ obs.vx = speed;
709
+ obstacles.push(obs);
710
+ this.ticksSinceSpawn = 0;
711
+ this.nextSpawnAt = this.randomGap();
712
+ }
713
+ }
714
+ randomGap() {
715
+ return this.minGapTicks + Math.floor(Math.random() * (this.maxGapTicks - this.minGapTicks));
716
+ }
717
+ reset() {
718
+ this.ticksSinceSpawn = 0;
719
+ this.nextSpawnAt = this.randomGap();
720
+ }
721
+ };
722
+
723
+ // src/systems/DifficultySystem.ts
724
+ var DifficultySystem = class {
725
+ ticks = 0;
726
+ maxSpeedMultiplier = 2.5;
727
+ rampDurationTicks = 30 * 120;
728
+ // ~120 seconds at 30fps to reach max
729
+ /** Current difficulty 0..1 */
730
+ difficulty = 0;
731
+ /** Current game scroll speed (negative = moving left) */
732
+ speed = -1.2;
733
+ baseSpeed = -1.2;
734
+ update() {
735
+ this.ticks++;
736
+ this.difficulty = Math.min(1, this.ticks / this.rampDurationTicks);
737
+ this.speed = this.baseSpeed * (1 + this.difficulty * (this.maxSpeedMultiplier - 1));
738
+ }
739
+ reset() {
740
+ this.ticks = 0;
741
+ this.difficulty = 0;
742
+ this.speed = this.baseSpeed;
743
+ }
744
+ };
745
+
746
+ // src/systems/ScoreSystem.ts
747
+ var ScoreSystem = class {
748
+ score = 0;
749
+ tickAccumulator = 0;
750
+ pointsPerSecond = 10;
751
+ ticksPerPoint;
752
+ milestone = false;
753
+ // true on the tick a milestone is hit
754
+ constructor(fps = 30) {
755
+ this.ticksPerPoint = Math.floor(fps / this.pointsPerSecond);
756
+ }
757
+ update() {
758
+ this.milestone = false;
759
+ this.tickAccumulator++;
760
+ if (this.tickAccumulator >= this.ticksPerPoint) {
761
+ this.tickAccumulator = 0;
762
+ this.score++;
763
+ if (this.score > 0 && this.score % 100 === 0) {
764
+ this.milestone = true;
765
+ }
766
+ }
767
+ }
768
+ getDisplay() {
769
+ return String(this.score).padStart(5, "0");
770
+ }
771
+ reset() {
772
+ this.score = 0;
773
+ this.tickAccumulator = 0;
774
+ this.milestone = false;
775
+ }
776
+ };
777
+
778
+ // src/ui/HUD.ts
779
+ var HUD = class {
780
+ render(renderer, score, highScore) {
781
+ const w = renderer.getWidth();
782
+ renderer.drawText(w - 7, 1, score, SCORE_FG);
783
+ renderer.drawText(w - 16, 1, "HI", HI_SCORE_FG);
784
+ renderer.drawText(w - 13, 1, highScore, HI_SCORE_FG);
785
+ }
786
+ };
787
+
788
+ // src/engine/Physics.ts
789
+ var GRAVITY = 0.35;
790
+ var JUMP_VELOCITY = -3.5;
791
+ var MAX_FALL_SPEED = 5;
792
+ function applyGravity(entity, groundY) {
793
+ if (entity.grounded) return;
794
+ entity.vy += GRAVITY;
795
+ if (entity.vy > MAX_FALL_SPEED) {
796
+ entity.vy = MAX_FALL_SPEED;
797
+ }
798
+ entity.y += entity.vy;
799
+ if (entity.y >= groundY) {
800
+ entity.y = groundY;
801
+ entity.vy = 0;
802
+ entity.grounded = true;
803
+ }
804
+ }
805
+ function jump(entity) {
806
+ if (!entity.grounded) return;
807
+ entity.vy = JUMP_VELOCITY;
808
+ entity.grounded = false;
809
+ }
810
+
811
+ // src/engine/Collision.ts
812
+ function checkCollision(a, b) {
813
+ return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
814
+ }
815
+
816
+ // src/scenes/PlayScene.ts
817
+ var PlayScene = class {
818
+ dino;
819
+ ground;
820
+ obstacles = [];
821
+ clouds = [];
822
+ spawnSystem = new SpawnSystem();
823
+ difficulty = new DifficultySystem();
824
+ scoreSystem = new ScoreSystem();
825
+ hud = new HUD();
826
+ groundY;
827
+ screenWidth;
828
+ screenHeight;
829
+ dead = false;
830
+ highScoreDisplay;
831
+ onGameOver;
832
+ cloudSpawnTick = 0;
833
+ stars = [];
834
+ constructor(screenWidth, screenHeight, highScore, onGameOver) {
835
+ this.screenWidth = screenWidth;
836
+ this.screenHeight = screenHeight;
837
+ this.groundY = screenHeight - 3;
838
+ this.dino = new Dino(6, this.groundY - 6);
839
+ this.dino.setState(1 /* RUNNING */);
840
+ this.ground = new Ground();
841
+ this.highScoreDisplay = String(highScore).padStart(5, "0");
842
+ this.onGameOver = onGameOver;
843
+ for (let i = 0; i < 25; i++) {
844
+ this.stars.push({
845
+ x: Math.floor(Math.random() * screenWidth),
846
+ y: Math.floor(Math.random() * (this.groundY - 2)),
847
+ char: STAR_CHARS[Math.floor(Math.random() * STAR_CHARS.length)],
848
+ bright: 40 + Math.floor(Math.random() * 80)
849
+ });
850
+ }
851
+ }
852
+ update(dt) {
853
+ if (this.dead) return;
854
+ this.difficulty.update();
855
+ this.scoreSystem.update();
856
+ applyGravity(this.dino, this.groundY - 6);
857
+ this.dino.update(dt);
858
+ this.ground.update(Math.abs(this.difficulty.speed));
859
+ this.spawnSystem.update(
860
+ this.obstacles,
861
+ this.screenWidth,
862
+ this.groundY,
863
+ this.difficulty.speed,
864
+ this.difficulty.difficulty
865
+ );
866
+ for (const obs of this.obstacles) {
867
+ obs.vx = this.difficulty.speed;
868
+ obs.update(dt);
869
+ }
870
+ this.obstacles = this.obstacles.filter((o) => o.x + 20 > 0);
871
+ this.cloudSpawnTick++;
872
+ if (this.cloudSpawnTick > 60 + Math.random() * 120) {
873
+ this.cloudSpawnTick = 0;
874
+ if (this.clouds.length < 5) {
875
+ this.clouds.push(new Cloud(this.screenWidth + 5, 1 + Math.floor(Math.random() * 5)));
876
+ }
877
+ }
878
+ for (const cloud of this.clouds) cloud.update(dt);
879
+ this.clouds = this.clouds.filter((c) => c.x + 12 > 0);
880
+ const dinoBox = this.dino.getHitbox();
881
+ for (const obs of this.obstacles) {
882
+ if (checkCollision(dinoBox, obs.getHitbox())) {
883
+ this.dead = true;
884
+ this.dino.setState(4 /* DEAD */);
885
+ this.onGameOver(this.scoreSystem.score);
886
+ return;
887
+ }
888
+ }
889
+ }
890
+ render(renderer) {
891
+ renderer.clearBuffer();
892
+ for (let y = 0; y < this.groundY; y++) {
893
+ renderer.fillRow(y, getSkyBg(y, this.groundY));
894
+ }
895
+ for (const star of this.stars) {
896
+ renderer.drawChar(
897
+ star.x,
898
+ star.y,
899
+ star.char,
900
+ Color.fgRgb(star.bright, star.bright, star.bright + 30)
901
+ );
902
+ }
903
+ for (let y = this.groundY; y < this.screenHeight; y++) {
904
+ renderer.fillRow(y, y === this.groundY ? GROUND_BG : GROUND_DEEP);
905
+ }
906
+ for (let y = this.groundY + 1; y < this.screenHeight; y++) {
907
+ const offset = Math.floor(this.ground.getOffset() * 0.5) % this.screenWidth;
908
+ for (let x = 0; x < this.screenWidth; x++) {
909
+ if ((x + offset + y * 7) % 13 === 0) {
910
+ renderer.drawChar(x, y, "\xB7", Color.fgRgb(80, 65, 40), GROUND_DEEP);
911
+ }
912
+ if ((x + offset + y * 11) % 19 === 0) {
913
+ renderer.drawChar(x, y, "\u2219", Color.fgRgb(70, 55, 35), GROUND_DEEP);
914
+ }
915
+ }
916
+ }
917
+ for (const cloud of this.clouds) {
918
+ renderer.drawSprite(Math.round(cloud.x), Math.round(cloud.y), cloud.sprite, CLOUD_FG);
919
+ }
920
+ const groundLine = this.ground.getLine(this.screenWidth);
921
+ renderer.drawText(0, this.groundY, groundLine, GROUND_FG, GROUND_BG);
922
+ for (const obs of this.obstacles) {
923
+ const color = getObstacleColor(obs.type);
924
+ renderer.drawSprite(Math.round(obs.x), Math.round(obs.y), obs.sprite, color);
925
+ }
926
+ const dinoColor = this.dead ? GAMEOVER_FG : DINO_FG;
927
+ renderer.drawSprite(
928
+ Math.round(this.dino.x),
929
+ Math.round(this.dino.y),
930
+ this.dino.sprite,
931
+ dinoColor
932
+ );
933
+ this.hud.render(renderer, this.scoreSystem.getDisplay(), this.highScoreDisplay);
934
+ if (this.scoreSystem.milestone) {
935
+ const msg = `+${this.scoreSystem.score}!`;
936
+ renderer.drawText(
937
+ Math.floor(this.screenWidth / 2) - Math.floor(msg.length / 2),
938
+ 2,
939
+ msg,
940
+ GAMEOVER_FG
941
+ );
942
+ }
943
+ renderer.present();
944
+ }
945
+ handleInput(actions) {
946
+ if (this.dead) return;
947
+ for (const action of actions) {
948
+ switch (action) {
949
+ case 0 /* JUMP */:
950
+ if (this.dino.state === 3 /* DUCKING */) {
951
+ this.dino.setState(1 /* RUNNING */);
952
+ }
953
+ jump(this.dino);
954
+ if (!this.dino.grounded) {
955
+ this.dino.setState(2 /* JUMPING */);
956
+ }
957
+ break;
958
+ case 1 /* DUCK */:
959
+ if (this.dino.grounded) {
960
+ this.dino.setState(3 /* DUCKING */);
961
+ }
962
+ break;
963
+ case 2 /* DUCK_RELEASE */:
964
+ if (this.dino.state === 3 /* DUCKING */) {
965
+ this.dino.setState(1 /* RUNNING */);
966
+ }
967
+ break;
968
+ case 4 /* QUIT */:
969
+ this.dead = true;
970
+ this.onGameOver(this.scoreSystem.score);
971
+ break;
972
+ }
973
+ }
974
+ }
975
+ };
976
+
977
+ // src/scenes/GameOverScene.ts
978
+ var GameOverScene = class {
979
+ score;
980
+ highScore;
981
+ isNewHighScore;
982
+ quip;
983
+ onRestart;
984
+ onQuit;
985
+ blinkTick = 0;
986
+ constructor(score, highScore, isNewHighScore, onRestart, onQuit) {
987
+ this.score = score;
988
+ this.highScore = highScore;
989
+ this.isNewHighScore = isNewHighScore;
990
+ this.quip = getGameOverQuip();
991
+ this.onRestart = onRestart;
992
+ this.onQuit = onQuit;
993
+ }
994
+ update(_dt) {
995
+ this.blinkTick++;
996
+ }
997
+ render(renderer) {
998
+ renderer.clearBuffer();
999
+ const w = renderer.getWidth();
1000
+ const h = renderer.getHeight();
1001
+ for (let y = 0; y < h; y++) {
1002
+ renderer.fillRow(y, Color.bgRgb(20, 15, 30));
1003
+ }
1004
+ const centerY = Math.floor(h / 2) - 6;
1005
+ const bannerX = Math.floor(w / 2) - Math.floor(GAME_OVER_TEXT[0].length / 2);
1006
+ renderer.drawSprite(bannerX, centerY, GAME_OVER_TEXT, GAMEOVER_FG);
1007
+ const dinoX = Math.floor(w / 2) - Math.floor(DINO_DEAD[0].length / 2);
1008
+ renderer.drawSprite(dinoX, centerY + 4, DINO_DEAD, Color.fgRgb(100, 100, 100));
1009
+ const scoreY = centerY + 4 + DINO_DEAD.length + 1;
1010
+ const scoreText = `Score ${String(this.score).padStart(5, "0")}`;
1011
+ renderer.drawText(Math.floor(w / 2) - Math.floor(scoreText.length / 2), scoreY, scoreText, SCORE_FG);
1012
+ const hiY = scoreY + 2;
1013
+ if (this.isNewHighScore) {
1014
+ const hiText = "* * * NEW HIGH SCORE! * * *";
1015
+ if (Math.floor(this.blinkTick / 8) % 2 === 0) {
1016
+ renderer.drawText(Math.floor(w / 2) - Math.floor(hiText.length / 2), hiY, hiText, HI_SCORE_FG);
1017
+ }
1018
+ } else {
1019
+ const hiText = `Best ${String(this.highScore).padStart(5, "0")}`;
1020
+ renderer.drawText(Math.floor(w / 2) - Math.floor(hiText.length / 2), hiY, hiText, HI_SCORE_FG);
1021
+ }
1022
+ renderer.drawText(Math.floor(w / 2) - Math.floor(this.quip.length / 2), hiY + 2, this.quip, QUIP_FG);
1023
+ if (Math.floor(this.blinkTick / 18) % 2 === 0) {
1024
+ const prompt = "[R] Restart [Q] Quit";
1025
+ renderer.drawText(Math.floor(w / 2) - Math.floor(prompt.length / 2), hiY + 4, prompt, PROMPT_FG);
1026
+ }
1027
+ renderer.present();
1028
+ }
1029
+ handleInput(actions) {
1030
+ for (const action of actions) {
1031
+ if (action === 3 /* RESTART */) {
1032
+ this.onRestart();
1033
+ return;
1034
+ }
1035
+ if (action === 4 /* QUIT */) {
1036
+ this.onQuit();
1037
+ return;
1038
+ }
1039
+ }
1040
+ }
1041
+ };
1042
+
1043
+ // src/storage/ScoreStore.ts
1044
+ var import_fs = require("fs");
1045
+ var import_path = require("path");
1046
+ var import_os = require("os");
1047
+ var STORE_DIR = (0, import_path.join)((0, import_os.homedir)(), ".vibedino");
1048
+ var SCORES_FILE = (0, import_path.join)(STORE_DIR, "scores.json");
1049
+ function ensureDir() {
1050
+ if (!(0, import_fs.existsSync)(STORE_DIR)) {
1051
+ (0, import_fs.mkdirSync)(STORE_DIR, { recursive: true });
1052
+ }
1053
+ }
1054
+ function load() {
1055
+ try {
1056
+ const raw = (0, import_fs.readFileSync)(SCORES_FILE, "utf-8");
1057
+ return JSON.parse(raw);
1058
+ } catch {
1059
+ return { highScore: 0, scores: [] };
1060
+ }
1061
+ }
1062
+ function save(data) {
1063
+ ensureDir();
1064
+ (0, import_fs.writeFileSync)(SCORES_FILE, JSON.stringify(data, null, 2), "utf-8");
1065
+ }
1066
+ function getHighScore() {
1067
+ return load().highScore;
1068
+ }
1069
+ function saveScore(score) {
1070
+ const data = load();
1071
+ const isNew = score > data.highScore;
1072
+ if (isNew) {
1073
+ data.highScore = score;
1074
+ }
1075
+ data.scores.push({ score, date: (/* @__PURE__ */ new Date()).toISOString() });
1076
+ data.scores.sort((a, b) => b.score - a.score);
1077
+ data.scores = data.scores.slice(0, 10);
1078
+ save(data);
1079
+ return { highScore: data.highScore, isNew };
1080
+ }
1081
+
1082
+ // src/index.ts
1083
+ function startGame(options = {}) {
1084
+ return new Promise((resolve) => {
1085
+ const startTime = Date.now();
1086
+ const trigger = {
1087
+ source: options.source ?? "cli",
1088
+ reason: options.reason,
1089
+ metadata: options.metadata
1090
+ };
1091
+ const renderer = new Renderer();
1092
+ const input = new Input();
1093
+ const sceneManager = new SceneManager();
1094
+ input.bind();
1095
+ let finalResult = null;
1096
+ const cleanup = () => {
1097
+ loop.stop();
1098
+ input.destroy();
1099
+ renderer.destroy();
1100
+ if (finalResult) resolve(finalResult);
1101
+ };
1102
+ const startPlay = () => {
1103
+ const highScore = getHighScore();
1104
+ const scene = new PlayScene(
1105
+ renderer.getWidth(),
1106
+ renderer.getHeight(),
1107
+ highScore,
1108
+ (score) => {
1109
+ const { highScore: newHigh, isNew } = saveScore(score);
1110
+ finalResult = {
1111
+ score,
1112
+ highScore: newHigh,
1113
+ isNewHighScore: isNew,
1114
+ duration: Date.now() - startTime,
1115
+ trigger
1116
+ };
1117
+ sceneManager.set(new GameOverScene(
1118
+ score,
1119
+ newHigh,
1120
+ isNew,
1121
+ () => {
1122
+ input.clear();
1123
+ startPlay();
1124
+ },
1125
+ () => {
1126
+ cleanup();
1127
+ }
1128
+ ));
1129
+ }
1130
+ );
1131
+ sceneManager.set(scene);
1132
+ };
1133
+ const titleScene = new TitleScene(trigger.source, () => {
1134
+ input.clear();
1135
+ startPlay();
1136
+ });
1137
+ sceneManager.set(titleScene);
1138
+ const loop = new GameLoop(
1139
+ (dt) => {
1140
+ const actions = input.poll();
1141
+ sceneManager.handleInput(actions);
1142
+ sceneManager.update(dt);
1143
+ },
1144
+ () => {
1145
+ sceneManager.render(renderer);
1146
+ }
1147
+ );
1148
+ loop.start();
1149
+ process.on("SIGINT", cleanup);
1150
+ process.on("SIGTERM", cleanup);
1151
+ });
1152
+ }
1153
+ // Annotate the CommonJS export names for ESM import in node:
1154
+ 0 && (module.exports = {
1155
+ startGame
1156
+ });