@pavus/snake-game 1.0.0 → 1.1.1

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 ADDED
@@ -0,0 +1,92 @@
1
+ # snake-game
2
+
3
+ Playable Snake in the terminal with MIDI music synthesis.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js 18+
8
+ - macOS (audio playback uses `afplay`)
9
+ - pnpm (for development)
10
+
11
+ ## Running
12
+
13
+ ### From source
14
+
15
+ ```bash
16
+ pnpm install
17
+ pnpm try # run with tsx (no build step)
18
+ ```
19
+
20
+ ### After building
21
+
22
+ ```bash
23
+ pnpm build
24
+ pnpm start # node dist/bin.js
25
+ ```
26
+
27
+ ### As a global CLI
28
+
29
+ ```bash
30
+ pnpm build:link # build + pnpm link --global
31
+ snake-game
32
+ ```
33
+
34
+ ### As a library
35
+
36
+ Install in your own project:
37
+
38
+ ```bash
39
+ npm install @pavus/snake-game
40
+ ```
41
+
42
+ Embed in an Ink app:
43
+
44
+ ```tsx
45
+ import { SnakeGame } from '@pavus/snake-game';
46
+
47
+ <SnakeGame onExit={() => process.exit(0)} />
48
+ ```
49
+
50
+ Or launch imperatively from any CLI:
51
+
52
+ ```ts
53
+ import { runSnakeGame } from '@pavus/snake-game';
54
+
55
+ await runSnakeGame({ music: false });
56
+ ```
57
+
58
+ ## Controls
59
+
60
+ | Key | Action |
61
+ |-----|--------|
62
+ | `W` `A` `S` `D` or arrow keys | Move |
63
+ | `Space` | Pause / unpause |
64
+ | `R` | Restart |
65
+ | `M` | Open music settings |
66
+ | `[` | Previous track |
67
+ | `]` | Next track (random) |
68
+ | `L` | Toggle loop |
69
+ | `Q` | Quit |
70
+
71
+ ## Music settings (`M`)
72
+
73
+ - **Tracks tab** — search and select from 58 curated MIDI tracks
74
+ - **Volumes tab** — adjust BGM, tink, and SFX volumes independently; toggle loop
75
+
76
+ The game remembers your last played track and loop preference across sessions (stored in `~/.snake-game.json`).
77
+
78
+ ## Options
79
+
80
+ ```tsx
81
+ <SnakeGame
82
+ music={true} // enable/disable all audio (default: true)
83
+ width={20} // grid width in cells (default: 20)
84
+ height={10} // grid height in cells (default: 10)
85
+ cacheDir="/tmp" // where to cache synthesized WAV files
86
+ settingsFile="~/.snake-game.json" // path for persistent settings
87
+ tracks={myTrackList} // custom MidiTrack[] list
88
+ colors={{ head: '#ff0000', accent: '#00ff00' }}
89
+ keybindings={{ quit: ['escape'] }}
90
+ onExit={() => process.exit(0)}
91
+ />
92
+ ```
@@ -20,6 +20,7 @@ export interface MusicConfig {
20
20
  musicVolume: number;
21
21
  tinkVolume: number;
22
22
  sfxVolume: number;
23
+ loopEnabled: boolean;
23
24
  }
24
25
  interface MusicSettingsProps {
25
26
  initial: MusicConfig;
@@ -36,11 +36,13 @@ const VOICES = [
36
36
  { key: 'tinkVolume', label: 'Tink (per frame)' },
37
37
  { key: 'sfxVolume', label: 'SFX (eat / die)' },
38
38
  ];
39
+ const LOOP_ROW_IDX = VOICES.length; // index of the loop toggle row
39
40
  export const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks }) => {
40
41
  const accent = accentColor ?? DEFAULT_COLORS.accent;
41
42
  const catalog = tracks ?? MIDI_CATALOG;
42
43
  const [section, setSection] = useState('tracks');
43
44
  const [voiceFocus, setVoiceFocus] = useState(0);
45
+ const [loopEnabled, setLoopEnabled] = useState(initial.loopEnabled);
44
46
  const [volumes, setVolumes] = useState({
45
47
  musicVolume: initial.musicVolume,
46
48
  tinkVolume: initial.tinkVolume,
@@ -69,7 +71,7 @@ export const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks
69
71
  const scrollOffset = scrollRef.current;
70
72
  const visible = filtered.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);
71
73
  const apply = (track) => {
72
- onApply({ track, ...volumes });
74
+ onApply({ track, ...volumes, loopEnabled });
73
75
  };
74
76
  useInput((input, key) => {
75
77
  if (key.escape) {
@@ -86,10 +88,18 @@ export const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks
86
88
  return;
87
89
  }
88
90
  if (key.downArrow) {
89
- setVoiceFocus((f) => Math.min(VOICES.length - 1, f + 1));
91
+ setVoiceFocus((f) => Math.min(LOOP_ROW_IDX, f + 1));
90
92
  return;
91
93
  }
92
- if (key.leftArrow || key.rightArrow) {
94
+ if (key.leftArrow || key.rightArrow || key.return) {
95
+ if (voiceFocus === LOOP_ROW_IDX) {
96
+ setLoopEnabled((v) => !v);
97
+ return;
98
+ }
99
+ if (key.return) {
100
+ apply(selectedTrack);
101
+ return;
102
+ }
93
103
  const voice = VOICES[voiceFocus];
94
104
  if (!voice)
95
105
  return;
@@ -135,10 +145,13 @@ export const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks
135
145
  const isFocused = i === voiceFocus;
136
146
  const val = volumes[voice.key];
137
147
  return (_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: isFocused ? '▶' : ' ' }), _jsx(Box, { width: 18, children: _jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: voice.label }) }), _jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: volBar(val) }), _jsx(Text, { dimColor: true, children: fmtPct(val) })] }, voice.key));
138
- }), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 select voice" }), _jsx(Text, { dimColor: true, children: "\u2190 \u2192 adjust" }), _jsx(Text, { dimColor: true, children: "Enter apply" }), _jsx(Text, { dimColor: true, children: "Esc cancel" })] })] })), section === 'tracks' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Now playing:" }), _jsx(Text, { color: accent, children: selectedTrack.title }), _jsxs(Text, { dimColor: true, children: ["\u2014 ", selectedTrack.artist] })] }), _jsxs(Box, { gap: 1, marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Search:" }), _jsx(Text, { color: accent, children: query || ' ' }), _jsx(Text, { dimColor: true, children: "\u258C" })] }), filtered.length === 0 ? (_jsx(Text, { dimColor: true, children: "No matches. Backspace to clear." })) : (_jsxs(Box, { flexDirection: "column", children: [scrollOffset > 0 && _jsxs(Text, { dimColor: true, children: [" \u2191 ", scrollOffset, " more"] }), visible.map((track, i) => {
148
+ }), (() => {
149
+ const isFocused = voiceFocus === LOOP_ROW_IDX;
150
+ return (_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: isFocused ? '▶' : ' ' }), _jsx(Box, { width: 18, children: _jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: "Loop track" }) }), _jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: loopEnabled ? '● On ' : '○ Off' })] }));
151
+ })(), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 select" }), _jsx(Text, { dimColor: true, children: "\u2190 \u2192/Enter toggle loop" }), _jsx(Text, { dimColor: true, children: "Enter apply" }), _jsx(Text, { dimColor: true, children: "Esc cancel" })] })] })), section === 'tracks' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Now playing:" }), _jsx(Text, { color: accent, children: selectedTrack.title }), _jsxs(Text, { dimColor: true, children: ["\u2014 ", selectedTrack.artist] })] }), _jsxs(Box, { gap: 1, marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Search:" }), _jsx(Text, { color: accent, children: query || ' ' }), _jsx(Text, { dimColor: true, children: "\u258C" })] }), filtered.length === 0 ? (_jsx(Text, { dimColor: true, children: "No matches. Backspace to clear." })) : (_jsxs(Box, { flexDirection: "column", children: [scrollOffset > 0 && _jsxs(Text, { dimColor: true, children: [" \u2191 ", scrollOffset, " more"] }), visible.map((track, i) => {
139
152
  const idx = scrollOffset + i;
140
153
  const isFocused = idx === safeFocus;
141
154
  return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: isFocused ? accent : undefined, dimColor: !isFocused, children: isFocused ? '▶' : ' ' }), _jsx(Text, { color: isFocused ? accent : undefined, bold: isFocused, dimColor: !isFocused, children: track.title }), _jsxs(Text, { dimColor: true, children: ["\u2014 ", track.artist] })] }, track.id));
142
- }), scrollOffset + VISIBLE_ROWS < filtered.length && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", filtered.length - scrollOffset - VISIBLE_ROWS, " more"] }))] })), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate" }), _jsx(Text, { dimColor: true, children: "Enter select" }), _jsx(Text, { dimColor: true, children: "Esc cancel" })] })] }))] }));
155
+ }), scrollOffset + VISIBLE_ROWS < filtered.length && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", filtered.length - scrollOffset - VISIBLE_ROWS, " more"] }))] })), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate" }), _jsx(Text, { dimColor: true, children: "Enter select" }), _jsx(Text, { dimColor: true, children: "Esc cancel" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "tip: use \"[\" or \"]\" to go back or forward songs \u00B7 \"L\" to toggle loop" }) })] }))] }));
143
156
  };
144
157
  //# sourceMappingURL=MusicSettings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MusicSettings.js","sourceRoot":"","sources":["../../src/MusicSettings.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * MusicSettings — In-game music configuration panel for Snake.\n *\n * Two sections (Tab to switch):\n * Volumes — BGM / Tink / SFX, adjust with ← →\n * Tracks — searchable list from freemidi.org\n *\n * Esc / Enter on a track closes and applies changes.\n */\n\nimport { Box, Text, useInput } from 'ink';\nimport { useState, useMemo, useRef } from 'react';\nimport { DEFAULT_COLORS } from './types.js';\nimport {\n MIDI_CATALOG,\n FALLBACK_TRACK,\n DEFAULT_TRACK_ID,\n type MidiTrack,\n freemidiUrls,\n} from './freemidi-catalog.js';\n\nexport type { MidiTrack };\n\nexport interface SelectedTrack {\n title: string;\n artist: string;\n url: string;\n downloadPage?: string;\n}\n\nexport interface MusicConfig {\n track: SelectedTrack;\n musicVolume: number;\n tinkVolume: number;\n sfxVolume: number;\n}\n\nfunction trackToSelected(track: MidiTrack): SelectedTrack {\n const urls = freemidiUrls(track);\n return { title: track.title, artist: track.artist, url: urls.getter, downloadPage: urls.downloadPage };\n}\n\nfunction fallbackSelected(): SelectedTrack {\n return { title: FALLBACK_TRACK.title, artist: FALLBACK_TRACK.artist, url: FALLBACK_TRACK.directUrl };\n}\n\nconst VISIBLE_ROWS = 8;\nconst VOLUME_STEP = 0.1;\n\nfunction volBar(v: number): string {\n const filled = Math.round(v * 10);\n return '█'.repeat(filled) + '░'.repeat(10 - filled);\n}\n\nfunction fmtPct(v: number): string {\n return `${Math.round(v * 100)}%`;\n}\n\nfunction snap(v: number): number {\n return Math.max(0, Math.min(1, Math.round(v * 10) / 10));\n}\n\ntype Section = 'volumes' | 'tracks';\n\nconst VOICES = [\n { key: 'musicVolume' as const, label: 'Music (BGM)' },\n { key: 'tinkVolume' as const, label: 'Tink (per frame)' },\n { key: 'sfxVolume' as const, label: 'SFX (eat / die)' },\n];\n\ninterface MusicSettingsProps {\n initial: MusicConfig;\n onApply: (config: MusicConfig) => void;\n onCancel: () => void;\n accentColor?: string;\n /** Track list to show in the browser. Defaults to the built-in MIDI_CATALOG. */\n tracks?: MidiTrack[];\n}\n\nexport const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks }: MusicSettingsProps) => {\n const accent = accentColor ?? DEFAULT_COLORS.accent;\n const catalog = tracks ?? MIDI_CATALOG;\n const [section, setSection] = useState<Section>('tracks');\n const [voiceFocus, setVoiceFocus] = useState(0);\n const [volumes, setVolumes] = useState({\n musicVolume: initial.musicVolume,\n tinkVolume: initial.tinkVolume,\n sfxVolume: initial.sfxVolume,\n });\n const [selectedTrack, setSelectedTrack] = useState<SelectedTrack>(initial.track);\n\n // Track browser state\n const [query, setQuery] = useState('');\n const scrollRef = useRef(0);\n const sorted = useMemo(\n () => [...catalog].sort((a, b) => a.title.localeCompare(b.title)),\n [catalog],\n );\n const [trackFocus, setTrackFocus] = useState(() => {\n const idx = sorted.findIndex((t) => t.id === DEFAULT_TRACK_ID);\n return idx >= 0 ? idx : 0;\n });\n const filtered = useMemo(() => {\n if (!query) return sorted;\n const q = query.toLowerCase();\n return sorted.filter((t) => t.title.toLowerCase().includes(q) || t.artist.toLowerCase().includes(q));\n }, [query, sorted]);\n\n const safeFocus = Math.min(trackFocus, Math.max(0, filtered.length - 1));\n if (safeFocus < scrollRef.current) scrollRef.current = safeFocus;\n else if (safeFocus >= scrollRef.current + VISIBLE_ROWS) scrollRef.current = safeFocus - VISIBLE_ROWS + 1;\n const scrollOffset = scrollRef.current;\n const visible = filtered.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);\n\n const apply = (track: SelectedTrack) => {\n onApply({ track, ...volumes });\n };\n\n useInput((input, key) => {\n if (key.escape) { onCancel(); return; }\n\n if (key.tab) {\n setSection((s) => s === 'tracks' ? 'volumes' : 'tracks');\n return;\n }\n\n if (section === 'volumes') {\n if (key.upArrow) { setVoiceFocus((f) => Math.max(0, f - 1)); return; }\n if (key.downArrow) { setVoiceFocus((f) => Math.min(VOICES.length - 1, f + 1)); return; }\n if (key.leftArrow || key.rightArrow) {\n const voice = VOICES[voiceFocus];\n if (!voice) return;\n const delta = key.leftArrow ? -VOLUME_STEP : VOLUME_STEP;\n setVolumes((v) => ({ ...v, [voice.key]: snap(v[voice.key] + delta) }));\n return;\n }\n if (key.return) { apply(selectedTrack); return; }\n }\n\n if (section === 'tracks') {\n if (key.return) {\n const track = filtered[safeFocus];\n const sel = track ? trackToSelected(track) : fallbackSelected();\n setSelectedTrack(sel);\n apply(sel);\n return;\n }\n if (key.upArrow) { setTrackFocus((f) => Math.max(0, Math.min(f, filtered.length - 1) - 1)); return; }\n if (key.downArrow) { setTrackFocus((f) => Math.min(filtered.length - 1, Math.min(f, filtered.length - 1) + 1)); return; }\n\n if (key.backspace || key.delete) {\n setQuery((q) => q.slice(0, -1));\n setTrackFocus(0); scrollRef.current = 0;\n return;\n }\n if (input && !key.ctrl && !key.meta && input.length === 1) {\n setQuery((q) => q + input);\n setTrackFocus(0); scrollRef.current = 0;\n }\n }\n });\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n {/* Header */}\n <Box gap={3} marginBottom={1}>\n <Text bold color={accent}>Music Settings</Text>\n <Box gap={1}>\n <Text\n bold={section === 'tracks'}\n color={section === 'tracks' ? accent : undefined}\n dimColor={section !== 'tracks'}\n >\n Tracks\n </Text>\n <Text dimColor>·</Text>\n <Text\n bold={section === 'volumes'}\n color={section === 'volumes' ? accent : undefined}\n dimColor={section !== 'volumes'}\n >\n Volumes\n </Text>\n </Box>\n <Text dimColor>Tab to switch</Text>\n </Box>\n\n {section === 'volumes' && (\n <Box flexDirection=\"column\" gap={0}>\n {VOICES.map((voice, i) => {\n const isFocused = i === voiceFocus;\n const val = volumes[voice.key];\n return (\n <Box key={voice.key} gap={2}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Box width={18}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {voice.label}\n </Text>\n </Box>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {volBar(val)}\n </Text>\n <Text dimColor>{fmtPct(val)}</Text>\n </Box>\n );\n })}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ select voice</Text>\n <Text dimColor>← → adjust</Text>\n <Text dimColor>Enter apply</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n </Box>\n )}\n\n {section === 'tracks' && (\n <Box flexDirection=\"column\">\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Now playing:</Text>\n <Text color={accent}>{selectedTrack.title}</Text>\n <Text dimColor>— {selectedTrack.artist}</Text>\n </Box>\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Search:</Text>\n <Text color={accent}>{query || ' '}</Text>\n <Text dimColor>▌</Text>\n </Box>\n {filtered.length === 0 ? (\n <Text dimColor>No matches. Backspace to clear.</Text>\n ) : (\n <Box flexDirection=\"column\">\n {scrollOffset > 0 && <Text dimColor> ↑ {scrollOffset} more</Text>}\n {visible.map((track, i) => {\n const idx = scrollOffset + i;\n const isFocused = idx === safeFocus;\n return (\n <Box key={track.id} gap={1}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Text color={isFocused ? accent : undefined} bold={isFocused} dimColor={!isFocused}>\n {track.title}\n </Text>\n <Text dimColor>— {track.artist}</Text>\n </Box>\n );\n })}\n {scrollOffset + VISIBLE_ROWS < filtered.length && (\n <Text dimColor> ↓ {filtered.length - scrollOffset - VISIBLE_ROWS} more</Text>\n )}\n </Box>\n )}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ navigate</Text>\n <Text dimColor>Enter select</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n </Box>\n )}\n </Box>\n );\n};\n"]}
1
+ {"version":3,"file":"MusicSettings.js","sourceRoot":"","sources":["../../src/MusicSettings.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * MusicSettings — In-game music configuration panel for Snake.\n *\n * Two sections (Tab to switch):\n * Volumes — BGM / Tink / SFX, adjust with ← →\n * Tracks — searchable list from freemidi.org\n *\n * Esc / Enter on a track closes and applies changes.\n */\n\nimport { Box, Text, useInput } from 'ink';\nimport { useState, useMemo, useRef } from 'react';\nimport { DEFAULT_COLORS } from './types.js';\nimport {\n MIDI_CATALOG,\n FALLBACK_TRACK,\n DEFAULT_TRACK_ID,\n type MidiTrack,\n freemidiUrls,\n} from './freemidi-catalog.js';\n\nexport type { MidiTrack };\n\nexport interface SelectedTrack {\n title: string;\n artist: string;\n url: string;\n downloadPage?: string;\n}\n\nexport interface MusicConfig {\n track: SelectedTrack;\n musicVolume: number;\n tinkVolume: number;\n sfxVolume: number;\n loopEnabled: boolean;\n}\n\nfunction trackToSelected(track: MidiTrack): SelectedTrack {\n const urls = freemidiUrls(track);\n return { title: track.title, artist: track.artist, url: urls.getter, downloadPage: urls.downloadPage };\n}\n\nfunction fallbackSelected(): SelectedTrack {\n return { title: FALLBACK_TRACK.title, artist: FALLBACK_TRACK.artist, url: FALLBACK_TRACK.directUrl };\n}\n\nconst VISIBLE_ROWS = 8;\nconst VOLUME_STEP = 0.1;\n\nfunction volBar(v: number): string {\n const filled = Math.round(v * 10);\n return '█'.repeat(filled) + '░'.repeat(10 - filled);\n}\n\nfunction fmtPct(v: number): string {\n return `${Math.round(v * 100)}%`;\n}\n\nfunction snap(v: number): number {\n return Math.max(0, Math.min(1, Math.round(v * 10) / 10));\n}\n\ntype Section = 'volumes' | 'tracks';\n\nconst VOICES = [\n { key: 'musicVolume' as const, label: 'Music (BGM)' },\n { key: 'tinkVolume' as const, label: 'Tink (per frame)' },\n { key: 'sfxVolume' as const, label: 'SFX (eat / die)' },\n];\n\nconst LOOP_ROW_IDX = VOICES.length; // index of the loop toggle row\n\ninterface MusicSettingsProps {\n initial: MusicConfig;\n onApply: (config: MusicConfig) => void;\n onCancel: () => void;\n accentColor?: string;\n /** Track list to show in the browser. Defaults to the built-in MIDI_CATALOG. */\n tracks?: MidiTrack[];\n}\n\nexport const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks }: MusicSettingsProps) => {\n const accent = accentColor ?? DEFAULT_COLORS.accent;\n const catalog = tracks ?? MIDI_CATALOG;\n const [section, setSection] = useState<Section>('tracks');\n const [voiceFocus, setVoiceFocus] = useState(0);\n const [loopEnabled, setLoopEnabled] = useState(initial.loopEnabled);\n const [volumes, setVolumes] = useState({\n musicVolume: initial.musicVolume,\n tinkVolume: initial.tinkVolume,\n sfxVolume: initial.sfxVolume,\n });\n const [selectedTrack, setSelectedTrack] = useState<SelectedTrack>(initial.track);\n\n // Track browser state\n const [query, setQuery] = useState('');\n const scrollRef = useRef(0);\n const sorted = useMemo(\n () => [...catalog].sort((a, b) => a.title.localeCompare(b.title)),\n [catalog],\n );\n const [trackFocus, setTrackFocus] = useState(() => {\n const idx = sorted.findIndex((t) => t.id === DEFAULT_TRACK_ID);\n return idx >= 0 ? idx : 0;\n });\n const filtered = useMemo(() => {\n if (!query) return sorted;\n const q = query.toLowerCase();\n return sorted.filter((t) => t.title.toLowerCase().includes(q) || t.artist.toLowerCase().includes(q));\n }, [query, sorted]);\n\n const safeFocus = Math.min(trackFocus, Math.max(0, filtered.length - 1));\n if (safeFocus < scrollRef.current) scrollRef.current = safeFocus;\n else if (safeFocus >= scrollRef.current + VISIBLE_ROWS) scrollRef.current = safeFocus - VISIBLE_ROWS + 1;\n const scrollOffset = scrollRef.current;\n const visible = filtered.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);\n\n const apply = (track: SelectedTrack) => {\n onApply({ track, ...volumes, loopEnabled });\n };\n\n useInput((input, key) => {\n if (key.escape) { onCancel(); return; }\n\n if (key.tab) {\n setSection((s) => s === 'tracks' ? 'volumes' : 'tracks');\n return;\n }\n\n if (section === 'volumes') {\n if (key.upArrow) { setVoiceFocus((f) => Math.max(0, f - 1)); return; }\n if (key.downArrow) { setVoiceFocus((f) => Math.min(LOOP_ROW_IDX, f + 1)); return; }\n if (key.leftArrow || key.rightArrow || key.return) {\n if (voiceFocus === LOOP_ROW_IDX) {\n setLoopEnabled((v) => !v);\n return;\n }\n if (key.return) { apply(selectedTrack); return; }\n const voice = VOICES[voiceFocus];\n if (!voice) return;\n const delta = key.leftArrow ? -VOLUME_STEP : VOLUME_STEP;\n setVolumes((v) => ({ ...v, [voice.key]: snap(v[voice.key] + delta) }));\n return;\n }\n if (key.return) { apply(selectedTrack); return; }\n }\n\n if (section === 'tracks') {\n if (key.return) {\n const track = filtered[safeFocus];\n const sel = track ? trackToSelected(track) : fallbackSelected();\n setSelectedTrack(sel);\n apply(sel);\n return;\n }\n if (key.upArrow) { setTrackFocus((f) => Math.max(0, Math.min(f, filtered.length - 1) - 1)); return; }\n if (key.downArrow) { setTrackFocus((f) => Math.min(filtered.length - 1, Math.min(f, filtered.length - 1) + 1)); return; }\n\n if (key.backspace || key.delete) {\n setQuery((q) => q.slice(0, -1));\n setTrackFocus(0); scrollRef.current = 0;\n return;\n }\n if (input && !key.ctrl && !key.meta && input.length === 1) {\n setQuery((q) => q + input);\n setTrackFocus(0); scrollRef.current = 0;\n }\n }\n });\n\n return (\n <Box flexDirection=\"column\" paddingX={1}>\n {/* Header */}\n <Box gap={3} marginBottom={1}>\n <Text bold color={accent}>Music Settings</Text>\n <Box gap={1}>\n <Text\n bold={section === 'tracks'}\n color={section === 'tracks' ? accent : undefined}\n dimColor={section !== 'tracks'}\n >\n Tracks\n </Text>\n <Text dimColor>·</Text>\n <Text\n bold={section === 'volumes'}\n color={section === 'volumes' ? accent : undefined}\n dimColor={section !== 'volumes'}\n >\n Volumes\n </Text>\n </Box>\n <Text dimColor>Tab to switch</Text>\n </Box>\n\n {section === 'volumes' && (\n <Box flexDirection=\"column\" gap={0}>\n {VOICES.map((voice, i) => {\n const isFocused = i === voiceFocus;\n const val = volumes[voice.key];\n return (\n <Box key={voice.key} gap={2}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Box width={18}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {voice.label}\n </Text>\n </Box>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {volBar(val)}\n </Text>\n <Text dimColor>{fmtPct(val)}</Text>\n </Box>\n );\n })}\n {(() => {\n const isFocused = voiceFocus === LOOP_ROW_IDX;\n return (\n <Box gap={2}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Box width={18}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n Loop track\n </Text>\n </Box>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {loopEnabled ? '● On ' : '○ Off'}\n </Text>\n </Box>\n );\n })()}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ select</Text>\n <Text dimColor>← →/Enter toggle loop</Text>\n <Text dimColor>Enter apply</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n </Box>\n )}\n\n {section === 'tracks' && (\n <Box flexDirection=\"column\">\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Now playing:</Text>\n <Text color={accent}>{selectedTrack.title}</Text>\n <Text dimColor>— {selectedTrack.artist}</Text>\n </Box>\n <Box gap={1} marginBottom={1}>\n <Text dimColor>Search:</Text>\n <Text color={accent}>{query || ' '}</Text>\n <Text dimColor>▌</Text>\n </Box>\n {filtered.length === 0 ? (\n <Text dimColor>No matches. Backspace to clear.</Text>\n ) : (\n <Box flexDirection=\"column\">\n {scrollOffset > 0 && <Text dimColor> ↑ {scrollOffset} more</Text>}\n {visible.map((track, i) => {\n const idx = scrollOffset + i;\n const isFocused = idx === safeFocus;\n return (\n <Box key={track.id} gap={1}>\n <Text color={isFocused ? accent : undefined} dimColor={!isFocused}>\n {isFocused ? '▶' : ' '}\n </Text>\n <Text color={isFocused ? accent : undefined} bold={isFocused} dimColor={!isFocused}>\n {track.title}\n </Text>\n <Text dimColor>— {track.artist}</Text>\n </Box>\n );\n })}\n {scrollOffset + VISIBLE_ROWS < filtered.length && (\n <Text dimColor> ↓ {filtered.length - scrollOffset - VISIBLE_ROWS} more</Text>\n )}\n </Box>\n )}\n <Box marginTop={1} gap={2}>\n <Text dimColor>↑↓ navigate</Text>\n <Text dimColor>Enter select</Text>\n <Text dimColor>Esc cancel</Text>\n </Box>\n <Box marginTop={1}>\n <Text dimColor>tip: use \"[\" or \"]\" to go back or forward songs · \"L\" to toggle loop</Text>\n </Box>\n </Box>\n )}\n </Box>\n );\n};\n"]}
@@ -7,8 +7,8 @@
7
7
  * the game loop never captures stale closure values.
8
8
  */
9
9
  import { type MidiTrack } from './freemidi-catalog.js';
10
- import { type SnakeColors } from './types.js';
11
- export type { SnakeColors };
10
+ import { type SnakeColors, type SnakeKeybindings } from './types.js';
11
+ export type { SnakeColors, SnakeKeybindings };
12
12
  interface SnakeGameProps {
13
13
  onExit?: () => void;
14
14
  /** Enable or disable all audio (default: true) */
@@ -34,5 +34,12 @@ interface SnakeGameProps {
34
34
  * Defaults to the built-in MIDI_CATALOG from freemidi.org.
35
35
  */
36
36
  tracks?: MidiTrack[];
37
+ /**
38
+ * Override individual key bindings.
39
+ * Each action accepts an array of keys. Use plain characters ('w', ' ', 'r')
40
+ * or arrow-key tokens: 'up', 'down', 'left', 'right', 'return', 'escape'.
41
+ * Defaults include both WASD and arrow keys for movement.
42
+ */
43
+ keybindings?: SnakeKeybindings;
37
44
  }
38
- export declare const SnakeGame: ({ onExit, music, colors, cacheDir, settingsFile, width, height, tracks, }?: SnakeGameProps) => import("react/jsx-runtime").JSX.Element;
45
+ export declare const SnakeGame: ({ onExit, music, colors, cacheDir, settingsFile, width, height, tracks, keybindings, }?: SnakeGameProps) => import("react/jsx-runtime").JSX.Element;
@@ -9,13 +9,38 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
9
9
  */
10
10
  import { Box, Text, useInput } from 'ink';
11
11
  import { useReducer, useRef, useEffect, useState } from 'react';
12
- import { getSnakeHighScore, setSnakeHighScore, getSnakeMusicVolume, setSnakeMusicVolume, getSnakeTinkVolume, setSnakeTinkVolume, getSnakeSfxVolume, setSnakeSfxVolume, } from './settings.js';
12
+ import { getSnakeHighScore, setSnakeHighScore, getSnakeMusicVolume, setSnakeMusicVolume, getSnakeTinkVolume, setSnakeTinkVolume, getSnakeSfxVolume, setSnakeSfxVolume, getSnakeLoopEnabled, setSnakeLoopEnabled, getSnakeLastTrack, setSnakeLastTrack, } from './settings.js';
13
13
  import { warmNotes, playNote, playSystemSound, SYSTEM_SOUNDS, startBgMusic, stopBgMusic, setMusicVolume, } from './snake-audio.js';
14
14
  import { MusicSettings } from './MusicSettings.js';
15
15
  import { MIDI_CATALOG, DEFAULT_TRACK_ID, freemidiUrls } from './freemidi-catalog.js';
16
- import { resolveColors } from './types.js';
16
+ import { resolveColors, resolveKeybindings } from './types.js';
17
17
  const DEFAULT_BPM = 120;
18
- function resolveDefaultTrack(catalog) {
18
+ // Maps keybinding token strings to Ink Key property names
19
+ const ARROW_KEY_MAP = {
20
+ up: 'upArrow', down: 'downArrow', left: 'leftArrow', right: 'rightArrow',
21
+ return: 'return', escape: 'escape', tab: 'tab',
22
+ };
23
+ function pressed(bindings, input, key) {
24
+ return bindings.some((b) => {
25
+ const prop = ARROW_KEY_MAP[b];
26
+ return prop ? key[prop] === true : input === b;
27
+ });
28
+ }
29
+ const KEY_LABELS = {
30
+ up: '↑', down: '↓', left: '←', right: '→',
31
+ return: '↵', escape: 'Esc', tab: 'Tab', ' ': 'Space',
32
+ };
33
+ function randomOtherTrack(catalog, currentUrl) {
34
+ const others = catalog.filter((t) => freemidiUrls(t).getter !== currentUrl);
35
+ return others.length > 0 ? (others[Math.floor(Math.random() * others.length)] ?? null) : null;
36
+ }
37
+ function displayKey(k) {
38
+ return KEY_LABELS[k] ?? k.toUpperCase();
39
+ }
40
+ function resolveDefaultTrack(catalog, settingsFile) {
41
+ const last = getSnakeLastTrack(settingsFile);
42
+ if (last)
43
+ return last;
19
44
  const entry = catalog.find((t) => t.id === DEFAULT_TRACK_ID) ?? catalog[0];
20
45
  if (!entry)
21
46
  return { title: 'No tracks', artist: '', url: '' };
@@ -62,8 +87,9 @@ function makeInitial(w, h) {
62
87
  started: false,
63
88
  };
64
89
  }
65
- export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile, width = 20, height = 10, tracks, } = {}) => {
90
+ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile, width = 20, height = 10, tracks, keybindings, } = {}) => {
66
91
  const c = resolveColors(colors);
92
+ const kb = resolveKeybindings(keybindings);
67
93
  const catalog = tracks ?? MIDI_CATALOG;
68
94
  const stateRef = useRef(makeInitial(width, height));
69
95
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
@@ -71,12 +97,34 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
71
97
  const [highScore, setHighScore] = useState(0);
72
98
  const bgMusic = useRef(null);
73
99
  const [bpm, setBpm] = useState(DEFAULT_BPM);
74
- const [track, setTrack] = useState(() => resolveDefaultTrack(catalog));
100
+ const [track, setTrack] = useState(() => resolveDefaultTrack(catalog, settingsFile));
75
101
  const [showSettings, setShowSettings] = useState(false);
102
+ const autoPlayRef = useRef(true);
103
+ const nextTrackRef = useRef(() => { });
104
+ const [loopEnabled, setLoopEnabled] = useState(() => getSnakeLoopEnabled(settingsFile));
105
+ const loopRef = useRef(loopEnabled);
106
+ const [trackRevision, setTrackRevision] = useState(0);
76
107
  const [beatPhase, setBeatPhase] = useState(0);
77
108
  const [musicVolume, setMusicVolumeState] = useState(() => getSnakeMusicVolume(settingsFile));
78
109
  const [tinkVolume, setTinkVolumeState] = useState(() => getSnakeTinkVolume(settingsFile));
79
110
  const [sfxVolume, setSfxVolumeState] = useState(() => getSnakeSfxVolume(settingsFile));
111
+ // Keep loopRef in sync with state
112
+ useEffect(() => { loopRef.current = loopEnabled; }, [loopEnabled]);
113
+ // Persist the current track whenever it changes
114
+ useEffect(() => {
115
+ if (track.url)
116
+ setSnakeLastTrack(track, settingsFile);
117
+ }, [track]); // eslint-disable-line react-hooks/exhaustive-deps
118
+ // Keep nextTrackRef pointed at a stable "advance to next track" callback
119
+ useEffect(() => {
120
+ nextTrackRef.current = () => {
121
+ const entry = randomOtherTrack(catalog, track.url);
122
+ if (entry) {
123
+ const urls = freemidiUrls(entry);
124
+ setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });
125
+ }
126
+ };
127
+ }, [track, catalog]); // eslint-disable-line react-hooks/exhaustive-deps
80
128
  useEffect(() => {
81
129
  if (!music)
82
130
  return;
@@ -84,13 +132,35 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
84
132
  stopBgMusic(bgMusic.current);
85
133
  bgMusic.current = null;
86
134
  setBpm(DEFAULT_BPM);
135
+ autoPlayRef.current = true;
136
+ let cancelled = false;
87
137
  void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {
138
+ if (cancelled) {
139
+ stopBgMusic(handle);
140
+ return;
141
+ }
88
142
  bgMusic.current = handle;
89
- if (handle)
143
+ if (handle) {
90
144
  setBpm(handle.bpm);
145
+ handle.proc.on('exit', (_code, signal) => {
146
+ if (signal == null && autoPlayRef.current) {
147
+ if (loopRef.current) {
148
+ setTrackRevision((r) => r + 1);
149
+ }
150
+ else {
151
+ nextTrackRef.current();
152
+ }
153
+ }
154
+ });
155
+ }
91
156
  });
92
- return () => { stopBgMusic(bgMusic.current); };
93
- }, [track, music]); // eslint-disable-line react-hooks/exhaustive-deps
157
+ return () => {
158
+ cancelled = true;
159
+ autoPlayRef.current = false;
160
+ stopBgMusic(bgMusic.current);
161
+ bgMusic.current = null;
162
+ };
163
+ }, [track, music, trackRevision]); // eslint-disable-line react-hooks/exhaustive-deps
94
164
  useEffect(() => {
95
165
  const saved = getSnakeHighScore(settingsFile);
96
166
  highScoreRef.current = saved;
@@ -157,15 +227,15 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
157
227
  }, tickMs);
158
228
  return () => clearInterval(id);
159
229
  }, [bpm]);
160
- useInput((input) => {
230
+ useInput((input, key) => {
161
231
  if (showSettings)
162
232
  return;
163
233
  const g = stateRef.current;
164
- if (input === 'q' && onExit) {
234
+ if (pressed(kb.quit, input, key) && onExit) {
165
235
  onExit();
166
236
  return;
167
237
  }
168
- if (input === 'm' && music) {
238
+ if (pressed(kb.music, input, key) && music) {
169
239
  if (g.started && !g.gameOver && !g.paused) {
170
240
  stateRef.current = { ...g, paused: true };
171
241
  forceUpdate();
@@ -173,38 +243,74 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
173
243
  setShowSettings(true);
174
244
  return;
175
245
  }
246
+ if (music) {
247
+ const goToTrack = (entry) => {
248
+ const urls = freemidiUrls(entry);
249
+ setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });
250
+ };
251
+ if (pressed(kb.nextTrack, input, key)) {
252
+ const entry = randomOtherTrack(catalog, track.url);
253
+ if (entry)
254
+ goToTrack(entry);
255
+ return;
256
+ }
257
+ if (pressed(kb.prevTrack, input, key)) {
258
+ const idx = catalog.findIndex((t) => freemidiUrls(t).getter === track.url);
259
+ const entry = catalog[(idx - 1 + catalog.length) % catalog.length];
260
+ if (entry)
261
+ goToTrack(entry);
262
+ return;
263
+ }
264
+ if (pressed(kb.loopTrack, input, key)) {
265
+ const next = !loopRef.current;
266
+ loopRef.current = next;
267
+ setLoopEnabled(next);
268
+ setSnakeLoopEnabled(next, settingsFile);
269
+ return;
270
+ }
271
+ }
176
272
  if (!g.started || g.gameOver) {
177
- if (input === 'r' || input === ' ')
273
+ if (pressed(kb.restart, input, key) || pressed(kb.pause, input, key))
178
274
  reset();
179
275
  return;
180
276
  }
181
- if (input === ' ') {
277
+ if (pressed(kb.pause, input, key)) {
182
278
  stateRef.current = { ...g, paused: !g.paused };
183
279
  forceUpdate();
184
280
  return;
185
281
  }
186
- if (input === 'r') {
282
+ if (pressed(kb.restart, input, key)) {
187
283
  reset();
188
284
  return;
189
285
  }
190
286
  const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;
191
287
  let next = null;
192
- if (input === 'w' && lastDir.dy !== 1)
288
+ if (pressed(kb.up, input, key) && lastDir.dy !== 1)
193
289
  next = DIRS.up;
194
- else if (input === 's' && lastDir.dy !== -1)
290
+ else if (pressed(kb.down, input, key) && lastDir.dy !== -1)
195
291
  next = DIRS.down;
196
- else if (input === 'a' && lastDir.dx !== 1)
292
+ else if (pressed(kb.left, input, key) && lastDir.dx !== 1)
197
293
  next = DIRS.left;
198
- else if (input === 'd' && lastDir.dx !== -1)
294
+ else if (pressed(kb.right, input, key) && lastDir.dx !== -1)
199
295
  next = DIRS.right;
200
296
  if (next && g.dirQueue.length < 2) {
201
297
  stateRef.current = { ...g, dirQueue: [...g.dirQueue, next] };
202
298
  }
203
299
  });
204
300
  if (showSettings) {
205
- return (_jsx(MusicSettings, { initial: { track, musicVolume, tinkVolume, sfxVolume }, accentColor: c.accent, tracks: catalog, onApply: (cfg) => {
301
+ return (_jsx(MusicSettings, { initial: { track, musicVolume, tinkVolume, sfxVolume, loopEnabled }, accentColor: c.accent, tracks: catalog, onApply: (cfg) => {
206
302
  if (cfg.musicVolume !== musicVolume && bgMusic.current) {
207
303
  bgMusic.current = setMusicVolume(bgMusic.current, cfg.musicVolume);
304
+ bgMusic.current.proc.on('exit', (_code, signal) => {
305
+ if (signal == null && autoPlayRef.current) {
306
+ if (loopRef.current) {
307
+ setTrackRevision((r) => r + 1);
308
+ }
309
+ else {
310
+ nextTrackRef.current();
311
+ }
312
+ }
313
+ });
208
314
  }
209
315
  if (cfg.musicVolume !== musicVolume) {
210
316
  setSnakeMusicVolume(cfg.musicVolume, settingsFile);
@@ -218,6 +324,11 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
218
324
  setSnakeSfxVolume(cfg.sfxVolume, settingsFile);
219
325
  setSfxVolumeState(cfg.sfxVolume);
220
326
  }
327
+ if (cfg.loopEnabled !== loopEnabled) {
328
+ loopRef.current = cfg.loopEnabled;
329
+ setLoopEnabled(cfg.loopEnabled);
330
+ setSnakeLoopEnabled(cfg.loopEnabled, settingsFile);
331
+ }
221
332
  if (cfg.track.url !== track.url)
222
333
  setTrack(cfg.track);
223
334
  setShowSettings(false);
@@ -233,7 +344,7 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
233
344
  return 'food';
234
345
  return 'empty';
235
346
  }));
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) => {
347
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, color: c.accent, children: "Snake" }), _jsxs(Text, { color: "white", children: ["Score: ", _jsx(Text, { bold: true, children: score })] }), highScore > 0 && _jsxs(Text, { color: "white", 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 }), loopEnabled && _jsx(Text, { dimColor: true, children: "\u27F3" })] }), _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
348
  if (cell === 'head')
238
349
  return _jsx(Text, { color: c.head, bold: true, children: '● ' }, x);
239
350
  if (cell === 'body')
@@ -241,6 +352,6 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
241
352
  if (cell === 'food')
242
353
  return _jsx(Text, { color: c.food, children: '◆ ' }, x);
243
354
  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"] })] }));
355
+ }), _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: [displayKey(kb.up[0]), "/", displayKey(kb.down[0]), "/", displayKey(kb.left[0]), "/", displayKey(kb.right[0]), " to move", ' · ', displayKey(kb.pause[0]), " to pause", ' · ', displayKey(kb.restart[0]), " to restart", music ? ` · ${displayKey(kb.music[0])} menu` : ''] })), !started && music && _jsxs(Text, { dimColor: true, children: [displayKey(kb.music[0]), " to change music"] }), onExit && _jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { bold: true, children: displayKey(kb.quit[0]) }), " to exit"] })] }));
245
356
  };
246
357
  //# sourceMappingURL=SnakeGame.js.map
@@ -1 +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"]}
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, type Key } 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 getSnakeLoopEnabled,\n setSnakeLoopEnabled,\n getSnakeLastTrack,\n setSnakeLastTrack,\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, type SnakeKeybindings, resolveKeybindings } from './types.js';\n\nexport type { SnakeColors, SnakeKeybindings };\n\nconst DEFAULT_BPM = 120;\n\n// Maps keybinding token strings to Ink Key property names\nconst ARROW_KEY_MAP: Record<string, keyof Key> = {\n up: 'upArrow', down: 'downArrow', left: 'leftArrow', right: 'rightArrow',\n return: 'return', escape: 'escape', tab: 'tab',\n};\n\nfunction pressed(bindings: string[], input: string, key: Key): boolean {\n return bindings.some((b) => {\n const prop = ARROW_KEY_MAP[b];\n return prop ? (key[prop] as boolean) === true : input === b;\n });\n}\n\nconst KEY_LABELS: Record<string, string> = {\n up: '↑', down: '↓', left: '←', right: '→',\n return: '↵', escape: 'Esc', tab: 'Tab', ' ': 'Space',\n};\nfunction randomOtherTrack(catalog: MidiTrack[], currentUrl: string): MidiTrack | null {\n const others = catalog.filter((t) => freemidiUrls(t).getter !== currentUrl);\n return others.length > 0 ? (others[Math.floor(Math.random() * others.length)] ?? null) : null;\n}\n\nfunction displayKey(k: string): string {\n return KEY_LABELS[k] ?? k.toUpperCase();\n}\n\nfunction resolveDefaultTrack(catalog: MidiTrack[], settingsFile?: string): SelectedTrack {\n const last = getSnakeLastTrack(settingsFile);\n if (last) return last;\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 * Override individual key bindings.\n * Each action accepts an array of keys. Use plain characters ('w', ' ', 'r')\n * or arrow-key tokens: 'up', 'down', 'left', 'right', 'return', 'escape'.\n * Defaults include both WASD and arrow keys for movement.\n */\n keybindings?: SnakeKeybindings;\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 keybindings,\n}: SnakeGameProps = {}) => {\n const c = resolveColors(colors);\n const kb = resolveKeybindings(keybindings);\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, settingsFile));\n const [showSettings, setShowSettings] = useState(false);\n const autoPlayRef = useRef(true);\n const nextTrackRef = useRef<() => void>(() => {});\n const [loopEnabled, setLoopEnabled] = useState(() => getSnakeLoopEnabled(settingsFile));\n const loopRef = useRef(loopEnabled);\n const [trackRevision, setTrackRevision] = useState(0);\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 // Keep loopRef in sync with state\n useEffect(() => { loopRef.current = loopEnabled; }, [loopEnabled]);\n\n // Persist the current track whenever it changes\n useEffect(() => {\n if (track.url) setSnakeLastTrack(track, settingsFile);\n }, [track]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Keep nextTrackRef pointed at a stable \"advance to next track\" callback\n useEffect(() => {\n nextTrackRef.current = () => {\n const entry = randomOtherTrack(catalog, track.url);\n if (entry) {\n const urls = freemidiUrls(entry);\n setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });\n }\n };\n }, [track, catalog]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (!music) return;\n warmNotes([69], cacheDir);\n stopBgMusic(bgMusic.current);\n bgMusic.current = null;\n setBpm(DEFAULT_BPM);\n autoPlayRef.current = true;\n let cancelled = false;\n void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {\n if (cancelled) { stopBgMusic(handle); return; }\n bgMusic.current = handle;\n if (handle) {\n setBpm(handle.bpm);\n handle.proc.on('exit', (_code, signal) => {\n if (signal == null && autoPlayRef.current) {\n if (loopRef.current) {\n setTrackRevision((r) => r + 1);\n } else {\n nextTrackRef.current();\n }\n }\n });\n }\n });\n return () => {\n cancelled = true;\n autoPlayRef.current = false;\n stopBgMusic(bgMusic.current);\n bgMusic.current = null;\n };\n }, [track, music, trackRevision]); // 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, key) => {\n if (showSettings) return;\n\n const g = stateRef.current;\n\n if (pressed(kb.quit, input, key) && onExit) { onExit(); return; }\n\n if (pressed(kb.music, input, key) && 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 (music) {\n const goToTrack = (entry: MidiTrack) => {\n const urls = freemidiUrls(entry);\n setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });\n };\n if (pressed(kb.nextTrack, input, key)) {\n const entry = randomOtherTrack(catalog, track.url);\n if (entry) goToTrack(entry);\n return;\n }\n if (pressed(kb.prevTrack, input, key)) {\n const idx = catalog.findIndex((t) => freemidiUrls(t).getter === track.url);\n const entry = catalog[(idx - 1 + catalog.length) % catalog.length];\n if (entry) goToTrack(entry);\n return;\n }\n if (pressed(kb.loopTrack, input, key)) {\n const next = !loopRef.current;\n loopRef.current = next;\n setLoopEnabled(next);\n setSnakeLoopEnabled(next, settingsFile);\n return;\n }\n }\n\n if (!g.started || g.gameOver) {\n if (pressed(kb.restart, input, key) || pressed(kb.pause, input, key)) reset();\n return;\n }\n\n if (pressed(kb.pause, input, key)) { stateRef.current = { ...g, paused: !g.paused }; forceUpdate(); return; }\n if (pressed(kb.restart, input, key)) { reset(); return; }\n\n const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;\n let next: Dir | null = null;\n if (pressed(kb.up, input, key) && lastDir.dy !== 1) next = DIRS.up;\n else if (pressed(kb.down, input, key) && lastDir.dy !== -1) next = DIRS.down;\n else if (pressed(kb.left, input, key) && lastDir.dx !== 1) next = DIRS.left;\n else if (pressed(kb.right, input, key) && 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, loopEnabled }}\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 bgMusic.current.proc.on('exit', (_code, signal) => {\n if (signal == null && autoPlayRef.current) {\n if (loopRef.current) { setTrackRevision((r) => r + 1); } else { nextTrackRef.current(); }\n }\n });\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.loopEnabled !== loopEnabled) {\n loopRef.current = cfg.loopEnabled;\n setLoopEnabled(cfg.loopEnabled);\n setSnakeLoopEnabled(cfg.loopEnabled, settingsFile);\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 color=\"white\">Score: <Text bold>{score}</Text></Text>\n {highScore > 0 && <Text color=\"white\">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 {loopEnabled && <Text dimColor>⟳</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 {displayKey(kb.up[0]!)}/{displayKey(kb.down[0]!)}/{displayKey(kb.left[0]!)}/{displayKey(kb.right[0]!)} to move\n {' · '}{displayKey(kb.pause[0]!)} to pause\n {' · '}{displayKey(kb.restart[0]!)} to restart\n {music ? ` · ${displayKey(kb.music[0]!)} menu` : ''}\n </Text>\n )}\n {!started && music && <Text dimColor>{displayKey(kb.music[0]!)} to change music</Text>}\n {onExit && <Text dimColor>Press <Text bold>{displayKey(kb.quit[0]!)}</Text> to exit</Text>}\n </Box>\n );\n};\n"]}
@@ -27,5 +27,5 @@ export declare const FALLBACK_TRACK: MidiTrack & {
27
27
  directUrl: string;
28
28
  };
29
29
  /** Default selected track. */
30
- export declare const DEFAULT_TRACK_ID = 28946;
30
+ export declare const DEFAULT_TRACK_ID = 11747;
31
31
  export declare const MIDI_CATALOG: MidiTrack[];
@@ -26,7 +26,7 @@ export const FALLBACK_TRACK = {
26
26
  directUrl: 'https://bitmidi.com/uploads/16752.mid',
27
27
  };
28
28
  /** Default selected track. */
29
- export const DEFAULT_TRACK_ID = 28946; // Abracadabra — Lady Gaga
29
+ export const DEFAULT_TRACK_ID = 11747; // Bad Romance — Lady Gaga
30
30
  export const MIDI_CATALOG = [
31
31
  // Lady Gaga — verified from https://freemidi.org/artist-1586-lady-gaga
32
32
  { id: 28946, title: 'Abracadabra', artist: 'Lady Gaga', slug: 'abracadabra-lady-gaga' },
@@ -94,6 +94,11 @@ export const MIDI_CATALOG = [
94
94
  { id: 4151, title: 'We Belong Together', artist: 'Mariah Carey', slug: 'we-belong-together-mariah-carey' },
95
95
  { id: 12750, title: 'My Heart Will Go On', artist: 'Celine Dion', slug: 'my-heart-will-go-on-celine-dion' },
96
96
  { id: 2415, title: 'The Power of Love', artist: 'Celine Dion', slug: 'power-of-love-celine-dion' },
97
+ // Taylor Swift
98
+ { id: 13607, title: 'Shake It Off', artist: 'Taylor Swift', slug: 'shake-it-off-taylor-swift' },
99
+ { id: 11750, title: 'Love Story', artist: 'Taylor Swift', slug: 'love-story-taylor-swift' },
100
+ { id: 25205, title: 'Blank Space', artist: 'Taylor Swift', slug: 'blank-space-taylor-swift' },
101
+ { id: 12257, title: 'You Belong With Me', artist: 'Taylor Swift', slug: 'you-belong-with-me-taylor-swift' },
97
102
  // 2000s pop
98
103
  { id: 2257, title: '...Baby One More Time', artist: 'Britney Spears', slug: 'baby-one-more-time-britney-spears' },
99
104
  { id: 2256, title: 'Toxic', artist: 'Britney Spears', slug: 'toxic-britney-spears' },
@@ -1 +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"]}
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 = 11747; // Bad Romance — 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 // Taylor Swift\n { id: 13607, title: 'Shake It Off', artist: 'Taylor Swift', slug: 'shake-it-off-taylor-swift' },\n { id: 11750, title: 'Love Story', artist: 'Taylor Swift', slug: 'love-story-taylor-swift' },\n { id: 25205, title: 'Blank Space', artist: 'Taylor Swift', slug: 'blank-space-taylor-swift' },\n { id: 12257, title: 'You Belong With Me', artist: 'Taylor Swift', slug: 'you-belong-with-me-taylor-swift' },\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"]}
@@ -10,8 +10,8 @@
10
10
  * await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });
11
11
  */
12
12
  export { SnakeGame } from './SnakeGame.js';
13
- export type { SnakeColors } from './types.js';
14
- export { DEFAULT_COLORS } from './types.js';
13
+ export type { SnakeColors, SnakeKeybindings } from './types.js';
14
+ export { DEFAULT_COLORS, DEFAULT_KEYBINDINGS } from './types.js';
15
15
  export type { SelectedTrack, MusicConfig } from './MusicSettings.js';
16
16
  export type { MidiTrack } from './freemidi-catalog.js';
17
17
  export { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';
package/dist/src/index.js CHANGED
@@ -11,7 +11,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
11
11
  * await runSnakeGame({ music: false, colors: { accent: '#00ff00' } });
12
12
  */
13
13
  export { SnakeGame } from './SnakeGame.js';
14
- export { DEFAULT_COLORS } from './types.js';
14
+ export { DEFAULT_COLORS, DEFAULT_KEYBINDINGS } from './types.js';
15
15
  export { MIDI_CATALOG, DEFAULT_TRACK_ID, FALLBACK_TRACK, freemidiUrls } from './freemidi-catalog.js';
16
16
  import { render } from 'ink';
17
17
  import { SnakeGame } from './SnakeGame.js';
@@ -1 +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"]}
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,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGjE,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, SnakeKeybindings } from './types.js';\nexport { DEFAULT_COLORS, DEFAULT_KEYBINDINGS } 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"]}
@@ -9,3 +9,13 @@ export declare function getSnakeTinkVolume(configPath?: string): number;
9
9
  export declare function setSnakeTinkVolume(volume: number, configPath?: string): void;
10
10
  export declare function getSnakeSfxVolume(configPath?: string): number;
11
11
  export declare function setSnakeSfxVolume(volume: number, configPath?: string): void;
12
+ export declare function getSnakeLoopEnabled(configPath?: string): boolean;
13
+ export declare function setSnakeLoopEnabled(enabled: boolean, configPath?: string): void;
14
+ export interface LastTrack {
15
+ title: string;
16
+ artist: string;
17
+ url: string;
18
+ downloadPage?: string;
19
+ }
20
+ export declare function getSnakeLastTrack(configPath?: string): LastTrack | null;
21
+ export declare function setSnakeLastTrack(track: LastTrack, configPath?: string): void;
@@ -54,4 +54,31 @@ export function setSnakeSfxVolume(volume, configPath) {
54
54
  config['sfx_volume'] = Math.max(0, Math.min(1, volume));
55
55
  writeConfig(config, configPath);
56
56
  }
57
+ export function getSnakeLoopEnabled(configPath) {
58
+ return readConfig(configPath)['loop_enabled'] === true;
59
+ }
60
+ export function setSnakeLoopEnabled(enabled, configPath) {
61
+ const config = readConfig(configPath);
62
+ config['loop_enabled'] = enabled;
63
+ writeConfig(config, configPath);
64
+ }
65
+ export function getSnakeLastTrack(configPath) {
66
+ const t = readConfig(configPath)['last_track'];
67
+ if (!t || typeof t !== 'object')
68
+ return null;
69
+ const track = t;
70
+ if (typeof track['url'] !== 'string' || !track['url'])
71
+ return null;
72
+ return {
73
+ title: typeof track['title'] === 'string' ? track['title'] : '',
74
+ artist: typeof track['artist'] === 'string' ? track['artist'] : '',
75
+ url: track['url'],
76
+ downloadPage: typeof track['downloadPage'] === 'string' ? track['downloadPage'] : undefined,
77
+ };
78
+ }
79
+ export function setSnakeLastTrack(track, configPath) {
80
+ const config = readConfig(configPath);
81
+ config['last_track'] = track;
82
+ writeConfig(config, configPath);
83
+ }
57
84
  //# sourceMappingURL=settings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/settings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * settings.ts — Persistent settings for snake-game stored in ~/.snake-game.json\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nconst CONFIG_PATH = path.join(os.homedir(), '.snake-game.json');\n\nfunction readConfig(configPath = CONFIG_PATH): Record<string, unknown> {\n try {\n const raw = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nfunction writeConfig(data: Record<string, unknown>, configPath = CONFIG_PATH): void {\n fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\nfunction clampVol(v: unknown, def: number): number {\n return typeof v === 'number' ? Math.max(0, Math.min(1, v)) : def;\n}\n\nexport function getSnakeHighScore(configPath?: string): number {\n const config = readConfig(configPath);\n const score = config['high_score'];\n return typeof score === 'number' ? score : 0;\n}\n\nexport function setSnakeHighScore(score: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['high_score'] = score;\n writeConfig(config, configPath);\n}\n\nexport function getSnakeMusicVolume(configPath?: string): number {\n return clampVol(readConfig(configPath)['music_volume'], 0.8);\n}\n\nexport function setSnakeMusicVolume(volume: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['music_volume'] = Math.max(0, Math.min(1, volume));\n writeConfig(config, configPath);\n}\n\nexport function getSnakeTinkVolume(configPath?: string): number {\n return clampVol(readConfig(configPath)['tink_volume'], 0.05);\n}\n\nexport function setSnakeTinkVolume(volume: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['tink_volume'] = Math.max(0, Math.min(1, volume));\n writeConfig(config, configPath);\n}\n\nexport function getSnakeSfxVolume(configPath?: string): number {\n return clampVol(readConfig(configPath)['sfx_volume'], 0.8);\n}\n\nexport function setSnakeSfxVolume(volume: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['sfx_volume'] = Math.max(0, Math.min(1, volume));\n writeConfig(config, configPath);\n}\n"]}
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/settings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * settings.ts — Persistent settings for snake-game stored in ~/.snake-game.json\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nconst CONFIG_PATH = path.join(os.homedir(), '.snake-game.json');\n\nfunction readConfig(configPath = CONFIG_PATH): Record<string, unknown> {\n try {\n const raw = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nfunction writeConfig(data: Record<string, unknown>, configPath = CONFIG_PATH): void {\n fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\nfunction clampVol(v: unknown, def: number): number {\n return typeof v === 'number' ? Math.max(0, Math.min(1, v)) : def;\n}\n\nexport function getSnakeHighScore(configPath?: string): number {\n const config = readConfig(configPath);\n const score = config['high_score'];\n return typeof score === 'number' ? score : 0;\n}\n\nexport function setSnakeHighScore(score: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['high_score'] = score;\n writeConfig(config, configPath);\n}\n\nexport function getSnakeMusicVolume(configPath?: string): number {\n return clampVol(readConfig(configPath)['music_volume'], 0.8);\n}\n\nexport function setSnakeMusicVolume(volume: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['music_volume'] = Math.max(0, Math.min(1, volume));\n writeConfig(config, configPath);\n}\n\nexport function getSnakeTinkVolume(configPath?: string): number {\n return clampVol(readConfig(configPath)['tink_volume'], 0.05);\n}\n\nexport function setSnakeTinkVolume(volume: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['tink_volume'] = Math.max(0, Math.min(1, volume));\n writeConfig(config, configPath);\n}\n\nexport function getSnakeSfxVolume(configPath?: string): number {\n return clampVol(readConfig(configPath)['sfx_volume'], 0.8);\n}\n\nexport function setSnakeSfxVolume(volume: number, configPath?: string): void {\n const config = readConfig(configPath);\n config['sfx_volume'] = Math.max(0, Math.min(1, volume));\n writeConfig(config, configPath);\n}\n\nexport function getSnakeLoopEnabled(configPath?: string): boolean {\n return readConfig(configPath)['loop_enabled'] === true;\n}\n\nexport function setSnakeLoopEnabled(enabled: boolean, configPath?: string): void {\n const config = readConfig(configPath);\n config['loop_enabled'] = enabled;\n writeConfig(config, configPath);\n}\n\nexport interface LastTrack {\n title: string;\n artist: string;\n url: string;\n downloadPage?: string;\n}\n\nexport function getSnakeLastTrack(configPath?: string): LastTrack | null {\n const t = readConfig(configPath)['last_track'];\n if (!t || typeof t !== 'object') return null;\n const track = t as Record<string, unknown>;\n if (typeof track['url'] !== 'string' || !track['url']) return null;\n return {\n title: typeof track['title'] === 'string' ? track['title'] : '',\n artist: typeof track['artist'] === 'string' ? track['artist'] : '',\n url: track['url'],\n downloadPage: typeof track['downloadPage'] === 'string' ? track['downloadPage'] : undefined,\n };\n}\n\nexport function setSnakeLastTrack(track: LastTrack, configPath?: string): void {\n const config = readConfig(configPath);\n config['last_track'] = track;\n writeConfig(config, configPath);\n}\n"]}
@@ -1,3 +1,29 @@
1
+ export interface SnakeKeybindings {
2
+ /** Keys to move up (default: ['w', 'up']) */
3
+ up?: string[];
4
+ /** Keys to move down (default: ['s', 'down']) */
5
+ down?: string[];
6
+ /** Keys to move left (default: ['a', 'left']) */
7
+ left?: string[];
8
+ /** Keys to move right (default: ['d', 'right']) */
9
+ right?: string[];
10
+ /** Keys to pause (default: [' ']) */
11
+ pause?: string[];
12
+ /** Keys to restart (default: ['r']) */
13
+ restart?: string[];
14
+ /** Keys to open music settings (default: ['m']) */
15
+ music?: string[];
16
+ /** Keys to quit (default: ['q']) */
17
+ quit?: string[];
18
+ /** Keys to skip to next track (default: [']']) */
19
+ nextTrack?: string[];
20
+ /** Keys to skip to previous track (default: ['[']) */
21
+ prevTrack?: string[];
22
+ /** Keys to toggle track loop (default: ['l']) */
23
+ loopTrack?: string[];
24
+ }
25
+ export declare const DEFAULT_KEYBINDINGS: Required<SnakeKeybindings>;
26
+ export declare function resolveKeybindings(kb?: SnakeKeybindings): Required<SnakeKeybindings>;
1
27
  export interface SnakeColors {
2
28
  /** UI accent color — title, score labels, controls (default: '#1e61f0') */
3
29
  accent?: string;
package/dist/src/types.js CHANGED
@@ -1,3 +1,33 @@
1
+ export const DEFAULT_KEYBINDINGS = {
2
+ up: ['w', 'up'],
3
+ down: ['s', 'down'],
4
+ left: ['a', 'left'],
5
+ right: ['d', 'right'],
6
+ pause: [' '],
7
+ restart: ['r'],
8
+ music: ['m'],
9
+ quit: ['q'],
10
+ nextTrack: [']'],
11
+ prevTrack: ['['],
12
+ loopTrack: ['l'],
13
+ };
14
+ export function resolveKeybindings(kb) {
15
+ if (!kb)
16
+ return DEFAULT_KEYBINDINGS;
17
+ return {
18
+ up: kb.up ?? DEFAULT_KEYBINDINGS.up,
19
+ down: kb.down ?? DEFAULT_KEYBINDINGS.down,
20
+ left: kb.left ?? DEFAULT_KEYBINDINGS.left,
21
+ right: kb.right ?? DEFAULT_KEYBINDINGS.right,
22
+ pause: kb.pause ?? DEFAULT_KEYBINDINGS.pause,
23
+ restart: kb.restart ?? DEFAULT_KEYBINDINGS.restart,
24
+ music: kb.music ?? DEFAULT_KEYBINDINGS.music,
25
+ quit: kb.quit ?? DEFAULT_KEYBINDINGS.quit,
26
+ nextTrack: kb.nextTrack ?? DEFAULT_KEYBINDINGS.nextTrack,
27
+ prevTrack: kb.prevTrack ?? DEFAULT_KEYBINDINGS.prevTrack,
28
+ loopTrack: kb.loopTrack ?? DEFAULT_KEYBINDINGS.loopTrack,
29
+ };
30
+ }
1
31
  export const DEFAULT_COLORS = {
2
32
  accent: '#1e61f0',
3
33
  head: '#f7a8b8',
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAqC;CAChF,CAAC;AAEX,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,cAAc,CAAC,MAAM;QAC/C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;KAC9C,CAAC;AACJ,CAAC","sourcesContent":["export interface SnakeColors {\n /** UI accent color — title, score labels, controls (default: '#1e61f0') */\n accent?: string;\n /** Snake head color (default: '#f7a8b8') */\n head?: string;\n /** Snake body color (default: '#ffffff') */\n body?: string;\n /** Food color (default: '#55cdfc') */\n food?: string;\n /** Beat visualizer colors for beats 1–4 (default: trans pride palette) */\n beat?: [string, string, string, string];\n}\n\nexport const DEFAULT_COLORS = {\n accent: '#1e61f0',\n head: '#f7a8b8',\n body: '#ffffff',\n food: '#55cdfc',\n beat: ['#55cdfc', '#ffffff', '#f7a8b8', '#ffffff'] as [string, string, string, string],\n} as const;\n\nexport function resolveColors(colors?: SnakeColors): Required<SnakeColors> {\n return {\n accent: colors?.accent ?? DEFAULT_COLORS.accent,\n head: colors?.head ?? DEFAULT_COLORS.head,\n body: colors?.body ?? DEFAULT_COLORS.body,\n food: colors?.food ?? DEFAULT_COLORS.food,\n beat: colors?.beat ?? DEFAULT_COLORS.beat,\n };\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAyBA,MAAM,CAAC,MAAM,mBAAmB,GAA+B;IAC7D,EAAE,EAAS,CAAC,GAAG,EAAE,IAAI,CAAC;IACtB,IAAI,EAAO,CAAC,GAAG,EAAE,MAAM,CAAC;IACxB,IAAI,EAAO,CAAC,GAAG,EAAE,MAAM,CAAC;IACxB,KAAK,EAAM,CAAC,GAAG,EAAE,OAAO,CAAC;IACzB,KAAK,EAAM,CAAC,GAAG,CAAC;IAChB,OAAO,EAAI,CAAC,GAAG,CAAC;IAChB,KAAK,EAAM,CAAC,GAAG,CAAC;IAChB,IAAI,EAAO,CAAC,GAAG,CAAC;IAChB,SAAS,EAAE,CAAC,GAAG,CAAC;IAChB,SAAS,EAAE,CAAC,GAAG,CAAC;IAChB,SAAS,EAAE,CAAC,GAAG,CAAC;CACjB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,EAAqB;IACtD,IAAI,CAAC,EAAE;QAAE,OAAO,mBAAmB,CAAC;IACpC,OAAO;QACL,EAAE,EAAO,EAAE,CAAC,EAAE,IAAS,mBAAmB,CAAC,EAAE;QAC7C,IAAI,EAAK,EAAE,CAAC,IAAI,IAAO,mBAAmB,CAAC,IAAI;QAC/C,IAAI,EAAK,EAAE,CAAC,IAAI,IAAO,mBAAmB,CAAC,IAAI;QAC/C,KAAK,EAAI,EAAE,CAAC,KAAK,IAAM,mBAAmB,CAAC,KAAK;QAChD,KAAK,EAAI,EAAE,CAAC,KAAK,IAAM,mBAAmB,CAAC,KAAK;QAChD,OAAO,EAAE,EAAE,CAAC,OAAO,IAAI,mBAAmB,CAAC,OAAO;QAClD,KAAK,EAAM,EAAE,CAAC,KAAK,IAAQ,mBAAmB,CAAC,KAAK;QACpD,IAAI,EAAO,EAAE,CAAC,IAAI,IAAS,mBAAmB,CAAC,IAAI;QACnD,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,mBAAmB,CAAC,SAAS;QACxD,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,mBAAmB,CAAC,SAAS;QACxD,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,mBAAmB,CAAC,SAAS;KACzD,CAAC;AACJ,CAAC;AAeD,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,SAAS;IACjB,IAAI,EAAI,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAqC;CAChF,CAAC;AAEX,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,cAAc,CAAC,MAAM;QAC/C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;QAC7C,IAAI,EAAI,MAAM,EAAE,IAAI,IAAM,cAAc,CAAC,IAAI;KAC9C,CAAC;AACJ,CAAC","sourcesContent":["export interface SnakeKeybindings {\n /** Keys to move up (default: ['w', 'up']) */\n up?: string[];\n /** Keys to move down (default: ['s', 'down']) */\n down?: string[];\n /** Keys to move left (default: ['a', 'left']) */\n left?: string[];\n /** Keys to move right (default: ['d', 'right']) */\n right?: string[];\n /** Keys to pause (default: [' ']) */\n pause?: string[];\n /** Keys to restart (default: ['r']) */\n restart?: string[];\n /** Keys to open music settings (default: ['m']) */\n music?: string[];\n /** Keys to quit (default: ['q']) */\n quit?: string[];\n /** Keys to skip to next track (default: [']']) */\n nextTrack?: string[];\n /** Keys to skip to previous track (default: ['[']) */\n prevTrack?: string[];\n /** Keys to toggle track loop (default: ['l']) */\n loopTrack?: string[];\n}\n\nexport const DEFAULT_KEYBINDINGS: Required<SnakeKeybindings> = {\n up: ['w', 'up'],\n down: ['s', 'down'],\n left: ['a', 'left'],\n right: ['d', 'right'],\n pause: [' '],\n restart: ['r'],\n music: ['m'],\n quit: ['q'],\n nextTrack: [']'],\n prevTrack: ['['],\n loopTrack: ['l'],\n};\n\nexport function resolveKeybindings(kb?: SnakeKeybindings): Required<SnakeKeybindings> {\n if (!kb) return DEFAULT_KEYBINDINGS;\n return {\n up: kb.up ?? DEFAULT_KEYBINDINGS.up,\n down: kb.down ?? DEFAULT_KEYBINDINGS.down,\n left: kb.left ?? DEFAULT_KEYBINDINGS.left,\n right: kb.right ?? DEFAULT_KEYBINDINGS.right,\n pause: kb.pause ?? DEFAULT_KEYBINDINGS.pause,\n restart: kb.restart ?? DEFAULT_KEYBINDINGS.restart,\n music: kb.music ?? DEFAULT_KEYBINDINGS.music,\n quit: kb.quit ?? DEFAULT_KEYBINDINGS.quit,\n nextTrack: kb.nextTrack ?? DEFAULT_KEYBINDINGS.nextTrack,\n prevTrack: kb.prevTrack ?? DEFAULT_KEYBINDINGS.prevTrack,\n loopTrack: kb.loopTrack ?? DEFAULT_KEYBINDINGS.loopTrack,\n };\n}\n\nexport interface SnakeColors {\n /** UI accent color — title, score labels, controls (default: '#1e61f0') */\n accent?: string;\n /** Snake head color (default: '#f7a8b8') */\n head?: string;\n /** Snake body color (default: '#ffffff') */\n body?: string;\n /** Food color (default: '#55cdfc') */\n food?: string;\n /** Beat visualizer colors for beats 1–4 (default: trans pride palette) */\n beat?: [string, string, string, string];\n}\n\nexport const DEFAULT_COLORS = {\n accent: '#1e61f0',\n head: '#f7a8b8',\n body: '#ffffff',\n food: '#55cdfc',\n beat: ['#55cdfc', '#ffffff', '#f7a8b8', '#ffffff'] as [string, string, string, string],\n} as const;\n\nexport function resolveColors(colors?: SnakeColors): Required<SnakeColors> {\n return {\n accent: colors?.accent ?? DEFAULT_COLORS.accent,\n head: colors?.head ?? DEFAULT_COLORS.head,\n body: colors?.body ?? DEFAULT_COLORS.body,\n food: colors?.food ?? DEFAULT_COLORS.food,\n beat: colors?.beat ?? DEFAULT_COLORS.beat,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pavus/snake-game",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Playable Snake in the terminal with MIDI music",
5
5
  "type": "module",
6
6
  "bin": {