@pavus/snake-game 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/MusicSettings.js +1 -1
- package/dist/src/MusicSettings.js.map +1 -1
- package/dist/src/SnakeGame.d.ts +10 -3
- package/dist/src/SnakeGame.js +90 -16
- package/dist/src/SnakeGame.js.map +1 -1
- package/dist/src/freemidi-catalog.d.ts +1 -1
- package/dist/src/freemidi-catalog.js +6 -1
- package/dist/src/freemidi-catalog.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +24 -0
- package/dist/src/types.js +28 -0
- package/dist/src/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -139,6 +139,6 @@ export const MusicSettings = ({ initial, onApply, onCancel, accentColor, tracks
|
|
|
139
139
|
const idx = scrollOffset + i;
|
|
140
140
|
const isFocused = idx === safeFocus;
|
|
141
141
|
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" })] })] }))] }));
|
|
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" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "tip: use \"[\" or \"]\" to go back or forward songs" }) })] }))] }));
|
|
143
143
|
};
|
|
144
144
|
//# 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}\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 marginTop={1}>\n <Text dimColor>tip: use \"[\" or \"]\" to go back or forward songs</Text>\n </Box>\n </Box>\n )}\n </Box>\n );\n};\n"]}
|
package/dist/src/SnakeGame.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/SnakeGame.js
CHANGED
|
@@ -13,8 +13,30 @@ import { getSnakeHighScore, setSnakeHighScore, getSnakeMusicVolume, setSnakeMusi
|
|
|
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
|
+
// 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
|
+
}
|
|
18
40
|
function resolveDefaultTrack(catalog) {
|
|
19
41
|
const entry = catalog.find((t) => t.id === DEFAULT_TRACK_ID) ?? catalog[0];
|
|
20
42
|
if (!entry)
|
|
@@ -62,8 +84,9 @@ function makeInitial(w, h) {
|
|
|
62
84
|
started: false,
|
|
63
85
|
};
|
|
64
86
|
}
|
|
65
|
-
export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile, width = 20, height = 10, tracks, } = {}) => {
|
|
87
|
+
export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile, width = 20, height = 10, tracks, keybindings, } = {}) => {
|
|
66
88
|
const c = resolveColors(colors);
|
|
89
|
+
const kb = resolveKeybindings(keybindings);
|
|
67
90
|
const catalog = tracks ?? MIDI_CATALOG;
|
|
68
91
|
const stateRef = useRef(makeInitial(width, height));
|
|
69
92
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
@@ -73,10 +96,22 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
|
|
|
73
96
|
const [bpm, setBpm] = useState(DEFAULT_BPM);
|
|
74
97
|
const [track, setTrack] = useState(() => resolveDefaultTrack(catalog));
|
|
75
98
|
const [showSettings, setShowSettings] = useState(false);
|
|
99
|
+
const autoPlayRef = useRef(true);
|
|
100
|
+
const nextTrackRef = useRef(() => { });
|
|
76
101
|
const [beatPhase, setBeatPhase] = useState(0);
|
|
77
102
|
const [musicVolume, setMusicVolumeState] = useState(() => getSnakeMusicVolume(settingsFile));
|
|
78
103
|
const [tinkVolume, setTinkVolumeState] = useState(() => getSnakeTinkVolume(settingsFile));
|
|
79
104
|
const [sfxVolume, setSfxVolumeState] = useState(() => getSnakeSfxVolume(settingsFile));
|
|
105
|
+
// Keep nextTrackRef pointed at a stable "advance to next track" callback
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
nextTrackRef.current = () => {
|
|
108
|
+
const entry = randomOtherTrack(catalog, track.url);
|
|
109
|
+
if (entry) {
|
|
110
|
+
const urls = freemidiUrls(entry);
|
|
111
|
+
setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}, [track, catalog]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
80
115
|
useEffect(() => {
|
|
81
116
|
if (!music)
|
|
82
117
|
return;
|
|
@@ -84,12 +119,28 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
|
|
|
84
119
|
stopBgMusic(bgMusic.current);
|
|
85
120
|
bgMusic.current = null;
|
|
86
121
|
setBpm(DEFAULT_BPM);
|
|
122
|
+
autoPlayRef.current = true;
|
|
123
|
+
let cancelled = false;
|
|
87
124
|
void startBgMusic(track.url, track.downloadPage, musicVolume, cacheDir).then((handle) => {
|
|
125
|
+
if (cancelled) {
|
|
126
|
+
stopBgMusic(handle);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
88
129
|
bgMusic.current = handle;
|
|
89
|
-
if (handle)
|
|
130
|
+
if (handle) {
|
|
90
131
|
setBpm(handle.bpm);
|
|
132
|
+
handle.proc.on('exit', (_code, signal) => {
|
|
133
|
+
if (signal == null && autoPlayRef.current)
|
|
134
|
+
nextTrackRef.current();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
91
137
|
});
|
|
92
|
-
return () => {
|
|
138
|
+
return () => {
|
|
139
|
+
cancelled = true;
|
|
140
|
+
autoPlayRef.current = false;
|
|
141
|
+
stopBgMusic(bgMusic.current);
|
|
142
|
+
bgMusic.current = null;
|
|
143
|
+
};
|
|
93
144
|
}, [track, music]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
94
145
|
useEffect(() => {
|
|
95
146
|
const saved = getSnakeHighScore(settingsFile);
|
|
@@ -157,15 +208,15 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
|
|
|
157
208
|
}, tickMs);
|
|
158
209
|
return () => clearInterval(id);
|
|
159
210
|
}, [bpm]);
|
|
160
|
-
useInput((input) => {
|
|
211
|
+
useInput((input, key) => {
|
|
161
212
|
if (showSettings)
|
|
162
213
|
return;
|
|
163
214
|
const g = stateRef.current;
|
|
164
|
-
if (input
|
|
215
|
+
if (pressed(kb.quit, input, key) && onExit) {
|
|
165
216
|
onExit();
|
|
166
217
|
return;
|
|
167
218
|
}
|
|
168
|
-
if (input
|
|
219
|
+
if (pressed(kb.music, input, key) && music) {
|
|
169
220
|
if (g.started && !g.gameOver && !g.paused) {
|
|
170
221
|
stateRef.current = { ...g, paused: true };
|
|
171
222
|
forceUpdate();
|
|
@@ -173,29 +224,48 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
|
|
|
173
224
|
setShowSettings(true);
|
|
174
225
|
return;
|
|
175
226
|
}
|
|
227
|
+
if (music) {
|
|
228
|
+
const goToTrack = (entry) => {
|
|
229
|
+
const urls = freemidiUrls(entry);
|
|
230
|
+
setTrack({ title: entry.title, artist: entry.artist, url: urls.getter, downloadPage: urls.downloadPage });
|
|
231
|
+
};
|
|
232
|
+
if (pressed(kb.nextTrack, input, key)) {
|
|
233
|
+
const entry = randomOtherTrack(catalog, track.url);
|
|
234
|
+
if (entry)
|
|
235
|
+
goToTrack(entry);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (pressed(kb.prevTrack, input, key)) {
|
|
239
|
+
const idx = catalog.findIndex((t) => freemidiUrls(t).getter === track.url);
|
|
240
|
+
const entry = catalog[(idx - 1 + catalog.length) % catalog.length];
|
|
241
|
+
if (entry)
|
|
242
|
+
goToTrack(entry);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
176
246
|
if (!g.started || g.gameOver) {
|
|
177
|
-
if (input
|
|
247
|
+
if (pressed(kb.restart, input, key) || pressed(kb.pause, input, key))
|
|
178
248
|
reset();
|
|
179
249
|
return;
|
|
180
250
|
}
|
|
181
|
-
if (input
|
|
251
|
+
if (pressed(kb.pause, input, key)) {
|
|
182
252
|
stateRef.current = { ...g, paused: !g.paused };
|
|
183
253
|
forceUpdate();
|
|
184
254
|
return;
|
|
185
255
|
}
|
|
186
|
-
if (input
|
|
256
|
+
if (pressed(kb.restart, input, key)) {
|
|
187
257
|
reset();
|
|
188
258
|
return;
|
|
189
259
|
}
|
|
190
260
|
const lastDir = g.dirQueue[g.dirQueue.length - 1] ?? g.dir;
|
|
191
261
|
let next = null;
|
|
192
|
-
if (input
|
|
262
|
+
if (pressed(kb.up, input, key) && lastDir.dy !== 1)
|
|
193
263
|
next = DIRS.up;
|
|
194
|
-
else if (input
|
|
264
|
+
else if (pressed(kb.down, input, key) && lastDir.dy !== -1)
|
|
195
265
|
next = DIRS.down;
|
|
196
|
-
else if (input
|
|
266
|
+
else if (pressed(kb.left, input, key) && lastDir.dx !== 1)
|
|
197
267
|
next = DIRS.left;
|
|
198
|
-
else if (input
|
|
268
|
+
else if (pressed(kb.right, input, key) && lastDir.dx !== -1)
|
|
199
269
|
next = DIRS.right;
|
|
200
270
|
if (next && g.dirQueue.length < 2) {
|
|
201
271
|
stateRef.current = { ...g, dirQueue: [...g.dirQueue, next] };
|
|
@@ -205,6 +275,10 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
|
|
|
205
275
|
return (_jsx(MusicSettings, { initial: { track, musicVolume, tinkVolume, sfxVolume }, accentColor: c.accent, tracks: catalog, onApply: (cfg) => {
|
|
206
276
|
if (cfg.musicVolume !== musicVolume && bgMusic.current) {
|
|
207
277
|
bgMusic.current = setMusicVolume(bgMusic.current, cfg.musicVolume);
|
|
278
|
+
bgMusic.current.proc.on('exit', (_code, signal) => {
|
|
279
|
+
if (signal == null && autoPlayRef.current)
|
|
280
|
+
nextTrackRef.current();
|
|
281
|
+
});
|
|
208
282
|
}
|
|
209
283
|
if (cfg.musicVolume !== musicVolume) {
|
|
210
284
|
setSnakeMusicVolume(cfg.musicVolume, settingsFile);
|
|
@@ -233,7 +307,7 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
|
|
|
233
307
|
return 'food';
|
|
234
308
|
return 'empty';
|
|
235
309
|
}));
|
|
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, {
|
|
310
|
+
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 })] }), _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
311
|
if (cell === 'head')
|
|
238
312
|
return _jsx(Text, { color: c.head, bold: true, children: '● ' }, x);
|
|
239
313
|
if (cell === 'body')
|
|
@@ -241,6 +315,6 @@ export const SnakeGame = ({ onExit, music = true, colors, cacheDir, settingsFile
|
|
|
241
315
|
if (cell === 'food')
|
|
242
316
|
return _jsx(Text, { color: c.food, children: '◆ ' }, x);
|
|
243
317
|
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: ["
|
|
318
|
+
}), _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
319
|
};
|
|
246
320
|
//# 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} 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[]): 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 * 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));\n const [showSettings, setShowSettings] = useState(false);\n const autoPlayRef = useRef(true);\n const nextTrackRef = useRef<() => void>(() => {});\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 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) nextTrackRef.current();\n });\n }\n });\n return () => {\n cancelled = true;\n autoPlayRef.current = false;\n stopBgMusic(bgMusic.current);\n bgMusic.current = null;\n };\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, 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 }\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 }}\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) nextTrackRef.current();\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.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 </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"]}
|
|
@@ -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 =
|
|
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"]}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/src/index.js.map
CHANGED
|
@@ -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;
|
|
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"]}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
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
|
+
}
|
|
23
|
+
export declare const DEFAULT_KEYBINDINGS: Required<SnakeKeybindings>;
|
|
24
|
+
export declare function resolveKeybindings(kb?: SnakeKeybindings): Required<SnakeKeybindings>;
|
|
1
25
|
export interface SnakeColors {
|
|
2
26
|
/** UI accent color — title, score labels, controls (default: '#1e61f0') */
|
|
3
27
|
accent?: string;
|
package/dist/src/types.js
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
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
|
+
};
|
|
13
|
+
export function resolveKeybindings(kb) {
|
|
14
|
+
if (!kb)
|
|
15
|
+
return DEFAULT_KEYBINDINGS;
|
|
16
|
+
return {
|
|
17
|
+
up: kb.up ?? DEFAULT_KEYBINDINGS.up,
|
|
18
|
+
down: kb.down ?? DEFAULT_KEYBINDINGS.down,
|
|
19
|
+
left: kb.left ?? DEFAULT_KEYBINDINGS.left,
|
|
20
|
+
right: kb.right ?? DEFAULT_KEYBINDINGS.right,
|
|
21
|
+
pause: kb.pause ?? DEFAULT_KEYBINDINGS.pause,
|
|
22
|
+
restart: kb.restart ?? DEFAULT_KEYBINDINGS.restart,
|
|
23
|
+
music: kb.music ?? DEFAULT_KEYBINDINGS.music,
|
|
24
|
+
quit: kb.quit ?? DEFAULT_KEYBINDINGS.quit,
|
|
25
|
+
nextTrack: kb.nextTrack ?? DEFAULT_KEYBINDINGS.nextTrack,
|
|
26
|
+
prevTrack: kb.prevTrack ?? DEFAULT_KEYBINDINGS.prevTrack,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
1
29
|
export const DEFAULT_COLORS = {
|
|
2
30
|
accent: '#1e61f0',
|
|
3
31
|
head: '#f7a8b8',
|
package/dist/src/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAuBA,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;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;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}\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};\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 };\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"]}
|