From eb2bdaa825a8303d30bcada94bbe8d994399ae30 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 26 May 2025 08:47:53 -0400 Subject: [PATCH] update block broadcasting and chat system --- html/index.html | 58 +++++++++------- html/js/socketHandlers.js | 47 ++++++++++++- src/backend/index.js | 135 ++++++++++++++++++++++++++++++-------- 3 files changed, 188 insertions(+), 52 deletions(-) diff --git a/html/index.html b/html/index.html index 31a521f..0dd4f2c 100644 --- a/html/index.html +++ b/html/index.html @@ -98,29 +98,41 @@ console.log("UI available:", typeof UI !== 'undefined'); console.log("InputHandler available:", typeof InputHandler !== 'undefined'); - // Initialize Game - if (typeof Game !== 'undefined') { - console.log("Initializing Game..."); - Game.init(); - } else { - console.error("Game object not found!"); - } - - // Initialize SocketHandlers (CRITICAL for receiving game_start events!) - if (typeof SocketHandlers !== 'undefined') { - console.log("Initializing SocketHandlers..."); - SocketHandlers.init(); - } else { - console.error("SocketHandlers object not found!"); - } - - // Initialize InputHandler - if (typeof InputHandler !== 'undefined') { - console.log("Initializing InputHandler..."); - InputHandler.init(); - } else { - console.error("InputHandler object not found!"); - } + // Small delay to ensure all modules are fully loaded + setTimeout(function() { + // Initialize Game first + if (typeof Game !== 'undefined') { + console.log("Initializing Game..."); + const gameInitResult = Game.init(); + console.log("Game initialization result:", gameInitResult); + + // Ensure welcome screen is shown after a brief delay + setTimeout(function() { + if (gameInitResult && typeof ScreenManager !== 'undefined' && ScreenManager.drawWelcomeScreen) { + console.log("Ensuring welcome screen is drawn..."); + ScreenManager.drawWelcomeScreen(); + } + }, 50); + } else { + console.error("Game object not found!"); + } + + // Initialize SocketHandlers (CRITICAL for receiving game_start events!) + if (typeof SocketHandlers !== 'undefined') { + console.log("Initializing SocketHandlers..."); + SocketHandlers.init(); + } else { + console.error("SocketHandlers object not found!"); + } + + // Initialize InputHandler + if (typeof InputHandler !== 'undefined') { + console.log("Initializing InputHandler..."); + InputHandler.init(); + } else { + console.error("InputHandler object not found!"); + } + }, 100); // 100ms delay to ensure everything is loaded }); diff --git a/html/js/socketHandlers.js b/html/js/socketHandlers.js index 4482f5f..650a2ec 100644 --- a/html/js/socketHandlers.js +++ b/html/js/socketHandlers.js @@ -25,6 +25,8 @@ const SocketHandlers = { socket.on('connect', this.onConnect); socket.on('welcome', this.onWelcome); socket.on('message', this.onMessage); + socket.on('status_update', this.onStatusUpdate); + socket.on('chat_broadcast', this.onChatBroadcast); // Game state handlers socket.on('waiting_status', this.onWaitingStatus); @@ -33,7 +35,7 @@ const SocketHandlers = { socket.on('game_over', this.onGameOver); socket.on('queue_cancelled', this.onQueueCancelled); - // Block height handler + // Block height handler (broadcast to all clients) socket.on('blockheight', this.onBlockHeight); }, @@ -59,6 +61,27 @@ const SocketHandlers = { UI.scrollChat(); }, + onStatusUpdate: function(data) { + // Handle player-specific status updates + console.log("📨 STATUS UPDATE received:", data); + + // Display status message with appropriate styling + const statusClass = data.type === 'error' ? 'error' : 'status'; + $('#messages').append($(`
  • `).text(data.message)); + UI.scrollChat(); + }, + + onChatBroadcast: function(data) { + // Handle chat messages broadcast to all players + console.log("💬 CHAT BROADCAST received:", data); + + const timestamp = new Date(data.timestamp || Date.now()).toLocaleTimeString(); + const chatMsg = `[${timestamp}] ${data.username || 'Anonymous'}: ${data.message}`; + + $('#messages').append($('
  • ').text(chatMsg)); + UI.scrollChat(); + }, + onWaitingStatus: function(data) { console.log("🕒 WAITING STATUS received:", data); @@ -181,6 +204,28 @@ const SocketHandlers = { if (Game) { Game._gameActive = false; } + + // Auto-return to title screen after 30 seconds + console.log("⏰ Setting 30-second timer to return to title screen..."); + setTimeout(() => { + console.log("⏰ 30 seconds elapsed - returning to title screen"); + $('#messages').append($('
  • ').text("Returning to title screen...")); + UI.scrollChat(); + + // Return to welcome screen + if (Game && Game._drawWelcomeScreen) { + Game._drawWelcomeScreen(); + } + + // Reset game state + if (typeof GameState !== 'undefined') { + GameState.reset(); + } + + // Focus chat for new commands + $('#chatInput').focus(); + UI.updateFocusIndicator(); + }, 30000); // 30 seconds } UI.scrollChat(); diff --git a/src/backend/index.js b/src/backend/index.js index 5191407..0b858c7 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -50,14 +50,14 @@ app.get('/', function(req, res) { if (DEBUG_MODE) { console.log("🐛 DEBUG MODE ENABLED - Simulating blocks every 30 seconds"); - // Initial debug block - io.emit('blockheight', debugBlockHeight); + // Initial debug block broadcast + broadcastBlockHeight(debugBlockHeight); - // Debug block height simulator + // Debug block height simulator - advances every 30 seconds setInterval(function() { debugBlockHeight++; console.log(`🐛 DEBUG: New simulated block: ${debugBlockHeight}`); - io.emit('blockheight', debugBlockHeight); + broadcastBlockHeight(debugBlockHeight); // Start games for waiting players startGamesForWaiting(debugBlockHeight); @@ -66,6 +66,13 @@ if (DEBUG_MODE) { checkGamesTimeout(debugBlockHeight); }, 30000); // Every 30 seconds + + // Regular status broadcasting - sends current block height every 5 seconds + // This ensures all clients stay up-to-date even if they miss a block change + setInterval(function() { + broadcastBlockHeight(debugBlockHeight); + }, 5000); // Every 5 seconds + } else { // Your original block check code setInterval(function() { @@ -107,7 +114,7 @@ function startGamesForWaiting(blockHeight) { if (currentUser) { // For queue entry, player gets 3 blocks starting from their entry block currentUser.blockRec = blockHeight; - console.log(`🕒 QUEUE ENTRY: Player starts on block ${currentUser.blockRec}, will die after block ${currentUser.blockRec + 2}`); + console.log(`🕒 QUEUE ENTRY: Player enters on block ${currentUser.blockRec}, will die when block ${currentUser.blockRec + 1} starts`); try { const game = currentUser.startGame(80, 40); @@ -138,11 +145,11 @@ function checkGamesTimeout(currentHeight) { activeGames.forEach((game, socketId) => { const user = getUserBySocket(socketId); - // Player has 3 blocks to complete the dungeon - // If they started on block 2, they must escape before block 5 is found - // Note: Auto-entry players start timing from next block for consistent behavior - if (user && user.blockRec && currentHeight > user.blockRec + 2) { - console.log(`💀 GAME TIMEOUT for player ${socketId}: started timing on block ${user.blockRec}, current block ${currentHeight} (died after block ${user.blockRec + 2})`); + // Players die the block after they enter + // If they entered on block 1, they die when block 2 starts + // If they entered on block 2, they die when block 3 starts + if (user && user.blockRec && currentHeight > user.blockRec) { + console.log(`💀 GAME TIMEOUT for player ${socketId}: entered on block ${user.blockRec}, died on block ${currentHeight}`); // Game has timed out - player didn't escape in time game.gameState = 'lost'; @@ -157,6 +164,52 @@ function checkGamesTimeout(currentHeight) { }); } +// ====== SOCKET EVENT BROADCASTING HELPERS ====== +// These functions abstract the broadcasting logic to support future features like spectating + +/** + * Send game state update to player and any spectators + * @param {string} playerSocketId - The socket ID of the player + * @param {object} gameState - The game state to broadcast + */ +function sendGameUpdate(playerSocketId, gameState) { + // Send to the player + io.to(playerSocketId).emit('game_update', gameState); + + // TODO: Future spectator support + // Get list of spectators for this game and broadcast to them too + // const spectators = getSpectatorsForPlayer(playerSocketId); + // spectators.forEach(spectatorId => { + // io.to(spectatorId).emit('spectator_update', { + // playerSocketId: playerSocketId, + // gameState: gameState + // }); + // }); +} + +/** + * Broadcast block height to all connected clients + * @param {number} blockHeight - Current block height + */ +function broadcastBlockHeight(blockHeight) { + console.log(`📡 Broadcasting block height ${blockHeight} to all clients`); + io.emit('blockheight', blockHeight); +} + +/** + * Send status update to a specific player + * @param {string} socketId - The socket ID of the player + * @param {string} type - Type of status (info, error, warning, etc.) + * @param {string} message - The status message + */ +function sendStatusUpdate(socketId, type, message) { + io.to(socketId).emit('status_update', { + type: type, + message: message, + timestamp: Date.now() + }); +} + // Socket.io handlers io.on('connection', function(socket) { console.log('A user connected'); @@ -175,23 +228,30 @@ io.on('connection', function(socket) { // Insert into visitors DB db.insertVisitor(socket.client.id, socket.handshake.address, 0); - // Send welcome message + // Send welcome message and immediate status io.to(socket.client.id).emit('welcome', socket.client.id); + + // Send current block height immediately to new connections + const currentBlock = DEBUG_MODE ? debugBlockHeight : lastBlockHeight; + console.log(`📈 Sending current block height ${currentBlock} to new connection ${socket.id}`); + io.to(socket.id).emit('blockheight', currentBlock); + + // Send connection status update (player-specific) + sendStatusUpdate(socket.id, 'connection', 'Connected to Wowngeon server'); socket.on('chat message', function(msg) { console.log('Message received:', msg); + // Handle game commands (player-specific responses) if (msg.toLowerCase() == 'hello') { - io.to(`${socket.client.id}`).emit('message', + // Send status update to this player only + sendStatusUpdate(socket.id, 'help', 'Welcome, to enter the dungeon type Enter, and you will be given a Wownero address, ' + 'which you must send between 10-100 WOW to enter. The more you send, the more you can win.'); + return; // Don't broadcast commands as chat } - // ====== GAME ENTRY HANDLERS ====== - // There are two ways to enter a game: - // 1. QUEUE ENTRY: Type "enter" and wait for next block (players get 3 blocks from entry block) - // 2. AUTO ENTRY: Type "enter" and start immediately in debug mode (players get 3 blocks from next block) - // Update the 'enter' command handler to start game immediately for testing + // Handle game entry commands if (msg.toLowerCase() == 'enter') { console.log(`Player ${socket.id} requested to enter the dungeon - STARTING IMMEDIATELY`); @@ -203,11 +263,11 @@ io.on('connection', function(socket) { // Start game immediately for faster testing try { - // For auto-entry in debug mode, give player 3 blocks starting from NEXT block - // This ensures consistent death timing regardless of entry method + // For auto-entry in debug mode, player enters on current block + // They will die when the next block starts const currentBlock = debugBlockHeight || lastBlockHeight || 1; - currentUser.blockRec = currentBlock + 1; // Start timing from NEXT block - console.log(`🕒 AUTO-ENTRY: Player starts on block ${currentUser.blockRec}, will die after block ${currentUser.blockRec + 2}`); + currentUser.blockRec = currentBlock; // Player enters on current block + console.log(`🕒 AUTO-ENTRY: Player enters on block ${currentUser.blockRec}, will die when block ${currentUser.blockRec + 1} starts`); const game = currentUser.startGame(80, 40); activeGames.set(socket.id, game); @@ -220,11 +280,12 @@ io.on('connection', function(socket) { console.log(`Game started immediately for player ${socket.id}`); } catch (error) { console.error(`Error creating game:`, error); - io.to(socket.id).emit('message', 'Error starting game: ' + error.message); + sendStatusUpdate(socket.id, 'error', 'Error starting game: ' + error.message); } } else { - io.to(socket.id).emit('message', 'Error: Could not start game. Please try again.'); + sendStatusUpdate(socket.id, 'error', 'Error: Could not start game. Please try again.'); } + return; // Don't broadcast commands as chat } // Add a handler for the 'cancel' command else if (msg.toLowerCase() == 'cancel') { @@ -233,16 +294,32 @@ io.on('connection', function(socket) { if (index !== -1) { WAITING_PLAYERS.splice(index, 1); - io.to(socket.id).emit('message', 'You have left the queue.'); + sendStatusUpdate(socket.id, 'info', 'You have left the queue.'); // Reset to welcome screen io.to(socket.id).emit('queue_cancelled'); } else { - io.to(socket.id).emit('message', 'You were not in the queue.'); + // Send status update to this player only + sendStatusUpdate(socket.id, 'error', 'You were not in the queue.'); } + return; // Don't broadcast commands as chat } + + // For all other messages (actual chat), broadcast to all clients else { - io.emit('message', msg); + console.log(`💬 Broadcasting chat message from ${socket.id}: "${msg}"`); + + // Get user info for chat display + const currentUser = getUserBySocket(socket.id); + const username = currentUser?.username || `User_${socket.id.substr(-4)}`; + + // Broadcast chat message to ALL clients + io.emit('chat_broadcast', { + username: username, + message: msg, + timestamp: Date.now(), + socketId: socket.id + }); } }); @@ -309,12 +386,14 @@ io.on('connection', function(socket) { // Log before sending to client for debugging console.log(`Sending game_update to ${socket.id} after player move. Player:`, updatedGameState.player, "Visible tiles keys:", Object.keys(updatedGameState.visibleTiles || {})); - io.to(socket.id).emit('game_update', updatedGameState); + // Send game update to player (current implementation) + // TODO: In future, also broadcast to spectators of this game + sendGameUpdate(socket.id, updatedGameState); } else { console.log(`Player move from ${socket.id} was invalid or resulted in no change.`); // Optionally, send an update even for invalid moves if you want to provide feedback // const currentGameState = game.getState(); - // io.to(socket.id).emit('game_update', currentGameState); + // sendGameUpdate(socket.id, currentGameState); } } else { console.error(`Invalid moveData received from ${socket.id}:`, moveData);