wip: animals audio

This commit is contained in:
Krzysztof Rudnicki 2025-08-01 18:31:48 +02:00
parent fbd95ece5e
commit cef204a973
11 changed files with 860 additions and 62 deletions

View File

@ -11,13 +11,27 @@
<div class="game-container">
<h1>Danger Field Game</h1>
<div class="game-info">
<p>Use WASD or Arrow Keys to move</p>
<p>Type an animal name to move in that direction:</p>
<div class="animal-directions">
<div class="direction-item" data-direction="up">🦅 <strong>Bird, Eagle, Hawk</strong> → Move UP</div>
<div class="direction-item" data-direction="down">🦔 <strong>Mole, Hedgehog, Badger</strong> → Move DOWN</div>
<div class="direction-item" data-direction="left">🐺 <strong>Wolf, Dog, Fox</strong> → Move LEFT</div>
<div class="direction-item" data-direction="right">🐎 <strong>Horse, Stallion, Mare</strong> → Move RIGHT</div>
</div>
<p class="visual-warning">⚠️ Red squares are deadly - avoid them!</p>
<p class="audio-warning">🔊 Hidden dangers end the game - listen for directions!</p>
<p class="audio-hint">🗣️ Speech directions: "left", "right", "up", "down"</p>
<p class="audio-hint">🗣️ Animal sounds indicate movement success or danger!</p>
</div>
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div id="debugInfo" class="debug-info" style="display: none;">
<h3>🐛 Debug Information</h3>
<p><strong>🔊 Audio Danger Squares:</strong> Blue squares with speaker icon</p>
<p><strong>⚠️ Note:</strong> These squares look normal during gameplay but trigger danger sounds when you get close!</p>
<p><strong>🎯 Current Animals:</strong> <span id="currentAnimals">Loading...</span></p>
</div>
<div class="controls">
<button id="newGameBtn">🎲 New Game</button>
<button id="debugToggle">🐛 Debug: OFF</button>
<button id="soundToggle">🔊 Sound: ON</button>
<button id="speechToggle">🗣️ Speech: ON</button>
<button id="vibrationToggle">📳 Vibration: ON</button>

52
public/sounds/README.md Normal file
View File

@ -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.

Binary file not shown.

BIN
sounds/bee_danger.mp3 Normal file

Binary file not shown.

181
src/animals.js Normal file
View File

@ -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]
}
};
}

View File

@ -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);
}
}

View File

@ -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} <strong>${info.names}</strong> → 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();

View File

@ -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} <strong>${info.names}</strong> → 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();
}
}
}

View File

@ -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:

BIN
src/sounds/bee_danger.mp3 Normal file

Binary file not shown.

View File

@ -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;
}
}