diff --git a/index.html b/index.html index 1d0cff8..4448012 100644 --- a/index.html +++ b/index.html @@ -11,13 +11,27 @@

Danger Field Game

-

Use WASD or Arrow Keys to move

+

Type an animal name to move in that direction:

+
+
🦅 Bird, Eagle, Hawk → Move UP
+
🦔 Mole, Hedgehog, Badger → Move DOWN
+
🐺 Wolf, Dog, Fox → Move LEFT
+
🐎 Horse, Stallion, Mare → Move RIGHT
+

⚠️ Red squares are deadly - avoid them!

🔊 Hidden dangers end the game - listen for directions!

-

🗣️ Speech directions: "left", "right", "up", "down"

+

🗣️ Animal sounds indicate movement success or danger!

+
+ + diff --git a/public/sounds/README.md b/public/sounds/README.md new file mode 100644 index 0000000..b054243 --- /dev/null +++ b/public/sounds/README.md @@ -0,0 +1,52 @@ +# Audio Files for Animal Movement System + +This directory should contain the following audio files for the randomized animal system: + +## Birds (Up Movement) +- `eagle.mp3` - Eagle/Hawk/Falcon sounds +- `eagle_danger.mp3` - Distressed eagle sounds +- `dove.mp3` - Dove/Pigeon sounds +- `dove_danger.mp3` - Distressed dove sounds +- `duck.mp3` - Duck/Goose sounds +- `duck_danger.mp3` - Distressed duck sounds +- `bee.mp3` - Bee/Wasp sounds +- `bee_danger.mp3` - Angry bee sounds + +## Ground Animals (Down Movement) +- `mole.mp3` - Mole/Hedgehog sounds +- `mole_danger.mp3` - Angry mole sounds +- `snake.mp3` - Snake hissing sounds +- `snake_danger.mp3` - Aggressive snake sounds +- `lizard.mp3` - Lizard/Gecko sounds +- `lizard_danger.mp3` - Distressed lizard sounds +- `spider.mp3` - Spider/Arachnid sounds +- `spider_danger.mp3` - Aggressive spider sounds + +## Left Movement Animals +- `wolf.mp3` - Wolf/Dog howling +- `wolf_danger.mp3` - Aggressive wolf sounds +- `cat.mp3` - Cat meowing/purring +- `cat_danger.mp3` - Angry cat sounds +- `raccoon.mp3` - Raccoon chittering +- `raccoon_danger.mp3` - Aggressive raccoon sounds +- `squirrel.mp3` - Squirrel chattering +- `squirrel_danger.mp3` - Angry squirrel sounds + +## Right Movement Animals +- `horse.mp3` - Horse neighing/galloping +- `horse_danger.mp3` - Rearing horse sounds +- `deer.mp3` - Deer/Elk sounds +- `deer_danger.mp3` - Distressed deer sounds +- `cow.mp3` - Cow mooing +- `cow_danger.mp3` - Angry bull sounds +- `goat.mp3` - Goat bleating +- `goat_danger.mp3` - Angry goat sounds + +## File Format Requirements +- Format: MP3 or WAV +- Duration: 0.5-2 seconds recommended +- Volume: Normalized for consistent playback +- Quality: 44.1kHz, 16-bit minimum + +## Usage +The game will automatically try to load these files. If a file is missing, it will fall back to generated sounds using Web Audio API. diff --git a/public/sounds/bee_danger.mp3 b/public/sounds/bee_danger.mp3 new file mode 100644 index 0000000..238317c Binary files /dev/null and b/public/sounds/bee_danger.mp3 differ diff --git a/sounds/bee_danger.mp3 b/sounds/bee_danger.mp3 new file mode 100644 index 0000000..238317c Binary files /dev/null and b/sounds/bee_danger.mp3 differ diff --git a/src/animals.js b/src/animals.js new file mode 100644 index 0000000..5f3578f --- /dev/null +++ b/src/animals.js @@ -0,0 +1,181 @@ +// Animal data constants for the labyrinth game +// Each direction has multiple animals with different names, emojis, and audio files + +export const ANIMAL_DATABASE = { + up: [ + { + emoji: '🦅', + names: ['bird', 'eagle', 'hawk', 'falcon', 'raven', 'crow'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🕊️', + names: ['dove', 'pigeon', 'seagull', 'owl', 'sparrow'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🦆', + names: ['duck', 'goose', 'swan', 'pelican'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🐝', + names: ['bee', 'wasp', 'hornet', 'bumblebee'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + } + ], + + down: [ + { + emoji: '🦔', + names: ['mole', 'hedgehog', 'badger', 'groundhog'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🐍', + names: ['snake', 'viper', 'cobra', 'python', 'adder'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🦎', + names: ['lizard', 'gecko', 'iguana', 'salamander'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🕷️', + names: ['spider', 'tarantula', 'arachnid'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + } + ], + + left: [ + { + emoji: '🐺', + names: ['wolf', 'dog', 'fox', 'coyote', 'jackal'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🐈', + names: ['cat', 'feline', 'kitten', 'lynx', 'bobcat'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🦝', + names: ['raccoon', 'bandit', 'ringtail'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🐿️', + names: ['squirrel', 'chipmunk', 'marmot'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + } + ], + + right: [ + { + emoji: '🐎', + names: ['horse', 'stallion', 'mare', 'pony', 'mustang'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🦌', + names: ['deer', 'stag', 'doe', 'elk', 'reindeer'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🐄', + names: ['cow', 'bull', 'ox', 'buffalo', 'bison'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + }, + { + emoji: '🐐', + names: ['goat', 'ram', 'sheep', 'lamb'], + audioFile: 'sounds/bee_danger.mp3', + dangerAudioFile: 'sounds/bee_danger.mp3' + } + ] +}; + +// Helper function to get a random animal for each direction +export function generateRandomAnimalSet() { + const directions = ['up', 'down', 'left', 'right']; + const selectedAnimals = {}; + + directions.forEach(direction => { + const animals = ANIMAL_DATABASE[direction]; + const randomIndex = Math.floor(Math.random() * animals.length); + selectedAnimals[direction] = animals[randomIndex]; + }); + + return selectedAnimals; +} + +// Helper function to create animal direction mappings for the input handler +export function createAnimalDirections(selectedAnimals) { + const animalDirections = {}; + const directionCoords = { + up: { x: 0, y: -1 }, + down: { x: 0, y: 1 }, + left: { x: -1, y: 0 }, + right: { x: 1, y: 0 } + }; + + Object.entries(selectedAnimals).forEach(([direction, animal]) => { + const coords = directionCoords[direction]; + + // Add all animal names for this direction + animal.names.forEach(name => { + animalDirections[name.toLowerCase()] = { + x: coords.x, + y: coords.y, + emoji: animal.emoji, + sound: direction, // Use direction as sound identifier + audioFile: animal.audioFile, + dangerAudioFile: animal.dangerAudioFile + }; + }); + }); + + return animalDirections; +} + +// Helper function to get display information for UI +export function getDisplayInfo(selectedAnimals) { + return { + up: { + emoji: selectedAnimals.up.emoji, + names: selectedAnimals.up.names.join(', '), + primaryName: selectedAnimals.up.names[0] + }, + down: { + emoji: selectedAnimals.down.emoji, + names: selectedAnimals.down.names.join(', '), + primaryName: selectedAnimals.down.names[0] + }, + left: { + emoji: selectedAnimals.left.emoji, + names: selectedAnimals.left.names.join(', '), + primaryName: selectedAnimals.left.names[0] + }, + right: { + emoji: selectedAnimals.right.emoji, + names: selectedAnimals.right.names.join(', '), + primaryName: selectedAnimals.right.names[0] + } + }; +} diff --git a/src/audio.js b/src/audio.js index 3090314..0d8ad34 100644 --- a/src/audio.js +++ b/src/audio.js @@ -6,10 +6,16 @@ export class AudioSystem { this.initialized = false; this.lastSpokenDirection = ''; this.speechEnabled = true; + this.animalData = null; // Store current animal data this.initializeAudio(); } + // Update animal data when animals are randomized + updateAnimalData(selectedAnimals) { + this.animalData = selectedAnimals; + } + async initializeAudio() { try { // Create audio context (requires user interaction) @@ -237,6 +243,135 @@ export class AudioSystem { this.createOscillator(440, 'sine', 0.05); } + // Placeholder method for animal sounds + async playAnimalSound(direction) { + await this.ensureAudioContext(); + + // Try to load and play actual audio file if available + if (this.animalData && this.animalData[direction]) { + const animal = this.animalData[direction]; + + // Try to play the actual audio file + try { + await this.playAudioFile(animal.audioFile); + console.log(`${animal.emoji} Playing ${animal.names[0]} sound: ${animal.audioFile}`); + return; + } catch (error) { + console.warn(`Could not play audio file ${animal.audioFile}, using placeholder sound`); + } + } + + // Fallback to placeholder sounds for different directions + switch (direction) { + case 'up': + // High pitched chirp + this.createOscillator(800, 'sine', 0.2); + setTimeout(() => this.createOscillator(1000, 'sine', 0.15), 100); + console.log('🦅 Playing bird sound placeholder'); + break; + case 'down': + // Low rumbling sound + this.createOscillator(150, 'sawtooth', 0.3); + console.log('🦔 Playing mole sound placeholder'); + break; + case 'left': + // Howling sound + this.createOscillator(300, 'triangle', 0.4); + setTimeout(() => this.createOscillator(350, 'triangle', 0.3), 200); + console.log('🐺 Playing wolf sound placeholder'); + break; + case 'right': + // Galloping rhythm + this.createOscillator(200, 'square', 0.1); + setTimeout(() => this.createOscillator(250, 'square', 0.1), 100); + setTimeout(() => this.createOscillator(200, 'square', 0.1), 200); + setTimeout(() => this.createOscillator(250, 'square', 0.1), 300); + console.log('🐎 Playing horse sound placeholder'); + break; + default: + this.playMoveSound(); + } + } + + // Danger sounds for when movement is blocked + async playDangerSound(direction) { + await this.ensureAudioContext(); + + // Try to load and play actual danger audio file if available + if (this.animalData && this.animalData[direction]) { + const animal = this.animalData[direction]; + + // Try to play the actual danger audio file + try { + await this.playAudioFile(animal.dangerAudioFile); + console.log(`${animal.emoji}⚠️ Playing ${animal.names[0]} danger sound: ${animal.dangerAudioFile}`); + return; + } catch (error) { + console.warn(`Could not play danger audio file ${animal.dangerAudioFile}, using placeholder sound`); + } + } + + // Play distorted/warning version of animal sounds + switch (direction) { + case 'up': + // Distressed bird sound + this.createOscillator(800, 'sawtooth', 0.3); + this.createOscillator(600, 'sawtooth', 0.3); + console.log('🦅⚠️ Playing bird danger sound placeholder'); + break; + case 'down': + // Angry mole sound + this.createOscillator(100, 'square', 0.4); + setTimeout(() => this.createOscillator(80, 'square', 0.3), 150); + console.log('🦔⚠️ Playing mole danger sound placeholder'); + break; + case 'left': + // Aggressive wolf sound + this.createOscillator(250, 'sawtooth', 0.5); + setTimeout(() => this.createOscillator(200, 'sawtooth', 0.4), 100); + console.log('🐺⚠️ Playing wolf danger sound placeholder'); + break; + case 'right': + // Rearing horse sound + this.createOscillator(300, 'triangle', 0.2); + setTimeout(() => this.createOscillator(400, 'triangle', 0.3), 100); + setTimeout(() => this.createOscillator(500, 'triangle', 0.2), 200); + console.log('🐎⚠️ Playing horse danger sound placeholder'); + break; + case 'invalid_animal': + // Generic error sound + this.createOscillator(150, 'sawtooth', 0.3); + setTimeout(() => this.createOscillator(120, 'sawtooth', 0.3), 200); + console.log('❌ Playing invalid animal sound placeholder'); + break; + default: + // Generic danger sound + this.createOscillator(200, 'sawtooth', 0.4); + } + } + + // Method to play actual audio files + async playAudioFile(audioPath) { + if (!this.audioContext || !this.initialized) { + throw new Error('Audio context not initialized'); + } + + return new Promise((resolve, reject) => { + const audio = new Audio(audioPath); + + audio.addEventListener('canplaythrough', () => { + audio.play() + .then(() => resolve()) + .catch(reject); + }); + + audio.addEventListener('error', reject); + + // Set volume + audio.volume = this.gainNode ? this.gainNode.gain.value : 0.3; + }); + } + stopAll() { this.activeSounds.forEach(sound => { try { @@ -255,4 +390,81 @@ export class AudioSystem { this.gainNode.gain.value = Math.max(0, Math.min(1, volume)); } } + + // General speech synthesis method + speak(text) { + if (!this.speechEnabled || !text) { + return; + } + + // Check if speech synthesis is supported + if (!('speechSynthesis' in window)) { + console.warn('Speech synthesis not supported'); + return; + } + + // Stop any current speech + speechSynthesis.cancel(); + + const utterance = new SpeechSynthesisUtterance(text); + utterance.rate = 0.9; + utterance.pitch = 1.0; + utterance.volume = 0.8; + + // Speak the text + speechSynthesis.speak(utterance); + } + + // Set speech enabled/disabled + setSpeechEnabled(enabled) { + this.speechEnabled = enabled; + + // If disabling speech, stop any current speech + if (!enabled && 'speechSynthesis' in window) { + speechSynthesis.cancel(); + } + } + + // Game over sound + async playGameOverSound() { + await this.ensureAudioContext(); + + // Create a descending game over sound + const notes = [330, 294, 262, 220]; // E, D, C, A (descending) + + notes.forEach((frequency, index) => { + setTimeout(() => { + this.createOscillator(frequency, 'sawtooth', 0.5); + }, index * 150); + }); + + // Add a final low note + setTimeout(() => { + this.createOscillator(147, 'square', 1.0); // Low D + }, 600); + } + + // Victory sound + async playWinSound() { + await this.ensureAudioContext(); + + // Create a victory melody + const notes = [262, 330, 392, 523]; // C, E, G, C (one octave higher) + + notes.forEach((frequency, index) => { + setTimeout(() => { + this.createOscillator(frequency, 'sine', 0.4); + }, index * 100); + }); + + // Add harmony + setTimeout(() => { + this.createOscillator(659, 'sine', 0.6); // E + }, 200); + } + + // Play directional proximity sound (uses the existing speakDirections method) + playDirectionalProximitySound(directions) { + this.speakDirections(directions); + } } diff --git a/src/game.js b/src/game.js index 21233e5..a17d55c 100644 --- a/src/game.js +++ b/src/game.js @@ -25,6 +25,7 @@ export class Game { this.soundEnabled = true; this.speechEnabled = true; this.vibrationEnabled = true; + this.debugMode = false; // Debug mode to show audio danger squares this.lastProximityWarning = 0; this.setupControls(); @@ -32,10 +33,37 @@ export class Game { } setupControls() { + const newGameBtn = document.getElementById('newGameBtn'); + const debugToggle = document.getElementById('debugToggle'); const soundToggle = document.getElementById('soundToggle'); const speechToggle = document.getElementById('speechToggle'); const vibrationToggle = document.getElementById('vibrationToggle'); + newGameBtn.addEventListener('click', () => { + this.newGame(); + }); + + debugToggle.addEventListener('click', () => { + this.debugMode = !this.debugMode; + debugToggle.textContent = `🐛 Debug: ${this.debugMode ? 'ON' : 'OFF'}`; + + // Show/hide debug info panel + const debugInfo = document.getElementById('debugInfo'); + debugInfo.style.display = this.debugMode ? 'block' : 'none'; + + // Update the maze to show/hide debug indicators + this.maze.setDebugMode(this.debugMode); + + // Update current animals info in debug panel + if (this.debugMode) { + this.updateDebugInfo(); + } + + if (this.speechEnabled) { + this.audioSystem.speak(this.debugMode ? 'Debug mode enabled. Audio danger squares are now visible.' : 'Debug mode disabled. Audio danger squares are hidden.'); + } + }); + soundToggle.addEventListener('click', () => { this.soundEnabled = !this.soundEnabled; soundToggle.textContent = `🔊 Sound: ${this.soundEnabled ? 'ON' : 'OFF'}`; @@ -57,12 +85,30 @@ export class Game { } bindEvents() { - this.inputHandler.onMove = (direction) => { - this.movePlayer(direction); + this.inputHandler.onMove = (animalMove) => { + this.movePlayer(animalMove); + }; + + this.inputHandler.onInvalidMove = (invalidAnimal) => { + this.handleInvalidAnimal(invalidAnimal); + }; + + this.inputHandler.onAnimalsChanged = (selectedAnimals) => { + this.handleAnimalsChanged(selectedAnimals); }; } - movePlayer(direction) { + handleAnimalsChanged(selectedAnimals) { + // Update audio system with new animal data + this.audioSystem.updateAnimalData(selectedAnimals); + + if (this.speechEnabled) { + this.audioSystem.speak('New animals selected! Check the updated directions.'); + } + } + + movePlayer(animalMove) { + const direction = { x: animalMove.x, y: animalMove.y }; const newX = this.player.x + direction.x; const newY = this.player.y + direction.y; @@ -70,6 +116,9 @@ export class Game { if (this.maze.canMoveTo(newX, newY)) { this.player.moveTo(newX, newY); + // Play animal sound for successful move (placeholder) + this.audioSystem.playAnimalSound(animalMove.sound); + // Check if player stepped on a visual danger square if (this.maze.isDangerousVisual(newX, newY)) { this.handleVisualDanger(); @@ -84,9 +133,39 @@ export class Game { if (this.maze.isExit(newX, newY)) { this.handleWin(); } + } else { + // Cannot move in that direction - play danger animal sound + this.handleBlockedMove(animalMove); } } + handleInvalidAnimal(invalidAnimal) { + // Play a danger sound for invalid animal name + this.audioSystem.playDangerSound('invalid_animal'); + + if (this.speechEnabled) { + this.audioSystem.speak(`Invalid animal: ${invalidAnimal}. Try bird, mole, wolf, or horse.`); + } + } + + handleBlockedMove(animalMove) { + // Play danger sound for the specific animal when blocked + this.audioSystem.playDangerSound(animalMove.sound); + + if (this.speechEnabled) { + const directionName = this.getDirectionName(animalMove); + this.audioSystem.speak(`Cannot go ${directionName}. Wall or boundary.`); + } + } + + getDirectionName(animalMove) { + if (animalMove.x === 0 && animalMove.y === -1) return 'up'; + if (animalMove.x === 0 && animalMove.y === 1) return 'down'; + if (animalMove.x === -1 && animalMove.y === 0) return 'left'; + if (animalMove.x === 1 && animalMove.y === 0) return 'right'; + return 'unknown'; + } + handleVisualDanger() { // Visual danger squares end the game this.gameOver(); @@ -182,9 +261,74 @@ export class Game { this.running = true; this.maze.generate(); this.lastTime = performance.now(); + + // Initialize the UI with current animals + this.updateAnimalDisplay(); + + // Initialize debug info if debug mode is on + if (this.debugMode) { + this.updateDebugInfo(); + } + requestAnimationFrame((time) => this.gameLoop(time)); } + // Restart game with new randomized animals + newGame() { + // Reset player position + this.player.moveTo(1, 1); + + // Generate new maze + this.maze.generate(); + + // Randomize animals + this.inputHandler.randomizeAnimals(); + + // Update display + this.updateAnimalDisplay(); + + if (this.speechEnabled) { + this.audioSystem.speak('New game started with different animals!'); + } + } + + // Update the animal display in the UI + updateAnimalDisplay() { + const displayInfo = this.inputHandler.getDisplayInfo(); + + // Update each direction display + const directions = ['up', 'down', 'left', 'right']; + directions.forEach(direction => { + const element = document.querySelector(`[data-direction="${direction}"]`); + if (element) { + const info = displayInfo[direction]; + element.innerHTML = `${info.emoji} ${info.names} → Move ${direction.toUpperCase()}`; + } + }); + + // Update debug info if debug mode is enabled + if (this.debugMode) { + this.updateDebugInfo(); + } + } + + // Update debug information panel + updateDebugInfo() { + const displayInfo = this.inputHandler.getDisplayInfo(); + const currentAnimalsElement = document.getElementById('currentAnimals'); + + if (currentAnimalsElement) { + const animalSummary = [ + `Up: ${displayInfo.up.emoji} ${displayInfo.up.primaryName}`, + `Down: ${displayInfo.down.emoji} ${displayInfo.down.primaryName}`, + `Left: ${displayInfo.left.emoji} ${displayInfo.left.primaryName}`, + `Right: ${displayInfo.right.emoji} ${displayInfo.right.primaryName}` + ].join(' | '); + + currentAnimalsElement.textContent = animalSummary; + } + } + stop() { this.running = false; this.audioSystem.stopAll(); diff --git a/src/input.js b/src/input.js index 9351b02..29bf93f 100644 --- a/src/input.js +++ b/src/input.js @@ -1,79 +1,143 @@ +import { generateRandomAnimalSet, createAnimalDirections, getDisplayInfo } from './animals.js'; + export class InputHandler { constructor() { - this.keys = new Set(); this.onMove = null; // Callback for movement - this.moveDelay = 150; // Milliseconds between moves - this.lastMoveTime = 0; + this.onInvalidMove = null; // Callback for invalid movement + this.onAnimalsChanged = null; // Callback when animals are randomized + this.currentInput = ''; + this.isListening = false; + + // Generate random animals for this game session + this.selectedAnimals = generateRandomAnimalSet(); + this.animalDirections = createAnimalDirections(this.selectedAnimals); + this.displayInfo = getDisplayInfo(this.selectedAnimals); this.bindEvents(); } bindEvents() { - document.addEventListener('keydown', (e) => this.handleKeyDown(e)); - document.addEventListener('keyup', (e) => this.handleKeyUp(e)); + // Create input field if it doesn't exist + this.createInputField(); - // Prevent default behavior for game keys - document.addEventListener('keydown', (e) => { - if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) { + // Listen for Enter key on the input field + this.inputField.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); + this.processAnimalInput(); + } + }); + + // Focus the input field + this.inputField.focus(); + } + + + createInputField() { + // Check if input field already exists + this.inputField = document.getElementById('animalInput'); + if (!this.inputField) { + // Create the input field and add it to the game container + this.inputField = document.createElement('input'); + this.inputField.id = 'animalInput'; + this.inputField.type = 'text'; + this.inputField.placeholder = 'Type an animal name to move...'; + this.inputField.className = 'animal-input'; + + // Add to the controls section + const controls = document.querySelector('.controls'); + if (controls) { + controls.appendChild(this.inputField); + } + } + } + + processAnimalInput() { + const input = this.inputField.value.toLowerCase().trim(); + this.inputField.value = ''; // Clear input + + if (input === '') return; + + // Check if the input matches any animal + const animalMove = this.animalDirections[input]; + + if (animalMove && this.onMove) { + // Valid animal - trigger movement + this.onMove(animalMove); + } else if (this.onInvalidMove) { + // Invalid animal - trigger danger sound + this.onInvalidMove(input); + } + } + + getDirectionEmojis() { + return { + up: '🦅', // Bird for up + down: '🦔', // Mole for down + left: '🐺', // Wolf for left + right: '🐎' // Horse for right + }; + } + + getValidAnimals() { + return Object.keys(this.animalDirections); + } + + // Randomize animals for a new game + randomizeAnimals() { + this.selectedAnimals = generateRandomAnimalSet(); + this.animalDirections = createAnimalDirections(this.selectedAnimals); + this.displayInfo = getDisplayInfo(this.selectedAnimals); + + // Update the UI display + this.updateAnimalDisplay(); + + // Notify game that animals have changed + if (this.onAnimalsChanged) { + this.onAnimalsChanged(this.selectedAnimals); + } + } + + // Update the animal direction display in the UI + updateAnimalDisplay() { + const directions = ['up', 'down', 'left', 'right']; + + directions.forEach(direction => { + const element = document.querySelector(`[data-direction="${direction}"]`); + if (element) { + const info = this.displayInfo[direction]; + element.innerHTML = `${info.emoji} ${info.names} → Move ${direction.toUpperCase()}`; } }); } - handleKeyDown(e) { - this.keys.add(e.code); - this.processMovement(); + // Get current selected animals info for display + getSelectedAnimals() { + return this.selectedAnimals; } - handleKeyUp(e) { - this.keys.delete(e.code); + // Get display info for UI + getDisplayInfo() { + return this.displayInfo; } - - processMovement() { - const now = Date.now(); - if (now - this.lastMoveTime < this.moveDelay) { - return; // Too soon to move again - } - - let direction = null; - - // Check for movement keys (WASD or Arrow keys) - if (this.keys.has('KeyW') || this.keys.has('ArrowUp')) { - direction = { x: 0, y: -1 }; - } else if (this.keys.has('KeyS') || this.keys.has('ArrowDown')) { - direction = { x: 0, y: 1 }; - } else if (this.keys.has('KeyA') || this.keys.has('ArrowLeft')) { - direction = { x: -1, y: 0 }; - } else if (this.keys.has('KeyD') || this.keys.has('ArrowRight')) { - direction = { x: 1, y: 0 }; - } - - if (direction && this.onMove) { - this.onMove(direction); - this.lastMoveTime = now; - } - } - - // Method to handle continuous key holding + + // Method to handle continuous key holding - no longer needed update() { - if (this.keys.size > 0) { - this.processMovement(); - } + // No longer needed for animal input system } - - // Get current input state + + // Get current input state - simplified for animal system getInputState() { return { - up: this.keys.has('KeyW') || this.keys.has('ArrowUp'), - down: this.keys.has('KeyS') || this.keys.has('ArrowDown'), - left: this.keys.has('KeyA') || this.keys.has('ArrowLeft'), - right: this.keys.has('KeyD') || this.keys.has('ArrowRight'), + currentInput: this.inputField ? this.inputField.value : '', + isActive: this.inputField === document.activeElement }; } - + // Clean up event listeners destroy() { - document.removeEventListener('keydown', this.handleKeyDown); - document.removeEventListener('keyup', this.handleKeyUp); + if (this.inputField) { + this.inputField.remove(); + } } } diff --git a/src/maze.js b/src/maze.js index 7e961e3..1e16490 100644 --- a/src/maze.js +++ b/src/maze.js @@ -6,6 +6,7 @@ export class Maze { this.dangerousSquares = new Set(); this.exitX = cols - 2; this.exitY = rows - 2; + this.debugMode = false; // Debug mode flag // Cell types this.WALL = 0; @@ -15,6 +16,11 @@ export class Maze { this.EXIT = 4; } + // Set debug mode to show/hide audio danger squares + setDebugMode(enabled) { + this.debugMode = enabled; + } + generate() { // Initialize grid with safe squares this.grid = Array(this.rows).fill().map(() => Array(this.cols).fill(this.SAFE)); @@ -291,8 +297,7 @@ export class Maze { break; case this.SAFE: - case this.DANGEROUS_AUDIO: - // Safe squares and audio-danger squares look identical (white) + // Regular safe squares (white) ctx.fillStyle = '#ffffff'; ctx.fillRect(pixelX, pixelY, cellSize, cellSize); // Add subtle grid lines @@ -300,8 +305,50 @@ export class Maze { ctx.lineWidth = 1; ctx.strokeRect(pixelX, pixelY, cellSize, cellSize); break; - //debug - // case this.DANGEROUS_AUDIO: + + case this.DANGEROUS_AUDIO: + if (this.debugMode) { + // In debug mode: show audio-danger squares with blue/yellow pattern + const debugTime = Date.now() * 0.004; + const debugIntensity = (Math.sin(debugTime + x * 2 + y * 2) + 1) * 0.5; + const blue = Math.floor(100 + debugIntensity * 155); + const yellow = Math.floor(150 + debugIntensity * 105); + + // Alternating blue/yellow background + ctx.fillStyle = `rgb(50, 50, ${blue})`; + ctx.fillRect(pixelX, pixelY, cellSize, cellSize); + + // Add warning pattern + ctx.fillStyle = `rgba(${yellow}, ${yellow}, 0, ${0.4 + debugIntensity * 0.3})`; + ctx.fillRect(pixelX + 3, pixelY + 3, cellSize - 6, cellSize - 6); + + // Add audio symbol + ctx.fillStyle = '#ffffff'; + ctx.font = `${cellSize * 0.4}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('🔊', pixelX + cellSize / 2, pixelY + cellSize / 2); + + // Add debug border + ctx.strokeStyle = '#4444ff'; + ctx.lineWidth = 2; + ctx.strokeRect(pixelX, pixelY, cellSize, cellSize); + + // Add small text indicator + ctx.fillStyle = '#000000'; + ctx.font = `${cellSize * 0.2}px Arial`; + ctx.textAlign = 'center'; + ctx.fillText('AUDIO', pixelX + cellSize / 2, pixelY + cellSize * 0.8); + } else { + // Normal mode: looks like safe squares (white) + ctx.fillStyle = '#ffffff'; + ctx.fillRect(pixelX, pixelY, cellSize, cellSize); + // Add subtle grid lines + ctx.strokeStyle = '#e0e0e0'; + ctx.lineWidth = 1; + ctx.strokeRect(pixelX, pixelY, cellSize, cellSize); + } + break; case this.DANGEROUS_VISUAL: diff --git a/src/sounds/bee_danger.mp3 b/src/sounds/bee_danger.mp3 new file mode 100644 index 0000000..238317c Binary files /dev/null and b/src/sounds/bee_danger.mp3 differ diff --git a/src/style.css b/src/style.css index 58fc1ec..d8b7156 100644 --- a/src/style.css +++ b/src/style.css @@ -102,7 +102,9 @@ h1 { display: flex; gap: 1rem; justify-content: center; + align-items: center; margin-top: 1.5rem; + flex-wrap: wrap; } .controls button { @@ -119,6 +121,80 @@ h1 { border: 1px solid rgba(255, 255, 255, 0.2); } +.animal-input { + background: rgba(255, 255, 255, 0.1); + color: white; + border: 2px solid rgba(76, 175, 80, 0.5); + padding: 0.8rem 1.5rem; + border-radius: 25px; + font-size: 1rem; + min-width: 250px; + transition: all 0.3s ease; + outline: none; +} + +.animal-input:focus { + border-color: #4CAF50; + box-shadow: 0 0 20px rgba(76, 175, 80, 0.3); + background: rgba(255, 255, 255, 0.15); +} + +.animal-input::placeholder { + color: rgba(255, 255, 255, 0.6); +} + +.animal-directions { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 0.8rem; + margin: 1rem 0; + padding: 1rem; + background: rgba(76, 175, 80, 0.1); + border-radius: 10px; + border: 1px solid rgba(76, 175, 80, 0.3); +} + +.direction-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + font-size: 0.9rem; + transition: all 0.3s ease; +} + +.direction-item:hover { + background: rgba(76, 175, 80, 0.2); + transform: translateY(-1px); +} + +.debug-info { + margin: 1rem 0; + padding: 1rem; + background: rgba(68, 68, 255, 0.1); + border: 2px solid rgba(68, 68, 255, 0.3); + border-radius: 10px; + color: #ffffff; + text-align: left; +} + +.debug-info h3 { + margin-top: 0; + color: #4444ff; + font-size: 1.2rem; +} + +.debug-info p { + margin: 0.5rem 0; + font-size: 0.9rem; +} + +.debug-info strong { + color: #ffff44; +} + .controls button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4); @@ -151,6 +227,14 @@ h1 { .controls { flex-direction: column; - gap: 0.5rem; + gap: 0.8rem; + } + + .animal-input { + min-width: 200px; + } + + .animal-directions { + grid-template-columns: 1fr; } }