All checks were successful
Publish Docker image / Push Docker image to Docker Hub (push) Successful in 2m3s
375 lines
11 KiB
JavaScript
375 lines
11 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 }, callback) => {
|
|
try {
|
|
const result = gameManager.handleReconnect(socket.id, lobbyId, playerName);
|
|
|
|
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 {
|
|
const result = gameManager.submitVote(socket.id, songId);
|
|
|
|
if (result.error) {
|
|
if (callback) callback(result);
|
|
return;
|
|
}
|
|
|
|
// Notify all players about vote count
|
|
io.to(result.lobbyId).emit('vote_submitted', {
|
|
lobby: result.lobby
|
|
});
|
|
|
|
// If battle is finished, notify about new battle
|
|
if (result.lobby.currentBattle && result.lobby.currentBattle !== result.lobby.battles[result.lobby.battles.length - 1]) {
|
|
io.to(result.lobbyId).emit('new_battle', {
|
|
battle: result.lobby.currentBattle
|
|
});
|
|
}
|
|
|
|
// If game is finished, notify about the winner
|
|
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 submitting vote:', error);
|
|
if (callback) callback({ error: 'Failed to submit vote' });
|
|
}
|
|
});
|
|
|
|
// Handle 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);
|
|
}
|
|
});
|
|
});
|
|
|
|
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);
|
|
});
|
|
}); |