brainrot-cli 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.
Files changed (162) hide show
  1. package/README.md +372 -0
  2. package/dist/AchievementNotification.d.ts +28 -0
  3. package/dist/AchievementNotification.d.ts.map +1 -0
  4. package/dist/AchievementNotification.js +74 -0
  5. package/dist/AchievementNotification.js.map +1 -0
  6. package/dist/GameSelector.d.ts +25 -0
  7. package/dist/GameSelector.d.ts.map +1 -0
  8. package/dist/GameSelector.js +105 -0
  9. package/dist/GameSelector.js.map +1 -0
  10. package/dist/HelpOverlay.d.ts +15 -0
  11. package/dist/HelpOverlay.d.ts.map +1 -0
  12. package/dist/HelpOverlay.js +134 -0
  13. package/dist/HelpOverlay.js.map +1 -0
  14. package/dist/Layout.d.ts +49 -0
  15. package/dist/Layout.d.ts.map +1 -0
  16. package/dist/Layout.js +83 -0
  17. package/dist/Layout.js.map +1 -0
  18. package/dist/Leaderboard.d.ts +46 -0
  19. package/dist/Leaderboard.d.ts.map +1 -0
  20. package/dist/Leaderboard.js +68 -0
  21. package/dist/Leaderboard.js.map +1 -0
  22. package/dist/LogViewer.d.ts +33 -0
  23. package/dist/LogViewer.d.ts.map +1 -0
  24. package/dist/LogViewer.js +179 -0
  25. package/dist/LogViewer.js.map +1 -0
  26. package/dist/LoopAlertOverlay.d.ts +15 -0
  27. package/dist/LoopAlertOverlay.d.ts.map +1 -0
  28. package/dist/LoopAlertOverlay.js +17 -0
  29. package/dist/LoopAlertOverlay.js.map +1 -0
  30. package/dist/LoopManagementPanel.d.ts +44 -0
  31. package/dist/LoopManagementPanel.d.ts.map +1 -0
  32. package/dist/LoopManagementPanel.js +220 -0
  33. package/dist/LoopManagementPanel.js.map +1 -0
  34. package/dist/SettingsMenu.d.ts +22 -0
  35. package/dist/SettingsMenu.d.ts.map +1 -0
  36. package/dist/SettingsMenu.js +367 -0
  37. package/dist/SettingsMenu.js.map +1 -0
  38. package/dist/SplitPane.d.ts +63 -0
  39. package/dist/SplitPane.d.ts.map +1 -0
  40. package/dist/SplitPane.js +104 -0
  41. package/dist/SplitPane.js.map +1 -0
  42. package/dist/StatsMenu.d.ts +15 -0
  43. package/dist/StatsMenu.d.ts.map +1 -0
  44. package/dist/StatsMenu.js +230 -0
  45. package/dist/StatsMenu.js.map +1 -0
  46. package/dist/StatusBar.d.ts +58 -0
  47. package/dist/StatusBar.d.ts.map +1 -0
  48. package/dist/StatusBar.js +106 -0
  49. package/dist/StatusBar.js.map +1 -0
  50. package/dist/__tests__/ralph-loop-parser.test.d.ts +2 -0
  51. package/dist/__tests__/ralph-loop-parser.test.d.ts.map +1 -0
  52. package/dist/__tests__/ralph-loop-parser.test.js +143 -0
  53. package/dist/__tests__/ralph-loop-parser.test.js.map +1 -0
  54. package/dist/claude-code-process.d.ts +76 -0
  55. package/dist/claude-code-process.d.ts.map +1 -0
  56. package/dist/claude-code-process.js +221 -0
  57. package/dist/claude-code-process.js.map +1 -0
  58. package/dist/cli.d.ts +42 -0
  59. package/dist/cli.d.ts.map +1 -0
  60. package/dist/cli.js +265 -0
  61. package/dist/cli.js.map +1 -0
  62. package/dist/config.d.ts +206 -0
  63. package/dist/config.d.ts.map +1 -0
  64. package/dist/config.js +270 -0
  65. package/dist/config.js.map +1 -0
  66. package/dist/game-types.d.ts +177 -0
  67. package/dist/game-types.d.ts.map +1 -0
  68. package/dist/game-types.js +55 -0
  69. package/dist/game-types.js.map +1 -0
  70. package/dist/games/MinesweeperGame.d.ts +15 -0
  71. package/dist/games/MinesweeperGame.d.ts.map +1 -0
  72. package/dist/games/MinesweeperGame.js +555 -0
  73. package/dist/games/MinesweeperGame.js.map +1 -0
  74. package/dist/games/PongGame.d.ts +15 -0
  75. package/dist/games/PongGame.d.ts.map +1 -0
  76. package/dist/games/PongGame.js +379 -0
  77. package/dist/games/PongGame.js.map +1 -0
  78. package/dist/games/SnakeGame.d.ts +15 -0
  79. package/dist/games/SnakeGame.d.ts.map +1 -0
  80. package/dist/games/SnakeGame.js +333 -0
  81. package/dist/games/SnakeGame.js.map +1 -0
  82. package/dist/games/TetrisGame.d.ts +15 -0
  83. package/dist/games/TetrisGame.d.ts.map +1 -0
  84. package/dist/games/TetrisGame.js +654 -0
  85. package/dist/games/TetrisGame.js.map +1 -0
  86. package/dist/games/index.d.ts +23 -0
  87. package/dist/games/index.d.ts.map +1 -0
  88. package/dist/games/index.js +47 -0
  89. package/dist/games/index.js.map +1 -0
  90. package/dist/high-scores.d.ts +57 -0
  91. package/dist/high-scores.d.ts.map +1 -0
  92. package/dist/high-scores.js +230 -0
  93. package/dist/high-scores.js.map +1 -0
  94. package/dist/index.d.ts +3 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +264 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/ralph-loop-parser.d.ts +58 -0
  99. package/dist/ralph-loop-parser.d.ts.map +1 -0
  100. package/dist/ralph-loop-parser.js +315 -0
  101. package/dist/ralph-loop-parser.js.map +1 -0
  102. package/dist/stats.d.ts +142 -0
  103. package/dist/stats.d.ts.map +1 -0
  104. package/dist/stats.js +521 -0
  105. package/dist/stats.js.map +1 -0
  106. package/dist/styled-components.d.ts +231 -0
  107. package/dist/styled-components.d.ts.map +1 -0
  108. package/dist/styled-components.js +192 -0
  109. package/dist/styled-components.js.map +1 -0
  110. package/dist/theme.d.ts +301 -0
  111. package/dist/theme.d.ts.map +1 -0
  112. package/dist/theme.js +372 -0
  113. package/dist/theme.js.map +1 -0
  114. package/dist/themes.d.ts +117 -0
  115. package/dist/themes.d.ts.map +1 -0
  116. package/dist/themes.js +296 -0
  117. package/dist/themes.js.map +1 -0
  118. package/dist/ui/index.d.ts +13 -0
  119. package/dist/ui/index.d.ts.map +1 -0
  120. package/dist/ui/index.js +29 -0
  121. package/dist/ui/index.js.map +1 -0
  122. package/dist/use-claude-code.d.ts +30 -0
  123. package/dist/use-claude-code.d.ts.map +1 -0
  124. package/dist/use-claude-code.js +84 -0
  125. package/dist/use-claude-code.js.map +1 -0
  126. package/dist/use-config.d.ts +58 -0
  127. package/dist/use-config.d.ts.map +1 -0
  128. package/dist/use-config.js +113 -0
  129. package/dist/use-config.js.map +1 -0
  130. package/dist/use-game-loop.d.ts +47 -0
  131. package/dist/use-game-loop.d.ts.map +1 -0
  132. package/dist/use-game-loop.js +136 -0
  133. package/dist/use-game-loop.js.map +1 -0
  134. package/dist/use-high-scores.d.ts +41 -0
  135. package/dist/use-high-scores.d.ts.map +1 -0
  136. package/dist/use-high-scores.js +94 -0
  137. package/dist/use-high-scores.js.map +1 -0
  138. package/dist/use-layout-state.d.ts +77 -0
  139. package/dist/use-layout-state.d.ts.map +1 -0
  140. package/dist/use-layout-state.js +160 -0
  141. package/dist/use-layout-state.js.map +1 -0
  142. package/dist/use-ralph-loop.d.ts +41 -0
  143. package/dist/use-ralph-loop.d.ts.map +1 -0
  144. package/dist/use-ralph-loop.js +106 -0
  145. package/dist/use-ralph-loop.js.map +1 -0
  146. package/dist/use-spinner.d.ts +46 -0
  147. package/dist/use-spinner.d.ts.map +1 -0
  148. package/dist/use-spinner.js +71 -0
  149. package/dist/use-spinner.js.map +1 -0
  150. package/dist/use-stats.d.ts +59 -0
  151. package/dist/use-stats.d.ts.map +1 -0
  152. package/dist/use-stats.js +150 -0
  153. package/dist/use-stats.js.map +1 -0
  154. package/dist/use-terminal-size.d.ts +29 -0
  155. package/dist/use-terminal-size.d.ts.map +1 -0
  156. package/dist/use-terminal-size.js +48 -0
  157. package/dist/use-terminal-size.js.map +1 -0
  158. package/dist/useTheme.d.ts +76 -0
  159. package/dist/useTheme.d.ts.map +1 -0
  160. package/dist/useTheme.js +136 -0
  161. package/dist/useTheme.js.map +1 -0
  162. package/package.json +58 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Game Framework Types and Interfaces
3
+ *
4
+ * Provides common types for building terminal games that run
5
+ * alongside Claude Code work.
6
+ */
7
+ /**
8
+ * Convert direction to vector
9
+ */
10
+ export function directionToVector(direction) {
11
+ switch (direction) {
12
+ case "up":
13
+ return { x: 0, y: -1 };
14
+ case "down":
15
+ return { x: 0, y: 1 };
16
+ case "left":
17
+ return { x: -1, y: 0 };
18
+ case "right":
19
+ return { x: 1, y: 0 };
20
+ }
21
+ }
22
+ /**
23
+ * Check if two points are equal
24
+ */
25
+ export function pointsEqual(a, b) {
26
+ return a.x === b.x && a.y === b.y;
27
+ }
28
+ /**
29
+ * Add two vectors
30
+ */
31
+ export function addVectors(a, b) {
32
+ return { x: a.x + b.x, y: a.y + b.y };
33
+ }
34
+ /**
35
+ * Create a game input object from ink's useInput callback params
36
+ */
37
+ export function createGameInput(input, key) {
38
+ return {
39
+ key: input,
40
+ ctrl: key.ctrl,
41
+ shift: key.shift,
42
+ meta: key.meta,
43
+ upArrow: key.upArrow,
44
+ downArrow: key.downArrow,
45
+ leftArrow: key.leftArrow,
46
+ rightArrow: key.rightArrow,
47
+ return: key.return,
48
+ escape: key.escape,
49
+ space: input === " ",
50
+ tab: key.tab,
51
+ backspace: key.backspace,
52
+ delete: key.delete,
53
+ };
54
+ }
55
+ //# sourceMappingURL=game-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-types.js","sourceRoot":"","sources":["../src/game-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6JH;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAoB;IACpD,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,IAAI;YACP,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM;YACT,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,KAAK,MAAM;YACT,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,CAAQ,EAAE,CAAQ;IAC5C,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,CAAW,EAAE,CAAW;IACjD,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAa,EACb,GAaC;IAED,OAAO;QACL,GAAG,EAAE,KAAK;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,KAAK,KAAK,GAAG;QACpB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,MAAM,EAAE,GAAG,CAAC,MAAM;KACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Minesweeper Game
3
+ *
4
+ * Classic Minesweeper puzzle game implemented with the game framework.
5
+ * Features: Multiple difficulty levels, flag mechanics, timer, mine count.
6
+ */
7
+ import type { GameComponentProps, GameInfo } from "../game-types.js";
8
+ /** Minesweeper game metadata */
9
+ export declare const minesweeperGameInfo: GameInfo;
10
+ /**
11
+ * Minesweeper game component
12
+ */
13
+ export declare function MinesweeperGame({ hasFocus, onExit, loopAttention, onLoopAlertDismiss, onGameStateChange }: GameComponentProps): import("react/jsx-runtime").JSX.Element;
14
+ export default MinesweeperGame;
15
+ //# sourceMappingURL=MinesweeperGame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MinesweeperGame.d.ts","sourceRoot":"","sources":["../../src/games/MinesweeperGame.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAS,MAAM,kBAAkB,CAAC;AAO5E,gCAAgC;AAChC,eAAO,MAAM,mBAAmB,EAAE,QAOjC,CAAC;AA4YF;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,EAAE,kBAAkB,2CAqd7H;AAED,eAAe,eAAe,CAAC"}
@@ -0,0 +1,555 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Minesweeper Game
4
+ *
5
+ * Classic Minesweeper puzzle game implemented with the game framework.
6
+ * Features: Multiple difficulty levels, flag mechanics, timer, mine count.
7
+ */
8
+ import { Box, Text, useInput } from "ink";
9
+ import { useState, useCallback, useRef, useEffect } from "react";
10
+ import { useGameLoop } from "../use-game-loop.js";
11
+ import { useBestTimes } from "../use-high-scores.js";
12
+ import { useGameSession } from "../use-stats.js";
13
+ import { Leaderboard, formatTime } from "../Leaderboard.js";
14
+ import { LoopAlertOverlay } from "../LoopAlertOverlay.js";
15
+ /** Minesweeper game metadata */
16
+ export const minesweeperGameInfo = {
17
+ id: "minesweeper",
18
+ name: "Minesweeper",
19
+ description: "Classic Minesweeper - find mines without triggering them!",
20
+ controls: "Arrows to move, Space to reveal, F to flag, D for difficulty",
21
+ minWidth: 30,
22
+ minHeight: 15,
23
+ };
24
+ const DIFFICULTIES = [
25
+ { name: "Easy", width: 9, height: 9, mines: 10 },
26
+ { name: "Medium", width: 16, height: 16, mines: 40 },
27
+ { name: "Hard", width: 20, height: 12, mines: 60 },
28
+ ];
29
+ /** Create an empty board */
30
+ function createEmptyBoard(width, height) {
31
+ return Array(height)
32
+ .fill(null)
33
+ .map(() => Array(width)
34
+ .fill(null)
35
+ .map(() => ({
36
+ hasMine: false,
37
+ revealed: false,
38
+ flagged: false,
39
+ adjacentMines: 0,
40
+ })));
41
+ }
42
+ /** Place mines on the board, avoiding the safe zone around first click */
43
+ function placeMines(board, mineCount, safePoint) {
44
+ const height = board.length;
45
+ const width = board[0].length;
46
+ const newBoard = board.map((row) => row.map((cell) => ({ ...cell })));
47
+ let placed = 0;
48
+ while (placed < mineCount) {
49
+ const x = Math.floor(Math.random() * width);
50
+ const y = Math.floor(Math.random() * height);
51
+ // Skip if already has mine or too close to safe point
52
+ if (newBoard[y][x].hasMine)
53
+ continue;
54
+ if (Math.abs(x - safePoint.x) <= 1 && Math.abs(y - safePoint.y) <= 1) {
55
+ continue;
56
+ }
57
+ newBoard[y][x].hasMine = true;
58
+ placed++;
59
+ }
60
+ // Calculate adjacent mine counts
61
+ for (let y = 0; y < height; y++) {
62
+ for (let x = 0; x < width; x++) {
63
+ if (newBoard[y][x].hasMine)
64
+ continue;
65
+ let count = 0;
66
+ for (let dy = -1; dy <= 1; dy++) {
67
+ for (let dx = -1; dx <= 1; dx++) {
68
+ const ny = y + dy;
69
+ const nx = x + dx;
70
+ if (ny >= 0 && ny < height && nx >= 0 && nx < width) {
71
+ if (newBoard[ny][nx].hasMine)
72
+ count++;
73
+ }
74
+ }
75
+ }
76
+ newBoard[y][x].adjacentMines = count;
77
+ }
78
+ }
79
+ return newBoard;
80
+ }
81
+ /** Reveal a cell and cascade if empty */
82
+ function revealCell(board, point) {
83
+ const height = board.length;
84
+ const width = board[0].length;
85
+ const newBoard = board.map((row) => row.map((cell) => ({ ...cell })));
86
+ const reveal = (x, y) => {
87
+ if (x < 0 || x >= width || y < 0 || y >= height)
88
+ return;
89
+ if (newBoard[y][x].revealed || newBoard[y][x].flagged)
90
+ return;
91
+ newBoard[y][x].revealed = true;
92
+ // If empty cell (no adjacent mines), reveal neighbors
93
+ if (newBoard[y][x].adjacentMines === 0 && !newBoard[y][x].hasMine) {
94
+ for (let dy = -1; dy <= 1; dy++) {
95
+ for (let dx = -1; dx <= 1; dx++) {
96
+ reveal(x + dx, y + dy);
97
+ }
98
+ }
99
+ }
100
+ };
101
+ reveal(point.x, point.y);
102
+ return newBoard;
103
+ }
104
+ /** Reveal all mines (when game is lost) */
105
+ function revealAllMines(board) {
106
+ return board.map((row) => row.map((cell) => ({
107
+ ...cell,
108
+ revealed: cell.hasMine ? true : cell.revealed,
109
+ })));
110
+ }
111
+ /** Check if the game is won */
112
+ function checkWin(board) {
113
+ for (const row of board) {
114
+ for (const cell of row) {
115
+ // All non-mine cells must be revealed
116
+ if (!cell.hasMine && !cell.revealed)
117
+ return false;
118
+ }
119
+ }
120
+ return true;
121
+ }
122
+ /** Count flagged cells */
123
+ function countFlags(board) {
124
+ let count = 0;
125
+ for (const row of board) {
126
+ for (const cell of row) {
127
+ if (cell.flagged)
128
+ count++;
129
+ }
130
+ }
131
+ return count;
132
+ }
133
+ /** Create initial game state */
134
+ function createInitialState(difficultyIndex) {
135
+ const diff = DIFFICULTIES[difficultyIndex];
136
+ return {
137
+ board: createEmptyBoard(diff.width, diff.height),
138
+ cursor: { x: Math.floor(diff.width / 2), y: Math.floor(diff.height / 2) },
139
+ status: "playing",
140
+ minesRemaining: diff.mines,
141
+ timeElapsed: 0,
142
+ difficultyIndex,
143
+ firstClick: true,
144
+ leaderboardPosition: 0,
145
+ };
146
+ }
147
+ /** Get cell display character */
148
+ function getCellDisplay(cell, isCursor, gameOver) {
149
+ const bgColor = isCursor ? "blue" : undefined;
150
+ if (!cell.revealed) {
151
+ if (cell.flagged) {
152
+ // Show incorrect flags on game over
153
+ if (gameOver && !cell.hasMine) {
154
+ return { char: "✗", color: "red", bgColor };
155
+ }
156
+ return { char: "⚑", color: "red", bgColor };
157
+ }
158
+ return { char: "■", color: "gray", bgColor };
159
+ }
160
+ if (cell.hasMine) {
161
+ return { char: "💣", color: "red", bgColor };
162
+ }
163
+ if (cell.adjacentMines === 0) {
164
+ return { char: " ", bgColor };
165
+ }
166
+ const colors = {
167
+ 1: "blue",
168
+ 2: "green",
169
+ 3: "red",
170
+ 4: "#000080", // Dark blue
171
+ 5: "#800000", // Maroon
172
+ 6: "cyan",
173
+ 7: "black",
174
+ 8: "gray",
175
+ };
176
+ return {
177
+ char: cell.adjacentMines.toString(),
178
+ color: colors[cell.adjacentMines] || "white",
179
+ bgColor,
180
+ };
181
+ }
182
+ /** Game board component */
183
+ function GameBoard({ board, cursor, status, }) {
184
+ const width = board[0].length;
185
+ const gameOver = status === "won" || status === "lost";
186
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: "┌" + "──".repeat(width) + "┐" }), board.map((row, y) => (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "\u2502" }), row.map((cell, x) => {
187
+ const isCursor = x === cursor.x && y === cursor.y;
188
+ const display = getCellDisplay(cell, isCursor, gameOver);
189
+ return (_jsx(Text, { color: display.color, backgroundColor: display.bgColor, children: display.char.length === 1 ? display.char + " " : display.char }, x));
190
+ }), _jsx(Text, { color: "gray", children: "\u2502" })] }, y))), _jsx(Text, { color: "gray", children: "└" + "──".repeat(width) + "┘" })] }));
191
+ }
192
+ /** HUD component */
193
+ function GameHUD({ minesRemaining, timeElapsed, difficulty, bestTime, fps, }) {
194
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Box, { borderStyle: "single", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Mines" }), _jsx(Text, { color: "red", children: minesRemaining.toString().padStart(3, " ") })] }), _jsxs(Box, { borderStyle: "single", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Time" }), _jsx(Text, { color: "yellow", children: formatTime(timeElapsed) })] }), _jsxs(Box, { borderStyle: "single", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Level" }), _jsx(Text, { color: "cyan", children: difficulty })] }), _jsxs(Box, { borderStyle: "single", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { dimColor: true, children: "Best" }), _jsx(Text, { dimColor: true, children: formatTime(bestTime) })] }), _jsxs(Text, { dimColor: true, children: [fps, " FPS"] })] }));
195
+ }
196
+ /** Difficulty selector */
197
+ function DifficultySelector({ selectedIndex, bestTimes, }) {
198
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", padding: 2, children: [_jsx(Text, { bold: true, color: "cyan", children: "Select Difficulty" }), _jsx(Text, { children: " " }), DIFFICULTIES.map((diff, i) => (_jsx(Box, { children: _jsxs(Text, { color: i === selectedIndex ? "green" : "white", bold: i === selectedIndex, children: [i === selectedIndex ? "▶ " : " ", diff.name.padEnd(8), " ", diff.width, "x", diff.height, " (", diff.mines, " ", "mines) Best: ", formatTime(bestTimes[i])] }) }, i))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "\u2191\u2193 to select, Enter to start" })] }));
199
+ }
200
+ /** Game over overlay */
201
+ function GameOverOverlay({ won, timeElapsed, leaderboardPosition, }) {
202
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 1, children: [_jsx(Text, { bold: true, color: won ? "green" : "red", children: won ? "YOU WIN!" : "GAME OVER" }), _jsxs(Text, { children: ["Time: ", _jsx(Text, { color: "yellow", children: formatTime(timeElapsed) })] }), won && leaderboardPosition > 0 && (_jsxs(Box, { flexDirection: "column", alignItems: "center", borderStyle: "double", borderColor: "green", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "green", children: "NEW BEST TIME!" }), _jsxs(Text, { children: ["You ranked", " ", _jsx(Text, { color: "cyan", children: leaderboardPosition === 1
203
+ ? "1st"
204
+ : leaderboardPosition === 2
205
+ ? "2nd"
206
+ : leaderboardPosition === 3
207
+ ? "3rd"
208
+ : `${leaderboardPosition}th` }), " ", "place!"] })] })), _jsx(Text, { dimColor: true, children: "R to restart | H for leaderboard | D to change difficulty" })] }));
209
+ }
210
+ /** Get game ID for a specific difficulty */
211
+ function getGameIdForDifficulty(difficultyIndex) {
212
+ const diffName = DIFFICULTIES[difficultyIndex].name.toLowerCase();
213
+ return `minesweeper-${diffName}`;
214
+ }
215
+ /**
216
+ * Minesweeper game component
217
+ */
218
+ export function MinesweeperGame({ hasFocus, onExit, loopAttention, onLoopAlertDismiss, onGameStateChange }) {
219
+ const [state, setState] = useState(() => createInitialState(0));
220
+ const timerRef = useRef(null);
221
+ const [selectedDifficulty, setSelectedDifficulty] = useState(0);
222
+ const scoreSubmittedRef = useRef(false);
223
+ const statsSubmittedRef = useRef(false);
224
+ const [showLoopAlert, setShowLoopAlert] = useState(false);
225
+ const [wasPlayingBeforeAlert, setWasPlayingBeforeAlert] = useState(false);
226
+ // High score persistence for current difficulty
227
+ const { bestTime, leaderboard, submitTime } = useBestTimes(getGameIdForDifficulty(state.difficultyIndex));
228
+ // Stats tracking
229
+ const { startSession, endSession, isSessionActive } = useGameSession("minesweeper");
230
+ // Report game state changes to status bar
231
+ useEffect(() => {
232
+ // Map minesweeper-specific status to generic game status
233
+ const mappedStatus = state.status === "won" || state.status === "lost" ? "game_over" :
234
+ state.status === "selecting_difficulty" || state.status === "leaderboard" ? "menu" :
235
+ state.status;
236
+ onGameStateChange?.({
237
+ score: state.timeElapsed, // Use time as "score" for minesweeper
238
+ status: mappedStatus,
239
+ highScore: bestTime > 0 ? bestTime : null,
240
+ });
241
+ }, [state.timeElapsed, state.status, bestTime, onGameStateChange]);
242
+ // Auto-pause when loop needs attention
243
+ useEffect(() => {
244
+ if (loopAttention?.needsAttention && state.status === "playing") {
245
+ setWasPlayingBeforeAlert(true);
246
+ setShowLoopAlert(true);
247
+ setState((prev) => ({ ...prev, status: "paused" }));
248
+ }
249
+ else if (!loopAttention?.needsAttention && showLoopAlert) {
250
+ setShowLoopAlert(false);
251
+ // Auto-resume if we were playing before the alert
252
+ if (wasPlayingBeforeAlert) {
253
+ setState((prev) => {
254
+ if (prev.status === "paused") {
255
+ return { ...prev, status: "playing" };
256
+ }
257
+ return prev;
258
+ });
259
+ setWasPlayingBeforeAlert(false);
260
+ }
261
+ }
262
+ }, [loopAttention?.needsAttention, state.status, showLoopAlert, wasPlayingBeforeAlert]);
263
+ // Get best times for all difficulties for the selector
264
+ const { bestTime: easyBest } = useBestTimes("minesweeper-easy");
265
+ const { bestTime: mediumBest } = useBestTimes("minesweeper-medium");
266
+ const { bestTime: hardBest } = useBestTimes("minesweeper-hard");
267
+ const allBestTimes = [easyBest, mediumBest, hardBest];
268
+ // Start session when game starts
269
+ useEffect(() => {
270
+ if (state.status === "playing" && !isSessionActive) {
271
+ startSession();
272
+ }
273
+ }, [state.status, isSessionActive, startSession]);
274
+ // Submit time when game is won
275
+ useEffect(() => {
276
+ if (state.status === "won" && !scoreSubmittedRef.current && state.timeElapsed > 0) {
277
+ scoreSubmittedRef.current = true;
278
+ submitTime(state.timeElapsed, { difficulty: DIFFICULTIES[state.difficultyIndex].name }).then((position) => {
279
+ if (position > 0) {
280
+ setState((prev) => ({ ...prev, leaderboardPosition: position }));
281
+ }
282
+ });
283
+ }
284
+ }, [state.status, state.timeElapsed, state.difficultyIndex, submitTime]);
285
+ // Record stats when game ends
286
+ useEffect(() => {
287
+ if ((state.status === "won" || state.status === "lost") && !statsSubmittedRef.current) {
288
+ statsSubmittedRef.current = true;
289
+ const won = state.status === "won";
290
+ const customStats = {};
291
+ if (won && state.timeElapsed > 0) {
292
+ customStats.fastestWin = state.timeElapsed;
293
+ }
294
+ void endSession(0, won, Object.keys(customStats).length > 0 ? customStats : undefined);
295
+ }
296
+ }, [state.status, state.timeElapsed, endSession]);
297
+ // Game timer
298
+ useEffect(() => {
299
+ if (state.status === "playing" &&
300
+ !state.firstClick &&
301
+ hasFocus &&
302
+ !timerRef.current) {
303
+ timerRef.current = setInterval(() => {
304
+ setState((prev) => ({
305
+ ...prev,
306
+ timeElapsed: Math.min(prev.timeElapsed + 1, 999),
307
+ }));
308
+ }, 1000);
309
+ }
310
+ else if ((state.status !== "playing" || state.firstClick || !hasFocus) &&
311
+ timerRef.current) {
312
+ clearInterval(timerRef.current);
313
+ timerRef.current = null;
314
+ }
315
+ return () => {
316
+ if (timerRef.current) {
317
+ clearInterval(timerRef.current);
318
+ timerRef.current = null;
319
+ }
320
+ };
321
+ }, [state.status, state.firstClick, hasFocus]);
322
+ // Reveal cell
323
+ const reveal = useCallback(() => {
324
+ setState((prev) => {
325
+ if (prev.status !== "playing")
326
+ return prev;
327
+ const { x, y } = prev.cursor;
328
+ const cell = prev.board[y][x];
329
+ // Can't reveal flagged cells
330
+ if (cell.flagged)
331
+ return prev;
332
+ // Already revealed - do chord reveal if adjacent flags match adjacent mines
333
+ if (cell.revealed && cell.adjacentMines > 0) {
334
+ // Count adjacent flags
335
+ let flagCount = 0;
336
+ for (let dy = -1; dy <= 1; dy++) {
337
+ for (let dx = -1; dx <= 1; dx++) {
338
+ const ny = y + dy;
339
+ const nx = x + dx;
340
+ if (ny >= 0 &&
341
+ ny < prev.board.length &&
342
+ nx >= 0 &&
343
+ nx < prev.board[0].length) {
344
+ if (prev.board[ny][nx].flagged)
345
+ flagCount++;
346
+ }
347
+ }
348
+ }
349
+ // If flags match, reveal adjacent non-flagged cells
350
+ if (flagCount === cell.adjacentMines) {
351
+ let newBoard = prev.board.map((row) => row.map((c) => ({ ...c })));
352
+ let hitMine = false;
353
+ for (let dy = -1; dy <= 1; dy++) {
354
+ for (let dx = -1; dx <= 1; dx++) {
355
+ const ny = y + dy;
356
+ const nx = x + dx;
357
+ if (ny >= 0 &&
358
+ ny < newBoard.length &&
359
+ nx >= 0 &&
360
+ nx < newBoard[0].length) {
361
+ const neighbor = newBoard[ny][nx];
362
+ if (!neighbor.flagged && !neighbor.revealed) {
363
+ if (neighbor.hasMine) {
364
+ hitMine = true;
365
+ }
366
+ newBoard = revealCell(newBoard, { x: nx, y: ny });
367
+ }
368
+ }
369
+ }
370
+ }
371
+ if (hitMine) {
372
+ return {
373
+ ...prev,
374
+ board: revealAllMines(newBoard),
375
+ status: "lost",
376
+ };
377
+ }
378
+ if (checkWin(newBoard)) {
379
+ return {
380
+ ...prev,
381
+ board: newBoard,
382
+ status: "won",
383
+ };
384
+ }
385
+ return { ...prev, board: newBoard };
386
+ }
387
+ return prev;
388
+ }
389
+ // First click - generate mines
390
+ if (prev.firstClick) {
391
+ const diff = DIFFICULTIES[prev.difficultyIndex];
392
+ let newBoard = placeMines(prev.board, diff.mines, prev.cursor);
393
+ newBoard = revealCell(newBoard, prev.cursor);
394
+ return {
395
+ ...prev,
396
+ board: newBoard,
397
+ firstClick: false,
398
+ };
399
+ }
400
+ // Hit a mine
401
+ if (cell.hasMine) {
402
+ return {
403
+ ...prev,
404
+ board: revealAllMines(prev.board),
405
+ status: "lost",
406
+ };
407
+ }
408
+ // Reveal the cell
409
+ const newBoard = revealCell(prev.board, prev.cursor);
410
+ // Check win condition
411
+ if (checkWin(newBoard)) {
412
+ return {
413
+ ...prev,
414
+ board: newBoard,
415
+ status: "won",
416
+ };
417
+ }
418
+ return { ...prev, board: newBoard };
419
+ });
420
+ }, []);
421
+ // Toggle flag
422
+ const toggleFlag = useCallback(() => {
423
+ setState((prev) => {
424
+ if (prev.status !== "playing" || prev.firstClick)
425
+ return prev;
426
+ const { x, y } = prev.cursor;
427
+ const cell = prev.board[y][x];
428
+ // Can't flag revealed cells
429
+ if (cell.revealed)
430
+ return prev;
431
+ const newBoard = prev.board.map((row) => row.map((c) => ({ ...c })));
432
+ newBoard[y][x].flagged = !newBoard[y][x].flagged;
433
+ const diff = DIFFICULTIES[prev.difficultyIndex];
434
+ const flaggedCount = countFlags(newBoard);
435
+ return {
436
+ ...prev,
437
+ board: newBoard,
438
+ minesRemaining: diff.mines - flaggedCount,
439
+ };
440
+ });
441
+ }, []);
442
+ // Move cursor
443
+ const moveCursor = useCallback((dx, dy) => {
444
+ setState((prev) => {
445
+ if (prev.status === "selecting_difficulty")
446
+ return prev;
447
+ const width = prev.board[0].length;
448
+ const height = prev.board.length;
449
+ const newX = Math.max(0, Math.min(width - 1, prev.cursor.x + dx));
450
+ const newY = Math.max(0, Math.min(height - 1, prev.cursor.y + dy));
451
+ return {
452
+ ...prev,
453
+ cursor: { x: newX, y: newY },
454
+ };
455
+ });
456
+ }, []);
457
+ // Game loop for FPS display
458
+ const { loopInfo } = useGameLoop({
459
+ targetFps: 10, // Low FPS needed, mostly cursor-driven
460
+ isActive: hasFocus && state.status === "playing",
461
+ });
462
+ // Handle input
463
+ useInput((input, key) => {
464
+ if (!hasFocus)
465
+ return;
466
+ // Exit
467
+ if (input === "q" || input === "Q" || key.escape) {
468
+ onExit();
469
+ return;
470
+ }
471
+ // Difficulty selection mode
472
+ if (state.status === "selecting_difficulty") {
473
+ if (key.upArrow) {
474
+ setSelectedDifficulty((prev) => prev > 0 ? prev - 1 : DIFFICULTIES.length - 1);
475
+ }
476
+ else if (key.downArrow) {
477
+ setSelectedDifficulty((prev) => prev < DIFFICULTIES.length - 1 ? prev + 1 : 0);
478
+ }
479
+ else if (key.return) {
480
+ scoreSubmittedRef.current = false;
481
+ setState(createInitialState(selectedDifficulty));
482
+ }
483
+ return;
484
+ }
485
+ // Change difficulty
486
+ if (input === "d" || input === "D") {
487
+ setSelectedDifficulty(state.difficultyIndex);
488
+ setState((prev) => ({ ...prev, status: "selecting_difficulty" }));
489
+ return;
490
+ }
491
+ // Show leaderboard (when game over)
492
+ if ((input === "h" || input === "H") && (state.status === "won" || state.status === "lost" || state.status === "leaderboard")) {
493
+ setState((prev) => ({
494
+ ...prev,
495
+ status: prev.status === "leaderboard" ? "won" : "leaderboard",
496
+ }));
497
+ return;
498
+ }
499
+ // Dismiss loop alert with Enter key when paused and showing alert
500
+ if (key.return && showLoopAlert && state.status === "paused") {
501
+ setShowLoopAlert(false);
502
+ onLoopAlertDismiss?.();
503
+ return;
504
+ }
505
+ // Pause/unpause
506
+ if (input === "p" || input === "P") {
507
+ // If we're showing loop alert, dismiss it and resume
508
+ if (showLoopAlert) {
509
+ setShowLoopAlert(false);
510
+ setWasPlayingBeforeAlert(false);
511
+ }
512
+ setState((prev) => ({
513
+ ...prev,
514
+ status: prev.status === "playing" ? "paused" : prev.status === "paused" ? "playing" : prev.status,
515
+ }));
516
+ return;
517
+ }
518
+ // Restart
519
+ if (input === "r" || input === "R") {
520
+ scoreSubmittedRef.current = false;
521
+ statsSubmittedRef.current = false;
522
+ setState(createInitialState(state.difficultyIndex));
523
+ return;
524
+ }
525
+ if (state.status !== "playing")
526
+ return;
527
+ // Movement
528
+ if (key.leftArrow || input === "a" || input === "A") {
529
+ moveCursor(-1, 0);
530
+ }
531
+ else if (key.rightArrow || input === "d" || input === "D") {
532
+ moveCursor(1, 0);
533
+ }
534
+ else if (key.upArrow || input === "w" || input === "W") {
535
+ moveCursor(0, -1);
536
+ }
537
+ else if (key.downArrow || input === "s" || input === "S") {
538
+ moveCursor(0, 1);
539
+ }
540
+ else if (input === " " || key.return) {
541
+ reveal();
542
+ }
543
+ else if (input === "f" || input === "F") {
544
+ toggleFlag();
545
+ }
546
+ }, { isActive: hasFocus });
547
+ const diff = DIFFICULTIES[state.difficultyIndex];
548
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [state.status === "selecting_difficulty" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsx(DifficultySelector, { selectedIndex: selectedDifficulty, bestTimes: allBestTimes }) })) : state.status === "leaderboard" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsx(Leaderboard, { title: `${diff.name} Best Times`, scores: leaderboard, lowerIsBetter: true, formatScore: formatTime, highlightPosition: state.leaderboardPosition }) })) : state.status === "paused" && showLoopAlert && loopAttention ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsx(LoopAlertOverlay, { attention: loopAttention, onDismiss: onLoopAlertDismiss }) })) : state.status === "paused" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "PAUSED" }), _jsx(Text, { dimColor: true, children: "Press P to resume" })] }) })) : state.status === "won" || state.status === "lost" ? (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { children: [_jsx(GameBoard, { board: state.board, cursor: state.cursor, status: state.status }), _jsx(GameHUD, { minesRemaining: state.minesRemaining, timeElapsed: state.timeElapsed, difficulty: diff.name, bestTime: bestTime, fps: loopInfo.fps })] }), _jsx(GameOverOverlay, { won: state.status === "won", timeElapsed: state.timeElapsed, leaderboardPosition: state.leaderboardPosition })] })) : (_jsxs(Box, { flexGrow: 1, children: [_jsx(GameBoard, { board: state.board, cursor: state.cursor, status: state.status }), _jsx(GameHUD, { minesRemaining: state.minesRemaining, timeElapsed: state.timeElapsed, difficulty: diff.name, bestTime: bestTime, fps: loopInfo.fps })] })), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: hasFocus
549
+ ? state.status === "leaderboard"
550
+ ? "H: Back | R: Restart | D: Difficulty | Q: Exit"
551
+ : "←→↑↓: Move | Space: Reveal | F: Flag | D: Difficulty | R: Restart | H: Times | Q: Exit"
552
+ : "Press Tab to focus" }) })] }));
553
+ }
554
+ export default MinesweeperGame;
555
+ //# sourceMappingURL=MinesweeperGame.js.map