From 448df7687637e2a77283d2ba82df59553db97e52 Mon Sep 17 00:00:00 2001 From: Pim de Wit Date: Fri, 17 Apr 2026 10:52:40 +0100 Subject: [PATCH] feat(chess): use audio hook --- .../v2/src/components/SoundEffects.tsx | 13 ++--- .../chess/visualizer/v2/src/hooks/useAudio.ts | 49 +++++++++++++++++++ 2 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/hooks/useAudio.ts diff --git a/kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/components/SoundEffects.tsx b/kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/components/SoundEffects.tsx index 22a80f78c..e125d6987 100644 --- a/kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/components/SoundEffects.tsx +++ b/kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/components/SoundEffects.tsx @@ -1,17 +1,15 @@ import { useEffect, useRef } from 'react'; -import { Howl } from 'howler'; import useGameStore from '../stores/useGameStore'; import usePreferences from '../stores/usePreferences'; +import useAudio from '../hooks/useAudio'; import moveSoundUrl from '../assets/audio/chess-move.mp3'; import captureSoundUrl from '../assets/audio/chess-capture.mp3'; import { SCRUB_THRESHOLD_MS } from '../constants'; -const placeSound = new Howl({ src: [moveSoundUrl], volume: 0.5 }); -const captureSound = new Howl({ src: [captureSoundUrl], volume: 0.5 }); - export function SoundEffects() { const { game, options } = useGameStore(); const soundEnabled = usePreferences((state) => state.soundEnabled); + const sounds = useAudio({ move: moveSoundUrl, capture: captureSoundUrl }); const lastPlayedRef = useRef(0); const lastStep = useRef(0); @@ -25,14 +23,13 @@ export function SoundEffects() { const currentMove = history.at(-1); const captured = currentMove?.isCapture(); - // Prevent audio spam when scrubbing quickly (e.g. holding arrow keys) const now = performance.now(); if (now - lastPlayedRef.current < SCRUB_THRESHOLD_MS) return; lastPlayedRef.current = now; - placeSound.play(); - if (captured) captureSound.play(); - }, [game, options.step, soundEnabled]); + sounds.move.play(); + if (captured) sounds.capture.play(); + }, [game, options.step, sounds, soundEnabled]); return null; } diff --git a/kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/hooks/useAudio.ts b/kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/hooks/useAudio.ts new file mode 100644 index 000000000..695f83b4a --- /dev/null +++ b/kaggle_environments/envs/open_spiel_env/games/chess/visualizer/v2/src/hooks/useAudio.ts @@ -0,0 +1,49 @@ +import { useEffect, useRef, useState } from 'react'; +import { Howl, Howler } from 'howler'; + +type SoundConfig = { src: string; volume?: number }; +type SoundMap = Record; +type Sounds = Record; + +function createSounds(map: SoundMap): Sounds { + const entries = Object.entries(map).map(([key, value]) => { + const config = typeof value === 'string' ? { src: value } : value; + return [key, new Howl({ src: [config.src], volume: config.volume ?? 0.5 })]; + }); + return Object.fromEntries(entries) as Sounds; +} + +export default function useAudio(map: SoundMap): Sounds { + const mapRef = useRef(map); + const [sounds, setSounds] = useState(() => createSounds(map)); + + useEffect(() => { + function cleanup() { + window.removeEventListener('pointerdown', resume); + window.removeEventListener('keydown', resume); + } + + function resume() { + Howler.unload(); + setSounds(createSounds(mapRef.current)); + cleanup(); + } + + const handleVisibilityChange = () => { + if (document.visibilityState !== 'visible') return; + const state = Howler.ctx?.state as string; + if (state !== 'interrupted' && state !== 'suspended') return; + + window.addEventListener('pointerdown', resume); + window.addEventListener('keydown', resume); + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange); + cleanup(); + }; + }, []); + + return sounds; +}