@pavus/snake-game 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.
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +9 -0
- package/dist/bin.js.map +1 -0
- package/dist/snake-synth.mjs +184 -0
- package/dist/src/MidiTrackBrowser.d.ts +24 -0
- package/dist/src/MidiTrackBrowser.js +104 -0
- package/dist/src/MidiTrackBrowser.js.map +1 -0
- package/dist/src/MusicSettings.d.ts +32 -0
- package/dist/src/MusicSettings.js +144 -0
- package/dist/src/MusicSettings.js.map +1 -0
- package/dist/src/SnakeGame.d.ts +38 -0
- package/dist/src/SnakeGame.js +246 -0
- package/dist/src/SnakeGame.js.map +1 -0
- package/dist/src/freemidi-catalog.d.ts +31 -0
- package/dist/src/freemidi-catalog.js +127 -0
- package/dist/src/freemidi-catalog.js.map +1 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.js +30 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/settings.d.ts +11 -0
- package/dist/src/settings.js +57 -0
- package/dist/src/settings.js.map +1 -0
- package/dist/src/snake-audio.d.ts +42 -0
- package/dist/src/snake-audio.js +193 -0
- package/dist/src/snake-audio.js.map +1 -0
- package/dist/src/styles.d.ts +7 -0
- package/dist/src/styles.js +8 -0
- package/dist/src/styles.js.map +1 -0
- package/dist/src/types.d.ts +20 -0
- package/dist/src/types.js +17 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* SnakeGame — Playable Snake in the terminal.
|
|
4
|
+
*
|
|
5
|
+
* Controls: WASD to move · Space to pause · R to restart · M to change music
|
|
6
|
+
*
|
|
7
|
+
* Uses a ref-backed game state with a single persistent interval so
|
|
8
|
+
* the game loop never captures stale closure values.
|
|
9
|
+
*/
|
|
10
|
+
import { Box, Text, useInput } from 'ink';
|
|
11
|
+
import { useReducer, useRef, useEffect, useState } from 'react';
|
|
12
|
+
import { getSnakeHighScore, setSnakeHighScore, getSnakeMusicVolume, setSnakeMusicVolume, getSnakeTinkVolume, setSnakeTinkVolume, getSnakeSfxVolume, setSnakeSfxVolume, } from './settings.js';
|
|
13
|
+
import { warmNotes, playNote, playSystemSound, SYSTEM_SOUNDS, startBgMusic, stopBgMusic, setMusicVolume, } from './snake-audio.js';
|
|
14
|
+
import { MusicSettings } from './MusicSettings.js';
|
|
15
|
+
import { MIDI_CATALOG, DEFAULT_TRACK_ID, freemidiUrls } from './freemidi-catalog.js';
|
|
16
|
+
import { resolveColors } from './types.js';
|
|
17
|
+
const DEFAULT_BPM = 120;
|
|
18
|
+
function resolveDefaultTrack(catalog) {
|
|
19
|
+
const entry = catalog.find((t) => t.id === DEFAULT_TRACK_ID) ?? catalog[0];
|
|
20
|
+
if (!entry)
|
|
21
|
+
return { title: 'No tracks', artist: '', url: '' };
|
|
22
|
+
const urls = freemidiUrls(entry);
|
|
23
|
+
return { title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage };
|
|
24
|
+
}
|
|
25
|
+
// ── Music visualizer ──────────────────────────────────────────────────
|
|
26
|
+
function MusicVisualizer({ beatPhase, active, beatColors, }) {
|
|
27
|
+
const beat = Math.floor(beatPhase / 4) % 4;
|
|
28
|
+
return (_jsx(Box, { children: [0, 1, 2, 3].map((b) => {
|
|
29
|
+
const isCurrent = active && b === beat;
|
|
30
|
+
return (_jsxs(Text, { color: isCurrent ? beatColors[b] : undefined, dimColor: !isCurrent, children: [isCurrent ? '●' : '○', ' '] }, b));
|
|
31
|
+
}) }));
|
|
32
|
+
}
|
|
33
|
+
const DIRS = {
|
|
34
|
+
up: { dx: 0, dy: -1 },
|
|
35
|
+
down: { dx: 0, dy: 1 },
|
|
36
|
+
left: { dx: -1, dy: 0 },
|
|
37
|
+
right: { dx: 1, dy: 0 },
|
|
38
|
+
};
|
|
39
|
+
function randomFood(snake, w, h) {
|
|
40
|
+
const free = [];
|
|
41
|
+
for (let y = 0; y < h; y++) {
|
|
42
|
+
for (let x = 0; x < w; x++) {
|
|
43
|
+
if (!snake.some((s) => s.x === x && s.y === y)) {
|
|
44
|
+
free.push({ x, y });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return free[Math.floor(Math.random() * free.length)] ?? { x: 0, y: 0 };
|
|
49
|
+
}
|
|
50
|
+
function makeInitial(w, h) {
|
|
51
|
+
const cx = Math.floor(w / 2);
|
|
52
|
+
const cy = Math.floor(h / 2);
|
|
53
|
+
const snake = [{ x: cx, y: cy }, { x: cx - 1, y: cy }, { x: cx - 2, y: cy }];
|
|
54
|
+
return {
|
|
55
|
+
snake,
|
|
56
|
+
dir: DIRS.right,
|
|
57
|
+
dirQueue: [],
|
|
58
|
+
food: randomFood(snake, w, h),
|
|
59
|
+
score: 0,
|
|
60
|
+
gameOver: false,
|
|
61
|
+
paused: false,
|
|
62
|
+
started: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile, width = 20, height = 10, tracks, } = {}) => {
|
|
66
|
+
const c = resolveColors(colors);
|
|
67
|
+
const catalog = tracks ?? MIDI_CATALOG;
|
|
68
|
+
const stateRef = useRef(makeInitial(width, height));
|
|
69
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
70
|
+
const highScoreRef = useRef(0);
|
|
71
|
+
const [highScore, setHighScore] = useState(0);
|
|
72
|
+
const bgMusic = useRef(null);
|
|
73
|
+
const [bpm, setBpm] = useState(DEFAULT_BPM);
|
|
74
|
+
const [track, setTrack] = useState(() => resolveDefaultTrack(catalog));
|
|
75
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
76
|
+
const [beatPhase, setBeatPhase] = useState(0);
|
|
77
|
+
const [musicVolume, setMusicVolumeState] = useState(() => getSnakeMusicVolume(settingsFile));
|
|
78
|
+
const [tinkVolume, setTinkVolumeState] = useState(() => getSnakeTinkVolume(settingsFile));
|
|
79
|
+
const [sfxVolume, setSfxVolumeState] = useState(() => getSnakeSfxVolume(settingsFile));
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!music)
|
|
82
|
+
return;
|
|
83
|
+
warmNotes([69], cacheDir);
|
|
84
|
+
stopBgMusic(bgMusic.current);
|
|
85
|
+
bgMusic.current = null;
|
|
86
|
+
setBpm(DEFAULT_BPM);
|
|
87
|
+
void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {
|
|
88
|
+
bgMusic.current = handle;
|
|
89
|
+
if (handle)
|
|
90
|
+
setBpm(handle.bpm);
|
|
91
|
+
});
|
|
92
|
+
return () => { stopBgMusic(bgMusic.current); };
|
|
93
|
+
}, [track, music]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const saved = getSnakeHighScore(settingsFile);
|
|
96
|
+
highScoreRef.current = saved;
|
|
97
|
+
setHighScore(saved);
|
|
98
|
+
}, []);
|
|
99
|
+
const reset = () => {
|
|
100
|
+
stateRef.current = { ...makeInitial(width, height), started: true };
|
|
101
|
+
forceUpdate();
|
|
102
|
+
};
|
|
103
|
+
// Beat phase for music visualizer — runs independently of game state
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!music)
|
|
106
|
+
return;
|
|
107
|
+
const tickMs = Math.round((60_000 / bpm) / 4);
|
|
108
|
+
const id = setInterval(() => setBeatPhase((p) => (p + 1) % 16), tickMs);
|
|
109
|
+
return () => clearInterval(id);
|
|
110
|
+
}, [bpm, music]);
|
|
111
|
+
// Game loop — interval recreates when bpm changes (once synthesis completes)
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const tickMs = Math.round((60_000 / bpm) / 4); // one 16th note
|
|
114
|
+
const id = setInterval(() => {
|
|
115
|
+
const g = stateRef.current;
|
|
116
|
+
if (!g.started || g.gameOver || g.paused)
|
|
117
|
+
return;
|
|
118
|
+
const [nextDir, ...restQueue] = g.dirQueue.length > 0 ? g.dirQueue : [g.dir];
|
|
119
|
+
const dir = nextDir;
|
|
120
|
+
const head = { x: g.snake[0].x + dir.dx, y: g.snake[0].y + dir.dy };
|
|
121
|
+
if (head.x < 0 ||
|
|
122
|
+
head.x >= width ||
|
|
123
|
+
head.y < 0 ||
|
|
124
|
+
head.y >= height ||
|
|
125
|
+
g.snake.some((s) => s.x === head.x && s.y === head.y)) {
|
|
126
|
+
stateRef.current = { ...g, dir, dirQueue: restQueue, gameOver: true };
|
|
127
|
+
if (music)
|
|
128
|
+
playSystemSound(SYSTEM_SOUNDS.die, sfxVolume);
|
|
129
|
+
forceUpdate();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const ate = head.x === g.food.x && head.y === g.food.y;
|
|
133
|
+
const snake = ate ? [head, ...g.snake] : [head, ...g.snake.slice(0, -1)];
|
|
134
|
+
const newScore = ate ? g.score + 1 : g.score;
|
|
135
|
+
if (ate) {
|
|
136
|
+
if (newScore > highScoreRef.current) {
|
|
137
|
+
highScoreRef.current = newScore;
|
|
138
|
+
setSnakeHighScore(newScore, settingsFile);
|
|
139
|
+
setHighScore(newScore);
|
|
140
|
+
}
|
|
141
|
+
if (music)
|
|
142
|
+
playSystemSound(SYSTEM_SOUNDS.eat, sfxVolume);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
if (music)
|
|
146
|
+
playNote(69, tinkVolume, cacheDir);
|
|
147
|
+
}
|
|
148
|
+
stateRef.current = {
|
|
149
|
+
...g,
|
|
150
|
+
snake,
|
|
151
|
+
dir,
|
|
152
|
+
dirQueue: restQueue,
|
|
153
|
+
food: ate ? randomFood(snake, width, height) : g.food,
|
|
154
|
+
score: newScore,
|
|
155
|
+
};
|
|
156
|
+
forceUpdate();
|
|
157
|
+
}, tickMs);
|
|
158
|
+
return () => clearInterval(id);
|
|
159
|
+
}, [bpm]);
|
|
160
|
+
useInput((input) => {
|
|
161
|
+
if (showSettings)
|
|
162
|
+
return;
|
|
163
|
+
const g = stateRef.current;
|
|
164
|
+
if (input === 'q' && onExit) {
|
|
165
|
+
onExit();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (input === 'm' && music) {
|
|
169
|
+
if (g.started && !g.gameOver && !g.paused) {
|
|
170
|
+
stateRef.current = { ...g, paused: true };
|
|
171
|
+
forceUpdate();
|
|
172
|
+
}
|
|
173
|
+
setShowSettings(true);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!g.started || g.gameOver) {
|
|
177
|
+
if (input === 'r' || input === ' ')
|
|
178
|
+
reset();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (input === ' ') {
|
|
182
|
+
stateRef.current = { ...g, paused: !g.paused };
|
|
183
|
+
forceUpdate();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (input === 'r') {
|
|
187
|
+
reset();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;
|
|
191
|
+
let next = null;
|
|
192
|
+
if (input === 'w' && lastDir.dy !== 1)
|
|
193
|
+
next = DIRS.up;
|
|
194
|
+
else if (input === 's' && lastDir.dy !== -1)
|
|
195
|
+
next = DIRS.down;
|
|
196
|
+
else if (input === 'a' && lastDir.dx !== 1)
|
|
197
|
+
next = DIRS.left;
|
|
198
|
+
else if (input === 'd' && lastDir.dx !== -1)
|
|
199
|
+
next = DIRS.right;
|
|
200
|
+
if (next && g.dirQueue.length < 2) {
|
|
201
|
+
stateRef.current = { ...g, dirQueue: [...g.dirQueue, next] };
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
if (showSettings) {
|
|
205
|
+
return (_jsx(MusicSettings, { initial: { track, musicVolume, tinkVolume, sfxVolume }, accentColor: c.accent, tracks: catalog, onApply: (cfg) => {
|
|
206
|
+
if (cfg.musicVolume !== musicVolume && bgMusic.current) {
|
|
207
|
+
bgMusic.current = setMusicVolume(bgMusic.current, cfg.musicVolume);
|
|
208
|
+
}
|
|
209
|
+
if (cfg.musicVolume !== musicVolume) {
|
|
210
|
+
setSnakeMusicVolume(cfg.musicVolume, settingsFile);
|
|
211
|
+
setMusicVolumeState(cfg.musicVolume);
|
|
212
|
+
}
|
|
213
|
+
if (cfg.tinkVolume !== tinkVolume) {
|
|
214
|
+
setSnakeTinkVolume(cfg.tinkVolume, settingsFile);
|
|
215
|
+
setTinkVolumeState(cfg.tinkVolume);
|
|
216
|
+
}
|
|
217
|
+
if (cfg.sfxVolume !== sfxVolume) {
|
|
218
|
+
setSnakeSfxVolume(cfg.sfxVolume, settingsFile);
|
|
219
|
+
setSfxVolumeState(cfg.sfxVolume);
|
|
220
|
+
}
|
|
221
|
+
if (cfg.track.url !== track.url)
|
|
222
|
+
setTrack(cfg.track);
|
|
223
|
+
setShowSettings(false);
|
|
224
|
+
}, onCancel: () => setShowSettings(false) }));
|
|
225
|
+
}
|
|
226
|
+
const { snake, food, score, gameOver, paused, started } = stateRef.current;
|
|
227
|
+
const cells = Array.from({ length: height }, (_, y) => Array.from({ length: width }, (_, x) => {
|
|
228
|
+
if (x === snake[0]?.x && y === snake[0]?.y)
|
|
229
|
+
return 'head';
|
|
230
|
+
if (snake.some((s) => s.x === x && s.y === y))
|
|
231
|
+
return 'body';
|
|
232
|
+
if (x === food.x && y === food.y)
|
|
233
|
+
return 'food';
|
|
234
|
+
return 'empty';
|
|
235
|
+
}));
|
|
236
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, color: c.accent, children: "Snake" }), _jsxs(Text, { dimColor: true, children: ["Score: ", _jsx(Text, { bold: true, children: score })] }), highScore > 0 && _jsxs(Text, { dimColor: true, children: ["Best: ", _jsx(Text, { bold: true, children: highScore })] }), paused && _jsx(Text, { color: "yellow", children: " PAUSED" })] }), music && (_jsxs(_Fragment, { children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "\u266A" }), _jsx(Text, { dimColor: true, children: track.title }), _jsx(Text, { dimColor: true, children: "\u2014" }), _jsx(Text, { dimColor: true, children: track.artist })] }), _jsx(MusicVisualizer, { beatPhase: beatPhase, active: started && !paused && !gameOver, beatColors: c.beat })] })), _jsx(Box, { height: 1 }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: '┌' + '──'.repeat(width) + '┐' }), cells.map((row, y) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2502" }), row.map((cell, x) => {
|
|
237
|
+
if (cell === 'head')
|
|
238
|
+
return _jsx(Text, { color: c.head, bold: true, children: '● ' }, x);
|
|
239
|
+
if (cell === 'body')
|
|
240
|
+
return _jsx(Text, { color: c.body, children: '● ' }, x);
|
|
241
|
+
if (cell === 'food')
|
|
242
|
+
return _jsx(Text, { color: c.food, children: '◆ ' }, x);
|
|
243
|
+
return _jsx(Text, { dimColor: true, children: '· ' }, x);
|
|
244
|
+
}), _jsx(Text, { dimColor: true, children: "\u2502" })] }, y))), _jsx(Text, { dimColor: true, children: '└' + '──'.repeat(width) + '┘' })] }), _jsx(Box, { height: 1 }), !started && (_jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { bold: true, color: c.accent, children: "Space" }), " or", ' ', _jsx(Text, { bold: true, color: c.accent, children: "R" }), " to start"] })), gameOver && (_jsxs(Text, { children: [_jsx(Text, { color: "red", children: "Game over! " }), _jsx(Text, { dimColor: true, children: "Press " }), _jsx(Text, { bold: true, color: c.accent, children: "R" }), _jsx(Text, { dimColor: true, children: " to restart" })] })), started && !gameOver && (_jsxs(Text, { dimColor: true, children: ["WASD to move \u00B7 Space to pause \u00B7 R to restart", music ? ' · M for music' : ''] })), !started && music && _jsx(Text, { dimColor: true, children: "M to change music" }), onExit && _jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { bold: true, children: "Q" }), " to exit"] })] }));
|
|
245
|
+
};
|
|
246
|
+
//# sourceMappingURL=SnakeGame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SnakeGame.js","sourceRoot":"","sources":["../../src/SnakeGame.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * SnakeGame — Playable Snake in the terminal.\n *\n * Controls: WASD to move · Space to pause · R to restart · M to change music\n *\n * Uses a ref-backed game state with a single persistent interval so\n * the game loop never captures stale closure values.\n */\n\nimport { Box, Text, useInput } from 'ink';\nimport { useReducer, useRef, useEffect, useState } from 'react';\nimport {\n getSnakeHighScore,\n setSnakeHighScore,\n getSnakeMusicVolume,\n setSnakeMusicVolume,\n getSnakeTinkVolume,\n setSnakeTinkVolume,\n getSnakeSfxVolume,\n setSnakeSfxVolume,\n} from './settings.js';\nimport {\n warmNotes, playNote, playSystemSound, SYSTEM_SOUNDS,\n startBgMusic, stopBgMusic, setMusicVolume, type BgMusicHandle,\n} from './snake-audio.js';\nimport { MusicSettings, type MusicConfig, type SelectedTrack } from './MusicSettings.js';\nimport { MIDI_CATALOG, DEFAULT_TRACK_ID, freemidiUrls, type MidiTrack } from './freemidi-catalog.js';\nimport { type SnakeColors, resolveColors } from './types.js';\n\nexport type { SnakeColors };\n\nconst DEFAULT_BPM = 120;\n\nfunction resolveDefaultTrack(catalog: MidiTrack[]): SelectedTrack {\n const entry = catalog.find((t) => t.id === DEFAULT_TRACK_ID) ?? catalog[0];\n if (!entry) return { title: 'No tracks', artist: '', url: '' };\n const urls = freemidiUrls(entry);\n return { title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage };\n}\n\n// ── Music visualizer ──────────────────────────────────────────────────\n\nfunction MusicVisualizer({\n beatPhase,\n active,\n beatColors,\n}: {\n beatPhase: number;\n active: boolean;\n beatColors: [string, string, string, string];\n}) {\n const beat = Math.floor(beatPhase / 4) % 4;\n\n return (\n <Box>\n {([0, 1, 2, 3] as const).map((b) => {\n const isCurrent = active && b === beat;\n return (\n <Text key={b} color={isCurrent ? beatColors[b] : undefined} dimColor={!isCurrent}>\n {isCurrent ? '●' : '○'}{' '}\n </Text>\n );\n })}\n </Box>\n );\n}\n\ntype Point = { x: number; y: number };\ntype Dir = { dx: number; dy: number };\n\nconst DIRS = {\n up: { dx: 0, dy: -1 },\n down: { dx: 0, dy: 1 },\n left: { dx: -1, dy: 0 },\n right: { dx: 1, dy: 0 },\n} as const;\n\nfunction randomFood(snake: Point[], w: number, h: number): Point {\n const free: Point[] = [];\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n if (!snake.some((s) => s.x === x && s.y === y)) {\n free.push({ x, y });\n }\n }\n }\n return free[Math.floor(Math.random() * free.length)] ?? { x: 0, y: 0 };\n}\n\ntype GameState = {\n snake: Point[];\n dir: Dir;\n dirQueue: Dir[];\n food: Point;\n score: number;\n gameOver: boolean;\n paused: boolean;\n started: boolean;\n};\n\nfunction makeInitial(w: number, h: number): GameState {\n const cx = Math.floor(w / 2);\n const cy = Math.floor(h / 2);\n const snake = [{ x: cx, y: cy }, { x: cx - 1, y: cy }, { x: cx - 2, y: cy }];\n return {\n snake,\n dir: DIRS.right,\n dirQueue: [],\n food: randomFood(snake, w, h),\n score: 0,\n gameOver: false,\n paused: false,\n started: false,\n };\n}\n\ninterface SnakeGameProps {\n onExit?: () => void;\n /** Enable or disable all audio (default: true) */\n music?: boolean;\n /** Override any of the game's colors */\n colors?: SnakeColors;\n /**\n * Directory for cached audio files (synthesized WAVs and per-note tinks).\n * Defaults to the OS temp directory.\n */\n cacheDir?: string;\n /**\n * Path to the JSON file used to persist settings (high score, volumes).\n * Defaults to ~/.snake-game.json\n */\n settingsFile?: string;\n /** Grid width in cells (default: 20) */\n width?: number;\n /** Grid height in cells (default: 10) */\n height?: number;\n /**\n * Track list shown in the music browser.\n * Defaults to the built-in MIDI_CATALOG from freemidi.org.\n */\n tracks?: MidiTrack[];\n}\n\nexport const SnakeGame = ({\n onExit,\n music = true,\n colors,\n cacheDir,\n settingsFile,\n width = 20,\n height = 10,\n tracks,\n}: SnakeGameProps = {}) => {\n const c = resolveColors(colors);\n const catalog = tracks ?? MIDI_CATALOG;\n\n const stateRef = useRef<GameState>(makeInitial(width, height));\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n const highScoreRef = useRef(0);\n const [highScore, setHighScore] = useState(0);\n const bgMusic = useRef<BgMusicHandle | null>(null);\n const [bpm, setBpm] = useState(DEFAULT_BPM);\n const [track, setTrack] = useState<SelectedTrack>(() => resolveDefaultTrack(catalog));\n const [showSettings, setShowSettings] = useState(false);\n const [beatPhase, setBeatPhase] = useState(0);\n const [musicVolume, setMusicVolumeState] = useState(() => getSnakeMusicVolume(settingsFile));\n const [tinkVolume, setTinkVolumeState] = useState(() => getSnakeTinkVolume(settingsFile));\n const [sfxVolume, setSfxVolumeState] = useState(() => getSnakeSfxVolume(settingsFile));\n\n useEffect(() => {\n if (!music) return;\n warmNotes([69], cacheDir);\n stopBgMusic(bgMusic.current);\n bgMusic.current = null;\n setBpm(DEFAULT_BPM);\n void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {\n bgMusic.current = handle;\n if (handle) setBpm(handle.bpm);\n });\n return () => { stopBgMusic(bgMusic.current); };\n }, [track, music]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n const saved = getSnakeHighScore(settingsFile);\n highScoreRef.current = saved;\n setHighScore(saved);\n }, []);\n\n const reset = () => {\n stateRef.current = { ...makeInitial(width, height), started: true };\n forceUpdate();\n };\n\n // Beat phase for music visualizer — runs independently of game state\n useEffect(() => {\n if (!music) return;\n const tickMs = Math.round((60_000 / bpm) / 4);\n const id = setInterval(() => setBeatPhase((p) => (p + 1) % 16), tickMs);\n return () => clearInterval(id);\n }, [bpm, music]);\n\n // Game loop — interval recreates when bpm changes (once synthesis completes)\n useEffect(() => {\n const tickMs = Math.round((60_000 / bpm) / 4); // one 16th note\n const id = setInterval(() => {\n const g = stateRef.current;\n if (!g.started || g.gameOver || g.paused) return;\n\n const [nextDir, ...restQueue] = g.dirQueue.length > 0 ? g.dirQueue : [g.dir];\n const dir = nextDir;\n const head = { x: g.snake[0].x + dir.dx, y: g.snake[0].y + dir.dy };\n\n if (\n head.x < 0 ||\n head.x >= width ||\n head.y < 0 ||\n head.y >= height ||\n g.snake.some((s) => s.x === head.x && s.y === head.y)\n ) {\n stateRef.current = { ...g, dir, dirQueue: restQueue, gameOver: true };\n if (music) playSystemSound(SYSTEM_SOUNDS.die, sfxVolume);\n forceUpdate();\n return;\n }\n\n const ate = head.x === g.food.x && head.y === g.food.y;\n const snake = ate ? [head, ...g.snake] : [head, ...g.snake.slice(0, -1)];\n const newScore = ate ? g.score + 1 : g.score;\n\n if (ate) {\n if (newScore > highScoreRef.current) {\n highScoreRef.current = newScore;\n setSnakeHighScore(newScore, settingsFile);\n setHighScore(newScore);\n }\n if (music) playSystemSound(SYSTEM_SOUNDS.eat, sfxVolume);\n } else {\n if (music) playNote(69, tinkVolume, cacheDir);\n }\n\n stateRef.current = {\n ...g,\n snake,\n dir,\n dirQueue: restQueue,\n food: ate ? randomFood(snake, width, height) : g.food,\n score: newScore,\n };\n forceUpdate();\n }, tickMs);\n\n return () => clearInterval(id);\n }, [bpm]);\n\n useInput((input) => {\n if (showSettings) return;\n\n const g = stateRef.current;\n\n if (input === 'q' && onExit) { onExit(); return; }\n\n if (input === 'm' && music) {\n if (g.started && !g.gameOver && !g.paused) {\n stateRef.current = { ...g, paused: true };\n forceUpdate();\n }\n setShowSettings(true);\n return;\n }\n\n if (!g.started || g.gameOver) {\n if (input === 'r' || input === ' ') reset();\n return;\n }\n\n if (input === ' ') { stateRef.current = { ...g, paused: !g.paused }; forceUpdate(); return; }\n if (input === 'r') { reset(); return; }\n\n const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;\n let next: Dir | null = null;\n if (input === 'w' && lastDir.dy !== 1) next = DIRS.up;\n else if (input === 's' && lastDir.dy !== -1) next = DIRS.down;\n else if (input === 'a' && lastDir.dx !== 1) next = DIRS.left;\n else if (input === 'd' && lastDir.dx !== -1) next = DIRS.right;\n if (next && g.dirQueue.length < 2) {\n stateRef.current = { ...g, dirQueue: [...g.dirQueue, next] };\n }\n });\n\n if (showSettings) {\n return (\n <MusicSettings\n initial={{ track, musicVolume, tinkVolume, sfxVolume }}\n accentColor={c.accent}\n tracks={catalog}\n onApply={(cfg: MusicConfig) => {\n if (cfg.musicVolume !== musicVolume && bgMusic.current) {\n bgMusic.current = setMusicVolume(bgMusic.current, cfg.musicVolume);\n }\n if (cfg.musicVolume !== musicVolume) {\n setSnakeMusicVolume(cfg.musicVolume, settingsFile);\n setMusicVolumeState(cfg.musicVolume);\n }\n if (cfg.tinkVolume !== tinkVolume) {\n setSnakeTinkVolume(cfg.tinkVolume, settingsFile);\n setTinkVolumeState(cfg.tinkVolume);\n }\n if (cfg.sfxVolume !== sfxVolume) {\n setSnakeSfxVolume(cfg.sfxVolume, settingsFile);\n setSfxVolumeState(cfg.sfxVolume);\n }\n if (cfg.track.url !== track.url) setTrack(cfg.track);\n setShowSettings(false);\n }}\n onCancel={() => setShowSettings(false)}\n />\n );\n }\n\n const { snake, food, score, gameOver, paused, started } = stateRef.current;\n\n const cells = Array.from({ length: height }, (_, y) =>\n Array.from({ length: width }, (_, x) => {\n if (x === snake[0]?.x && y === snake[0]?.y) return 'head';\n if (snake.some((s) => s.x === x && s.y === y)) return 'body';\n if (x === food.x && y === food.y) return 'food';\n return 'empty';\n }),\n );\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n <Box gap={2}>\n <Text bold color={c.accent}>Snake</Text>\n <Text dimColor>Score: <Text bold>{score}</Text></Text>\n {highScore > 0 && <Text dimColor>Best: <Text bold>{highScore}</Text></Text>}\n {paused && <Text color=\"yellow\"> PAUSED</Text>}\n </Box>\n {music && (\n <>\n <Box gap={1}>\n <Text dimColor>♪</Text>\n <Text dimColor>{track.title}</Text>\n <Text dimColor>—</Text>\n <Text dimColor>{track.artist}</Text>\n </Box>\n <MusicVisualizer beatPhase={beatPhase} active={started && !paused && !gameOver} beatColors={c.beat} />\n </>\n )}\n <Box height={1} />\n <Box flexDirection=\"column\">\n <Text dimColor>{'┌' + '──'.repeat(width) + '┐'}</Text>\n {cells.map((row, y) => (\n <Box key={y}>\n <Text dimColor>│</Text>\n {row.map((cell, x) => {\n if (cell === 'head') return <Text key={x} color={c.head} bold>{'● '}</Text>;\n if (cell === 'body') return <Text key={x} color={c.body}>{'● '}</Text>;\n if (cell === 'food') return <Text key={x} color={c.food}>{'◆ '}</Text>;\n return <Text key={x} dimColor>{'· '}</Text>;\n })}\n <Text dimColor>│</Text>\n </Box>\n ))}\n <Text dimColor>{'└' + '──'.repeat(width) + '┘'}</Text>\n </Box>\n <Box height={1} />\n {!started && (\n <Text dimColor>\n Press <Text bold color={c.accent}>Space</Text> or{' '}\n <Text bold color={c.accent}>R</Text> to start\n </Text>\n )}\n {gameOver && (\n <Text>\n <Text color=\"red\">Game over! </Text>\n <Text dimColor>Press </Text>\n <Text bold color={c.accent}>R</Text>\n <Text dimColor> to restart</Text>\n </Text>\n )}\n {started && !gameOver && (\n <Text dimColor>\n WASD to move · Space to pause · R to restart{music ? ' · M for music' : ''}\n </Text>\n )}\n {!started && music && <Text dimColor>M to change music</Text>}\n {onExit && <Text dimColor>Press <Text bold>Q</Text> to exit</Text>}\n </Box>\n );\n};\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* freemidi-catalog.ts — Curated list of tracks from freemidi.org
|
|
3
|
+
*
|
|
4
|
+
* Attribution: All tracks sourced from https://freemidi.org
|
|
5
|
+
* MIDI files are fetched on-demand (no local copies stored).
|
|
6
|
+
*
|
|
7
|
+
* IDs verified by cross-referencing the freemidi.org artist pages and
|
|
8
|
+
* Google site:freemidi.org searches. Entries with unverifiable IDs were
|
|
9
|
+
* removed rather than kept with guessed values.
|
|
10
|
+
*
|
|
11
|
+
* Fallback track (open source / direct URL): Beethoven Moonlight Sonata via bitmidi.com
|
|
12
|
+
*/
|
|
13
|
+
export interface MidiTrack {
|
|
14
|
+
id: number;
|
|
15
|
+
title: string;
|
|
16
|
+
artist: string;
|
|
17
|
+
/** freemidi.org download page slug, used to get session cookie */
|
|
18
|
+
slug: string;
|
|
19
|
+
}
|
|
20
|
+
/** Build the two-step freemidi.org fetch URLs for a track. */
|
|
21
|
+
export declare function freemidiUrls(track: MidiTrack): {
|
|
22
|
+
downloadPage: string;
|
|
23
|
+
getter: string;
|
|
24
|
+
};
|
|
25
|
+
/** Fallback track — direct URL, no auth required. */
|
|
26
|
+
export declare const FALLBACK_TRACK: MidiTrack & {
|
|
27
|
+
directUrl: string;
|
|
28
|
+
};
|
|
29
|
+
/** Default selected track. */
|
|
30
|
+
export declare const DEFAULT_TRACK_ID = 28946;
|
|
31
|
+
export declare const MIDI_CATALOG: MidiTrack[];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* freemidi-catalog.ts — Curated list of tracks from freemidi.org
|
|
3
|
+
*
|
|
4
|
+
* Attribution: All tracks sourced from https://freemidi.org
|
|
5
|
+
* MIDI files are fetched on-demand (no local copies stored).
|
|
6
|
+
*
|
|
7
|
+
* IDs verified by cross-referencing the freemidi.org artist pages and
|
|
8
|
+
* Google site:freemidi.org searches. Entries with unverifiable IDs were
|
|
9
|
+
* removed rather than kept with guessed values.
|
|
10
|
+
*
|
|
11
|
+
* Fallback track (open source / direct URL): Beethoven Moonlight Sonata via bitmidi.com
|
|
12
|
+
*/
|
|
13
|
+
/** Build the two-step freemidi.org fetch URLs for a track. */
|
|
14
|
+
export function freemidiUrls(track) {
|
|
15
|
+
return {
|
|
16
|
+
downloadPage: `https://freemidi.org/download3-${track.id}-${track.slug}`,
|
|
17
|
+
getter: `https://freemidi.org/getter-${track.id}`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Fallback track — direct URL, no auth required. */
|
|
21
|
+
export const FALLBACK_TRACK = {
|
|
22
|
+
id: 0,
|
|
23
|
+
title: 'Moonlight Sonata',
|
|
24
|
+
artist: 'Beethoven',
|
|
25
|
+
slug: '',
|
|
26
|
+
directUrl: 'https://bitmidi.com/uploads/16752.mid',
|
|
27
|
+
};
|
|
28
|
+
/** Default selected track. */
|
|
29
|
+
export const DEFAULT_TRACK_ID = 28946; // Abracadabra — Lady Gaga
|
|
30
|
+
export const MIDI_CATALOG = [
|
|
31
|
+
// Lady Gaga — verified from https://freemidi.org/artist-1586-lady-gaga
|
|
32
|
+
{ id: 28946, title: 'Abracadabra', artist: 'Lady Gaga', slug: 'abracadabra-lady-gaga' },
|
|
33
|
+
{ id: 11747, title: 'Bad Romance', artist: 'Lady Gaga', slug: 'bad-romance-lady-gaga' },
|
|
34
|
+
{ id: 11544, title: 'Poker Face', artist: 'Lady Gaga', slug: 'poker-face-lady-gaga' },
|
|
35
|
+
{ id: 11543, title: 'Just Dance', artist: 'Lady Gaga', slug: 'just-dance-lady-gaga' },
|
|
36
|
+
{ id: 12112, title: 'Telephone', artist: 'Lady Gaga', slug: 'telephone-lady-gaga' },
|
|
37
|
+
{ id: 11748, title: 'Paparazzi', artist: 'Lady Gaga', slug: 'paparazzi--lady-gaga' },
|
|
38
|
+
{ id: 12582, title: 'Alejandro', artist: 'Lady Gaga', slug: 'alejandro-lady-gaga' },
|
|
39
|
+
{ id: 12583, title: 'Born This Way', artist: 'Lady Gaga', slug: 'born-this-way-lady-gaga' },
|
|
40
|
+
{ id: 12669, title: 'Edge of Glory', artist: 'Lady Gaga', slug: 'the-edge-of-glory-lady-gaga' },
|
|
41
|
+
{ id: 28817, title: 'Applause', artist: 'Lady Gaga', slug: 'applause-lady-gaga' },
|
|
42
|
+
// Queen
|
|
43
|
+
{ id: 5772, title: 'Bohemian Rhapsody', artist: 'Queen', slug: 'bohemian-rhapsody-queen' },
|
|
44
|
+
{ id: 5786, title: "Don't Stop Me Now", artist: 'Queen', slug: 'dont-stop-me-now-queen' },
|
|
45
|
+
{ id: 5860, title: 'We Will Rock You', artist: 'Queen', slug: 'we-will-rock-you-queen' },
|
|
46
|
+
{ id: 5841, title: 'Radio Ga Ga', artist: 'Queen', slug: 'radio-gaga-queen' },
|
|
47
|
+
// Michael Jackson
|
|
48
|
+
{ id: 5169, title: 'Billie Jean', artist: 'Michael Jackson', slug: 'billie-jean-michael-jackson' },
|
|
49
|
+
{ id: 5187, title: 'Thriller', artist: 'Michael Jackson', slug: 'thriller-michael-jackson' },
|
|
50
|
+
{ id: 5168, title: 'Beat It', artist: 'Michael Jackson', slug: 'beat-it-michael-jackson' },
|
|
51
|
+
{ id: 5181, title: 'Man in the Mirror', artist: 'Michael Jackson', slug: 'man-in-the-mirror-michael-jackson' },
|
|
52
|
+
{ id: 5171, title: 'Black or White', artist: 'Michael Jackson', slug: 'black-or-white-michael-jackson' },
|
|
53
|
+
// Madonna
|
|
54
|
+
{ id: 4916, title: 'Like a Prayer', artist: 'Madonna', slug: 'like-a-prayer-madonna' },
|
|
55
|
+
{ id: 4897, title: 'Material Girl', artist: 'Madonna', slug: 'material-girl-madonna' },
|
|
56
|
+
{ id: 4903, title: 'Vogue', artist: 'Madonna', slug: 'vogue-madonna' },
|
|
57
|
+
{ id: 4899, title: "Papa Don't Preach", artist: 'Madonna', slug: 'papa-dont-preach-madonna' },
|
|
58
|
+
// Guns N' Roses
|
|
59
|
+
{ id: 3634, title: "Sweet Child O' Mine", artist: "Guns N' Roses", slug: 'sweet-child-of-mine-guns-n-roses' },
|
|
60
|
+
{ id: 3621, title: 'November Rain', artist: "Guns N' Roses", slug: 'november-rain-guns-n-roses' },
|
|
61
|
+
{ id: 21484, title: 'Welcome to the Jungle', artist: "Guns N' Roses", slug: 'welcome-to-the-jungle-guns-n-roses' },
|
|
62
|
+
// Nirvana
|
|
63
|
+
{ id: 26749, title: 'Smells Like Teen Spirit', artist: 'Nirvana', slug: 'smells-like-teen-spirit-nirvana' },
|
|
64
|
+
{ id: 5409, title: 'Come As You Are', artist: 'Nirvana', slug: 'come-as-you-are-nirvana' },
|
|
65
|
+
{ id: 5417, title: 'Heart-Shaped Box', artist: 'Nirvana', slug: 'heart-shaped-box-nirvana' },
|
|
66
|
+
// The Beatles
|
|
67
|
+
{ id: 1202, title: 'Yesterday', artist: 'The Beatles', slug: 'yesterday-beatles' },
|
|
68
|
+
{ id: 25870, title: 'Let It Be', artist: 'The Beatles', slug: 'let-it-be-beatles' },
|
|
69
|
+
{ id: 1047, title: 'Hey Jude', artist: 'The Beatles', slug: 'hey-jude-beatles' },
|
|
70
|
+
{ id: 1014, title: 'Come Together', artist: 'The Beatles', slug: 'come-together-beatles' },
|
|
71
|
+
{ id: 12092, title: 'Blackbird', artist: 'The Beatles', slug: 'blackbird-beatles' },
|
|
72
|
+
// Classic rock
|
|
73
|
+
{ id: 2896, title: 'Hotel California', artist: 'Eagles', slug: 'hotel-california-eagles' },
|
|
74
|
+
{ id: 2911, title: 'Take It Easy', artist: 'Eagles', slug: 'take-it-easy-eagles' },
|
|
75
|
+
{ id: 4445, title: 'Stairway to Heaven', artist: 'Led Zeppelin', slug: 'stairway-to-heaven-led-zeppelin' },
|
|
76
|
+
{ id: 4430, title: 'Whole Lotta Love', artist: 'Led Zeppelin', slug: 'whole-lotta-love-led-zeppelin' },
|
|
77
|
+
{ id: 4724, title: 'Purple Haze', artist: 'Jimi Hendrix', slug: 'purple-haze-jimi-hendrix' },
|
|
78
|
+
{ id: 1638, title: 'All Along the Watchtower', artist: 'Bob Dylan', slug: 'all-along-the-watchtower-bob-dylan' },
|
|
79
|
+
{ id: 9620, title: 'Johnny B. Goode', artist: 'Chuck Berry', slug: 'johnny-b-goode-chuck-berry' },
|
|
80
|
+
// Elvis Presley
|
|
81
|
+
{ id: 2966, title: 'Blue Suede Shoes', artist: 'Elvis Presley', slug: 'blue-suede-shoes-elvis' },
|
|
82
|
+
{ id: 2971, title: 'Hound Dog', artist: 'Elvis Presley', slug: 'hound-dog-elvis' },
|
|
83
|
+
{ id: 10228, title: 'Love Me Tender', artist: 'Elvis Presley', slug: 'love-me-tender-elvis' },
|
|
84
|
+
// Soul / R&B
|
|
85
|
+
{ id: 5012, title: "What's Going On", artist: 'Marvin Gaye', slug: 'whats-goin-on-marvin-gaye' },
|
|
86
|
+
{ id: 6659, title: 'Superstition', artist: 'Stevie Wonder', slug: 'superstitions-stevie-wonder' },
|
|
87
|
+
{ id: 6658, title: 'Sir Duke', artist: 'Stevie Wonder', slug: 'sir-duke-stevie-wonder' },
|
|
88
|
+
{ id: 6653, title: "Isn't She Lovely", artist: 'Stevie Wonder', slug: 'isnt-she-lovely-stevie-wonder' },
|
|
89
|
+
// Pop divas
|
|
90
|
+
{ id: 3989, title: 'I Will Always Love You', artist: 'Whitney Houston', slug: 'i-will-always-love-you-whitney-houston' },
|
|
91
|
+
{ id: 3982, title: 'Greatest Love of All', artist: 'Whitney Houston', slug: 'greatest-love-of-all-whitney-houston' },
|
|
92
|
+
{ id: 4975, title: 'Hero', artist: 'Mariah Carey', slug: 'hero-mariah-carey' },
|
|
93
|
+
{ id: 27556, title: 'All I Want for Christmas', artist: 'Mariah Carey', slug: 'all-i-want-for-christmas-is-you-mariah-carey' },
|
|
94
|
+
{ id: 4151, title: 'We Belong Together', artist: 'Mariah Carey', slug: 'we-belong-together-mariah-carey' },
|
|
95
|
+
{ id: 12750, title: 'My Heart Will Go On', artist: 'Celine Dion', slug: 'my-heart-will-go-on-celine-dion' },
|
|
96
|
+
{ id: 2415, title: 'The Power of Love', artist: 'Celine Dion', slug: 'power-of-love-celine-dion' },
|
|
97
|
+
// 2000s pop
|
|
98
|
+
{ id: 2257, title: '...Baby One More Time', artist: 'Britney Spears', slug: 'baby-one-more-time-britney-spears' },
|
|
99
|
+
{ id: 2256, title: 'Toxic', artist: 'Britney Spears', slug: 'toxic-britney-spears' },
|
|
100
|
+
{ id: 2265, title: "Oops!... I Did It Again", artist: 'Britney Spears', slug: 'oops-i-did-it-again-britney-spears' },
|
|
101
|
+
{ id: 1244, title: 'Crazy in Love', artist: 'Beyoncé', slug: 'crazy-in-love-beyonce' },
|
|
102
|
+
{ id: 11736, title: 'Single Ladies', artist: 'Beyoncé', slug: 'single-ladies-beyonce' },
|
|
103
|
+
{ id: 16275, title: 'Halo', artist: 'Beyoncé', slug: 'halo-beyonce' },
|
|
104
|
+
{ id: 11406, title: 'Umbrella', artist: 'Rihanna', slug: 'umbrella-rihanna' },
|
|
105
|
+
{ id: 15528, title: 'Diamonds', artist: 'Rihanna', slug: 'diamonds-rihanna' },
|
|
106
|
+
{ id: 12548, title: 'Rolling in the Deep', artist: 'Adele', slug: 'rolling-in-the-deep-adele' },
|
|
107
|
+
{ id: 12550, title: 'Someone Like You', artist: 'Adele', slug: 'someone-like-you-adele' },
|
|
108
|
+
{ id: 25272, title: 'Hello', artist: 'Adele', slug: 'hello-adele' },
|
|
109
|
+
// Electronic / Dance
|
|
110
|
+
{ id: 12139, title: 'Around the World', artist: 'Daft Punk', slug: 'around-the-world-daft-punk' },
|
|
111
|
+
{ id: 12138, title: 'One More Time', artist: 'Daft Punk', slug: 'one-more-time-daft-punk' },
|
|
112
|
+
{ id: 14492, title: 'Get Lucky', artist: 'Daft Punk', slug: 'get-lucky-feat-pharrell-williams-daft-punk' },
|
|
113
|
+
{ id: 2925, title: 'Blue (Da Ba Dee)', artist: 'Eiffel 65', slug: 'blue-eiffel-65' },
|
|
114
|
+
{ id: 7791, title: 'Sandstorm', artist: 'Darude', slug: 'sandstorm-darude' },
|
|
115
|
+
{ id: 3640, title: 'What Is Love', artist: 'Haddaway', slug: 'what-is-love-haddaway' },
|
|
116
|
+
{ id: 6576, title: 'Rhythm Is a Dancer', artist: 'Snap!', slug: 'rhythm-is-a-dancer-snap' },
|
|
117
|
+
// The Killers
|
|
118
|
+
{ id: 9105, title: 'Mr. Brightside', artist: 'The Killers', slug: 'mr-brightside-killers' },
|
|
119
|
+
{ id: 24620, title: 'Human', artist: 'The Killers', slug: 'human-killers' },
|
|
120
|
+
// Classical
|
|
121
|
+
{ id: 26718, title: 'Für Elise', artist: 'Beethoven', slug: 'fur-elise-artists-bands' },
|
|
122
|
+
// Video games
|
|
123
|
+
{ id: 8373, title: 'Super Mario Bros Theme', artist: 'Video Games', slug: 'super-mario-brothers-video-games' },
|
|
124
|
+
{ id: 8840, title: 'Tetris Theme (Korobeiniki)', artist: 'Video Games', slug: 'theme-a-tetris' },
|
|
125
|
+
{ id: 8687, title: 'Mega Man 2 — Dr. Wily', artist: 'Video Games', slug: 'wiley-stage-i-mega-man-ii' },
|
|
126
|
+
];
|
|
127
|
+
//# sourceMappingURL=freemidi-catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freemidi-catalog.js","sourceRoot":"","sources":["../../src/freemidi-catalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * freemidi-catalog.ts — Curated list of tracks from freemidi.org\n *\n * Attribution: All tracks sourced from https://freemidi.org\n * MIDI files are fetched on-demand (no local copies stored).\n *\n * IDs verified by cross-referencing the freemidi.org artist pages and\n * Google site:freemidi.org searches. Entries with unverifiable IDs were\n * removed rather than kept with guessed values.\n *\n * Fallback track (open source / direct URL): Beethoven Moonlight Sonata via bitmidi.com\n */\n\nexport interface MidiTrack {\n id: number;\n title: string;\n artist: string;\n /** freemidi.org download page slug, used to get session cookie */\n slug: string;\n}\n\n/** Build the two-step freemidi.org fetch URLs for a track. */\nexport function freemidiUrls(track: MidiTrack): { downloadPage: string; getter: string } {\n return {\n downloadPage: `https://freemidi.org/download3-${track.id}-${track.slug}`,\n getter: `https://freemidi.org/getter-${track.id}`,\n };\n}\n\n/** Fallback track — direct URL, no auth required. */\nexport const FALLBACK_TRACK: MidiTrack & { directUrl: string } = {\n id: 0,\n title: 'Moonlight Sonata',\n artist: 'Beethoven',\n slug: '',\n directUrl: 'https://bitmidi.com/uploads/16752.mid',\n};\n\n/** Default selected track. */\nexport const DEFAULT_TRACK_ID = 28946; // Abracadabra — Lady Gaga\n\nexport const MIDI_CATALOG: MidiTrack[] = [\n // Lady Gaga — verified from https://freemidi.org/artist-1586-lady-gaga\n { id: 28946, title: 'Abracadabra', artist: 'Lady Gaga', slug: 'abracadabra-lady-gaga' },\n { id: 11747, title: 'Bad Romance', artist: 'Lady Gaga', slug: 'bad-romance-lady-gaga' },\n { id: 11544, title: 'Poker Face', artist: 'Lady Gaga', slug: 'poker-face-lady-gaga' },\n { id: 11543, title: 'Just Dance', artist: 'Lady Gaga', slug: 'just-dance-lady-gaga' },\n { id: 12112, title: 'Telephone', artist: 'Lady Gaga', slug: 'telephone-lady-gaga' },\n { id: 11748, title: 'Paparazzi', artist: 'Lady Gaga', slug: 'paparazzi--lady-gaga' },\n { id: 12582, title: 'Alejandro', artist: 'Lady Gaga', slug: 'alejandro-lady-gaga' },\n { id: 12583, title: 'Born This Way', artist: 'Lady Gaga', slug: 'born-this-way-lady-gaga' },\n { id: 12669, title: 'Edge of Glory', artist: 'Lady Gaga', slug: 'the-edge-of-glory-lady-gaga' },\n { id: 28817, title: 'Applause', artist: 'Lady Gaga', slug: 'applause-lady-gaga' },\n\n // Queen\n { id: 5772, title: 'Bohemian Rhapsody', artist: 'Queen', slug: 'bohemian-rhapsody-queen' },\n { id: 5786, title: \"Don't Stop Me Now\", artist: 'Queen', slug: 'dont-stop-me-now-queen' },\n { id: 5860, title: 'We Will Rock You', artist: 'Queen', slug: 'we-will-rock-you-queen' },\n { id: 5841, title: 'Radio Ga Ga', artist: 'Queen', slug: 'radio-gaga-queen' },\n\n // Michael Jackson\n { id: 5169, title: 'Billie Jean', artist: 'Michael Jackson', slug: 'billie-jean-michael-jackson' },\n { id: 5187, title: 'Thriller', artist: 'Michael Jackson', slug: 'thriller-michael-jackson' },\n { id: 5168, title: 'Beat It', artist: 'Michael Jackson', slug: 'beat-it-michael-jackson' },\n { id: 5181, title: 'Man in the Mirror', artist: 'Michael Jackson', slug: 'man-in-the-mirror-michael-jackson' },\n { id: 5171, title: 'Black or White', artist: 'Michael Jackson', slug: 'black-or-white-michael-jackson' },\n\n // Madonna\n { id: 4916, title: 'Like a Prayer', artist: 'Madonna', slug: 'like-a-prayer-madonna' },\n { id: 4897, title: 'Material Girl', artist: 'Madonna', slug: 'material-girl-madonna' },\n { id: 4903, title: 'Vogue', artist: 'Madonna', slug: 'vogue-madonna' },\n { id: 4899, title: \"Papa Don't Preach\", artist: 'Madonna', slug: 'papa-dont-preach-madonna' },\n\n // Guns N' Roses\n { id: 3634, title: \"Sweet Child O' Mine\", artist: \"Guns N' Roses\", slug: 'sweet-child-of-mine-guns-n-roses' },\n { id: 3621, title: 'November Rain', artist: \"Guns N' Roses\", slug: 'november-rain-guns-n-roses' },\n { id: 21484, title: 'Welcome to the Jungle', artist: \"Guns N' Roses\", slug: 'welcome-to-the-jungle-guns-n-roses' },\n\n // Nirvana\n { id: 26749, title: 'Smells Like Teen Spirit', artist: 'Nirvana', slug: 'smells-like-teen-spirit-nirvana' },\n { id: 5409, title: 'Come As You Are', artist: 'Nirvana', slug: 'come-as-you-are-nirvana' },\n { id: 5417, title: 'Heart-Shaped Box', artist: 'Nirvana', slug: 'heart-shaped-box-nirvana' },\n\n // The Beatles\n { id: 1202, title: 'Yesterday', artist: 'The Beatles', slug: 'yesterday-beatles' },\n { id: 25870, title: 'Let It Be', artist: 'The Beatles', slug: 'let-it-be-beatles' },\n { id: 1047, title: 'Hey Jude', artist: 'The Beatles', slug: 'hey-jude-beatles' },\n { id: 1014, title: 'Come Together', artist: 'The Beatles', slug: 'come-together-beatles' },\n { id: 12092, title: 'Blackbird', artist: 'The Beatles', slug: 'blackbird-beatles' },\n\n // Classic rock\n { id: 2896, title: 'Hotel California', artist: 'Eagles', slug: 'hotel-california-eagles' },\n { id: 2911, title: 'Take It Easy', artist: 'Eagles', slug: 'take-it-easy-eagles' },\n { id: 4445, title: 'Stairway to Heaven', artist: 'Led Zeppelin', slug: 'stairway-to-heaven-led-zeppelin' },\n { id: 4430, title: 'Whole Lotta Love', artist: 'Led Zeppelin', slug: 'whole-lotta-love-led-zeppelin' },\n { id: 4724, title: 'Purple Haze', artist: 'Jimi Hendrix', slug: 'purple-haze-jimi-hendrix' },\n { id: 1638, title: 'All Along the Watchtower', artist: 'Bob Dylan', slug: 'all-along-the-watchtower-bob-dylan' },\n { id: 9620, title: 'Johnny B. Goode', artist: 'Chuck Berry', slug: 'johnny-b-goode-chuck-berry' },\n\n // Elvis Presley\n { id: 2966, title: 'Blue Suede Shoes', artist: 'Elvis Presley', slug: 'blue-suede-shoes-elvis' },\n { id: 2971, title: 'Hound Dog', artist: 'Elvis Presley', slug: 'hound-dog-elvis' },\n { id: 10228, title: 'Love Me Tender', artist: 'Elvis Presley', slug: 'love-me-tender-elvis' },\n\n // Soul / R&B\n { id: 5012, title: \"What's Going On\", artist: 'Marvin Gaye', slug: 'whats-goin-on-marvin-gaye' },\n { id: 6659, title: 'Superstition', artist: 'Stevie Wonder', slug: 'superstitions-stevie-wonder' },\n { id: 6658, title: 'Sir Duke', artist: 'Stevie Wonder', slug: 'sir-duke-stevie-wonder' },\n { id: 6653, title: \"Isn't She Lovely\", artist: 'Stevie Wonder', slug: 'isnt-she-lovely-stevie-wonder' },\n\n // Pop divas\n { id: 3989, title: 'I Will Always Love You', artist: 'Whitney Houston', slug: 'i-will-always-love-you-whitney-houston' },\n { id: 3982, title: 'Greatest Love of All', artist: 'Whitney Houston', slug: 'greatest-love-of-all-whitney-houston' },\n { id: 4975, title: 'Hero', artist: 'Mariah Carey', slug: 'hero-mariah-carey' },\n { id: 27556, title: 'All I Want for Christmas', artist: 'Mariah Carey', slug: 'all-i-want-for-christmas-is-you-mariah-carey' },\n { id: 4151, title: 'We Belong Together', artist: 'Mariah Carey', slug: 'we-belong-together-mariah-carey' },\n { id: 12750, title: 'My Heart Will Go On', artist: 'Celine Dion', slug: 'my-heart-will-go-on-celine-dion' },\n { id: 2415, title: 'The Power of Love', artist: 'Celine Dion', slug: 'power-of-love-celine-dion' },\n\n // 2000s pop\n { id: 2257, title: '...Baby One More Time', artist: 'Britney Spears', slug: 'baby-one-more-time-britney-spears' },\n { id: 2256, title: 'Toxic', artist: 'Britney Spears', slug: 'toxic-britney-spears' },\n { id: 2265, title: \"Oops!... I Did It Again\", artist: 'Britney Spears', slug: 'oops-i-did-it-again-britney-spears' },\n { id: 1244, title: 'Crazy in Love', artist: 'Beyoncé', slug: 'crazy-in-love-beyonce' },\n { id: 11736, title: 'Single Ladies', artist: 'Beyoncé', slug: 'single-ladies-beyonce' },\n { id: 16275, title: 'Halo', artist: 'Beyoncé', slug: 'halo-beyonce' },\n { id: 11406, title: 'Umbrella', artist: 'Rihanna', slug: 'umbrella-rihanna' },\n { id: 15528, title: 'Diamonds', artist: 'Rihanna', slug: 'diamonds-rihanna' },\n { id: 12548, title: 'Rolling in the Deep', artist: 'Adele', slug: 'rolling-in-the-deep-adele' },\n { id: 12550, title: 'Someone Like You', artist: 'Adele', slug: 'someone-like-you-adele' },\n { id: 25272, title: 'Hello', artist: 'Adele', slug: 'hello-adele' },\n\n // Electronic / Dance\n { id: 12139, title: 'Around the World', artist: 'Daft Punk', slug: 'around-the-world-daft-punk' },\n { id: 12138, title: 'One More Time', artist: 'Daft Punk', slug: 'one-more-time-daft-punk' },\n { id: 14492, title: 'Get Lucky', artist: 'Daft Punk', slug: 'get-lucky-feat-pharrell-williams-daft-punk' },\n { id: 2925, title: 'Blue (Da Ba Dee)', artist: 'Eiffel 65', slug: 'blue-eiffel-65' },\n { id: 7791, title: 'Sandstorm', artist: 'Darude', slug: 'sandstorm-darude' },\n { id: 3640, title: 'What Is Love', artist: 'Haddaway', slug: 'what-is-love-haddaway' },\n { id: 6576, title: 'Rhythm Is a Dancer', artist: 'Snap!', slug: 'rhythm-is-a-dancer-snap' },\n\n // The Killers\n { id: 9105, title: 'Mr. Brightside', artist: 'The Killers', slug: 'mr-brightside-killers' },\n { id: 24620, title: 'Human', artist: 'The Killers', slug: 'human-killers' },\n\n // Classical\n { id: 26718, title: 'Für Elise', artist: 'Beethoven', slug: 'fur-elise-artists-bands' },\n\n // Video games\n { id: 8373, title: 'Super Mario Bros Theme', artist: 'Video Games', slug: 'super-mario-brothers-video-games' },\n { id: 8840, title: 'Tetris Theme (Korobeiniki)', artist: 'Video Games', slug: 'theme-a-tetris' },\n { id: 8687, title: 'Mega Man 2 — Dr. Wily', artist: 'Video Games', slug: 'wiley-stage-i-mega-man-ii' },\n];\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snake-game — public API
|
|
3
|
+
*
|
|
4
|
+
* Embed the game in an existing Ink app:
|
|
5
|
+
* import { SnakeGame } from 'snake-game';
|
|
6
|
+
* <SnakeGame onExit={() => { ... }} colors={{ head: '#ff0000' }} music={false} />
|
|
7
|
+
*
|
|
8
|
+
* Or launch it imperatively from any CLI and await completion:
|
|
9
|
+
* import { runSnakeGame } from 'snake-game';
|
|
10
|
+
* await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });
|
|
11
|
+
*/
|
|
12
|
+
export { SnakeGame } from './SnakeGame.js';
|
|
13
|
+
export type { SnakeColors } from './types.js';
|
|
14
|
+
export { DEFAULT_COLORS } from './types.js';
|
|
15
|
+
export type { SelectedTrack, MusicConfig } from './MusicSettings.js';
|
|
16
|
+
export type { MidiTrack } from './freemidi-catalog.js';
|
|
17
|
+
export { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';
|
|
18
|
+
import type { SnakeColors } from './types.js';
|
|
19
|
+
import type { MidiTrack } from './freemidi-catalog.js';
|
|
20
|
+
interface RunSnakeGameOptions {
|
|
21
|
+
music?: boolean;
|
|
22
|
+
colors?: SnakeColors;
|
|
23
|
+
cacheDir?: string;
|
|
24
|
+
settingsFile?: string;
|
|
25
|
+
width?: number;
|
|
26
|
+
height?: number;
|
|
27
|
+
tracks?: MidiTrack[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Launch Snake in the current terminal and resolve when the user exits.
|
|
31
|
+
* Safe to call from any CLI — manages its own Ink render lifecycle.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runSnakeGame(options?: RunSnakeGameOptions): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* snake-game — public API
|
|
4
|
+
*
|
|
5
|
+
* Embed the game in an existing Ink app:
|
|
6
|
+
* import { SnakeGame } from 'snake-game';
|
|
7
|
+
* <SnakeGame onExit={() => { ... }} colors={{ head: '#ff0000' }} music={false} />
|
|
8
|
+
*
|
|
9
|
+
* Or launch it imperatively from any CLI and await completion:
|
|
10
|
+
* import { runSnakeGame } from 'snake-game';
|
|
11
|
+
* await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });
|
|
12
|
+
*/
|
|
13
|
+
export { SnakeGame } from './SnakeGame.js';
|
|
14
|
+
export { DEFAULT_COLORS } from './types.js';
|
|
15
|
+
export { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';
|
|
16
|
+
import { render } from 'ink';
|
|
17
|
+
import { SnakeGame } from './SnakeGame.js';
|
|
18
|
+
/**
|
|
19
|
+
* Launch Snake in the current terminal and resolve when the user exits.
|
|
20
|
+
* Safe to call from any CLI — manages its own Ink render lifecycle.
|
|
21
|
+
*/
|
|
22
|
+
export function runSnakeGame(options = {}) {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const app = render(_jsx(SnakeGame, { music: options.music, colors: options.colors, cacheDir: options.cacheDir, settingsFile: options.settingsFile, width: options.width, height: options.height, tracks: options.tracks, onExit: () => {
|
|
25
|
+
app.unmount();
|
|
26
|
+
resolve();
|
|
27
|
+
} }));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErG,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAc3C;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,UAA+B,EAAE;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,MAAM,CAChB,KAAC,SAAS,IACR,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAC1B,YAAY,EAAE,OAAO,CAAC,YAAY,EAClC,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,MAAM,EAAE,OAAO,CAAC,MAAM,EACtB,MAAM,EAAE,GAAG,EAAE;gBACX,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,OAAO,EAAE,CAAC;YACZ,CAAC,GACD,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * snake-game — public API\n *\n * Embed the game in an existing Ink app:\n * import { SnakeGame } from 'snake-game';\n * <SnakeGame onExit={() => { ... }} colors={{ head: '#ff0000' }} music={false} />\n *\n * Or launch it imperatively from any CLI and await completion:\n * import { runSnakeGame } from 'snake-game';\n * await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });\n */\n\nexport { SnakeGame } from './SnakeGame.js';\nexport type { SnakeColors } from './types.js';\nexport { DEFAULT_COLORS } from './types.js';\nexport type { SelectedTrack, MusicConfig } from './MusicSettings.js';\nexport type { MidiTrack } from './freemidi-catalog.js';\nexport { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';\n\nimport { render } from 'ink';\nimport { SnakeGame } from './SnakeGame.js';\nimport type { SnakeColors } from './types.js';\nimport type { MidiTrack } from './freemidi-catalog.js';\n\ninterface RunSnakeGameOptions {\n music?: boolean;\n colors?: SnakeColors;\n cacheDir?: string;\n settingsFile?: string;\n width?: number;\n height?: number;\n tracks?: MidiTrack[];\n}\n\n/**\n * Launch Snake in the current terminal and resolve when the user exits.\n * Safe to call from any CLI — manages its own Ink render lifecycle.\n */\nexport function runSnakeGame(options: RunSnakeGameOptions = {}): Promise<void> {\n return new Promise((resolve) => {\n const app = render(\n <SnakeGame\n music={options.music}\n colors={options.colors}\n cacheDir={options.cacheDir}\n settingsFile={options.settingsFile}\n width={options.width}\n height={options.height}\n tracks={options.tracks}\n onExit={() => {\n app.unmount();\n resolve();\n }}\n />,\n );\n });\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* settings.ts — Persistent settings for snake-game stored in ~/.snake-game.json
|
|
3
|
+
*/
|
|
4
|
+
export declare function getSnakeHighScore(configPath?: string): number;
|
|
5
|
+
export declare function setSnakeHighScore(score: number, configPath?: string): void;
|
|
6
|
+
export declare function getSnakeMusicVolume(configPath?: string): number;
|
|
7
|
+
export declare function setSnakeMusicVolume(volume: number, configPath?: string): void;
|
|
8
|
+
export declare function getSnakeTinkVolume(configPath?: string): number;
|
|
9
|
+
export declare function setSnakeTinkVolume(volume: number, configPath?: string): void;
|
|
10
|
+
export declare function getSnakeSfxVolume(configPath?: string): number;
|
|
11
|
+
export declare function setSnakeSfxVolume(volume: number, configPath?: string): void;
|