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!
+
+
🐛 Debug Information
+
🔊 Audio Danger Squares: Blue squares with speaker icon
+
⚠️ Note: These squares look normal during gameplay but trigger danger sounds when you get close!
+
🎯 Current Animals: Loading...
+
+
+
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;
}
}