Files
battle-royal-tetris/public/js/ui.js
T
jozamudi a0ab4ff5cd Add spectator mode for late-joining players
- Server: Late joiners are added as spectators instead of players
- Server: Send forced-spectator event only to joining spectator (not broadcast)
- Server: Track spectators separately and move them to players after game ends
- Client: Handle forced-spectator event to show all player boards
- Client: Spectators see all boards equally without main/spectator highlighting
- Client: Mobile view shows scrollable vertical list of all boards for spectators
- Fix: All cleared lines are sent as garbage to each opponent (not randomized)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 18:26:48 +00:00

156 lines
4.6 KiB
JavaScript

// UI Module - Handle screens and user interactions
class UIManager {
constructor() {
this.screens = {
room: document.getElementById('room-screen'),
lobby: document.getElementById('lobby-screen'),
game: document.getElementById('game-screen'),
gameover: document.getElementById('gameover-screen')
};
this.inputs = {
playerName: document.getElementById('player-name')
};
this.buttons = {
join: document.getElementById('join-btn'),
ready: document.getElementById('ready-btn'),
leave: document.getElementById('leave-btn'),
backToLobby: document.getElementById('back-to-lobby')
};
this.displays = {
gameRoomName: document.getElementById('game-room-name'),
playerList: document.getElementById('player-list'),
spectatorList: document.getElementById('spectator-list'),
battleGrid: document.getElementById('battle-grid'),
gameStatus: document.getElementById('game-status'),
winnerDisplay: document.getElementById('winner-display'),
finalScores: document.getElementById('final-scores')
};
this.bindEvents();
}
bindEvents() {
this.buttons.join.addEventListener('click', () => this.handleJoin());
this.buttons.ready.addEventListener('click', () => this.handleReady());
this.buttons.leave.addEventListener('click', () => this.handleLeave());
this.buttons.backToLobby.addEventListener('click', () => this.handleBackToLobby());
// Allow Enter key to submit forms
this.inputs.playerName.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.handleJoin();
});
}
showScreen(screenName) {
Object.values(this.screens).forEach(screen => screen.classList.remove('active'));
this.screens[screenName].classList.add('active');
}
handleJoin() {
const playerName = this.inputs.playerName.value.trim();
if (!playerName) {
this.showMessage('Please enter your name');
return;
}
network.joinLobby(playerName);
}
handleReady() {
network.ready();
this.buttons.ready.textContent = 'READY!';
this.buttons.ready.disabled = true;
}
handleLeave() {
network.leaveLobby();
this.showScreen('room');
}
handleBackToLobby() {
this.hideGameOver();
this.showScreen('room');
}
updatePlayerList(players) {
this.displays.playerList.innerHTML = '';
Object.values(players).forEach(player => {
const item = document.createElement('div');
item.className = 'player-item';
const statusClass = player.ready ? 'ready' : '';
item.innerHTML = `
<span class="name">${this.escapeHtml(player.name)}</span>
<span class="status ${statusClass}">${player.ready ? 'READY' : 'WAITING'}</span>
`;
this.displays.playerList.appendChild(item);
});
}
updateSpectatorList(spectators) {
this.displays.spectatorList.innerHTML = '';
Object.values(spectators).forEach(spectator => {
const item = document.createElement('div');
item.className = 'player-item spectator';
item.innerHTML = `
<span class="name">${this.escapeHtml(spectator.name)}</span>
<span class="status">WATCHING</span>
`;
this.displays.spectatorList.appendChild(item);
});
}
showSpectatorMode() {
// Update UI to indicate spectator mode
this.displays.gameRoomName.textContent = 'SPECTATOR MODE - Waiting for next game';
this.displays.gameRoomName.classList.add('spectator-mode');
}
hideSpectatorMode() {
this.displays.gameRoomName.classList.remove('spectator-mode');
}
showMessage(message) {
this.displays.gameStatus.textContent = message;
setTimeout(() => {
this.displays.gameStatus.textContent = '';
}, 3000);
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
showGameOver(winner, scores) {
this.displays.winnerDisplay.textContent = winner
? `Winner: ${winner}!`
: 'Game Over!';
this.displays.winnerDisplay.style.color = winner ? '#0f0' : '#fff';
this.displays.finalScores.innerHTML = '';
Object.entries(scores).forEach(([name, score], index) => {
const item = document.createElement('div');
item.className = 'score-item' + (index === 0 ? ' winner' : '');
item.innerHTML = `
<span>${this.escapeHtml(name)}</span>
<span>${score}</span>
`;
this.displays.finalScores.appendChild(item);
});
this.screens.gameover.classList.add('active');
}
hideGameOver() {
this.screens.gameover.classList.remove('active');
}
}
window.ui = new UIManager();