diff --git a/public/click1.mp3 b/public/click1.mp3 new file mode 100644 index 0000000..1dcaefa Binary files /dev/null and b/public/click1.mp3 differ diff --git a/public/click2.mp3 b/public/click2.mp3 new file mode 100644 index 0000000..c5ca6c4 Binary files /dev/null and b/public/click2.mp3 differ diff --git a/public/click3.mp3 b/public/click3.mp3 new file mode 100644 index 0000000..d38cb51 Binary files /dev/null and b/public/click3.mp3 differ diff --git a/public/levelup.mp3 b/public/levelup.mp3 new file mode 100644 index 0000000..7422491 Binary files /dev/null and b/public/levelup.mp3 differ diff --git a/public/onPurchase.mp3 b/public/onPurchase.mp3 new file mode 100644 index 0000000..8f9749e Binary files /dev/null and b/public/onPurchase.mp3 differ diff --git a/public/upgrade.mp3 b/public/upgrade.mp3 new file mode 100644 index 0000000..1b1908d Binary files /dev/null and b/public/upgrade.mp3 differ diff --git a/public/upgrade.mp3.bak b/public/upgrade.mp3.bak new file mode 100644 index 0000000..3ad5679 Binary files /dev/null and b/public/upgrade.mp3.bak differ diff --git a/src/App.tsx b/src/App.tsx index 5c72efe..aa73f85 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { BuildingButton } from './components/BuildingButton' import { NextBuildingPreview } from './components/NextBuildingPreview' import { ResetButton } from './components/ResetButton' import { useGameStore } from './store/gameStore' +import { playClickSound, initAudio } from './utils/soundUtils' import { ChakraProvider, Box, @@ -62,7 +63,9 @@ function App() { const handleKeyPress = (e: KeyboardEvent) => { // Only allow clicks after terms are accepted and modal is closed if (hasStarted) { - click() + console.log('Key pressed:', e.key); + playClickSound(); // Play click sound + click(); } } @@ -74,13 +77,17 @@ function App() { const handleClick = (e: React.MouseEvent) => { // Only allow clicks after terms are accepted and modal is closed if (hasStarted) { - click() + console.log('Mouse clicked'); + playClickSound(); // Play click sound + click(); } } // Handle starting the game const handleStartGame = () => { if (agreedToTerms) { + // Initialize audio on first user interaction + initAudio(); setHasStarted(true) onClose() } diff --git a/src/components/ResourceDisplay.tsx b/src/components/ResourceDisplay.tsx index dcbfdea..053a90a 100644 --- a/src/components/ResourceDisplay.tsx +++ b/src/components/ResourceDisplay.tsx @@ -2,6 +2,7 @@ import { Box, HStack, Text, Progress, Flex, Divider, Tooltip, Image } from '@cha import { useGameStore } from '../store/gameStore' import logoImg from '../assets/logo.png' import { ResetButton } from './ResetButton' +import { SoundToggleButton } from './SoundToggleButton' export function ResourceDisplay() { const { points, pointsPerSecond, clickPower, playerLevel } = useGameStore() @@ -57,16 +58,19 @@ export function ResourceDisplay() { /> - {/* Reset button */} - + - + {/* Main content - centered */} { + // Initialize audio when toggling (helps with browser autoplay policies) + initAudio(); + const newState = toggleSound() + setSoundOn(newState) + } + + return ( + + {soundOn ? '🔊' : '🔇'}} + onClick={handleToggle} + variant="ghost" + colorScheme="cyan" + size="md" + /> + + ) +} \ No newline at end of file diff --git a/src/store/gameStore.ts b/src/store/gameStore.ts index 8a92df6..80fc76f 100644 --- a/src/store/gameStore.ts +++ b/src/store/gameStore.ts @@ -1,5 +1,6 @@ import { create } from 'zustand' import { persist } from 'zustand/middleware' +import { playPurchaseSound, playLevelUpSound, playUpgradeSound } from '../utils/soundUtils' // Building information interface export interface BuildingInfo { @@ -364,7 +365,19 @@ export const useGameStore = create()( buyBuilding: (buildingType: BuildingType) => { const state = get() const cost = BUILDING_COSTS[buildingType] + + // Get building info for level requirement check + const info = BUILDING_INFO[buildingType]; + + // Check if player meets level requirement + if (state.playerLevel < info.levelRequirement) { + return; + } + if (state.points >= cost) { + // Play purchase sound + playPurchaseSound() + set((state) => { const newCount = state[buildingType] + 1 const level = state[`${buildingType}Level` as keyof GameState] as number @@ -383,20 +396,24 @@ export const useGameStore = create()( } }, - buyUpgrade: (upgradeType: UpgradeType) => { - const state = get() + buyUpgrade: (type: UpgradeType) => { + const state = get(); - // Handle upgrade purchase - if (upgradeType === 'clickPower') { - const cost = calculateClickPowerUpgradeCost(state.clickPowerUpgrades) + if (type === 'clickPower') { + const cost = get().getClickPowerUpgradeCost(); - if (state.points >= cost) { - set((state) => ({ - points: state.points - cost, - clickPower: state.clickPower + 1, - clickPowerUpgrades: state.clickPowerUpgrades + 1 - })) - } + // Check if we have enough points + if (state.points < cost) return; + + // Apply the upgrade + set({ + points: state.points - cost, + clickPower: state.clickPower + 1, + clickPowerUpgrades: state.clickPowerUpgrades + 1 + }); + + // Play upgrade sound instead of purchase sound + playUpgradeSound(); } }, @@ -411,6 +428,9 @@ export const useGameStore = create()( const cost = calculateUpgradeCost(buildingType, currentLevel) if (state.points >= cost && state[buildingType] > 0) { + // Play upgrade sound instead of purchase sound + playUpgradeSound() + set((state) => { const newLevel = (state[`${buildingType}Level` as keyof GameState] as number) + 1 const count = state[buildingType] @@ -444,9 +464,16 @@ export const useGameStore = create()( } // Update player level based on points per second - const playerLevel = Math.max(1, Math.floor(Math.log10(state.pointsPerSecond) + 1)) - if (playerLevel !== state.playerLevel) { - set({ playerLevel }) + const currentLevel = state.playerLevel + const newLevel = Math.max(1, Math.floor(Math.log10(state.pointsPerSecond) + 1)) + + if (newLevel !== currentLevel) { + // If level has increased, play the level up sound + if (newLevel > currentLevel) { + playLevelUpSound() + } + + set({ playerLevel: newLevel }) } }, diff --git a/src/utils/soundUtils.ts b/src/utils/soundUtils.ts new file mode 100644 index 0000000..de49462 --- /dev/null +++ b/src/utils/soundUtils.ts @@ -0,0 +1,192 @@ +// Sound effect file paths +const SOUND_FILES = { + CLICK: ['/click1.mp3', '/click2.mp3', '/click3.mp3'], + PURCHASE: '/onPurchase.mp3', + LEVEL_UP: '/levelup.mp3', + UPGRADE: '/upgrade.mp3' +}; + +// AudioManager singleton for handling all game audio +class AudioManager { + private soundEnabled: boolean = true; + private audioContext: AudioContext | null = null; + private sounds: Map = new Map(); + private lastClickIndex: number = -1; + private initialized: boolean = false; + + // Volume levels for different sound types + private readonly VOLUMES = { + CLICK: 0.4, + PURCHASE: 0.5, + LEVEL_UP: 0.7, + UPGRADE: 0.6 + }; + + constructor() { + this.preloadSounds(); + } + + // Initialize audio context - must be called on user interaction + public init(): void { + console.log('Initializing AudioManager...'); + + if (this.initialized) { + console.log('AudioManager already initialized'); + return; + } + + try { + // Create AudioContext if possible (will only work after user interaction) + if (window.AudioContext || (window as any).webkitAudioContext) { + const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext; + this.audioContext = new AudioContextClass(); + } + + // Play a silent sound to unlock audio on iOS/Safari + const silentSound = new Audio(); + silentSound.play().catch(e => console.log('Silent sound failed to play:', e)); + + // Mark as initialized + this.initialized = true; + console.log('AudioManager initialized successfully'); + } catch (error) { + console.error('Failed to initialize AudioManager:', error); + } + } + + // Preload all sound effects + private preloadSounds(): void { + try { + // Preload click sounds + SOUND_FILES.CLICK.forEach((path, index) => { + const audio = new Audio(path); + this.sounds.set(`click_${index}`, audio); + audio.load(); // Begin loading + }); + + // Preload level up sound + const levelUpAudio = new Audio(SOUND_FILES.LEVEL_UP); + this.sounds.set('level_up', levelUpAudio); + levelUpAudio.load(); + + // Preload purchase sound + const purchaseAudio = new Audio(SOUND_FILES.PURCHASE); + this.sounds.set('purchase', purchaseAudio); + purchaseAudio.load(); + + // Preload upgrade sound + const upgradeAudio = new Audio(SOUND_FILES.UPGRADE); + this.sounds.set('upgrade', upgradeAudio); + upgradeAudio.load(); + + console.log("All sounds preloaded"); + } catch (error) { + console.error("Error preloading sounds:", error); + } + } + + // Play a specific sound file + private playSound(key: string, volume: number): void { + if (!this.soundEnabled) { + console.log('Sound is disabled, not playing', key); + return; + } + + // Try to initialize audio if not already done + if (!this.initialized) { + this.init(); + } + + try { + const sound = this.sounds.get(key); + + if (sound) { + console.log(`Playing sound: ${key}`); + + // Clone the audio to allow overlapping sounds + const clonedSound = sound.cloneNode() as HTMLAudioElement; + clonedSound.volume = volume; + + clonedSound.play().catch(error => { + console.error(`Error playing sound ${key}:`, error); + + // If the first attempt failed due to interaction policy, + // try again after a slight delay + if (error.name === 'NotAllowedError') { + console.log('Attempting to play after delay due to browser restrictions'); + setTimeout(() => { + const retrySound = new Audio(sound.src); + retrySound.volume = volume; + retrySound.play().catch(e => console.error('Retry failed:', e)); + }, 100); + } + }); + } else { + console.warn(`Sound not found: ${key}`); + } + } catch (error) { + console.error(`Error playing sound ${key}:`, error); + } + } + + // Play a random click sound + public playClickSound(): void { + console.log('Playing click sound...'); + + // Get a random index that's different from the last one + let index; + do { + index = Math.floor(Math.random() * SOUND_FILES.CLICK.length); + } while (index === this.lastClickIndex && SOUND_FILES.CLICK.length > 1); + + this.lastClickIndex = index; + this.playSound(`click_${index}`, this.VOLUMES.CLICK); + } + + // Play the purchase sound + public playPurchaseSound(): void { + console.log('Playing purchase sound...'); + this.playSound('purchase', this.VOLUMES.PURCHASE); + } + + // Play the level up sound + public playLevelUpSound(): void { + console.log('Playing level up sound...'); + this.playSound('level_up', this.VOLUMES.LEVEL_UP); + } + + // Play the upgrade sound + public playUpgradeSound(): void { + console.log('Playing upgrade sound...'); + this.playSound('upgrade', this.VOLUMES.UPGRADE); + } + + // Toggle sound on/off + public toggleSound(state?: boolean): boolean { + if (typeof state === 'boolean') { + this.soundEnabled = state; + } else { + this.soundEnabled = !this.soundEnabled; + } + + console.log(`Sound is now ${this.soundEnabled ? 'enabled' : 'disabled'}`); + return this.soundEnabled; + } + + // Check if sound is enabled + public isSoundEnabled(): boolean { + return this.soundEnabled; + } +} + +// Create a singleton instance +const audioManager = new AudioManager(); + +// Export the functions to use throughout the application +export const initAudio = () => audioManager.init(); +export const playClickSound = () => audioManager.playClickSound(); +export const playPurchaseSound = () => audioManager.playPurchaseSound(); +export const playLevelUpSound = () => audioManager.playLevelUpSound(); +export const playUpgradeSound = () => audioManager.playUpgradeSound(); +export const toggleSound = (state?: boolean) => audioManager.toggleSound(state); +export const isSoundEnabled = () => audioManager.isSoundEnabled(); \ No newline at end of file