348 lines
8.2 KiB
JavaScript
348 lines
8.2 KiB
JavaScript
/**
|
|
* Arcade Games List - Interactive Controls
|
|
* Handles keyboard navigation, joystick visuals, button presses, and sound effects
|
|
*/
|
|
|
|
class ArcadeGamesController {
|
|
constructor() {
|
|
// DOM elements
|
|
this.gamesGrid = document.getElementById('games-grid');
|
|
this.joystick = document.getElementById('arcade-joystick');
|
|
this.selectButton = document.getElementById('button-select');
|
|
this.backButton = document.getElementById('button-back');
|
|
this.soundToggle = document.getElementById('sound-toggle');
|
|
this.insertCoinMessage = document.querySelector('.insert-coin-message');
|
|
|
|
// State
|
|
this.currentGameIndex = 0;
|
|
this.gameCards = [];
|
|
this.soundEnabled = true;
|
|
this.sounds = {};
|
|
|
|
// Initialize
|
|
if (this.gamesGrid) {
|
|
this.init();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the arcade controller
|
|
*/
|
|
init() {
|
|
this.gameCards = Array.from(this.gamesGrid.querySelectorAll('.game-card'));
|
|
|
|
// Select first game by default
|
|
if (this.gameCards.length > 0) {
|
|
this.selectGame(0);
|
|
} else {
|
|
this.showInsertCoin();
|
|
}
|
|
|
|
// Load sound effects
|
|
this.loadSounds();
|
|
|
|
// Set up event listeners
|
|
this.setupKeyboardControls();
|
|
this.setupMouseControls();
|
|
this.setupSoundToggle();
|
|
|
|
// Hide insert coin message after a moment
|
|
setTimeout(() => this.hideInsertCoin(), 2000);
|
|
}
|
|
|
|
/**
|
|
* Load sound effect files
|
|
*/
|
|
loadSounds() {
|
|
const soundFiles = {
|
|
move: '/audio/arcade-move.mp3',
|
|
select: '/audio/arcade-select.mp3',
|
|
coin: '/audio/arcade-coin.mp3',
|
|
back: '/audio/arcade-back.mp3'
|
|
};
|
|
|
|
Object.keys(soundFiles).forEach(key => {
|
|
this.sounds[key] = new Audio(soundFiles[key]);
|
|
this.sounds[key].volume = 0.3;
|
|
|
|
// Preload
|
|
this.sounds[key].load();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Play a sound effect
|
|
*/
|
|
playSound(soundName) {
|
|
if (!this.soundEnabled || !this.sounds[soundName]) return;
|
|
|
|
// Clone and play to allow overlapping sounds
|
|
const sound = this.sounds[soundName].cloneNode();
|
|
sound.volume = this.sounds[soundName].volume;
|
|
sound.play().catch(err => console.warn('Sound play failed:', err));
|
|
}
|
|
|
|
/**
|
|
* Set up keyboard event listeners
|
|
*/
|
|
setupKeyboardControls() {
|
|
document.addEventListener('keydown', (e) => {
|
|
// Prevent default for arrow keys and space
|
|
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' ', 'Enter'].includes(e.key)) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
switch (e.key) {
|
|
case 'ArrowUp':
|
|
this.moveUp();
|
|
this.updateJoystickVisual('up');
|
|
break;
|
|
case 'ArrowDown':
|
|
this.moveDown();
|
|
this.updateJoystickVisual('down');
|
|
break;
|
|
case 'ArrowLeft':
|
|
this.moveLeft();
|
|
this.updateJoystickVisual('left');
|
|
break;
|
|
case 'ArrowRight':
|
|
this.moveRight();
|
|
this.updateJoystickVisual('right');
|
|
break;
|
|
case 'Enter':
|
|
case ' ':
|
|
this.pressSelect();
|
|
this.updateButtonVisual('select', true);
|
|
break;
|
|
case 'Escape':
|
|
this.pressBack();
|
|
this.updateButtonVisual('back', true);
|
|
break;
|
|
}
|
|
});
|
|
|
|
// Reset joystick and button visuals on key up
|
|
document.addEventListener('keyup', (e) => {
|
|
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
|
this.updateJoystickVisual('neutral');
|
|
}
|
|
if (['Enter', ' '].includes(e.key)) {
|
|
this.updateButtonVisual('select', false);
|
|
}
|
|
if (e.key === 'Escape') {
|
|
this.updateButtonVisual('back', false);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set up mouse/touch event listeners
|
|
*/
|
|
setupMouseControls() {
|
|
// Game cards click
|
|
this.gameCards.forEach((card, index) => {
|
|
card.addEventListener('click', () => {
|
|
this.selectGame(index);
|
|
this.pressSelect();
|
|
});
|
|
|
|
card.addEventListener('mouseenter', () => {
|
|
this.selectGame(index);
|
|
});
|
|
});
|
|
|
|
// Buttons
|
|
this.selectButton.addEventListener('click', () => {
|
|
this.pressSelect();
|
|
});
|
|
|
|
this.backButton.addEventListener('click', () => {
|
|
this.pressBack();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set up sound toggle button
|
|
*/
|
|
setupSoundToggle() {
|
|
this.soundToggle.addEventListener('click', () => {
|
|
this.soundEnabled = !this.soundEnabled;
|
|
|
|
const soundOn = this.soundToggle.querySelector('.sound-on');
|
|
const soundOff = this.soundToggle.querySelector('.sound-off');
|
|
|
|
if (this.soundEnabled) {
|
|
soundOn.style.display = 'inline-block';
|
|
soundOff.style.display = 'none';
|
|
this.playSound('coin');
|
|
} else {
|
|
soundOn.style.display = 'none';
|
|
soundOff.style.display = 'inline-block';
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Navigate up in grid
|
|
*/
|
|
moveUp() {
|
|
// Calculate columns based on grid layout
|
|
const columns = this.calculateColumns();
|
|
const newIndex = this.currentGameIndex - columns;
|
|
|
|
if (newIndex >= 0) {
|
|
this.selectGame(newIndex);
|
|
this.playSound('move');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Navigate down in grid
|
|
*/
|
|
moveDown() {
|
|
const columns = this.calculateColumns();
|
|
const newIndex = this.currentGameIndex + columns;
|
|
|
|
if (newIndex < this.gameCards.length) {
|
|
this.selectGame(newIndex);
|
|
this.playSound('move');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Navigate left in grid
|
|
*/
|
|
moveLeft() {
|
|
if (this.currentGameIndex > 0) {
|
|
this.selectGame(this.currentGameIndex - 1);
|
|
this.playSound('move');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Navigate right in grid
|
|
*/
|
|
moveRight() {
|
|
if (this.currentGameIndex < this.gameCards.length - 1) {
|
|
this.selectGame(this.currentGameIndex + 1);
|
|
this.playSound('move');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate number of columns in grid
|
|
*/
|
|
calculateColumns() {
|
|
if (this.gameCards.length === 0) return 1;
|
|
|
|
const firstCard = this.gameCards[0];
|
|
const gridWidth = this.gamesGrid.offsetWidth;
|
|
const cardWidth = firstCard.offsetWidth;
|
|
const gap = 20; // From CSS
|
|
|
|
return Math.floor((gridWidth + gap) / (cardWidth + gap)) || 1;
|
|
}
|
|
|
|
/**
|
|
* Select a game card
|
|
*/
|
|
selectGame(index) {
|
|
// Remove previous selection
|
|
this.gameCards.forEach(card => card.classList.remove('selected'));
|
|
|
|
// Add new selection
|
|
this.currentGameIndex = index;
|
|
this.gameCards[index].classList.add('selected');
|
|
|
|
// Scroll into view if needed
|
|
this.gameCards[index].scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'nearest'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Press select button (navigate to game)
|
|
*/
|
|
pressSelect() {
|
|
this.playSound('select');
|
|
|
|
const selectedCard = this.gameCards[this.currentGameIndex];
|
|
const link = selectedCard.querySelector('a');
|
|
|
|
if (link) {
|
|
// Add visual feedback
|
|
selectedCard.style.transform = 'scale(0.95)';
|
|
setTimeout(() => {
|
|
selectedCard.style.transform = '';
|
|
window.location.href = link.href;
|
|
}, 150);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Press back button (go to main page or previous)
|
|
*/
|
|
pressBack() {
|
|
this.playSound('back');
|
|
|
|
// Navigate back in history or to home
|
|
if (window.history.length > 1) {
|
|
window.history.back();
|
|
} else {
|
|
window.location.href = '/';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update joystick visual state
|
|
*/
|
|
updateJoystickVisual(direction) {
|
|
this.joystick.classList.remove('up', 'down', 'left', 'right');
|
|
|
|
if (direction !== 'neutral') {
|
|
this.joystick.classList.add(direction);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update button visual state
|
|
*/
|
|
updateButtonVisual(button, pressed) {
|
|
const buttonElement = button === 'select' ? this.selectButton : this.backButton;
|
|
|
|
if (pressed) {
|
|
buttonElement.classList.add('pressed');
|
|
} else {
|
|
buttonElement.classList.remove('pressed');
|
|
|
|
// Slight delay to show button release
|
|
setTimeout(() => {
|
|
buttonElement.classList.remove('pressed');
|
|
}, 100);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show insert coin message
|
|
*/
|
|
showInsertCoin() {
|
|
this.insertCoinMessage.classList.add('visible');
|
|
}
|
|
|
|
/**
|
|
* Hide insert coin message
|
|
*/
|
|
hideInsertCoin() {
|
|
this.insertCoinMessage.classList.remove('visible');
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new ArcadeGamesController();
|
|
});
|
|
} else {
|
|
new ArcadeGamesController();
|
|
}
|