extremely claude-assisted go game based on atproto! working on cleaning up and giving a more unique design, still has a bit of a slop vibe to it.
at master 105 lines 2.9 kB view raw
1import { browser } from '$app/environment'; 2 3export type SoundEffect = 4 | 'capture' // When you capture opponent's stones 5 | 'captured' // When your stones get captured 6 | 'move_made' // When someone makes a move in your game (homepage notification) 7 | 'opened_game' // When you open/load a game 8 | 'played_stone'; // When placing a stone on the board 9 10export class SoundManager { 11 private sounds: Map<SoundEffect, HTMLAudioElement> = new Map(); 12 private enabled: boolean = true; 13 14 constructor() { 15 if (!browser) return; 16 17 // Load SFX preference from cookie 18 this.enabled = this.getSfxEnabled(); 19 20 // Preload all sound effects 21 this.preloadSound('capture', '/sfx/capture.wav'); 22 this.preloadSound('captured', '/sfx/captured.wav'); 23 this.preloadSound('move_made', '/sfx/move_made.wav'); 24 this.preloadSound('opened_game', '/sfx/opened_game.wav'); 25 this.preloadSound('played_stone', '/sfx/played_stone.wav'); 26 } 27 28 private preloadSound(name: SoundEffect, path: string) { 29 if (!browser) return; 30 31 const audio = new Audio(path); 32 audio.preload = 'auto'; 33 audio.volume = 0.5; // Default volume at 50% 34 this.sounds.set(name, audio); 35 } 36 37 play(sound: SoundEffect) { 38 if (!browser || !this.enabled) return; 39 40 const audio = this.sounds.get(sound); 41 if (audio) { 42 // Clone the audio to allow overlapping sounds 43 const clone = audio.cloneNode() as HTMLAudioElement; 44 clone.volume = audio.volume; 45 clone.play().catch(err => { 46 console.warn('Failed to play sound:', sound, err); 47 }); 48 } 49 } 50 51 setEnabled(enabled: boolean) { 52 this.enabled = enabled; 53 this.setSfxCookie(enabled); 54 } 55 56 isEnabled(): boolean { 57 return this.enabled; 58 } 59 60 setVolume(volume: number) { 61 if (!browser) return; 62 63 // Clamp volume between 0 and 1 64 volume = Math.max(0, Math.min(1, volume)); 65 66 for (const audio of this.sounds.values()) { 67 audio.volume = volume; 68 } 69 } 70 71 private getSfxEnabled(): boolean { 72 if (!browser) return true; 73 74 const value = this.getCookie('sfx-enabled'); 75 if (value === null) return true; // Default to enabled 76 return value === 'true'; 77 } 78 79 private setSfxCookie(enabled: boolean) { 80 if (!browser) return; 81 82 const expires = new Date(); 83 expires.setFullYear(expires.getFullYear() + 1); // Expire in 1 year 84 document.cookie = `sfx-enabled=${enabled};expires=${expires.toUTCString()};path=/`; 85 } 86 87 private getCookie(name: string): string | null { 88 if (!browser) return null; 89 90 const value = `; ${document.cookie}`; 91 const parts = value.split(`; ${name}=`); 92 if (parts.length === 2) return parts.pop()?.split(';').shift() || null; 93 return null; 94 } 95} 96 97// Global sound manager instance 98let soundManager: SoundManager | null = null; 99 100export function getSoundManager(): SoundManager { 101 if (!soundManager) { 102 soundManager = new SoundManager(); 103 } 104 return soundManager; 105}