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