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.
- package/README.md +372 -0
- package/dist/AchievementNotification.d.ts +28 -0
- package/dist/AchievementNotification.d.ts.map +1 -0
- package/dist/AchievementNotification.js +74 -0
- package/dist/AchievementNotification.js.map +1 -0
- package/dist/GameSelector.d.ts +25 -0
- package/dist/GameSelector.d.ts.map +1 -0
- package/dist/GameSelector.js +105 -0
- package/dist/GameSelector.js.map +1 -0
- package/dist/HelpOverlay.d.ts +15 -0
- package/dist/HelpOverlay.d.ts.map +1 -0
- package/dist/HelpOverlay.js +134 -0
- package/dist/HelpOverlay.js.map +1 -0
- package/dist/Layout.d.ts +49 -0
- package/dist/Layout.d.ts.map +1 -0
- package/dist/Layout.js +83 -0
- package/dist/Layout.js.map +1 -0
- package/dist/Leaderboard.d.ts +46 -0
- package/dist/Leaderboard.d.ts.map +1 -0
- package/dist/Leaderboard.js +68 -0
- package/dist/Leaderboard.js.map +1 -0
- package/dist/LogViewer.d.ts +33 -0
- package/dist/LogViewer.d.ts.map +1 -0
- package/dist/LogViewer.js +179 -0
- package/dist/LogViewer.js.map +1 -0
- package/dist/LoopAlertOverlay.d.ts +15 -0
- package/dist/LoopAlertOverlay.d.ts.map +1 -0
- package/dist/LoopAlertOverlay.js +17 -0
- package/dist/LoopAlertOverlay.js.map +1 -0
- package/dist/LoopManagementPanel.d.ts +44 -0
- package/dist/LoopManagementPanel.d.ts.map +1 -0
- package/dist/LoopManagementPanel.js +220 -0
- package/dist/LoopManagementPanel.js.map +1 -0
- package/dist/SettingsMenu.d.ts +22 -0
- package/dist/SettingsMenu.d.ts.map +1 -0
- package/dist/SettingsMenu.js +367 -0
- package/dist/SettingsMenu.js.map +1 -0
- package/dist/SplitPane.d.ts +63 -0
- package/dist/SplitPane.d.ts.map +1 -0
- package/dist/SplitPane.js +104 -0
- package/dist/SplitPane.js.map +1 -0
- package/dist/StatsMenu.d.ts +15 -0
- package/dist/StatsMenu.d.ts.map +1 -0
- package/dist/StatsMenu.js +230 -0
- package/dist/StatsMenu.js.map +1 -0
- package/dist/StatusBar.d.ts +58 -0
- package/dist/StatusBar.d.ts.map +1 -0
- package/dist/StatusBar.js +106 -0
- package/dist/StatusBar.js.map +1 -0
- package/dist/__tests__/ralph-loop-parser.test.d.ts +2 -0
- package/dist/__tests__/ralph-loop-parser.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-loop-parser.test.js +143 -0
- package/dist/__tests__/ralph-loop-parser.test.js.map +1 -0
- package/dist/claude-code-process.d.ts +76 -0
- package/dist/claude-code-process.d.ts.map +1 -0
- package/dist/claude-code-process.js +221 -0
- package/dist/claude-code-process.js.map +1 -0
- package/dist/cli.d.ts +42 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +265 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +206 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +270 -0
- package/dist/config.js.map +1 -0
- package/dist/game-types.d.ts +177 -0
- package/dist/game-types.d.ts.map +1 -0
- package/dist/game-types.js +55 -0
- package/dist/game-types.js.map +1 -0
- package/dist/games/MinesweeperGame.d.ts +15 -0
- package/dist/games/MinesweeperGame.d.ts.map +1 -0
- package/dist/games/MinesweeperGame.js +555 -0
- package/dist/games/MinesweeperGame.js.map +1 -0
- package/dist/games/PongGame.d.ts +15 -0
- package/dist/games/PongGame.d.ts.map +1 -0
- package/dist/games/PongGame.js +379 -0
- package/dist/games/PongGame.js.map +1 -0
- package/dist/games/SnakeGame.d.ts +15 -0
- package/dist/games/SnakeGame.d.ts.map +1 -0
- package/dist/games/SnakeGame.js +333 -0
- package/dist/games/SnakeGame.js.map +1 -0
- package/dist/games/TetrisGame.d.ts +15 -0
- package/dist/games/TetrisGame.d.ts.map +1 -0
- package/dist/games/TetrisGame.js +654 -0
- package/dist/games/TetrisGame.js.map +1 -0
- package/dist/games/index.d.ts +23 -0
- package/dist/games/index.d.ts.map +1 -0
- package/dist/games/index.js +47 -0
- package/dist/games/index.js.map +1 -0
- package/dist/high-scores.d.ts +57 -0
- package/dist/high-scores.d.ts.map +1 -0
- package/dist/high-scores.js +230 -0
- package/dist/high-scores.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +264 -0
- package/dist/index.js.map +1 -0
- package/dist/ralph-loop-parser.d.ts +58 -0
- package/dist/ralph-loop-parser.d.ts.map +1 -0
- package/dist/ralph-loop-parser.js +315 -0
- package/dist/ralph-loop-parser.js.map +1 -0
- package/dist/stats.d.ts +142 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +521 -0
- package/dist/stats.js.map +1 -0
- package/dist/styled-components.d.ts +231 -0
- package/dist/styled-components.d.ts.map +1 -0
- package/dist/styled-components.js +192 -0
- package/dist/styled-components.js.map +1 -0
- package/dist/theme.d.ts +301 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +372 -0
- package/dist/theme.js.map +1 -0
- package/dist/themes.d.ts +117 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +296 -0
- package/dist/themes.js.map +1 -0
- package/dist/ui/index.d.ts +13 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +29 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/use-claude-code.d.ts +30 -0
- package/dist/use-claude-code.d.ts.map +1 -0
- package/dist/use-claude-code.js +84 -0
- package/dist/use-claude-code.js.map +1 -0
- package/dist/use-config.d.ts +58 -0
- package/dist/use-config.d.ts.map +1 -0
- package/dist/use-config.js +113 -0
- package/dist/use-config.js.map +1 -0
- package/dist/use-game-loop.d.ts +47 -0
- package/dist/use-game-loop.d.ts.map +1 -0
- package/dist/use-game-loop.js +136 -0
- package/dist/use-game-loop.js.map +1 -0
- package/dist/use-high-scores.d.ts +41 -0
- package/dist/use-high-scores.d.ts.map +1 -0
- package/dist/use-high-scores.js +94 -0
- package/dist/use-high-scores.js.map +1 -0
- package/dist/use-layout-state.d.ts +77 -0
- package/dist/use-layout-state.d.ts.map +1 -0
- package/dist/use-layout-state.js +160 -0
- package/dist/use-layout-state.js.map +1 -0
- package/dist/use-ralph-loop.d.ts +41 -0
- package/dist/use-ralph-loop.d.ts.map +1 -0
- package/dist/use-ralph-loop.js +106 -0
- package/dist/use-ralph-loop.js.map +1 -0
- package/dist/use-spinner.d.ts +46 -0
- package/dist/use-spinner.d.ts.map +1 -0
- package/dist/use-spinner.js +71 -0
- package/dist/use-spinner.js.map +1 -0
- package/dist/use-stats.d.ts +59 -0
- package/dist/use-stats.d.ts.map +1 -0
- package/dist/use-stats.js +150 -0
- package/dist/use-stats.js.map +1 -0
- package/dist/use-terminal-size.d.ts +29 -0
- package/dist/use-terminal-size.d.ts.map +1 -0
- package/dist/use-terminal-size.js +48 -0
- package/dist/use-terminal-size.js.map +1 -0
- package/dist/useTheme.d.ts +76 -0
- package/dist/useTheme.d.ts.map +1 -0
- package/dist/useTheme.js +136 -0
- package/dist/useTheme.js.map +1 -0
- 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
|