150 // --- AUDIO CONTEXT for UI Sounds ---
151 let audioCtx;
152 function initAudio() {
153 if (!audioCtx) {
154 audioCtx = new (window.AudioContext || window.webkitAudioContext)();
155 }
156 }
157 function playSound(type = 'click') {
158 if (!audioCtx) return;
159 const oscillator = audioCtx.createOscillator();
272 });
273
274 // --- Game Logic Functions ---
275 function loadGameState() {
276 try {
277 const savedData = localStorage.getItem(CONFIG.SAVE_KEY);
288 }
289 }
290 function saveGameState() {
291 try {
292 localStorage.setItem(CONFIG.SAVE_KEY, JSON.stringify(gameState));
295 }
296 }
297 function applyShake() {
298 if (shakeDuration > 0) {
299 const dx = (Math.random() - 0.5) * shakeIntensity * 2;
307 }
308 }
309 function triggerShake(intensity, duration) {
310 shakeIntensity = Math.max(shakeIntensity, intensity);
311 shakeDuration = Math.max(shakeDuration, duration);
312 }
313 function getDistance(x1, y1, x2, y2) {
314 const dx = x2 - x1; const dy = y2 - y1; return Math.sqrt(dx * dx + dy * dy);
315 }
316 function createFloatingText(text, x, y, color = 'white', size = 16) {
317 floatingTexts.push({ text, x, y, color, size, opacity: 1, yOffset: 0, creationTime: Date.now() });
318 }
596 }
597
598 // --- Game Flow & UI Functions ---
599 function startNewWave() {
600 currentWave++;
601 ui.wave.textContent = currentWave;
611 }
612 }
613 function spawnMenForWave(count, isBossWave = false) {
614 const numToSpawn = Math.min(count, menToSpawnThisWave - menSpawnedThisWave);
615 const currentPercentRanged = Math.min(CONFIG.MAX_PERCENT_RANGED, CONFIG.PERCENT_RANGED_MEN_BASE + (currentWave - 1) * CONFIG.PERCENT_RANGED_INCREMENT_PER_WAVE);
632 }
633 }
634 function initGame() {
635 resizeCanvas();
636 loadGameState();
653 canvas.focus();
654 }
655 function updateUI() {
656 displayedScore += (currentScore - displayedScore) * TWEEN_FACTOR;
657 displayedBananas += (gameState.totalBananas - displayedBananas) * TWEEN_FACTOR;
675 }
676
677 function gameLoop(timestamp) {
678 if (!gameRunning) { animationFrameId = null; return; }
679 if (gamePaused) { animationFrameId = requestAnimationFrame(gameLoop); return; }
715 animationFrameId = requestAnimationFrame(gameLoop);
716 }
717 function endGame(gorillaWins) {
718 gameRunning = false; gamePaused = false;
719 cancelAnimationFrame(animationFrameId); animationFrameId = null;
734
735 // --- Shop Logic ---
736 function getUpgradeCost(level) {
737 return Math.floor(CONFIG.UPGRADE_BASE_COST * Math.pow(CONFIG.UPGRADE_COST_INCREASE_FACTOR, level));
738 }
739 function updateShopUI() {
740 ui.shopBananasDisplay.textContent = gameState.totalBananas;
741 ui.hpUpgradeLevel.textContent = gameState.upgrades.maxHpLvl;
750 ui.atkUpgradeCost.textContent = getUpgradeCost(gameState.upgrades.attackPowerLvl);
751 }
752 function purchaseUpgrade(type) {
753 playSound('click');
754 let levelKey = type + 'Lvl';
807 clearTimeout(window.resizeTimeout); window.resizeTimeout = setTimeout(resizeCanvas, 100);
808 });
809 function resizeCanvas() {
810 const container = document.querySelector('.game-container');
811 if (!container) return;