mirror of
https://github.com/kuhyx/slavic_game_jam.git
synced 2026-07-04 15:23:13 +02:00
wip: animals audio
This commit is contained in:
parent
fbd95ece5e
commit
cef204a973
18
index.html
18
index.html
@ -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
52
public/sounds/README.md
Normal 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.
|
||||
BIN
public/sounds/bee_danger.mp3
Normal file
BIN
public/sounds/bee_danger.mp3
Normal file
Binary file not shown.
BIN
sounds/bee_danger.mp3
Normal file
BIN
sounds/bee_danger.mp3
Normal file
Binary file not shown.
181
src/animals.js
Normal file
181
src/animals.js
Normal 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]
|
||||
}
|
||||
};
|
||||
}
|
||||
212
src/audio.js
212
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);
|
||||
}
|
||||
}
|
||||
|
||||
150
src/game.js
150
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} <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();
|
||||
|
||||
168
src/input.js
168
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} <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
55
src/maze.js
55
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:
|
||||
|
||||
BIN
src/sounds/bee_danger.mp3
Normal file
BIN
src/sounds/bee_danger.mp3
Normal file
Binary file not shown.
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user