@readme/cli 0.1.0 → 0.3.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/bin/git-format.js +80 -0
- package/bin/readme.js +1 -1
- package/package.json +54 -26
- package/src/bootstrap.js +9 -13
- package/src/cli.js +17 -11
- package/src/commands/components.js +35 -18
- package/src/commands/dev.js +12 -8
- package/src/commands/eyes.js +12 -8
- package/src/commands/import.js +2200 -2049
- package/src/commands/lint.js +33 -29
- package/src/commands/oas-bundle.js +6 -2
- package/src/commands/oas-sync.js +271 -161
- package/src/commands/oas-validate.js +10 -7
- package/src/commands/pretty.js +188 -165
- package/src/commands/setup.js +24 -26
- package/src/commands/versions.js +6 -2
- package/src/dev/app/[...slug]/page.js +1 -0
- package/src/dev/app/layout.js +18 -11
- package/src/dev/app/page.js +1 -0
- package/src/dev/lib/docs.js +27 -19
- package/src/dev/next.config.mjs +2 -2
- package/src/gha.js +7 -7
- package/src/prompts/index.js +16 -6
- package/src/utils/containment.js +87 -0
- package/src/utils/eyes.js +143 -51
- package/src/utils/git.js +3 -3
- package/src/utils/lint.js +39 -38
- package/src/utils/llms.js +94 -91
- package/src/utils/markdown.js +256 -0
- package/src/utils/marketplace.js +12 -5
- package/src/utils/multi-spinner.js +5 -7
- package/src/utils/promise-pool.js +7 -5
- package/src/utils/reporter.js +88 -94
- package/src/utils/setup-templates.js +12 -6
- package/src/utils/styles.js +2 -1
- package/src/utils/tamagotchi.js +257 -91
- package/src/utils/tips.js +18 -9
- package/src/utils/url-segs.js +46 -41
- package/src/validators/components.js +55 -63
- package/src/validators/content.js +16 -14
- package/src/validators/duplicates.js +6 -2
- package/src/validators/frontmatter.js +21 -29
- package/src/validators/links.js +4 -8
- package/src/validators/oas-reference.js +25 -18
- package/src/validators/oas-schema.js +2 -0
- package/src/validators/ordering.js +5 -5
- package/src/validators/recipes.js +4 -12
- package/vendor/git-format/CLAUDE.md +352 -0
- package/vendor/git-format/frontmatter.schema.json +199 -0
- package/src/commands/import-oas.test.js +0 -277
- package/src/commands/import.classify.test.js +0 -172
- package/src/commands/import.landing.test.js +0 -217
- package/src/commands/import.slug-planner.test.js +0 -86
- package/src/commands/import.staging.test.js +0 -230
- package/src/commands/import.test.js +0 -382
- package/src/utils/llms.test.js +0 -302
- package/src/utils/url-segs.test.js +0 -239
package/src/utils/tamagotchi.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import readline from 'node:readline';
|
|
5
|
+
|
|
5
6
|
import chalk from 'chalk';
|
|
7
|
+
|
|
6
8
|
import { eyes, expressions, palettes, setPalette } from './eyes.js';
|
|
7
9
|
|
|
8
10
|
// ── Data directory (XDG spec) ───────────────────────────
|
|
@@ -21,7 +23,16 @@ function getSavePath() {
|
|
|
21
23
|
const MAX_STAT = 10;
|
|
22
24
|
const DECAY_INTERVAL_MS = 1000 * 60 * 5; // stats decay every 5 minutes of real time
|
|
23
25
|
|
|
24
|
-
const ALL_TRICKS = [
|
|
26
|
+
const ALL_TRICKS = [
|
|
27
|
+
'hide and seek',
|
|
28
|
+
'spin',
|
|
29
|
+
'fetch',
|
|
30
|
+
'bow',
|
|
31
|
+
'chameleon',
|
|
32
|
+
'dance',
|
|
33
|
+
'owl impression',
|
|
34
|
+
'writes an OAS file',
|
|
35
|
+
];
|
|
25
36
|
|
|
26
37
|
function newPet(name = 'Eyes', color = 'blue') {
|
|
27
38
|
return {
|
|
@@ -29,12 +40,12 @@ function newPet(name = 'Eyes', color = 'blue') {
|
|
|
29
40
|
color,
|
|
30
41
|
born: Date.now(),
|
|
31
42
|
lastVisit: Date.now(),
|
|
32
|
-
hunger: 8,
|
|
43
|
+
hunger: 8, // 0 = starving, 10 = full
|
|
33
44
|
happiness: 8, // 0 = miserable, 10 = ecstatic
|
|
34
|
-
energy: 8,
|
|
45
|
+
energy: 8, // 0 = exhausted, 10 = wide awake
|
|
35
46
|
sleeping: false,
|
|
36
|
-
age: 0,
|
|
37
|
-
tricks: [],
|
|
47
|
+
age: 0, // total real-time minutes alive
|
|
48
|
+
tricks: [], // learned trick names
|
|
38
49
|
};
|
|
39
50
|
}
|
|
40
51
|
|
|
@@ -83,7 +94,7 @@ function trickPhrase(trick) {
|
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
const ok = (message, extra = {}) => ({ message, ok: true, ...extra });
|
|
86
|
-
const fail =
|
|
97
|
+
const fail = message => ({ message, ok: false });
|
|
87
98
|
|
|
88
99
|
function feed(pet) {
|
|
89
100
|
if (pet.hunger >= MAX_STAT) return fail('Already full!');
|
|
@@ -169,11 +180,16 @@ function getMood(pet) {
|
|
|
169
180
|
|
|
170
181
|
function getExpression(mood) {
|
|
171
182
|
switch (mood) {
|
|
172
|
-
case 'sleeping':
|
|
173
|
-
|
|
174
|
-
case '
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
case 'sleeping':
|
|
184
|
+
return 'closed';
|
|
185
|
+
case 'tired':
|
|
186
|
+
return 'squint';
|
|
187
|
+
case 'sad':
|
|
188
|
+
return 'squint';
|
|
189
|
+
case 'happy':
|
|
190
|
+
return 'right';
|
|
191
|
+
default:
|
|
192
|
+
return 'right';
|
|
177
193
|
}
|
|
178
194
|
}
|
|
179
195
|
|
|
@@ -202,22 +218,80 @@ function getAnimFrames(mood) {
|
|
|
202
218
|
};
|
|
203
219
|
case 'tired':
|
|
204
220
|
return {
|
|
205
|
-
frames: [
|
|
221
|
+
frames: [
|
|
222
|
+
'half-blink',
|
|
223
|
+
'half-blink',
|
|
224
|
+
'half-blink-left',
|
|
225
|
+
'half-blink',
|
|
226
|
+
'half-blink',
|
|
227
|
+
'half-blink-right',
|
|
228
|
+
'squint',
|
|
229
|
+
'half-blink',
|
|
230
|
+
'half-blink-left',
|
|
231
|
+
'half-blink',
|
|
232
|
+
'half-blink',
|
|
233
|
+
'half-blink-right',
|
|
234
|
+
'closed',
|
|
235
|
+
'half-blink',
|
|
236
|
+
'half-blink',
|
|
237
|
+
],
|
|
206
238
|
durations: [800, 600, 500, 1000, 400, 500, 150, 800, 500, 600, 800, 500, 200, 300, 1200],
|
|
207
239
|
};
|
|
208
240
|
case 'sad':
|
|
209
241
|
return {
|
|
210
|
-
frames: [
|
|
242
|
+
frames: [
|
|
243
|
+
'left',
|
|
244
|
+
'left',
|
|
245
|
+
'right',
|
|
246
|
+
'right',
|
|
247
|
+
'left',
|
|
248
|
+
'half-blink',
|
|
249
|
+
'squint',
|
|
250
|
+
'half-blink',
|
|
251
|
+
'left',
|
|
252
|
+
'left',
|
|
253
|
+
'right',
|
|
254
|
+
'left',
|
|
255
|
+
],
|
|
211
256
|
durations: [800, 400, 200, 800, 600, 80, 400, 80, 600, 400, 300, 800],
|
|
212
257
|
};
|
|
213
258
|
case 'happy':
|
|
214
259
|
return {
|
|
215
|
-
frames: [
|
|
260
|
+
frames: [
|
|
261
|
+
'right',
|
|
262
|
+
'right',
|
|
263
|
+
'right:up',
|
|
264
|
+
'right:up',
|
|
265
|
+
'right',
|
|
266
|
+
'left',
|
|
267
|
+
'right',
|
|
268
|
+
'half-blink',
|
|
269
|
+
'squint',
|
|
270
|
+
'closed',
|
|
271
|
+
'squint',
|
|
272
|
+
'half-blink',
|
|
273
|
+
'right',
|
|
274
|
+
'right',
|
|
275
|
+
],
|
|
216
276
|
durations: [600, 300, 150, 150, 300, 400, 400, 50, 50, 50, 50, 50, 300, 800],
|
|
217
277
|
};
|
|
218
278
|
default:
|
|
219
279
|
return {
|
|
220
|
-
frames: [
|
|
280
|
+
frames: [
|
|
281
|
+
'right',
|
|
282
|
+
'right',
|
|
283
|
+
'right',
|
|
284
|
+
'left',
|
|
285
|
+
'left',
|
|
286
|
+
'right',
|
|
287
|
+
'half-blink',
|
|
288
|
+
'squint',
|
|
289
|
+
'closed',
|
|
290
|
+
'squint',
|
|
291
|
+
'half-blink',
|
|
292
|
+
'right',
|
|
293
|
+
'right',
|
|
294
|
+
],
|
|
221
295
|
durations: [1200, 200, 400, 120, 600, 120, 50, 50, 50, 50, 50, 200, 1000],
|
|
222
296
|
};
|
|
223
297
|
}
|
|
@@ -246,7 +320,7 @@ function boxVisLen(s) {
|
|
|
246
320
|
let len = 0;
|
|
247
321
|
for (const ch of stripped) {
|
|
248
322
|
const code = ch.codePointAt(0);
|
|
249
|
-
len += code >=
|
|
323
|
+
len += code >= 0x1f000 ? 2 : 1;
|
|
250
324
|
}
|
|
251
325
|
return len;
|
|
252
326
|
}
|
|
@@ -259,15 +333,15 @@ function boxLine(content = '', bg = null) {
|
|
|
259
333
|
}
|
|
260
334
|
|
|
261
335
|
function boxTop() {
|
|
262
|
-
return chalk.dim('
|
|
336
|
+
return chalk.dim(`┌${'─'.repeat(BOX_WIDTH - 2)}┐`);
|
|
263
337
|
}
|
|
264
338
|
|
|
265
339
|
function boxBottom() {
|
|
266
|
-
return chalk.dim('
|
|
340
|
+
return chalk.dim(`└${'─'.repeat(BOX_WIDTH - 2)}┘`);
|
|
267
341
|
}
|
|
268
342
|
|
|
269
343
|
function boxDivider() {
|
|
270
|
-
return chalk.dim('
|
|
344
|
+
return chalk.dim(`├${'─'.repeat(BOX_WIDTH - 2)}┤`);
|
|
271
345
|
}
|
|
272
346
|
|
|
273
347
|
const STATUS_LEFT = 22; // inner width of left column
|
|
@@ -282,7 +356,7 @@ function statusRow(leftContent, rightContent) {
|
|
|
282
356
|
}
|
|
283
357
|
|
|
284
358
|
function statusDivider() {
|
|
285
|
-
return chalk.dim('─'.repeat(STATUS_LEFT)
|
|
359
|
+
return chalk.dim(`${'─'.repeat(STATUS_LEFT)}┼${'─'.repeat(STATUS_RIGHT)}`);
|
|
286
360
|
}
|
|
287
361
|
|
|
288
362
|
function renderStatus(pet) {
|
|
@@ -290,10 +364,7 @@ function renderStatus(pet) {
|
|
|
290
364
|
const moodEmoji = { sleeping: 'sleeping', tired: 'tired', sad: 'lonely', happy: 'happy!', normal: 'content' }[mood];
|
|
291
365
|
|
|
292
366
|
const lines = [
|
|
293
|
-
statusRow(
|
|
294
|
-
` ${chalk.bold(pet.name)}`,
|
|
295
|
-
` ${chalk.dim('vitals')}`,
|
|
296
|
-
),
|
|
367
|
+
statusRow(` ${chalk.bold(pet.name)}`, ` ${chalk.dim('vitals')}`),
|
|
297
368
|
statusRow(
|
|
298
369
|
` ${chalk.dim('age:')} ${formatAge(pet.age)}`,
|
|
299
370
|
` ${chalk.hex('#ff6b6b')('hunger')} ${statBar(pet.hunger, MAX_STAT, chalk.hex('#ff6b6b'))} ${String(pet.hunger).padStart(2)}/${MAX_STAT}`,
|
|
@@ -350,9 +421,10 @@ async function askSetup() {
|
|
|
350
421
|
console.log('');
|
|
351
422
|
|
|
352
423
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
353
|
-
const ask =
|
|
354
|
-
|
|
355
|
-
|
|
424
|
+
const ask = q =>
|
|
425
|
+
new Promise(resolve => {
|
|
426
|
+
rl.question(q, answer => resolve(answer.trim()));
|
|
427
|
+
});
|
|
356
428
|
|
|
357
429
|
// Ask name
|
|
358
430
|
const name = (await ask(chalk.hex('#63D2FF')(' A new friend hatched! What will you name them? '))) || 'Eyes';
|
|
@@ -444,7 +516,7 @@ export async function startGame() {
|
|
|
444
516
|
for (const ch of stripped) {
|
|
445
517
|
const code = ch.codePointAt(0);
|
|
446
518
|
// Emoji (surrogate pairs / high codepoints) are 2 cols, most symbols are 1
|
|
447
|
-
len += code >=
|
|
519
|
+
len += code >= 0x1f000 ? 2 : 1;
|
|
448
520
|
}
|
|
449
521
|
return len;
|
|
450
522
|
}
|
|
@@ -496,7 +568,9 @@ export async function startGame() {
|
|
|
496
568
|
} else if (trickSceneOverlay && (trickSceneOverlay.right || trickSceneOverlay[r])) {
|
|
497
569
|
if (trickSceneOverlay.left) leftStr = padRight(trickSceneOverlay.left[r] || '', effLeft);
|
|
498
570
|
if (trickSceneOverlay.right) rightStr = padRight(trickSceneOverlay.right[r] || '', effRight);
|
|
499
|
-
if (!trickSceneOverlay.left && !trickSceneOverlay.right)
|
|
571
|
+
if (!trickSceneOverlay.left && !trickSceneOverlay.right) {
|
|
572
|
+
rightStr = padRight(` ${trickSceneOverlay[r] || ''}`, effRight);
|
|
573
|
+
}
|
|
500
574
|
}
|
|
501
575
|
|
|
502
576
|
return leftStr + iconLine + rightStr;
|
|
@@ -514,31 +588,28 @@ export async function startGame() {
|
|
|
514
588
|
const pos = PAD_LEFT + 2 + offset;
|
|
515
589
|
const pad = ' '.repeat(Math.max(0, pos));
|
|
516
590
|
const skin = chalk.hex('#FFCC88');
|
|
517
|
-
return [
|
|
518
|
-
pad + skin(' ╷╷╷╷'),
|
|
519
|
-
pad + skin('╭┴┴┴┴╮'),
|
|
520
|
-
];
|
|
591
|
+
return [pad + skin(' ╷╷╷╷'), pad + skin('╭┴┴┴┴╮')];
|
|
521
592
|
}
|
|
522
593
|
|
|
523
594
|
// Render with box border
|
|
524
595
|
const footer = chalk.dim('[r] reset [q] quit');
|
|
525
596
|
const footerVis = '[r] reset [q] quit'.length;
|
|
526
597
|
console.log('');
|
|
527
|
-
console.log(
|
|
528
|
-
console.log(
|
|
598
|
+
console.log(` ${' '.repeat(BOX_WIDTH - 2 - footerVis + 1)}${footer}`);
|
|
599
|
+
console.log(` ${boxTop()}`);
|
|
529
600
|
const sceneBg = petState.sleeping ? '#05051a' : null;
|
|
530
601
|
const handLines = getPetHand();
|
|
531
602
|
if (handLines.length) {
|
|
532
603
|
// Hand takes 2 rows, so skip the empty top row and trim the last scene row
|
|
533
|
-
console.log(
|
|
534
|
-
console.log(
|
|
604
|
+
console.log(` ${boxLine(handLines[0], sceneBg)}`);
|
|
605
|
+
console.log(` ${boxLine(handLines[1], sceneBg)}`);
|
|
535
606
|
for (let i = 0; i < sceneRows.length - 1; i++) {
|
|
536
|
-
console.log(
|
|
607
|
+
console.log(` ${boxLine(sceneRows[i], sceneBg)}`);
|
|
537
608
|
}
|
|
538
609
|
} else {
|
|
539
|
-
console.log(
|
|
610
|
+
console.log(` ${boxLine('', sceneBg)}`);
|
|
540
611
|
for (const row of sceneRows) {
|
|
541
|
-
console.log(
|
|
612
|
+
console.log(` ${boxLine(row, sceneBg)}`);
|
|
542
613
|
}
|
|
543
614
|
}
|
|
544
615
|
// Message centered between scene and vitals
|
|
@@ -547,25 +618,27 @@ export async function startGame() {
|
|
|
547
618
|
if (boxVisLen(displayMsg) > inner) {
|
|
548
619
|
// Strip ANSI, truncate, re-apply no color (plain truncation)
|
|
549
620
|
const plain = displayMsg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
550
|
-
displayMsg = plain.slice(0, inner - 1)
|
|
621
|
+
displayMsg = `${plain.slice(0, inner - 1)}…`;
|
|
551
622
|
}
|
|
552
623
|
const msgVis = boxVisLen(displayMsg);
|
|
553
624
|
const msgPadL = Math.max(0, Math.floor((inner - msgVis) / 2));
|
|
554
625
|
const msgPadR = Math.max(0, inner - msgVis - msgPadL);
|
|
555
|
-
console.log(
|
|
556
|
-
console.log(
|
|
557
|
-
console.log(
|
|
626
|
+
console.log(` ${boxDivider()}`);
|
|
627
|
+
console.log(` ${chalk.dim('│')}${' '.repeat(msgPadL)}${displayMsg}${' '.repeat(msgPadR)}${chalk.dim('│')}`);
|
|
628
|
+
console.log(` ${chalk.dim(`├${'─'.repeat(STATUS_LEFT)}┬${'─'.repeat(STATUS_RIGHT)}┤`)}`);
|
|
558
629
|
for (const line of statusLines) {
|
|
559
|
-
console.log(
|
|
630
|
+
console.log(` ${boxLine(line)}`);
|
|
560
631
|
}
|
|
561
|
-
console.log(
|
|
632
|
+
console.log(` ${chalk.dim(`├${'─'.repeat(STATUS_LEFT)}┴${'─'.repeat(STATUS_RIGHT)}┤`)}`);
|
|
562
633
|
for (const line of actionLines) {
|
|
563
|
-
console.log(
|
|
634
|
+
console.log(` ${boxLine(line)}`);
|
|
564
635
|
}
|
|
565
|
-
console.log(
|
|
636
|
+
console.log(` ${boxBottom()}`);
|
|
566
637
|
if (confirmingReset) {
|
|
567
638
|
console.log('');
|
|
568
|
-
console.log(
|
|
639
|
+
console.log(
|
|
640
|
+
` ${chalk.hex('#ff6b6b')(`Reset ${petState.name}? They'll be gone forever!`)}${chalk.dim(' [y] yes [n] no')}`,
|
|
641
|
+
);
|
|
569
642
|
} else {
|
|
570
643
|
console.log('');
|
|
571
644
|
}
|
|
@@ -590,15 +663,30 @@ export async function startGame() {
|
|
|
590
663
|
}
|
|
591
664
|
|
|
592
665
|
const leftGrid = [
|
|
593
|
-
[
|
|
666
|
+
[
|
|
667
|
+
{ col: 3, seed: 0 },
|
|
668
|
+
{ col: PAD_LEFT - 4, seed: 8 },
|
|
669
|
+
],
|
|
594
670
|
[{ col: 7, seed: 5 }],
|
|
595
|
-
[
|
|
671
|
+
[
|
|
672
|
+
{ col: 2, seed: 2 },
|
|
673
|
+
{ col: PAD_LEFT - 3, seed: 9 },
|
|
674
|
+
],
|
|
596
675
|
];
|
|
597
676
|
|
|
598
677
|
const rightGrid = [
|
|
599
|
-
[
|
|
600
|
-
|
|
601
|
-
|
|
678
|
+
[
|
|
679
|
+
{ col: 2, seed: 3 },
|
|
680
|
+
{ col: Math.min(10, PAD_RIGHT - 3), seed: 6 },
|
|
681
|
+
],
|
|
682
|
+
[
|
|
683
|
+
{ col: 5, seed: 1 },
|
|
684
|
+
{ col: Math.min(12, PAD_RIGHT - 4), seed: 10 },
|
|
685
|
+
],
|
|
686
|
+
[
|
|
687
|
+
{ col: 1, seed: 7 },
|
|
688
|
+
{ col: Math.min(8, PAD_RIGHT - 2), seed: 4 },
|
|
689
|
+
],
|
|
602
690
|
];
|
|
603
691
|
|
|
604
692
|
const leftLines = leftGrid.map(row => {
|
|
@@ -677,11 +765,11 @@ export async function startGame() {
|
|
|
677
765
|
if (heartFrame < 0) return { left: null, right: null };
|
|
678
766
|
const hearts = [
|
|
679
767
|
{ startFrame: 0, side: 'right', startRow: 2, offset: 2 },
|
|
680
|
-
{ startFrame: 1, side: 'left',
|
|
768
|
+
{ startFrame: 1, side: 'left', startRow: 2, offset: 2 },
|
|
681
769
|
{ startFrame: 3, side: 'right', startRow: 2, offset: 6 },
|
|
682
|
-
{ startFrame: 4, side: 'left',
|
|
770
|
+
{ startFrame: 4, side: 'left', startRow: 1, offset: 5 },
|
|
683
771
|
{ startFrame: 6, side: 'right', startRow: 2, offset: 10 },
|
|
684
|
-
{ startFrame: 7, side: 'left',
|
|
772
|
+
{ startFrame: 7, side: 'left', startRow: 2, offset: 9 },
|
|
685
773
|
];
|
|
686
774
|
const leftLines = ['', '', '', ''];
|
|
687
775
|
const rightLines = ['', '', '', ''];
|
|
@@ -748,7 +836,7 @@ export async function startGame() {
|
|
|
748
836
|
const cells = s.side === 'left' ? leftCells[s.r] : rightCells[s.r];
|
|
749
837
|
if (s.col < cells.length) cells[s.col] = chalk.hex(color)(ch);
|
|
750
838
|
}
|
|
751
|
-
const toStr =
|
|
839
|
+
const toStr = cells => cells.map(c => c || ' ').join('');
|
|
752
840
|
return {
|
|
753
841
|
left: leftCells.map(toStr),
|
|
754
842
|
right: rightCells.map(toStr),
|
|
@@ -757,7 +845,21 @@ export async function startGame() {
|
|
|
757
845
|
|
|
758
846
|
async function animatePlay() {
|
|
759
847
|
playAnimating = true;
|
|
760
|
-
const bounceFrames = [
|
|
848
|
+
const bounceFrames = [
|
|
849
|
+
'right:up',
|
|
850
|
+
'right:up',
|
|
851
|
+
'right',
|
|
852
|
+
'right',
|
|
853
|
+
'right:up',
|
|
854
|
+
'right:up',
|
|
855
|
+
'right',
|
|
856
|
+
'right',
|
|
857
|
+
'right:up',
|
|
858
|
+
'right:up',
|
|
859
|
+
'right',
|
|
860
|
+
'right',
|
|
861
|
+
'right',
|
|
862
|
+
];
|
|
761
863
|
for (playFrame = 0; playFrame <= 12; playFrame++) {
|
|
762
864
|
frameOverride = bounceFrames[playFrame % bounceFrames.length];
|
|
763
865
|
draw();
|
|
@@ -772,8 +874,12 @@ export async function startGame() {
|
|
|
772
874
|
let teachAnimating = false;
|
|
773
875
|
|
|
774
876
|
async function animateTrick(name) {
|
|
775
|
-
const wait =
|
|
776
|
-
const setFrame = async (f, ms) => {
|
|
877
|
+
const wait = ms => new Promise(r => setTimeout(r, ms));
|
|
878
|
+
const setFrame = async (f, ms) => {
|
|
879
|
+
frameOverride = f;
|
|
880
|
+
draw();
|
|
881
|
+
await wait(ms);
|
|
882
|
+
};
|
|
777
883
|
|
|
778
884
|
switch (name) {
|
|
779
885
|
case 'hide and seek': {
|
|
@@ -783,10 +889,14 @@ export async function startGame() {
|
|
|
783
889
|
await setFrame('right', 180);
|
|
784
890
|
}
|
|
785
891
|
// peek — bob up slightly then back down
|
|
786
|
-
iconShift = 2;
|
|
787
|
-
|
|
788
|
-
iconShift =
|
|
789
|
-
|
|
892
|
+
iconShift = 2;
|
|
893
|
+
await setFrame('right', 200);
|
|
894
|
+
iconShift = 3;
|
|
895
|
+
await setFrame('right', 300);
|
|
896
|
+
iconShift = 2;
|
|
897
|
+
await setFrame('right', 150);
|
|
898
|
+
iconShift = 3;
|
|
899
|
+
await setFrame('right', 400);
|
|
790
900
|
// come back up
|
|
791
901
|
for (const shift of [2, 1, 0]) {
|
|
792
902
|
iconShift = shift;
|
|
@@ -795,14 +905,15 @@ export async function startGame() {
|
|
|
795
905
|
break;
|
|
796
906
|
}
|
|
797
907
|
case 'spin': {
|
|
798
|
-
for (const f of ['left', 'up-left', 'up-right', 'right', 'up-right', 'up-left', 'left', 'up-left', 'right'])
|
|
908
|
+
for (const f of ['left', 'up-left', 'up-right', 'right', 'up-right', 'up-left', 'left', 'up-left', 'right']) {
|
|
799
909
|
await setFrame(f, 110);
|
|
910
|
+
}
|
|
800
911
|
break;
|
|
801
912
|
}
|
|
802
913
|
case 'fetch': {
|
|
803
914
|
iconHShift = -8;
|
|
804
915
|
const method = chalk.hex('#63D2FF')('POST');
|
|
805
|
-
const pending = chalk.dim('○')
|
|
916
|
+
const pending = `${chalk.dim('○')} ${method} /api/pets`;
|
|
806
917
|
const d = chalk.dim;
|
|
807
918
|
const json = [
|
|
808
919
|
d(' "id": 1,'),
|
|
@@ -842,8 +953,16 @@ export async function startGame() {
|
|
|
842
953
|
break;
|
|
843
954
|
}
|
|
844
955
|
case 'bow': {
|
|
845
|
-
for (const [f, ms] of [
|
|
956
|
+
for (const [f, ms] of [
|
|
957
|
+
['right', 200],
|
|
958
|
+
['right:up', 300],
|
|
959
|
+
['right:up', 300],
|
|
960
|
+
['squint', 500],
|
|
961
|
+
['right:up', 300],
|
|
962
|
+
['right', 200],
|
|
963
|
+
]) {
|
|
846
964
|
await setFrame(f, ms);
|
|
965
|
+
}
|
|
847
966
|
break;
|
|
848
967
|
}
|
|
849
968
|
case 'chameleon': {
|
|
@@ -857,19 +976,41 @@ export async function startGame() {
|
|
|
857
976
|
case 'dance': {
|
|
858
977
|
const notes = ['♩', '♪', '♫', '♬'];
|
|
859
978
|
const nc = chalk.hex('#ffd93d');
|
|
860
|
-
const place = (width, col, note) =>
|
|
979
|
+
const place = (width, col, note) =>
|
|
980
|
+
' '.repeat(Math.min(col, width - 1)) + nc(note) + ' '.repeat(Math.max(0, width - col - 1));
|
|
861
981
|
// Each frame: scattered note positions on left and right, shifting each beat
|
|
862
|
-
const frames = [
|
|
982
|
+
const frames = [
|
|
983
|
+
'left:up',
|
|
984
|
+
'left',
|
|
985
|
+
'right:up',
|
|
986
|
+
'right',
|
|
987
|
+
'left:up',
|
|
988
|
+
'left',
|
|
989
|
+
'right:up',
|
|
990
|
+
'right',
|
|
991
|
+
'right:up',
|
|
992
|
+
'right',
|
|
993
|
+
];
|
|
863
994
|
const spots = [
|
|
864
995
|
{ lr: [14, 3], ll: [2, 11], rr: [4, 15], rl: [8, 1] },
|
|
865
|
-
{ lr: [6, 16], ll: [9, 4],
|
|
996
|
+
{ lr: [6, 16], ll: [9, 4], rr: [12, 2], rl: [14, 7] },
|
|
866
997
|
];
|
|
867
998
|
for (let i = 0; i < frames.length; i++) {
|
|
868
999
|
const s = spots[i % 2];
|
|
869
|
-
const ni =
|
|
1000
|
+
const ni = k => notes[(i + k) % notes.length];
|
|
870
1001
|
trickSceneOverlay = {
|
|
871
|
-
left:
|
|
872
|
-
|
|
1002
|
+
left: [
|
|
1003
|
+
place(PAD_LEFT, s.ll[0], ni(0)),
|
|
1004
|
+
place(PAD_LEFT, s.ll[1], ni(2)),
|
|
1005
|
+
place(PAD_LEFT, s.lr[0], ni(1)),
|
|
1006
|
+
place(PAD_LEFT, s.lr[1], ni(3)),
|
|
1007
|
+
],
|
|
1008
|
+
right: [
|
|
1009
|
+
place(PAD_RIGHT, s.rr[0], ni(2)),
|
|
1010
|
+
place(PAD_RIGHT, s.rl[0], ni(0)),
|
|
1011
|
+
place(PAD_RIGHT, s.rr[1], ni(3)),
|
|
1012
|
+
place(PAD_RIGHT, s.rl[1], ni(1)),
|
|
1013
|
+
],
|
|
873
1014
|
};
|
|
874
1015
|
await setFrame(frames[i], 130);
|
|
875
1016
|
}
|
|
@@ -879,8 +1020,14 @@ export async function startGame() {
|
|
|
879
1020
|
case 'owl impression': {
|
|
880
1021
|
setPalette('owl');
|
|
881
1022
|
const hoot = chalk.hex('#ffd93d');
|
|
882
|
-
for (const [f, ms] of [
|
|
1023
|
+
for (const [f, ms] of [
|
|
1024
|
+
['left', 200],
|
|
1025
|
+
['right', 200],
|
|
1026
|
+
['left', 200],
|
|
1027
|
+
['right', 200],
|
|
1028
|
+
]) {
|
|
883
1029
|
await setFrame(f, ms);
|
|
1030
|
+
}
|
|
884
1031
|
const rpad = () => ' '.repeat(2 + Math.floor(Math.random() * 8));
|
|
885
1032
|
trickSceneOverlay = { right: [rpad() + hoot('hoot!'), '', '', ''] };
|
|
886
1033
|
await setFrame('up-right', 500);
|
|
@@ -911,8 +1058,9 @@ export async function startGame() {
|
|
|
911
1058
|
break;
|
|
912
1059
|
}
|
|
913
1060
|
default: {
|
|
914
|
-
for (const f of ['right:up', 'right', 'right:up', 'right'])
|
|
1061
|
+
for (const f of ['right:up', 'right', 'right:up', 'right']) {
|
|
915
1062
|
await setFrame(f, 150);
|
|
1063
|
+
}
|
|
916
1064
|
}
|
|
917
1065
|
}
|
|
918
1066
|
frameOverride = null;
|
|
@@ -975,8 +1123,15 @@ export async function startGame() {
|
|
|
975
1123
|
});
|
|
976
1124
|
}
|
|
977
1125
|
|
|
978
|
-
const actionKeyMap = new Map([
|
|
979
|
-
|
|
1126
|
+
const actionKeyMap = new Map([
|
|
1127
|
+
[feed, 'f'],
|
|
1128
|
+
[play, 'p'],
|
|
1129
|
+
[petAction, 'h'],
|
|
1130
|
+
[teach, 't'],
|
|
1131
|
+
]);
|
|
1132
|
+
function getnapKey() {
|
|
1133
|
+
return petState.sleeping ? 'w' : 's';
|
|
1134
|
+
}
|
|
980
1135
|
|
|
981
1136
|
function doAction(actionFn) {
|
|
982
1137
|
if (actionBusy) return;
|
|
@@ -987,7 +1142,7 @@ export async function startGame() {
|
|
|
987
1142
|
savePet(petState);
|
|
988
1143
|
animFrame = 0;
|
|
989
1144
|
actionBusy = true;
|
|
990
|
-
activeAction = actionFn === nap ? getnapKey() :
|
|
1145
|
+
activeAction = actionFn === nap ? getnapKey() : actionKeyMap.get(actionFn) || null;
|
|
991
1146
|
|
|
992
1147
|
function done() {
|
|
993
1148
|
activeAction = null;
|
|
@@ -1001,7 +1156,10 @@ export async function startGame() {
|
|
|
1001
1156
|
done();
|
|
1002
1157
|
} else if (actionFn === nap) {
|
|
1003
1158
|
// Only nap/wake gets the eye open/close animation
|
|
1004
|
-
animateSleep(petState.sleeping).then(() => {
|
|
1159
|
+
animateSleep(petState.sleeping).then(() => {
|
|
1160
|
+
draw();
|
|
1161
|
+
done();
|
|
1162
|
+
});
|
|
1005
1163
|
} else if (actionFn === feed) {
|
|
1006
1164
|
animateFeed().then(done);
|
|
1007
1165
|
} else if (actionFn === petAction) {
|
|
@@ -1010,9 +1168,8 @@ export async function startGame() {
|
|
|
1010
1168
|
animatePlay().then(done);
|
|
1011
1169
|
} else if (actionFn === teach) {
|
|
1012
1170
|
const tricksAfter = (petState.tricks || []).length;
|
|
1013
|
-
const learnedTrick =
|
|
1014
|
-
? petState.tricks[petState.tricks.length - 1]
|
|
1015
|
-
: (result.trick || null);
|
|
1171
|
+
const learnedTrick =
|
|
1172
|
+
tricksAfter > tricksBefore ? petState.tricks[petState.tricks.length - 1] : result.trick || null;
|
|
1016
1173
|
const learned = tricksAfter > tricksBefore;
|
|
1017
1174
|
animateTeach(learnedTrick).then(() => {
|
|
1018
1175
|
activeAction = null;
|
|
@@ -1034,14 +1191,16 @@ export async function startGame() {
|
|
|
1034
1191
|
}
|
|
1035
1192
|
|
|
1036
1193
|
// Input handler
|
|
1037
|
-
process.stdin.on('data',
|
|
1194
|
+
process.stdin.on('data', key => {
|
|
1038
1195
|
if (stopped) return;
|
|
1039
1196
|
|
|
1040
1197
|
if (confirmingReset) {
|
|
1041
1198
|
if (key === 'y' || key === 'Y') {
|
|
1042
1199
|
cleanup();
|
|
1043
1200
|
const savePath = getSavePath();
|
|
1044
|
-
try {
|
|
1201
|
+
try {
|
|
1202
|
+
fs.unlinkSync(savePath);
|
|
1203
|
+
} catch {}
|
|
1045
1204
|
console.log('');
|
|
1046
1205
|
console.log(` ${chalk.dim('Save data cleared. A new friend will hatch next time!')}`);
|
|
1047
1206
|
console.log('');
|
|
@@ -1072,8 +1231,14 @@ export async function startGame() {
|
|
|
1072
1231
|
case 't':
|
|
1073
1232
|
doAction(teach);
|
|
1074
1233
|
break;
|
|
1075
|
-
case '1':
|
|
1076
|
-
case '
|
|
1234
|
+
case '1':
|
|
1235
|
+
case '2':
|
|
1236
|
+
case '3':
|
|
1237
|
+
case '4':
|
|
1238
|
+
case '5':
|
|
1239
|
+
case '6':
|
|
1240
|
+
case '7':
|
|
1241
|
+
case '8': {
|
|
1077
1242
|
const idx = parseInt(key) - 1;
|
|
1078
1243
|
const trickName = ALL_TRICKS[idx];
|
|
1079
1244
|
const known = (petState.tricks || []).filter(t => ALL_TRICKS.includes(t));
|
|
@@ -1097,7 +1262,7 @@ export async function startGame() {
|
|
|
1097
1262
|
draw();
|
|
1098
1263
|
cleanup();
|
|
1099
1264
|
process.exit();
|
|
1100
|
-
|
|
1265
|
+
// oxlint-disable-next-line no-fallthrough -- Can't add a `break` above this because it's unreachable.
|
|
1101
1266
|
default:
|
|
1102
1267
|
break;
|
|
1103
1268
|
}
|
|
@@ -1128,12 +1293,13 @@ export async function startGame() {
|
|
|
1128
1293
|
const moodAfter = getMood(petState);
|
|
1129
1294
|
if (moodBefore !== moodAfter) {
|
|
1130
1295
|
if (moodAfter === 'tired') message = chalk.hex('#63D2FF')(`${petState.name} is getting sleepy...`);
|
|
1131
|
-
else if (moodAfter === 'sad' && petState.hunger <= 1)
|
|
1296
|
+
else if (moodAfter === 'sad' && petState.hunger <= 1) {
|
|
1297
|
+
message = chalk.hex('#ff6b6b')(`${petState.name} is hungry!`);
|
|
1298
|
+
}
|
|
1132
1299
|
}
|
|
1133
1300
|
}
|
|
1134
1301
|
}
|
|
1135
1302
|
}
|
|
1136
1303
|
|
|
1137
|
-
|
|
1138
1304
|
await gameLoop();
|
|
1139
1305
|
}
|