Start of the arcade page
This commit is contained in:
parent
1a61dec9cf
commit
a3bcdd4d08
9 changed files with 1471 additions and 0 deletions
348
assets/js/pages/games/arcade.js
Normal file
348
assets/js/pages/games/arcade.js
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/**
|
||||
* 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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue