Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 6m59s
413 lines
13 KiB
JavaScript
413 lines
13 KiB
JavaScript
const express = require("express");
|
|
const { Server } = require("socket.io");
|
|
const http = require("http");
|
|
const app = express();
|
|
const path = require("path");
|
|
const GameManager = require("./game");
|
|
|
|
app.use(express.static(path.join(__dirname, '../client/dist')));
|
|
app.disable("x-powered-by");
|
|
|
|
app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../client/dist', 'index.html')));
|
|
|
|
const server = http.createServer(app);
|
|
|
|
const io = new Server(server, {
|
|
cors: {origin: "*"},
|
|
pingTimeout: 30000,
|
|
pingInterval: 10000
|
|
});
|
|
|
|
// Initialize game manager
|
|
const gameManager = new GameManager();
|
|
|
|
// Socket.IO event handlers
|
|
io.on('connection', (socket) => {
|
|
console.log(`User connected: ${socket.id}`);
|
|
|
|
// Create a new game lobby
|
|
socket.on('create_lobby', ({ playerName }, callback) => {
|
|
try {
|
|
const result = gameManager.createLobby(socket.id, playerName);
|
|
|
|
// Join the socket room for this lobby
|
|
socket.join(result.lobbyId);
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
|
|
console.log(`Lobby created: ${result.lobbyId} by ${playerName}`);
|
|
} catch (error) {
|
|
console.error('Error creating lobby:', error);
|
|
if (callback) callback({ error: 'Failed to create lobby' });
|
|
}
|
|
});
|
|
|
|
// Join an existing lobby
|
|
socket.on('join_lobby', ({ lobbyId, playerName }, callback) => {
|
|
try {
|
|
const result = gameManager.joinLobby(socket.id, playerName, lobbyId);
|
|
|
|
if (result.error) {
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Join the socket room for this lobby
|
|
socket.join(lobbyId);
|
|
|
|
// Notify all players in the lobby
|
|
socket.to(lobbyId).emit('player_joined', {
|
|
playerId: socket.id,
|
|
playerName
|
|
});
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
|
|
console.log(`Player ${playerName} joined lobby ${lobbyId}`);
|
|
} catch (error) {
|
|
console.error('Error joining lobby:', error);
|
|
if (callback) callback({ error: 'Failed to join lobby' });
|
|
}
|
|
});
|
|
|
|
// Attempt to reconnect to a lobby
|
|
socket.on('reconnect_to_lobby', ({ lobbyId, playerName, lastKnownState }, callback) => {
|
|
try {
|
|
const result = gameManager.handleReconnect(socket.id, lobbyId, playerName, lastKnownState);
|
|
|
|
if (result.error) {
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Join the socket room for this lobby
|
|
socket.join(lobbyId);
|
|
|
|
// Notify all players in the lobby
|
|
socket.to(lobbyId).emit('player_reconnected', {
|
|
playerId: socket.id,
|
|
playerName
|
|
});
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
|
|
console.log(`Player ${playerName} reconnected to lobby ${lobbyId}`);
|
|
} catch (error) {
|
|
console.error('Error reconnecting to lobby:', error);
|
|
if (callback) callback({ error: 'Failed to reconnect to lobby' });
|
|
}
|
|
});
|
|
|
|
// Update lobby settings
|
|
socket.on('update_settings', ({ settings }, callback) => {
|
|
try {
|
|
const result = gameManager.updateSettings(socket.id, settings);
|
|
|
|
if (result.error) {
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Notify all players in the lobby
|
|
io.to(result.lobbyId).emit('settings_updated', {
|
|
settings: result.lobby.settings
|
|
});
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
} catch (error) {
|
|
console.error('Error updating settings:', error);
|
|
if (callback) callback({ error: 'Failed to update settings' });
|
|
}
|
|
});
|
|
|
|
// Start the game from lobby
|
|
socket.on('start_game', (_, callback) => {
|
|
try {
|
|
const result = gameManager.startGame(socket.id);
|
|
|
|
if (result.error) {
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Notify all players in the lobby
|
|
io.to(result.lobbyId).emit('game_started', {
|
|
state: result.lobby.state
|
|
});
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
|
|
console.log(`Game started in lobby ${result.lobbyId}`);
|
|
} catch (error) {
|
|
console.error('Error starting game:', error);
|
|
if (callback) callback({ error: 'Failed to start game' });
|
|
}
|
|
});
|
|
|
|
// Add a song
|
|
socket.on('add_song', async ({ song }, callback) => {
|
|
try {
|
|
console.log(`[DEBUG] Attempting to add song: ${JSON.stringify(song)}`);
|
|
// Fix: Properly await the result of the async addSong function
|
|
const result = await gameManager.addSong(socket.id, song);
|
|
|
|
if (result.error) {
|
|
console.log(`[DEBUG] Error adding song: ${result.error}`);
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Add better error handling to prevent crash if lobby is not defined
|
|
if (!result.lobby || !result.lobbyId) {
|
|
console.log(`[DEBUG] Warning: Song added but lobby information is missing`);
|
|
if (callback) callback({ error: 'Failed to associate song with lobby' });
|
|
return;
|
|
}
|
|
|
|
console.log(`[DEBUG] Song added successfully, notifying lobby ${result.lobbyId}`);
|
|
console.log(`[DEBUG] Lobby songs: ${JSON.stringify(result.lobby.songs.map(s => s.title))}`);
|
|
|
|
// Notify all players in the lobby about updated song count
|
|
io.to(result.lobbyId).emit('songs_updated', {
|
|
lobby: result.lobby
|
|
});
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
} catch (error) {
|
|
console.error('Error adding song:', error);
|
|
if (callback) callback({ error: 'Failed to add song' });
|
|
}
|
|
});
|
|
|
|
// Remove a song
|
|
socket.on('remove_song', ({ songId }, callback) => {
|
|
try {
|
|
const result = gameManager.removeSong(socket.id, songId);
|
|
|
|
if (result.error) {
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Notify all players in the lobby
|
|
io.to(result.lobbyId).emit('songs_updated', {
|
|
lobby: result.lobby
|
|
});
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
} catch (error) {
|
|
console.error('Error removing song:', error);
|
|
if (callback) callback({ error: 'Failed to remove song' });
|
|
}
|
|
});
|
|
|
|
// Search YouTube for songs
|
|
socket.on('search_youtube', async ({ query }, callback) => {
|
|
try {
|
|
if (!query || typeof query !== 'string') {
|
|
if (callback) callback({ error: 'Invalid search query' });
|
|
return;
|
|
}
|
|
|
|
const results = await gameManager.searchYouTube(query);
|
|
|
|
// Send response to client
|
|
if (callback) callback({ results });
|
|
} catch (error) {
|
|
console.error('Error searching YouTube:', error);
|
|
if (callback) callback({ error: 'Failed to search YouTube' });
|
|
}
|
|
});
|
|
|
|
// Get metadata for a single YouTube video
|
|
socket.on('get_video_metadata', async ({ videoId }, callback) => {
|
|
try {
|
|
if (!videoId || typeof videoId !== 'string') {
|
|
if (callback) callback({ error: 'Invalid video ID' });
|
|
return;
|
|
}
|
|
|
|
const metadata = await gameManager.getYouTubeVideoMetadata(videoId);
|
|
|
|
// Send response to client
|
|
if (callback) callback({ metadata });
|
|
} catch (error) {
|
|
console.error('Error getting YouTube metadata:', error);
|
|
if (callback) callback({ error: 'Failed to get YouTube metadata' });
|
|
}
|
|
});
|
|
|
|
// Mark player as ready
|
|
socket.on('player_ready', (_, callback) => {
|
|
try {
|
|
const result = gameManager.setPlayerReady(socket.id);
|
|
|
|
if (result.error) {
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Notify all players in the lobby
|
|
io.to(result.lobbyId).emit('player_status_changed', {
|
|
lobby: result.lobby
|
|
});
|
|
|
|
// If the game state just changed to VOTING, notify about the first battle
|
|
if (result.lobby.state === 'VOTING' && result.lobby.currentBattle) {
|
|
console.log('Sending new_battle event to clients');
|
|
io.to(result.lobbyId).emit('new_battle', {
|
|
battle: result.lobby.currentBattle
|
|
});
|
|
}
|
|
|
|
// If tournament just started, explicitly notify about the state change and first battle
|
|
if (result.tournamentStarted && result.lobby.currentBattle) {
|
|
console.log('Tournament started, sending battle data');
|
|
// First send state change
|
|
io.to(result.lobbyId).emit('tournament_started', {
|
|
state: 'VOTING'
|
|
});
|
|
|
|
// Then send battle data with a slight delay to ensure clients process in the right order
|
|
setTimeout(() => {
|
|
io.to(result.lobbyId).emit('new_battle', {
|
|
battle: result.lobby.currentBattle
|
|
});
|
|
}, 300);
|
|
}
|
|
|
|
// If game finished (edge case where only one song was submitted)
|
|
if (result.lobby.state === 'FINISHED') {
|
|
io.to(result.lobbyId).emit('game_finished', {
|
|
winner: result.lobby.finalWinner,
|
|
battles: result.lobby.battles
|
|
});
|
|
}
|
|
|
|
// Send response to client
|
|
if (callback) callback(result);
|
|
} catch (error) {
|
|
console.error('Error setting player ready:', error);
|
|
if (callback) callback({ error: 'Failed to set player status' });
|
|
}
|
|
});
|
|
|
|
// Submit a vote in a battle
|
|
socket.on('submit_vote', ({ songId }, callback) => {
|
|
try {
|
|
console.log(`Player ${socket.id} voting for song ${songId}`);
|
|
const result = gameManager.submitVote(socket.id, songId);
|
|
|
|
if (result.error) {
|
|
if (callback) callback({ error: result.error });
|
|
} else {
|
|
// Broadcast updated game state to all players in the lobby
|
|
socket.to(result.lobbyId).emit('vote_submitted', result);
|
|
|
|
// Check if we've entered the BATTLE state (all votes are in and battle has ended)
|
|
const lobby = result.lobby;
|
|
if (lobby.state === 'BATTLE') {
|
|
// Broadcast battle result to all players in the lobby
|
|
io.to(result.lobbyId).emit('battle_ended', {
|
|
previousBattle: lobby.previousBattle,
|
|
state: 'BATTLE'
|
|
});
|
|
}
|
|
// Check if the game has finished (edge case - last battle with automatic winner)
|
|
else if (lobby.state === 'FINISHED') {
|
|
console.log('Game finished after vote submission');
|
|
io.to(result.lobbyId).emit('game_finished', {
|
|
winner: lobby.finalWinner,
|
|
battles: lobby.battles
|
|
});
|
|
}
|
|
|
|
if (callback) callback(result);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error submitting vote:', error);
|
|
if (callback) callback({ error: 'Server error while submitting vote' });
|
|
}
|
|
});
|
|
|
|
// Proceed to next battle after the battle result screen
|
|
socket.on('proceed_to_next_battle', (data, callback) => {
|
|
try {
|
|
const result = gameManager.proceedToNextBattle(socket.id);
|
|
|
|
if (result.error) {
|
|
if (callback) callback({ error: result.error });
|
|
} else {
|
|
// Check if the game has finished
|
|
if (result.lobby.state === 'FINISHED') {
|
|
console.log('Game finished, broadcasting final winner');
|
|
io.to(result.lobbyId).emit('game_finished', {
|
|
winner: result.lobby.finalWinner,
|
|
battles: result.lobby.battles
|
|
});
|
|
} else {
|
|
// Broadcast updated game state to all players in the lobby
|
|
io.to(result.lobbyId).emit('new_battle', {
|
|
battle: result.lobby.currentBattle,
|
|
state: result.lobby.state
|
|
});
|
|
}
|
|
|
|
if (callback) callback(result);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error proceeding to next battle:', error);
|
|
if (callback) callback({ error: 'Server error while proceeding to next battle' });
|
|
}
|
|
});
|
|
|
|
// Handle player disconnection
|
|
socket.on('disconnect', () => {
|
|
try {
|
|
const result = gameManager.handleDisconnect(socket.id);
|
|
|
|
if (result) {
|
|
// Notify remaining players about the disconnection
|
|
io.to(result.lobbyId).emit('player_disconnected', {
|
|
playerId: socket.id,
|
|
lobby: result.lobby
|
|
});
|
|
|
|
console.log(`Player ${socket.id} disconnected from lobby ${result.lobbyId}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error handling disconnect:', error);
|
|
}
|
|
});
|
|
|
|
// Simple ping handler to help with connection testing
|
|
socket.on('ping', (callback) => {
|
|
if (callback && typeof callback === 'function') {
|
|
callback({ success: true, timestamp: Date.now() });
|
|
}
|
|
});
|
|
});
|
|
|
|
server.on('error', (error) => {
|
|
console.error('Server error:', error);
|
|
});
|
|
|
|
const PORT = process.env.PORT || 5237;
|
|
server.listen(PORT, () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
console.log('Server shutting down...');
|
|
server.close(() => {
|
|
console.log('Server closed');
|
|
process.exit(0);
|
|
});
|
|
}); |