Initial commit: Tetris Battle Royale multiplayer game
Features: - 2-8 player multiplayer via Socket.io WebSocket - Real-time board synchronization - all players see all boards - Battle royale mechanic: clearing rows sends garbage to opponents - Classic Tetris gameplay with all 7 tetrominoes - Retro visual styling with CRT scanlines and pixel font - Automatic level progression and speed increase - Player elimination and winner announcement Files: - server/index.js: Node.js + Socket.io game server - public/js/: Frontend game logic, rendering, network, and UI - public/css/style.css: Retro Tetris styling - README.md: Setup and usage instructions - PLAN.md: Implementation plan with all phases completed
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
// Main Application - Ties everything together
|
||||
|
||||
let localGame = null;
|
||||
let renderer = null;
|
||||
let lastTime = 0;
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Connect to server
|
||||
network.connect();
|
||||
|
||||
// Initialize renderer
|
||||
renderer = new TetrisRenderer('battle-grid');
|
||||
|
||||
// Setup network listeners
|
||||
setupNetworkListeners();
|
||||
|
||||
// Setup keyboard controls
|
||||
setupKeyboardControls();
|
||||
});
|
||||
|
||||
function setupNetworkListeners() {
|
||||
// Player joined lobby
|
||||
network.setListener('player-joined', (player) => {
|
||||
ui.updatePlayerList(network.getAllPlayers());
|
||||
});
|
||||
|
||||
// Player left lobby
|
||||
network.setListener('player-left', (playerId) => {
|
||||
ui.updatePlayerList(network.getAllPlayers());
|
||||
});
|
||||
|
||||
// Game started
|
||||
network.setListener('game-started', (players, states) => {
|
||||
ui.showScreen('game');
|
||||
ui.displays.gameRoomName.textContent = network.currentRoom;
|
||||
|
||||
// Clear old boards
|
||||
renderer.clearAll();
|
||||
|
||||
// Create boards for all players
|
||||
states.forEach((state) => {
|
||||
const player = network.getPlayer(state.playerId);
|
||||
renderer.createPlayerBoard(state.playerId, player.name);
|
||||
|
||||
// Initialize local game for current player
|
||||
if (state.playerId === network.currentPlayerId) {
|
||||
localGame = new TetrisGame(state.playerId);
|
||||
localGame.loadState(state);
|
||||
}
|
||||
});
|
||||
|
||||
// Set up battle grid layout
|
||||
updateBattleGridLayout(players.length);
|
||||
|
||||
// Start game loop
|
||||
lastTime = performance.now();
|
||||
requestAnimationFrame(gameLoop);
|
||||
});
|
||||
|
||||
// State update during game
|
||||
network.setListener('state-update', (states) => {
|
||||
// Update local game if it's our state
|
||||
const localState = states.find(s => s.playerId === network.currentPlayerId);
|
||||
if (localState) {
|
||||
localGame.loadState(localState);
|
||||
}
|
||||
|
||||
// Check for game over
|
||||
const allStates = network.getAllGameStates();
|
||||
const activePlayers = Object.values(allStates).filter(s => !s.eliminated);
|
||||
|
||||
if (activePlayers.length <= 1) {
|
||||
endGame(allStates);
|
||||
}
|
||||
});
|
||||
|
||||
// Game over
|
||||
network.setListener('game-over', (data) => {
|
||||
endGame(data.states);
|
||||
});
|
||||
}
|
||||
|
||||
function setupKeyboardControls() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (ui.screens.game.classList.contains('active') && localGame) {
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
network.sendMove('left');
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
network.sendMove('right');
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
network.sendDrop();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
network.sendRotate();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case ' ':
|
||||
network.sendHardDrop();
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateBattleGridLayout(playerCount) {
|
||||
ui.displays.battleGrid.classList.remove('grid-2x2', 'grid-2x4');
|
||||
|
||||
if (playerCount <= 4) {
|
||||
ui.displays.battleGrid.classList.add('grid-2x2');
|
||||
} else {
|
||||
ui.displays.battleGrid.classList.add('grid-2x4');
|
||||
}
|
||||
}
|
||||
|
||||
function endGame(states) {
|
||||
// Find winner
|
||||
const activePlayers = Object.values(states).filter(s => !s.eliminated);
|
||||
const eliminatedPlayers = Object.values(states).filter(s => s.eliminated);
|
||||
|
||||
let winner = null;
|
||||
let scores = {};
|
||||
|
||||
if (activePlayers.length === 1) {
|
||||
const winnerState = activePlayers[0];
|
||||
const winnerPlayer = network.getPlayer(winnerState.playerId);
|
||||
winner = winnerPlayer.name;
|
||||
}
|
||||
|
||||
// Build scores list
|
||||
Object.values(states).forEach(state => {
|
||||
const player = network.getPlayer(state.playerId);
|
||||
if (player) {
|
||||
scores[player.name] = state.score;
|
||||
}
|
||||
});
|
||||
|
||||
ui.showGameOver(winner, scores);
|
||||
}
|
||||
|
||||
function gameLoop(currentTime) {
|
||||
if (!ui.screens.game.classList.contains('active')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaTime = currentTime - lastTime;
|
||||
lastTime = currentTime;
|
||||
|
||||
// Update local game
|
||||
if (localGame) {
|
||||
localGame.update(deltaTime);
|
||||
}
|
||||
|
||||
// Render all players
|
||||
const allStates = network.getAllGameStates();
|
||||
Object.values(allStates).forEach(state => {
|
||||
renderer.renderPlayer(state.playerId, state);
|
||||
});
|
||||
|
||||
// Set active player highlight
|
||||
renderer.setActivePlayer(network.currentPlayerId);
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
Reference in New Issue
Block a user