clibuddy 1.0.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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/adventure/adventureUI.d.ts +24 -0
  4. package/dist/adventure/adventureUI.js +290 -0
  5. package/dist/adventure/adventures.d.ts +4 -0
  6. package/dist/adventure/adventures.js +206 -0
  7. package/dist/adventure/biomes.d.ts +30 -0
  8. package/dist/adventure/biomes.js +80 -0
  9. package/dist/adventure/combat/combat.d.ts +14 -0
  10. package/dist/adventure/combat/combat.js +638 -0
  11. package/dist/adventure/combat/combatUI.d.ts +5 -0
  12. package/dist/adventure/combat/combatUI.js +116 -0
  13. package/dist/adventure/combat/conditions.d.ts +20 -0
  14. package/dist/adventure/combat/conditions.js +111 -0
  15. package/dist/adventure/combat/enemies.d.ts +4 -0
  16. package/dist/adventure/combat/enemies.js +430 -0
  17. package/dist/adventure/combat/gear.d.ts +3 -0
  18. package/dist/adventure/combat/gear.js +199 -0
  19. package/dist/adventure/combat/skills.d.ts +6 -0
  20. package/dist/adventure/combat/skills.js +197 -0
  21. package/dist/adventure/combat.d.ts +31 -0
  22. package/dist/adventure/combat.js +732 -0
  23. package/dist/adventure/combatUI.d.ts +5 -0
  24. package/dist/adventure/combatUI.js +116 -0
  25. package/dist/adventure/endless.d.ts +18 -0
  26. package/dist/adventure/endless.js +154 -0
  27. package/dist/adventure/enemies.d.ts +4 -0
  28. package/dist/adventure/enemies.js +320 -0
  29. package/dist/adventure/engine.d.ts +20 -0
  30. package/dist/adventure/engine.js +137 -0
  31. package/dist/adventure/gear.d.ts +3 -0
  32. package/dist/adventure/gear.js +149 -0
  33. package/dist/adventure/generation/biomes.d.ts +30 -0
  34. package/dist/adventure/generation/biomes.js +102 -0
  35. package/dist/adventure/generation/endless.d.ts +18 -0
  36. package/dist/adventure/generation/endless.js +154 -0
  37. package/dist/adventure/generation/generator.d.ts +9 -0
  38. package/dist/adventure/generation/generator.js +245 -0
  39. package/dist/adventure/generation/templates.d.ts +25 -0
  40. package/dist/adventure/generation/templates.js +228 -0
  41. package/dist/adventure/generator.d.ts +9 -0
  42. package/dist/adventure/generator.js +245 -0
  43. package/dist/adventure/skills.d.ts +6 -0
  44. package/dist/adventure/skills.js +197 -0
  45. package/dist/adventure/templates.d.ts +25 -0
  46. package/dist/adventure/templates.js +228 -0
  47. package/dist/adventure/types.d.ts +236 -0
  48. package/dist/adventure/types.js +97 -0
  49. package/dist/app/state.d.ts +49 -0
  50. package/dist/app/state.js +51 -0
  51. package/dist/buddy/activities.d.ts +16 -0
  52. package/dist/buddy/activities.js +90 -0
  53. package/dist/buddy/decay.d.ts +3 -0
  54. package/dist/buddy/decay.js +45 -0
  55. package/dist/buddy/leveling.d.ts +11 -0
  56. package/dist/buddy/leveling.js +25 -0
  57. package/dist/buddy/roll.d.ts +4 -0
  58. package/dist/buddy/roll.js +61 -0
  59. package/dist/buddy/species.d.ts +4 -0
  60. package/dist/buddy/species.js +592 -0
  61. package/dist/buddy/titles.d.ts +17 -0
  62. package/dist/buddy/titles.js +89 -0
  63. package/dist/buddy/types.d.ts +92 -0
  64. package/dist/buddy/types.js +21 -0
  65. package/dist/commands/actions.d.ts +2 -0
  66. package/dist/commands/actions.js +141 -0
  67. package/dist/commands/admin.d.ts +2 -0
  68. package/dist/commands/admin.js +202 -0
  69. package/dist/commands/registry.d.ts +25 -0
  70. package/dist/commands/registry.js +31 -0
  71. package/dist/commands/social.d.ts +2 -0
  72. package/dist/commands/social.js +92 -0
  73. package/dist/dialogue/engine.d.ts +7 -0
  74. package/dist/dialogue/engine.js +68 -0
  75. package/dist/dialogue/lines.d.ts +26 -0
  76. package/dist/dialogue/lines.js +294 -0
  77. package/dist/events/engine.d.ts +20 -0
  78. package/dist/events/engine.js +51 -0
  79. package/dist/events/events.d.ts +13 -0
  80. package/dist/events/events.js +149 -0
  81. package/dist/index.d.ts +10 -0
  82. package/dist/index.js +1665 -0
  83. package/dist/inventory/finding.d.ts +11 -0
  84. package/dist/inventory/finding.js +99 -0
  85. package/dist/inventory/items.d.ts +31 -0
  86. package/dist/inventory/items.js +63 -0
  87. package/dist/minigames/copycat.d.ts +2 -0
  88. package/dist/minigames/copycat.js +153 -0
  89. package/dist/minigames/fetch.d.ts +2 -0
  90. package/dist/minigames/fetch.js +179 -0
  91. package/dist/minigames/moodmatch.d.ts +2 -0
  92. package/dist/minigames/moodmatch.js +144 -0
  93. package/dist/minigames/quickpaws.d.ts +2 -0
  94. package/dist/minigames/quickpaws.js +142 -0
  95. package/dist/minigames/registry.d.ts +5 -0
  96. package/dist/minigames/registry.js +16 -0
  97. package/dist/minigames/rpsplus.d.ts +2 -0
  98. package/dist/minigames/rpsplus.js +168 -0
  99. package/dist/minigames/treasurehunt.d.ts +2 -0
  100. package/dist/minigames/treasurehunt.js +146 -0
  101. package/dist/minigames/types.d.ts +30 -0
  102. package/dist/minigames/types.js +69 -0
  103. package/dist/rendering/commandPalette.d.ts +16 -0
  104. package/dist/rendering/commandPalette.js +77 -0
  105. package/dist/rendering/display.d.ts +9 -0
  106. package/dist/rendering/display.js +231 -0
  107. package/dist/rendering/inventoryUI.d.ts +14 -0
  108. package/dist/rendering/inventoryUI.js +99 -0
  109. package/dist/rendering/items.d.ts +7 -0
  110. package/dist/rendering/items.js +34 -0
  111. package/dist/rendering/listUtils.d.ts +3 -0
  112. package/dist/rendering/listUtils.js +24 -0
  113. package/dist/rendering/minigameUI.d.ts +11 -0
  114. package/dist/rendering/minigameUI.js +37 -0
  115. package/dist/rendering/overlayUI.d.ts +24 -0
  116. package/dist/rendering/overlayUI.js +184 -0
  117. package/dist/rendering/scene.d.ts +8 -0
  118. package/dist/rendering/scene.js +87 -0
  119. package/dist/rendering/screen.d.ts +43 -0
  120. package/dist/rendering/screen.js +97 -0
  121. package/dist/sound/sound.d.ts +11 -0
  122. package/dist/sound/sound.js +55 -0
  123. package/dist/state/save.d.ts +5 -0
  124. package/dist/state/save.js +100 -0
  125. package/dist/state/settings.d.ts +7 -0
  126. package/dist/state/settings.js +81 -0
  127. package/dist/story/mainStory.d.ts +4 -0
  128. package/dist/story/mainStory.js +3111 -0
  129. package/dist/story/npcs.d.ts +17 -0
  130. package/dist/story/npcs.js +137 -0
  131. package/dist/story/progress.d.ts +26 -0
  132. package/dist/story/progress.js +168 -0
  133. package/dist/story/seasonal.d.ts +6 -0
  134. package/dist/story/seasonal.js +96 -0
  135. package/dist/story/shop.d.ts +7 -0
  136. package/dist/story/shop.js +26 -0
  137. package/dist/story/sideQuests.d.ts +4 -0
  138. package/dist/story/sideQuests.js +151 -0
  139. package/dist/story/types.d.ts +61 -0
  140. package/dist/story/types.js +36 -0
  141. package/dist/updates.d.ts +23 -0
  142. package/dist/updates.js +142 -0
  143. package/package.json +53 -0
@@ -0,0 +1,11 @@
1
+ import { BuddyState } from "../buddy/types";
2
+ import { ItemDef } from "./items";
3
+ /**
4
+ * Check if the buddy finds an item this tick.
5
+ * ~0.1% chance per tick during idle = roughly every 10-15 minutes on average.
6
+ */
7
+ export declare function checkForItemFind(state: BuddyState): {
8
+ item: ItemDef;
9
+ flavorText: string;
10
+ } | null;
11
+ //# sourceMappingURL=finding.d.ts.map
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkForItemFind = checkForItemFind;
4
+ const items_1 = require("./items");
5
+ const RARITY_WEIGHTS = { common: 60, uncommon: 30, rare: 10 };
6
+ const GENERIC_FLAVOR = [
7
+ "was digging around and found",
8
+ "stumbled upon",
9
+ "spotted something shiny —",
10
+ "tripped over something... it's",
11
+ "sniffed out",
12
+ "was nosing around and discovered",
13
+ "found something hidden behind a rock:",
14
+ "noticed something glinting in the corner —",
15
+ ];
16
+ const SPECIES_FLAVOR = {
17
+ rabbit: [
18
+ "was hopping around and kicked up",
19
+ "found something buried near a carrot patch:",
20
+ "dug a hole and uncovered",
21
+ ],
22
+ cat: [
23
+ "knocked something off a shelf —",
24
+ "batted something out from under the couch:",
25
+ "gracefully presented you with",
26
+ ],
27
+ frog: [
28
+ "found something in the pond:",
29
+ "caught something with its tongue! It's",
30
+ "sat on something interesting —",
31
+ ],
32
+ owl: [
33
+ "spotted something from above:",
34
+ "wisely located",
35
+ "used its keen eyes to find",
36
+ ],
37
+ fox: [
38
+ "snuck off and came back with",
39
+ "used its cunning to acquire",
40
+ "raided a stash and found",
41
+ ],
42
+ phoenix: [
43
+ "pulled something from the ashes:",
44
+ "set something ablaze and revealed",
45
+ "found something fireproof:",
46
+ ],
47
+ dragon: [
48
+ "added to its hoard:",
49
+ "declared ownership of",
50
+ "guarded a new treasure:",
51
+ ],
52
+ };
53
+ function pickRandom(arr) {
54
+ return arr[Math.floor(Math.random() * arr.length)];
55
+ }
56
+ const CONSUMABLE_TYPES = new Set(["food", "toy", "medicine"]);
57
+ function rollItem() {
58
+ const total = RARITY_WEIGHTS.common + RARITY_WEIGHTS.uncommon + RARITY_WEIGHTS.rare;
59
+ let roll = Math.random() * total;
60
+ let rarity = "common";
61
+ roll -= RARITY_WEIGHTS.common;
62
+ if (roll > 0) {
63
+ rarity = "uncommon";
64
+ roll -= RARITY_WEIGHTS.uncommon;
65
+ }
66
+ if (roll > 0) {
67
+ rarity = "rare";
68
+ }
69
+ // Only consumables from idle finding — gear comes from adventures
70
+ const candidates = items_1.ITEMS.filter((i) => i.rarity === rarity && CONSUMABLE_TYPES.has(i.type));
71
+ if (candidates.length === 0)
72
+ return items_1.ITEMS.filter((i) => CONSUMABLE_TYPES.has(i.type))[0];
73
+ return pickRandom(candidates);
74
+ }
75
+ /**
76
+ * Check if the buddy finds an item this tick.
77
+ * ~0.1% chance per tick during idle = roughly every 10-15 minutes on average.
78
+ */
79
+ function checkForItemFind(state) {
80
+ if (!state.alive)
81
+ return null;
82
+ if (state.activity && state.activity.type !== "idle")
83
+ return null;
84
+ if (Math.random() > 0.001)
85
+ return null;
86
+ const item = rollItem();
87
+ // Pick flavor text — species-specific 40% of the time
88
+ const speciesPool = SPECIES_FLAVOR[state.speciesId];
89
+ let intro;
90
+ if (speciesPool && Math.random() < 0.4) {
91
+ intro = pickRandom(speciesPool);
92
+ }
93
+ else {
94
+ intro = pickRandom(GENERIC_FLAVOR);
95
+ }
96
+ const flavorText = `${state.name} ${intro} a ${item.name}!`;
97
+ return { item, flavorText };
98
+ }
99
+ //# sourceMappingURL=finding.js.map
@@ -0,0 +1,31 @@
1
+ import { BuddyStats } from "../buddy/types";
2
+ export type ItemRarity = "common" | "uncommon" | "rare" | "epic" | "legendary";
3
+ export type ItemType = "food" | "toy" | "medicine" | "weapon" | "armor" | "helmet" | "boots" | "accessory";
4
+ export interface ItemDef {
5
+ id: string;
6
+ name: string;
7
+ description: string;
8
+ rarity: ItemRarity;
9
+ type: ItemType;
10
+ statEffect: Partial<BuddyStats>;
11
+ xpBonus: number;
12
+ combatATK?: number;
13
+ combatDEF?: number;
14
+ combatHP?: number;
15
+ combatSPD?: number;
16
+ critBonus?: number;
17
+ dodgeBonus?: number;
18
+ energyBonus?: number;
19
+ damageReduction?: number;
20
+ gearElement?: string;
21
+ onHitEffect?: string;
22
+ onHitChance?: number;
23
+ onHitPower?: number;
24
+ }
25
+ export declare const ITEMS: ItemDef[];
26
+ export declare function getItem(id: string): ItemDef | undefined;
27
+ export declare function isGear(item: ItemDef): boolean;
28
+ /** Add an item to inventory. Gear is unique (count always 1, no duplicates). Consumables stack. */
29
+ export declare function addItemToInventory(inventory: import("../buddy/types").InventorySlot[], itemId: string): import("../buddy/types").InventorySlot[];
30
+ export declare const ITEM_RARITY_COLORS: Record<ItemRarity, string>;
31
+ //# sourceMappingURL=items.d.ts.map
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ITEM_RARITY_COLORS = exports.ITEMS = void 0;
4
+ exports.getItem = getItem;
5
+ exports.isGear = isGear;
6
+ exports.addItemToInventory = addItemToInventory;
7
+ const gear_1 = require("../adventure/combat/gear");
8
+ exports.ITEMS = [
9
+ // ─── Consumables ───────────────────────────────────
10
+ // Food
11
+ { id: "berry", name: "Berry", description: "+15 hunger", rarity: "common", type: "food", statEffect: { hunger: 15 }, xpBonus: 3 },
12
+ { id: "fish", name: "Fish", description: "+25 hunger", rarity: "uncommon", type: "food", statEffect: { hunger: 25 }, xpBonus: 5 },
13
+ { id: "cake", name: "Cake", description: "+40 hunger, +10 happiness", rarity: "rare", type: "food", statEffect: { hunger: 40, happiness: 10 }, xpBonus: 8 },
14
+ // Toys
15
+ { id: "stick", name: "Stick", description: "+15 happiness", rarity: "common", type: "toy", statEffect: { happiness: 15 }, xpBonus: 5 },
16
+ { id: "ball", name: "Ball", description: "+25 happiness", rarity: "uncommon", type: "toy", statEffect: { happiness: 25 }, xpBonus: 8 },
17
+ { id: "puzzle", name: "Puzzle", description: "+30 happiness, +15 XP", rarity: "rare", type: "toy", statEffect: { happiness: 30 }, xpBonus: 15 },
18
+ // Medicine
19
+ { id: "herb", name: "Herb", description: "+15 health", rarity: "common", type: "medicine", statEffect: { health: 15 }, xpBonus: 2 },
20
+ { id: "potion", name: "Potion", description: "+35 health", rarity: "uncommon", type: "medicine", statEffect: { health: 35 }, xpBonus: 5 },
21
+ { id: "elixir", name: "Elixir", description: "+50 health, +20 energy", rarity: "rare", type: "medicine", statEffect: { health: 50, energy: 20 }, xpBonus: 10 },
22
+ // Epic/Legendary consumables
23
+ { id: "golden_apple", name: "Golden Apple", description: "+60 hunger, +15 happiness", rarity: "epic", type: "food", statEffect: { hunger: 60, happiness: 15 }, xpBonus: 12 },
24
+ { id: "dragon_steak", name: "Dragon Steak", description: "+50 hunger, +10 energy", rarity: "epic", type: "food", statEffect: { hunger: 50, energy: 10 }, xpBonus: 15 },
25
+ { id: "enchanted_ball", name: "Enchanted Ball", description: "+40 happiness, +10 energy", rarity: "epic", type: "toy", statEffect: { happiness: 40, energy: 10 }, xpBonus: 12 },
26
+ { id: "phoenix_tears", name: "Phoenix Tears", description: "+60 health, +30 energy", rarity: "epic", type: "medicine", statEffect: { health: 60, energy: 30 }, xpBonus: 15 },
27
+ { id: "ambrosia", name: "Ambrosia", description: "+80 hunger, +20 happiness, +10 hp", rarity: "legendary", type: "food", statEffect: { hunger: 80, happiness: 20, health: 10 }, xpBonus: 20 },
28
+ { id: "elixir_of_life", name: "Elixir of Life", description: "+100 health, +50 energy", rarity: "legendary", type: "medicine", statEffect: { health: 100, energy: 50 }, xpBonus: 25 },
29
+ // ─── Gear (from adventure system) ──────────────────
30
+ ...gear_1.GEAR_ITEMS,
31
+ ];
32
+ function getItem(id) {
33
+ return exports.ITEMS.find((i) => i.id === id);
34
+ }
35
+ function isGear(item) {
36
+ return item.type === "weapon" || item.type === "armor" || item.type === "helmet" || item.type === "boots" || item.type === "accessory";
37
+ }
38
+ /** Add an item to inventory. Gear is unique (count always 1, no duplicates). Consumables stack. */
39
+ function addItemToInventory(inventory, itemId) {
40
+ const item = getItem(itemId);
41
+ if (!item)
42
+ return inventory;
43
+ const existing = inventory.find((s) => s.itemId === itemId);
44
+ if (isGear(item)) {
45
+ // Gear: own 0 or 1, never stack
46
+ if (existing)
47
+ return inventory; // already owned
48
+ return [...inventory, { itemId, count: 1 }];
49
+ }
50
+ // Consumable: stack
51
+ if (existing) {
52
+ return inventory.map((s) => s.itemId === itemId ? { ...s, count: s.count + 1 } : s);
53
+ }
54
+ return [...inventory, { itemId, count: 1 }];
55
+ }
56
+ exports.ITEM_RARITY_COLORS = {
57
+ common: "\x1b[37m",
58
+ uncommon: "\x1b[32m",
59
+ rare: "\x1b[33m",
60
+ epic: "\x1b[35m",
61
+ legendary: "\x1b[33m",
62
+ };
63
+ //# sourceMappingURL=items.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=copycat.d.ts.map
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const types_1 = require("./types");
4
+ const registry_1 = require("./registry");
5
+ const screen_1 = require("../rendering/screen");
6
+ const SYMBOLS = ["★", "♦", "●", "▲"];
7
+ const KEYS = ["1", "2", "3", "4"];
8
+ function pickRandom(arr) {
9
+ return arr[Math.floor(Math.random() * arr.length)];
10
+ }
11
+ const handler = {
12
+ def: {
13
+ id: "copycat",
14
+ name: "Copycat",
15
+ description: "Remember and repeat the sequence!",
16
+ icon: "★",
17
+ },
18
+ start() {
19
+ return {
20
+ active: true,
21
+ gameId: "copycat",
22
+ phase: "intro",
23
+ score: 0,
24
+ round: 1,
25
+ maxRounds: 10,
26
+ buddyReaction: "Watch closely!",
27
+ data: {
28
+ sequence: [Math.floor(Math.random() * 4)],
29
+ showIndex: 0, // which symbol is being shown
30
+ playerIndex: 0, // which symbol player is inputting
31
+ subPhase: "showing", // "showing" | "input"
32
+ },
33
+ ticksInPhase: 0,
34
+ };
35
+ },
36
+ tick(game) {
37
+ game = { ...game, ticksInPhase: game.ticksInPhase + 1 };
38
+ if (game.phase === "intro" && game.ticksInPhase > 3) {
39
+ return { ...game, phase: "playing", ticksInPhase: 0, data: { ...game.data, subPhase: "showing", showIndex: 0 } };
40
+ }
41
+ if (game.phase === "playing" && game.data.subPhase === "showing") {
42
+ const seq = game.data.sequence;
43
+ const showIndex = game.data.showIndex;
44
+ // Show each symbol for 2 ticks (1.6s)
45
+ if (game.ticksInPhase > 0 && game.ticksInPhase % 2 === 0) {
46
+ const nextIndex = showIndex + 1;
47
+ if (nextIndex >= seq.length) {
48
+ // Done showing, switch to input
49
+ return { ...game, ticksInPhase: 0, data: { ...game.data, subPhase: "input", playerIndex: 0 }, buddyReaction: "Your turn!" };
50
+ }
51
+ return { ...game, data: { ...game.data, showIndex: nextIndex } };
52
+ }
53
+ }
54
+ if (game.phase === "result") {
55
+ return game;
56
+ }
57
+ return game;
58
+ },
59
+ handleInput(key, game) {
60
+ if (game.phase === "intro") {
61
+ if (key === "\r" || key === "\n") {
62
+ return { ...game, phase: "playing", ticksInPhase: 0 };
63
+ }
64
+ return game;
65
+ }
66
+ if (game.phase === "result") {
67
+ if (key === "\r" || key === "\n") {
68
+ return { ...game, ticksInPhase: 99 }; // Signal to finish
69
+ }
70
+ return game;
71
+ }
72
+ if (game.phase !== "playing" || game.data.subPhase !== "input")
73
+ return game;
74
+ const keyIndex = KEYS.indexOf(key);
75
+ if (keyIndex === -1)
76
+ return game;
77
+ const seq = game.data.sequence;
78
+ const playerIndex = game.data.playerIndex;
79
+ if (keyIndex !== seq[playerIndex]) {
80
+ // Wrong! Game over
81
+ return {
82
+ ...game,
83
+ phase: "result",
84
+ ticksInPhase: 0,
85
+ buddyReaction: (0, types_1.getReaction)(game.score, game.maxRounds),
86
+ };
87
+ }
88
+ // Correct
89
+ const nextPlayerIndex = playerIndex + 1;
90
+ if (nextPlayerIndex >= seq.length) {
91
+ // Completed the sequence! Next round
92
+ const newSeq = [...seq, Math.floor(Math.random() * 4)];
93
+ const newScore = game.score + 1;
94
+ if (newScore >= game.maxRounds) {
95
+ return { ...game, score: newScore, phase: "result", ticksInPhase: 0, buddyReaction: "PERFECT! You got them all!" };
96
+ }
97
+ return {
98
+ ...game,
99
+ score: newScore,
100
+ round: game.round + 1,
101
+ ticksInPhase: 0,
102
+ buddyReaction: newScore >= 5 ? "Wow, you remembered that?!" : "Nice!",
103
+ data: { ...game.data, sequence: newSeq, showIndex: 0, subPhase: "showing", playerIndex: 0 },
104
+ };
105
+ }
106
+ return { ...game, data: { ...game.data, playerIndex: nextPlayerIndex } };
107
+ },
108
+ render(game, buddyName) {
109
+ const lines = [];
110
+ lines.push("");
111
+ lines.push(` ${screen_1.ansi.bold}Copycat${screen_1.ansi.reset} ${screen_1.ansi.dim}Round ${game.round} Score: ${game.score}${screen_1.ansi.reset}`);
112
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(40)}${screen_1.ansi.reset}`);
113
+ lines.push("");
114
+ if (game.phase === "intro") {
115
+ lines.push(` ${buddyName}: "Watch closely and repeat the sequence!"`);
116
+ lines.push("");
117
+ lines.push(` ${screen_1.ansi.dim}Keys: [1]${SYMBOLS[0]} [2]${SYMBOLS[1]} [3]${SYMBOLS[2]} [4]${SYMBOLS[3]}${screen_1.ansi.reset}`);
118
+ lines.push("");
119
+ lines.push(` ${screen_1.ansi.dim}Press Enter to start...${screen_1.ansi.reset}`);
120
+ }
121
+ else if (game.phase === "playing") {
122
+ const seq = game.data.sequence;
123
+ if (game.data.subPhase === "showing") {
124
+ lines.push(` ${buddyName}: "Watch closely!"`);
125
+ lines.push("");
126
+ const showIndex = game.data.showIndex;
127
+ const symbol = SYMBOLS[seq[showIndex]];
128
+ lines.push(` ${screen_1.ansi.bold}${screen_1.ansi.colors.yellow} ${symbol}${screen_1.ansi.reset}`);
129
+ lines.push("");
130
+ lines.push(` ${screen_1.ansi.dim}Showing ${showIndex + 1}/${seq.length}...${screen_1.ansi.reset}`);
131
+ }
132
+ else {
133
+ const playerIndex = game.data.playerIndex;
134
+ lines.push(` ${buddyName}: "${game.buddyReaction}"`);
135
+ lines.push("");
136
+ lines.push(` ${screen_1.ansi.dim}Keys: [1]${SYMBOLS[0]} [2]${SYMBOLS[1]} [3]${SYMBOLS[2]} [4]${SYMBOLS[3]}${screen_1.ansi.reset}`);
137
+ lines.push("");
138
+ lines.push(` ${screen_1.ansi.dim}Input ${playerIndex + 1}/${seq.length}...${screen_1.ansi.reset}`);
139
+ }
140
+ }
141
+ else {
142
+ lines.push(` ${buddyName}: "${game.buddyReaction}"`);
143
+ lines.push("");
144
+ lines.push(` ${screen_1.ansi.bold}Score: ${game.score}${screen_1.ansi.reset}`);
145
+ lines.push("");
146
+ lines.push(` ${screen_1.ansi.dim}Press Enter to finish${screen_1.ansi.reset}`);
147
+ }
148
+ lines.push("");
149
+ return lines.join("\n");
150
+ },
151
+ };
152
+ (0, registry_1.registerGame)(handler);
153
+ //# sourceMappingURL=copycat.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const types_1 = require("./types");
4
+ const registry_1 = require("./registry");
5
+ const screen_1 = require("../rendering/screen");
6
+ const FIELD_WIDTH = 30;
7
+ const CATCH_ZONE_START = 12;
8
+ const CATCH_ZONE_END = 18;
9
+ const handler = {
10
+ def: {
11
+ id: "fetch",
12
+ name: "Fetch",
13
+ description: "Catch the ball at the right moment!",
14
+ icon: "◎",
15
+ },
16
+ start() {
17
+ return {
18
+ active: true,
19
+ gameId: "fetch",
20
+ phase: "intro",
21
+ score: 0,
22
+ round: 1,
23
+ maxRounds: 5,
24
+ buddyReaction: "Throw it! Throw it!",
25
+ data: {
26
+ ballPos: 0,
27
+ ballSpeed: 1,
28
+ ballDirection: 1,
29
+ caught: false,
30
+ roundResult: "", // "perfect" | "good" | "miss" | ""
31
+ },
32
+ ticksInPhase: 0,
33
+ };
34
+ },
35
+ tick(game) {
36
+ game = { ...game, ticksInPhase: game.ticksInPhase + 1 };
37
+ if (game.phase === "intro" && game.ticksInPhase > 3) {
38
+ return { ...game, phase: "playing", ticksInPhase: 0 };
39
+ }
40
+ if (game.phase === "playing" && !game.data.caught) {
41
+ // Move ball
42
+ let pos = game.data.ballPos;
43
+ const speed = game.data.ballSpeed;
44
+ const dir = game.data.ballDirection;
45
+ pos += speed * dir;
46
+ // Bounce at edges
47
+ let newDir = dir;
48
+ if (pos >= FIELD_WIDTH - 1) {
49
+ pos = FIELD_WIDTH - 1;
50
+ newDir = -1;
51
+ }
52
+ if (pos <= 0) {
53
+ pos = 0;
54
+ newDir = 1;
55
+ }
56
+ return { ...game, data: { ...game.data, ballPos: pos, ballDirection: newDir } };
57
+ }
58
+ // After catching/missing, advance to next round
59
+ if (game.phase === "playing" && game.data.caught && game.ticksInPhase > 3) {
60
+ if (game.round >= game.maxRounds) {
61
+ return { ...game, phase: "result", ticksInPhase: 0, buddyReaction: (0, types_1.getReaction)(game.score, game.maxRounds * 2) };
62
+ }
63
+ // Next round — faster ball
64
+ return {
65
+ ...game,
66
+ round: game.round + 1,
67
+ ticksInPhase: 0,
68
+ data: {
69
+ ballPos: 0,
70
+ ballSpeed: 1 + game.round * 0.5,
71
+ ballDirection: 1,
72
+ caught: false,
73
+ roundResult: "",
74
+ },
75
+ buddyReaction: "Again! Again!",
76
+ };
77
+ }
78
+ return game;
79
+ },
80
+ handleInput(key, game) {
81
+ if (game.phase === "intro") {
82
+ if (key === "\r" || key === "\n")
83
+ return { ...game, phase: "playing", ticksInPhase: 0 };
84
+ return game;
85
+ }
86
+ if (game.phase === "result") {
87
+ if (key === "\r" || key === "\n")
88
+ return { ...game, ticksInPhase: 99 };
89
+ return game;
90
+ }
91
+ if (game.data.caught)
92
+ return game;
93
+ // Space to catch
94
+ if (key === " ") {
95
+ const pos = game.data.ballPos;
96
+ const center = (CATCH_ZONE_START + CATCH_ZONE_END) / 2;
97
+ const dist = Math.abs(pos - center);
98
+ let result;
99
+ let points;
100
+ let reaction;
101
+ if (dist <= 1) {
102
+ result = "perfect";
103
+ points = 2;
104
+ reaction = "PERFECT CATCH!";
105
+ }
106
+ else if (pos >= CATCH_ZONE_START && pos <= CATCH_ZONE_END) {
107
+ result = "good";
108
+ points = 1;
109
+ reaction = "Nice catch!";
110
+ }
111
+ else {
112
+ result = "miss";
113
+ points = 0;
114
+ reaction = "Missed it! Aww...";
115
+ }
116
+ return {
117
+ ...game,
118
+ score: game.score + points,
119
+ ticksInPhase: 0,
120
+ data: { ...game.data, caught: true, roundResult: result },
121
+ buddyReaction: reaction,
122
+ };
123
+ }
124
+ return game;
125
+ },
126
+ render(game, buddyName) {
127
+ const lines = [];
128
+ lines.push("");
129
+ lines.push(` ${screen_1.ansi.bold}Fetch${screen_1.ansi.reset} ${screen_1.ansi.dim}Round ${game.round}/${game.maxRounds} Points: ${game.score}${screen_1.ansi.reset}`);
130
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(40)}${screen_1.ansi.reset}`);
131
+ lines.push("");
132
+ if (game.phase === "intro") {
133
+ lines.push(` ${buddyName}: "${game.buddyReaction}"`);
134
+ lines.push("");
135
+ lines.push(` ${screen_1.ansi.dim}Press Space when the ball is in the [===] zone!${screen_1.ansi.reset}`);
136
+ lines.push("");
137
+ lines.push(` ${screen_1.ansi.dim}Press Enter to start...${screen_1.ansi.reset}`);
138
+ }
139
+ else if (game.phase === "playing") {
140
+ lines.push(` ${buddyName}: "${game.buddyReaction}"`);
141
+ lines.push("");
142
+ // Draw the field
143
+ const pos = Math.round(game.data.ballPos);
144
+ let field = "";
145
+ for (let i = 0; i < FIELD_WIDTH; i++) {
146
+ if (i === pos && !game.data.caught) {
147
+ field += `${screen_1.ansi.bold}o${screen_1.ansi.reset}`;
148
+ }
149
+ else if (i >= CATCH_ZONE_START && i <= CATCH_ZONE_END) {
150
+ field += `${screen_1.ansi.colors.green}=${screen_1.ansi.reset}`;
151
+ }
152
+ else {
153
+ field += `${screen_1.ansi.dim}-${screen_1.ansi.reset}`;
154
+ }
155
+ }
156
+ lines.push(` ${field}`);
157
+ lines.push("");
158
+ if (!game.data.caught) {
159
+ lines.push(` ${screen_1.ansi.dim}Press Space to catch!${screen_1.ansi.reset}`);
160
+ }
161
+ else {
162
+ const result = game.data.roundResult;
163
+ const color = result === "perfect" ? screen_1.ansi.colors.yellow : result === "good" ? screen_1.ansi.colors.green : screen_1.ansi.colors.red;
164
+ lines.push(` ${color}${result.toUpperCase()}!${screen_1.ansi.reset}`);
165
+ }
166
+ }
167
+ else {
168
+ lines.push(` ${buddyName}: "${game.buddyReaction}"`);
169
+ lines.push("");
170
+ lines.push(` ${screen_1.ansi.bold}Total Points: ${game.score}/${game.maxRounds * 2}${screen_1.ansi.reset}`);
171
+ lines.push("");
172
+ lines.push(` ${screen_1.ansi.dim}Press Enter to finish${screen_1.ansi.reset}`);
173
+ }
174
+ lines.push("");
175
+ return lines.join("\n");
176
+ },
177
+ };
178
+ (0, registry_1.registerGame)(handler);
179
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=moodmatch.d.ts.map