From e001f13d122756cb8e5beb346f79b00e437887e3 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sun, 27 Apr 2025 21:51:30 -0400 Subject: [PATCH] working game logic pretty much --- game.js | 122 +++--- html/index.html | 484 +++++------------------ html/js/game.js | 931 +++++++++++++++++++++++++++++---------------- html/js/options.js | 10 + index.js | 46 ++- 5 files changed, 803 insertions(+), 790 deletions(-) diff --git a/game.js b/game.js index 0a53a8f..a564041 100644 --- a/game.js +++ b/game.js @@ -152,7 +152,7 @@ class Game { this.moveMonster(); // Check if monster caught player - if (this.monster.x === this.player.x && this.monster.y === this.player.y) { + if (checkMonsterKill(this.player, this.monster)) { this.gameState = 'lost'; return { status: 'lost', reason: 'caught' }; } @@ -199,84 +199,100 @@ class Game { } } - // Replace getState() method with this relative-coordinates version + // Update the getState() method to include all entities with relative positions getState() { - // Only include visible information and translate to player-relative coordinates + // Calculate relative position of all entities from player's perspective const playerX = this.player.x; const playerY = this.player.y; - // Create relative FOV map + // Convert all visible tiles to relative coordinates const relativeVisibleTiles = {}; for (const y in this.visibleTiles) { for (const x in this.visibleTiles[y]) { const relX = parseInt(x) - playerX; const relY = parseInt(y) - playerY; - // Initialize row if needed + // Create row if needed if (!relativeVisibleTiles[relY]) { relativeVisibleTiles[relY] = {}; } - // Add relative position tile + // Store tile type at relative position relativeVisibleTiles[relY][relX] = this.visibleTiles[y][x]; } } - // Create relative entity positions only if visible in FOV - const entityInfo = {}; - - // Check if monster is visible - if (this.visibleTiles[this.monster.y] && - this.visibleTiles[this.monster.y][this.monster.x] !== undefined) { - entityInfo.monster = { - x: this.monster.x - playerX, - y: this.monster.y - playerY - }; - } - - // Check if entrance is visible - if (this.dungeon.entrance && - this.visibleTiles[this.dungeon.entrance[1]] && - this.visibleTiles[this.dungeon.entrance[1]][this.dungeon.entrance[0]] !== undefined) { - entityInfo.entrance = [ - this.dungeon.entrance[0] - playerX, - this.dungeon.entrance[1] - playerY - ]; - } - - // Check if exit is visible - if (this.dungeon.exit && - this.visibleTiles[this.dungeon.exit[1]] && - this.visibleTiles[this.dungeon.exit[1]][this.dungeon.exit[0]] !== undefined) { - entityInfo.exit = [ - this.dungeon.exit[0] - playerX, - this.dungeon.exit[1] - playerY - ]; - } - - // Check if treasure is visible - if (this.dungeon.treasure && - this.visibleTiles[this.dungeon.treasure[1]] && - this.visibleTiles[this.dungeon.treasure[1]][this.dungeon.treasure[0]] !== undefined) { - entityInfo.treasure = [ - this.dungeon.treasure[0] - playerX, - this.dungeon.treasure[1] - playerY - ]; - } - - return { + // Create the state object + const state = { gameState: this.gameState, player: { - // Player is always at relative (0,0) - x: 0, + x: 0, // Player is always at (0,0) in relative coordinates y: 0, hasKey: this.player.hasKey || false, hasTreasure: this.player.hasTreasure || false }, - visibleTiles: relativeVisibleTiles, - ...entityInfo // Only include visible entities + visibleTiles: relativeVisibleTiles }; + + // Add monster if in view + if (this.monster && this.visibleTiles[this.monster.y] && + this.visibleTiles[this.monster.y][this.monster.x] !== undefined) { + state.monster = { + x: this.monster.x - playerX, // Relative position + y: this.monster.y - playerY + }; + } + + // Add entrance if defined + if (this.dungeon && this.dungeon.entrance) { + // Use array format for consistency + state.entrance = [ + this.dungeon.entrance.x - playerX, + this.dungeon.entrance.y - playerY + ]; + } + + // Add exit if in view + if (this.dungeon && this.dungeon.exit && + this.visibleTiles[this.dungeon.exit.y] && + this.visibleTiles[this.dungeon.exit.y][this.dungeon.exit.x] !== undefined) { + state.exit = [ + this.dungeon.exit.x - playerX, + this.dungeon.exit.y - playerY + ]; + } + + // Add treasure if in view + if (this.dungeon && this.dungeon.treasure && + this.visibleTiles[this.dungeon.treasure.y] && + this.visibleTiles[this.dungeon.treasure.y][this.dungeon.treasure.x] !== undefined) { + state.treasure = [ + this.dungeon.treasure.x - playerX, + this.dungeon.treasure.y - playerY + ]; + } + + return state; } } +// Check if monster killed player +function checkMonsterKill(player, monster) { + // Check for direct overlap - monster on same tile as player + if (monster.x === player.x && monster.y === player.y) { + return true; // Monster killed player + } + + // Check for adjacent tiles (optional - if you want 1-tile proximity kills) + const dx = Math.abs(monster.x - player.x); + const dy = Math.abs(monster.y - player.y); + + // If monster is adjacent (one tile away in any direction) + if ((dx <= 1 && dy === 0) || (dx === 0 && dy <= 1)) { + return true; // Monster killed player + } + + return false; // Player is safe +} + module.exports = Game; \ No newline at end of file diff --git a/html/index.html b/html/index.html index 6137395..a32ff62 100644 --- a/html/index.html +++ b/html/index.html @@ -197,73 +197,36 @@ scrollChat(); }); - // Critical game events - // Replace the game_start handler with this improved version + // Add or update the game_start handler socket.on('game_start', function(data) { - console.log("๐ŸŽฎ Received game_start broadcast:", data); + console.log("๐ŸŽฎ Game start received with data:", data); + $('#messages').append($('
  • ').text("Starting game...")); - // Log all socketIds for debugging - console.log("Current socket ID:", socket.id); - console.log("Target socket ID:", data.targetSocket); - - // Check if this event is for us - const isForUs = !data.targetSocket || data.targetSocket === socket.id; - console.log("Is this event for us?", isForUs ? "YES" : "NO"); - - // Only process if directed at us or broadcast - if (!isForUs) { - console.log("Ignoring game_start message meant for another client"); - return; + // Stop waiting screen animation if it's running + if (typeof Game !== 'undefined') { + Game.stopWaitingScreen(); } - // Check for required data if (!data) { - console.error("โŒ Error: Received empty game data!"); $('#messages').append($('
  • ').text("Error: No game data received")); return; } - // Check if we have player data - if (!data.player || typeof data.player.x === 'undefined') { - console.error("โŒ Error: Invalid player data in game_start"); - $('#messages').append($('
  • ').text("Error: Invalid player data")); - return; - } - - // Track visibleTiles - if (!data.visibleTiles) { - console.warn("โš ๏ธ Warning: No visibleTiles in game data, creating empty object"); - data.visibleTiles = {}; - } - - $('#messages').append($('
  • ').text("Starting game...")); - try { - // Display what we received - $('#messages').append($('
  • ').text(`Entering the dungeon at position (${data.player.x},${data.player.y})...`)); + // Start the game with received data + var success = Game.startGame(data); + console.log("Game start result:", success ? "SUCCESS" : "FAILED"); - // Start the game with received data (check if Game is available) - if (typeof Game !== 'undefined' && Game.startGame) { - var success = Game.startGame(data); - console.log("Game start result:", success ? "SUCCESS โœ…" : "FAILED โŒ"); + if (!success) { + $('#messages').append($('
  • ').text("Game start failed - trying ASCII mode")); - if (!success) { - $('#messages').append($('
  • ').text("Game start failed - trying ASCII mode")); - - // Try ASCII mode as fallback - if (typeof Game.switchToAsciiMode === 'function') { - Game.switchToAsciiMode(); - success = Game.startGame(data); - } - } - } else { - console.error("Game object not available yet!"); - // Try again in a moment - setTimeout(function() { - if (typeof Game !== 'undefined' && Game.startGame) { + // Try switching to ASCII mode + if (document.getElementById('toggle-mode')) { + document.getElementById('toggle-mode').click(); + setTimeout(function() { Game.startGame(data); - } - }, 500); + }, 100); + } } window.focus(); @@ -271,44 +234,49 @@ console.error("Error starting game:", err); $('#messages').append($('
  • ').text("Error: " + err.message)); } - - // Ensure chat is scrolled - if (typeof scrollChat === 'function') { - scrollChat(); - } }); - - // Add game update handler + + // Game update handler socket.on('game_update', function(data) { - console.log("๐ŸŽฎ Received game update:", data); + console.log("๐ŸŽฎ GAME UPDATE received:", data); - // Update game state - if (typeof Game !== 'undefined' && typeof Game.updateGameState === 'function') { - Game.updateGameState(data); - } - - // Log player position - if (data.player) { - console.log(`Player now at: (${data.player.x}, ${data.player.y})`); + if (typeof Game !== 'undefined') { + // Update all entity positions + Game._player = data.player; + Game._visibleTiles = data.visibleTiles; + Game._monster = data.monster; + Game._entrance = data.entrance; + Game._exit = data.exit; + Game._treasure = data.treasure; + + // Draw updated screen + Game._drawGameScreen(); } }); - // Replace the chat form submission handler + // Update the chat form submission handler that processes the "enter" command + $('#chatForm').submit(function(e) { e.preventDefault(); var msg = $('#chatInput').val().trim(); - if (msg) { - console.log(`๐Ÿ“ SENDING CHAT: "${msg}"`); + + // Don't send empty messages + if (!msg) return false; + + console.log("๐Ÿ“ SENDING CHAT:", msg); + socket.emit('chat message', msg); + $('#chatInput').val(''); + + // If the message is "enter", show the waiting screen + if (msg.toLowerCase() === 'enter') { + console.log("๐Ÿ”‘ 'ENTER' COMMAND DETECTED - expecting game_start response"); - // Special handling for enter command - if (msg.toLowerCase() === 'enter') { - console.log("๐Ÿ”‘ 'ENTER' COMMAND DETECTED - expecting game_start response"); - $('#messages').append($('
  • ').text("Requesting to enter the dungeon...")); + // Show waiting screen + if (typeof Game !== 'undefined') { + Game.drawWaitingScreen(); } - - socket.emit('chat message', msg); - $('#chatInput').val(''); } + return false; }); }); @@ -437,317 +405,65 @@ document.getElementById('toggle-mode').addEventListener('click', function() { }); - + + + + - - - - - - - - - - - - - - - - - - -
    โŒจ๏ธ Game Controls Active
    - - - - - - - - - diff --git a/html/js/game.js b/html/js/game.js index ec053b8..30bd294 100644 --- a/html/js/game.js +++ b/html/js/game.js @@ -24,153 +24,29 @@ var Game = { _message: "", // To display messages like 'waiting' _visibleTiles: {}, // Store currently visible tiles _exploredTiles: {}, // Store all tiles the player has seen - + _isTileMode: false, // Track if we're in tile mode + _isFirefox: function() { return navigator.userAgent.toLowerCase().indexOf('firefox') > -1; }, init: function() { - console.log("Initializing Game..."); + console.log("Initializing game"); - // Check for Firefox - var isFirefox = this._isFirefox(); - if (isFirefox) { - console.log("Firefox detected, applying special handling"); - } - - try { - // Create a display with the defined options - var displayOptions = { ...options }; // Clone options - - // Firefox-specific tweaks if needed - if (isFirefox) { - // Force numerical dimensions for Firefox - displayOptions.width = parseInt(displayOptions.width); - displayOptions.height = parseInt(displayOptions.height); - } - - console.log("Creating ROT.Display with options:", displayOptions); - this._display = new ROT.Display(displayOptions); - console.log("ROT.Display created."); - - // Get the DOM element and append display - var container = document.querySelector('.rotdis'); - console.log("Found display container:", container); - - if (container) { - // Clear any existing content - container.innerHTML = ''; - - // Add the new canvas - var canvas = this._display.getContainer(); - - // Firefox-specific attributes - if (isFirefox) { - canvas.style.width = 'auto'; - canvas.style.height = 'auto'; - canvas.style.display = 'block'; - canvas.style.margin = 'auto'; - } - - container.appendChild(canvas); - console.log("Display container attached to DOM"); - - // Draw initial welcome screen - console.log("Drawing initial welcome screen"); - this._drawWelcomeScreen(); - - // Extra check for Firefox - if (isFirefox) { - // Give Firefox a moment to render properly - setTimeout(() => { - console.log("Firefox re-render check"); - this._drawWelcomeScreen(); - }, 100); - } - + // Verify if tileset is actually loaded + this.checkTileset(function(isLoaded) { + if (isLoaded) { + Game._setupTileDisplay(); } else { - console.error("Could not find .rotdis container"); + Game._setupAsciiDisplay(); } - } catch (e) { - console.error("Error during game initialization:", e); - } - - // Initialize empty map - this._map = []; - for (var i = 0; i < this._mapWidth; i++) { - this._map[i] = []; - for (var j = 0; j < this._mapHeight; j++) { - // Default to wall visually; adjust as needed - this._map[i][j] = 1; - } - } - - var game = this; - // Set up keyboard input - ONLY FOR MOVEMENT, not for starting the game - window.addEventListener("keydown", function(e) { - // Check if an input/textarea has focus - if so, don't capture keys for game - const activeElement = document.activeElement; - const isInputFocused = activeElement && ( - activeElement.tagName === 'INPUT' || - activeElement.tagName === 'TEXTAREA' || - activeElement.id === 'm' // Your chat input id - ); - // If input is focused or game isn't active, don't process game controls - if (isInputFocused || !game._gameActive) { - return; - } - - // Movement controls (only when game is active AND no input has focus) - var direction = ""; - switch (e.keyCode) { - case 38: case 75: case 87: // up, k, w - direction = "up"; - e.preventDefault(); // Prevent page scrolling - break; - case 40: case 74: case 83: // down, j, s - direction = "down"; - e.preventDefault(); // Prevent page scrolling - break; - case 37: case 72: case 65: // left, h, a - direction = "left"; - e.preventDefault(); // Prevent page scrolling - break; - case 39: case 76: case 68: // right, l, d - direction = "right"; - e.preventDefault(); // Prevent page scrolling - break; - default: - return; - } - - // If a direction key was pressed, update local state and send to server - if (direction) { - console.log("Game state updated:", direction); - - // Send the move command to the server - if (window.socket) { - console.log("Sending move command to server:", direction); - window.socket.emit('move', direction); - } - } + // Draw the welcome screen + Game._drawWelcomeScreen(); + + // Set up the keyboard handler + Game._initializeKeyboardHandler(); }); - - // Force draw welcome screen - this._gameActive = false; - this._message = "Type 'enter' in chat to play!"; - console.log("Drawing initial welcome screen"); - this._drawWelcomeScreen(); - - // Verify welcome screen was drawn by checking for canvas content - setTimeout(() => { - const container = this._display.getContainer(); - const canvas = container.querySelector("canvas"); - if (canvas) { - console.log("Canvas dimensions:", canvas.width, canvas.height); - console.log("Canvas is visible:", canvas.style.display !== "none"); - } - }, 500); }, // Initialize game with data from server @@ -315,244 +191,637 @@ var Game = { if (tileType === 1) { // Wall this._display.draw(screenX, screenY, '#', '#fff', '#000'); } else { // Floor - this._display.draw(screenX, screenY, '.', '#888', '#000'); + const tileChar = this.isAsciiMode() ? '.' : '\''; + this._display.draw(screenX, screenY, tileChar, '#888', '#000'); } } } } + // Draw special entities if they exist in game state + + // Check for entrance/exit at player position + const playerIsAtEntrance = this._entrance && + this._entrance.x === 0 && + this._entrance.y === 0; + + const playerIsAtExit = this._exit && + this._exit[0] === 0 && + this._exit[1] === 0; + + // Draw entrance if it exists in the game state + if (this._entrance) { + // Handle different data formats - could be [x,y] array or {x,y} object + const entranceX = Array.isArray(this._entrance) ? this._entrance[0] : this._entrance.x; + const entranceY = Array.isArray(this._entrance) ? this._entrance[1] : this._entrance.y; + const entranceScreenX = centerX + entranceX; + const entranceScreenY = centerY + entranceY; + + console.log("Entrance position:", entranceX, entranceY); + console.log("Player relative position:", this._player.x, this._player.y); + + if (entranceScreenX >= 0 && entranceScreenY >= 0 && + entranceScreenX < this._screenWidth && entranceScreenY < this._screenHeight) { + // Draw entrance with bright green color for visibility + this._display.draw(entranceScreenX, entranceScreenY, '<', '#0f0', '#000'); + console.log(`Drew entrance at screen position (${entranceScreenX},${entranceScreenY})`); + } + } + + // Draw exit if it exists in the game state + if (this._exit) { + // Handle different data formats - could be [x,y] array or {x,y} object + const exitX = Array.isArray(this._exit) ? this._exit[0] : this._exit.x; + const exitY = Array.isArray(this._exit) ? this._exit[1] : this._exit.y; + const exitScreenX = centerX + exitX; + const exitScreenY = centerY + exitY; + + console.log("Exit position:", exitX, exitY); + + if (exitScreenX >= 0 && exitScreenY >= 0 && + exitScreenX < this._screenWidth && exitScreenY < this._screenHeight) { + // Draw exit with bright green color for visibility + this._display.draw(exitScreenX, exitScreenY, '>', '#0f0', '#000'); + console.log(`Drew exit at screen position (${exitScreenX},${exitScreenY})`); + } + } + + // Draw treasure if it exists in the game state + if (this._treasure) { + const treasureX = Array.isArray(this._treasure) ? this._treasure[0] : this._treasure.x; + const treasureY = Array.isArray(this._treasure) ? this._treasure[1] : this._treasure.y; + const treasureScreenX = centerX + treasureX; + const treasureScreenY = centerY + treasureY; + + if (treasureScreenX >= 0 && treasureScreenY >= 0 && + treasureScreenX < this._screenWidth && treasureScreenY < this._screenHeight) { + this._display.draw(treasureScreenX, treasureScreenY, '$', '#ff0', '#000'); + console.log(`Drew treasure at screen position (${treasureScreenX},${treasureScreenY})`); + } + } + + // Draw monster if it exists in the game state + if (this._monster) { + const monsterX = this._monster.x; + const monsterY = this._monster.y; + const monsterScreenX = centerX + monsterX; + const monsterScreenY = centerY + monsterY; + + if (monsterScreenX >= 0 && monsterScreenY >= 0 && + monsterScreenX < this._screenWidth && monsterScreenY < this._screenHeight) { + this._display.draw(monsterScreenX, monsterScreenY, '~', '#f00', '#000'); + console.log(`Drew monster at screen position (${monsterScreenX},${monsterScreenY})`); + } + } + // Always draw player at center of screen - this._display.draw(centerX, centerY, '@', '#ff0', '#000'); - console.log("Drew player at center:", centerX, centerY); + // Special case: If player is at entrance/exit, add visual indicator + if (playerIsAtEntrance) { + console.log("Player is at entrance - drawing special indicator"); + this._display.draw(centerX, centerY, '@', '#0ff', '#000'); // Special color for player on entrance + } else if (playerIsAtExit) { + console.log("Player is at exit - drawing special indicator"); + this._display.draw(centerX, centerY, '@', '#0ff', '#000'); // Special color for player on exit + } else { + this._display.draw(centerX, centerY, '@', '#ff0', '#000'); // Normal player + } - // Draw other entities if visible (treasure, exit, entrance) - // ... additional drawing code for other entities ... - - console.log("Game screen drawing complete"); + console.log(`Drew player at center of screen (${centerX},${centerY})`); }, _isVisible: function(x, y) { // Check if a tile is currently visible return this._visibleTiles[y] && this._visibleTiles[y][x] !== undefined; }, - - // Add these functions to your client-side Game object (not the server-side one) - // Function to switch to ASCII mode - switchToAsciiMode: function() { - console.log("Switching to ASCII mode"); + // Add this helper method near other drawing functions + _drawBorder: function(color) { + const w = this._screenWidth; + const h = this._screenHeight; - var asciiOptions = { - width: 25, - height: 19, - fontSize: 16, - fontFamily: "monospace", - bg: "#000", - fg: "#fff" - }; + // Use the stored mode state to determine character + const borderChar = this._isTileMode ? "#" : "+"; + color = color || '#888'; // Default color is gray - try { - // Create new display with ASCII mode - var newDisplay = new ROT.Display(asciiOptions); - - // Replace the existing display - var container = document.querySelector('.rotdis'); - if (container) { - container.innerHTML = ''; - container.appendChild(newDisplay.getContainer()); - this._display = newDisplay; - - // Redraw current game state - if (this._gameActive) { - this._drawGameScreen(); - } else { - this._drawWelcomeScreen(); - } - } else { - console.error("Could not find .rotdis container"); - } - } catch (err) { - console.error("Error switching to ASCII mode:", err); - } - }, - - // Fix the welcome screen drawing - _drawWelcomeScreen: function() { - console.log("Drawing welcome screen..."); - this._display.clear(); - - // Draw borders - for (var x = 0; x < this._display.getOptions().width; x++) { - for (var y = 0; y < this._display.getOptions().height; y++) { - if (x === 0 || y === 0 || x === this._display.getOptions().width-1 || y === this._display.getOptions().height-1) { - this._display.draw(x, y, "#", "#666", "#000"); + // Draw the border + for (let x = 0; x < w; x++) { + for (let y = 0; y < h; y++) { + if (x === 0 || y === 0 || x === w-1 || y === h-1) { + this._display.draw(x, y, borderChar, color, '#000'); } } } - - // Draw welcome text - var centerX = Math.floor(this._display.getOptions().width / 2); - var centerY = Math.floor(this._display.getOptions().height / 2); - - // For ASCII mode, we can use drawText with colors - var y = 5; - this._display.drawText(this._screenWidth / 2 - 5, y++, "%c{yellow}WOWGUE"); - y++; - this._display.drawText(this._screenWidth / 2 - 11, y++, "Type enter to start!"); - this._display.drawText(this._screenWidth / 2 - 11, y++, "Or type help..."); - y+=2; - this._display.drawText(this._screenWidth / 2 - 10, y++, "@ - This is you"); - this._display.drawText(this._screenWidth / 2 - 10, y++, "> - Escape the dungeon"); - this._display.drawText(this._screenWidth / 2 - 10, y++, "~ - Avoid the wonster"); - this._display.drawText(this._screenWidth / 2 - 10, y++, "$ - Secure the bag"); - y+=2; - - // Draw instructions - this._display.drawText(2, this._display.getOptions().height - 2, "Type enter in chat ="); }, - _drawWinScreen: function(hasTreasure) { + // Add these functions right before the closing }; of the Game object + + // Win screen + drawWinScreen: function(hasTreasure) { + console.log("Drawing win screen, hasTreasure:", hasTreasure); + + // Clear the display this.clearDisplay(); - var y = 5; - this._display.drawText(this._screenWidth / 2 - 5, y++, "%c{green}YOU WIN!"); - y++; + + // Draw green border + this._drawBorder('#0f0'); + + // Draw win message + let y = 5; + y = this._drawCenteredText(y, "YOU ESCAPED!", "green"); + y += 1; + + const subtitle = hasTreasure ? "WITH THE TREASURE!" : "ALIVE!"; + y = this._drawCenteredText(y, subtitle, "yellow"); + y += 2; if (hasTreasure) { - this._display.drawText(this._screenWidth / 2 - 15, y++, "You found the treasure and escaped!"); - } else { - this._display.drawText(this._screenWidth / 2 - 10, y++, "You escaped the dungeon!"); + y = this._drawCenteredText(y, "You won extra Wownero!", "yellow"); + y += 1; } - y++; - this._gameActive = false; - this._message = "Press Enter to Play Again!"; - this._display.drawText(this._screenWidth / 2 - (this._message.length / 2), y++, `%c{yellow}${this._message}`); + y += 1; + this._drawCenteredText(y, "Type ENTER to retry", "white"); }, - _drawLoseScreen: function(reason) { + // Lose screen + drawLoseScreen: function(reason) { + console.log("Drawing lose screen, reason:", reason); + + // Clear the display this.clearDisplay(); - var y = 5; - this._display.drawText(this._screenWidth / 2 - 5, y++, "%c{red}GAME OVER!"); - y++; - - if (reason === 'timeout') { - this._display.drawText(this._screenWidth / 2 - 20, y++, "You didn't escape before the next block!"); - } else if (reason === 'caught') { - this._display.drawText(this._screenWidth / 2 - 15, y++, "The monster caught you!"); - } else { - this._display.drawText(this._screenWidth / 2 - 10, y++, `Reason: ${reason}`); + + // Draw red border + this._drawBorder('#f00'); + + // Draw lose message + let y = 5; + y = this._drawCenteredText(y, "YOU DIED!", "red"); + y += 1; + + let subtitle = ""; + if (reason === 'monster') { + subtitle = "KILLED BY THE MONSTER"; + } else if (reason === 'timeout') { + subtitle = "YOU RAN OUT OF TIME"; } - y++; - this._gameActive = false; - this._message = "Press Enter to Play Again!"; - this._display.drawText(this._screenWidth / 2 - (this._message.length / 2), y++, `%c{yellow}${this._message}`); - }, - - getDisplay: function() { - return this._display; - }, - - getScreenWidth: function() { - return this._screenWidth; - }, - - getScreenHeight: function() { - return this._screenHeight; - }, - - // Add these functions to your Game object - - // Check if we're in ASCII mode - isAsciiMode: function() { - if (!this._display) return false; - var options = this._display.getOptions(); - return !options.layout || options.layout !== "tile"; - }, - - // Switch to ASCII mode - switchToAsciiMode: function() { - console.log("Switching to ASCII mode"); + if (subtitle) { + y = this._drawCenteredText(y, subtitle, "red"); + y += 1; + } - var asciiOptions = { - width: 25, - height: 19, - fontSize: 16, - fontFamily: "monospace", - bg: "#000", - fg: "#fff" - }; + y += 2; + this._drawCenteredText(y, "Type ENTER to retry", "white"); + }, + + // Public welcome screen method - add if missing + drawWelcomeScreen: function() { + this._drawWelcomeScreen(); + }, + + _drawWelcomeScreen: function() { + console.log("Drawing welcome screen"); - try { - // Create new display with ASCII mode - var newDisplay = new ROT.Display(asciiOptions); - - // Replace the existing display in DOM - var container = document.querySelector('.rotdis'); - if (container) { - container.innerHTML = ''; - container.appendChild(newDisplay.getContainer()); - - // Update the game's display reference - this._display = newDisplay; - - console.log("Switched to ASCII mode successfully"); - - // Redraw current game state - if (this._gameActive) { - this._drawGameScreen(); - } else { - this._drawWelcomeScreen(); - } - - return true; - } else { - console.error("Could not find .rotdis container to replace display"); - return false; + // Clear the display + this.clearDisplay(); + + // Draw the border with gray color + this._drawBorder('#888'); + + // Draw content with centered text + let y = 3; + y = this._drawCenteredText(y, "WOWGUE", "white"); + y += 4; + + // Draw instructions + y = this._drawCenteredText(y, "Type enter to start!", "yellow"); + y = this._drawCenteredText(y, "Or type help...", "yellow"); + y += 2; + + // Draw legend - keep these special characters with tile mappings + const centerX = Math.floor(this._screenWidth / 2) - 11; // Manual alignment for legend + this._display.drawText(centerX, y, "%c{cyan}@ %c{white}- This is you"); y++; + this._display.drawText(centerX, y, "%c{green}> %c{white}- Escape the dungeon"); y++; + this._display.drawText(centerX, y, "%c{red}~ %c{white}- Avoid the monster"); y++; + this._display.drawText(centerX, y, "%c{yellow}$ %c{white}- Secure the bag"); y++; + + // Draw footer + y += 1; + this._drawCenteredText(17, "Type enter in chat =", "gray"); + }, + + _initializeKeyboardHandler: function() { + console.log("Initializing keyboard handler"); + + // Use a local reference to avoid 'this' scope issues in event handler + const game = this; + + // Add event listener for keydown + window.addEventListener('keydown', function(e) { + // Only process keys when game is active + if (!game._gameActive) { + return; } - } catch (err) { - console.error("Error switching to ASCII mode:", err); - return false; - } + + let direction = null; + + // Determine direction based on key + switch (e.key) { + case 'ArrowUp': + case 'w': + case 'W': + direction = 'up'; + break; + + case 'ArrowDown': + case 's': + case 'S': + direction = 'down'; + break; + + case 'ArrowLeft': + case 'a': + case 'A': + direction = 'left'; + break; + + case 'ArrowRight': + case 'd': + case 'D': + direction = 'right'; + break; + + default: + // Not a movement key + return; + } + + // Prevent default behavior for these keys (like scrolling) + e.preventDefault(); + + console.log(`Key pressed: ${e.key}, sending direction: ${direction}`); + + // Send the movement to the server + if (window.socket && direction) { + window.socket.emit('move', direction); + } + }); + + console.log("Keyboard handler initialized"); + }, + + // Mode switching methods + isAsciiMode: function() { + return !this._isTileMode; }, - // Switch to tile mode switchToTileMode: function() { console.log("Switching to tile mode"); + if (!window.tileSet || !window.tileMap) { + console.error("Cannot switch to tile mode: tileset or tilemap not available"); + return false; + } + + // Save current game state and screen center + const gameWasActive = this._gameActive; + + // Create new display with tile options + const oldContainer = this._display.getContainer(); + const parentElement = oldContainer.parentElement; + try { - // Create new display with tile mode - var newDisplay = new ROT.Display(options); + this._isTileMode = true; // Update mode state + this._display = new ROT.Display({ + width: this._screenWidth, + height: this._screenHeight, + fontSize: 16, + fontFamily: "monospace", + fg: "#fff", + bg: "#000", + layout: "tile", + tileWidth: 32, + tileHeight: 32, + tileSet: window.tileSet, + tileMap: window.tileMap + }); - // Replace the existing display in DOM - var container = document.querySelector('.rotdis'); - if (container) { - container.innerHTML = ''; - container.appendChild(newDisplay.getContainer()); - - // Update the game's display reference - this._display = newDisplay; - - console.log("Switched to tile mode successfully"); - - // Redraw current game state - if (this._gameActive) { - this._drawGameScreen(); - } else { - this._drawWelcomeScreen(); - } - - return true; - } else { - console.error("Could not find .rotdis container to replace display"); - return false; + console.log("Created tile display with options:", this._display.getOptions()); + + // Replace old container with new one + if (parentElement) { + parentElement.removeChild(oldContainer); + parentElement.appendChild(this._display.getContainer()); } + + // Redraw the screen + if (gameWasActive) { + this._drawGameScreen(); + } else { + this._drawWelcomeScreen(); + } + + return true; } catch (err) { + this._isTileMode = false; // Revert if failed console.error("Error switching to tile mode:", err); return false; } + }, + + switchToAsciiMode: function() { + console.log("Switching to ASCII mode"); + + // Save current game state + const gameWasActive = this._gameActive; + + // Create new display with ASCII options + const oldContainer = this._display.getContainer(); + const parentElement = oldContainer.parentElement; + + try { + this._isTileMode = false; // Update mode state + this._display = new ROT.Display({ + width: this._screenWidth, + height: this._screenHeight, + fontSize: 18, + fontFamily: "monospace", + fg: "#fff", + bg: "#000", + layout: "rect" + }); + + console.log("Created ASCII display with options:", this._display.getOptions()); + + // Replace old container with new one + if (parentElement) { + parentElement.removeChild(oldContainer); + parentElement.appendChild(this._display.getContainer()); + } + + // Redraw the screen + if (gameWasActive) { + this._drawGameScreen(); + } else { + this._drawWelcomeScreen(); + } + + return true; + } catch (err) { + console.error("Error switching to ASCII mode:", err); + return false; + } + }, + + // Update the init function to properly handle the tileset + + init: function() { + console.log("Initializing game"); + + // Verify if tileset is actually loaded + this.checkTileset(function(isLoaded) { + if (isLoaded) { + Game._setupTileDisplay(); + } else { + Game._setupAsciiDisplay(); + } + + // Draw the welcome screen + Game._drawWelcomeScreen(); + + // Set up the keyboard handler + Game._initializeKeyboardHandler(); + }); + }, + + // Add helper function to check if tileset is truly loaded + checkTileset: function(callback) { + console.log("Checking if tileset is loaded..."); + + // Give a short delay to ensure scripts are fully loaded + setTimeout(function() { + // More detailed logging to help debug + console.log("Tileset check:"); + console.log(" window.tileSet exists:", !!window.tileSet); + console.log(" window.tileMap exists:", !!window.tileMap); + console.log(" tileMap keys:", window.tileMap ? Object.keys(window.tileMap).length : 0); + + // First do a quick check + if (!window.tileSet || !window.tileMap || + !Object.keys(window.tileMap).length) { + console.log("โŒ Tileset or tilemap not available or empty"); + callback(false); + return; + } + + // For images, we need to make sure it's really loaded + if (window.tileSet.complete) { + console.log("โœ… Tileset already loaded!"); + callback(true); + } else { + console.log("โณ Tileset still loading, adding load event listener"); + // Add load listener to handle when it completes + window.tileSet.addEventListener('load', function() { + console.log("โœ… Tileset loaded via event!"); + callback(true); + }); + + window.tileSet.addEventListener('error', function() { + console.log("โŒ Error loading tileset"); + callback(false); + }); + + // Set a timeout just in case + setTimeout(function() { + if (!window.tileSet.complete) { + console.log("โฐ Tileset load timed out"); + callback(false); + } + }, 3000); + } + }, 100); // Short delay to ensure scripts are loaded + }, + + // Setup tile display + _setupTileDisplay: function() { + console.log("Setting up tile display"); + + this._isTileMode = true; // Set tile mode state + + this._display = new ROT.Display({ + width: this._screenWidth, + height: this._screenHeight, + fontSize: 16, + fontFamily: "monospace", + fg: "#fff", + bg: "#000", + layout: "tile", + tileWidth: 32, + tileHeight: 32, + tileSet: window.tileSet, + tileMap: window.tileMap + }); + + // Place the display in the div with class "rotdis" + const container = this._display.getContainer(); + const rotdisElement = document.querySelector(".rotdis"); + if (rotdisElement) { + // Clear any existing content first + rotdisElement.innerHTML = ''; + rotdisElement.appendChild(container); + } + + console.log("โœ… Tile display setup complete"); + }, + + // Setup ASCII display + _setupAsciiDisplay: function() { + console.log("Setting up ASCII display"); + + this._isTileMode = false; // Set ASCII mode state + + this._display = new ROT.Display({ + width: this._screenWidth, + height: this._screenHeight, + fontSize: 18, + fontFamily: "monospace", + fg: "#fff", + bg: "#000", + layout: "rect" + }); + + // Place the display in the div with class "rotdis" + const container = this._display.getContainer(); + const rotdisElement = document.querySelector(".rotdis"); + if (rotdisElement) { + // Clear any existing content first + rotdisElement.innerHTML = ''; + rotdisElement.appendChild(container); + } + + console.log("โœ… ASCII display setup complete"); + }, + + // Add this method to the Game object + + // Enhanced waiting screen with improved animation + + drawWaitingScreen: function() { + console.log("Drawing waiting screen"); + + // Clear the display + this.clearDisplay(); + + // Draw border + this._drawBorder('#888'); + + // Draw title + this._drawCenteredText(5, "WAITING FOR DUNGEON...", "yellow"); + + const _this = this; + let dots = 0; + let frame = 0; + + // Set up dungeon scene elements (static) + const width = this._screenWidth; + const centerY = 14; + const floorY = centerY + 1; + + // Define wall positions - now as constants for collision detection + const leftWallX = 5; + const rightWallX = width - 6; + + // Store the interval ID so we can clear it later + this._waitingInterval = setInterval(function() { + // Clear the animation area only (not the whole screen) + for (let x = 1; x < width-1; x++) { + _this._display.draw(x, centerY, ' ', '#fff', '#000'); + _this._display.draw(x, centerY-1, ' ', '#fff', '#000'); + } + + // Draw floor + for (let x = 1; x < width-1; x++) { + _this._display.draw(x, floorY, '.', '#888', '#000'); + } + + // Draw walls and items (static elements) + _this._display.draw(leftWallX, centerY, '#', '#fff', '#000'); // Left Wall + _this._display.draw(rightWallX, centerY, '#', '#fff', '#000'); // Right Wall + _this._display.draw(width-9, floorY-1, '$', '#ff0', '#000'); // Treasure + _this._display.draw(width-4, floorY-1, '>', '#0f0', '#000'); // Exit + + // Calculate player position using a smoother sine wave + // This gives a natural back-and-forth motion + const playerPhase = (frame % 120) / 120; // 0.0 to 1.0 over 120 frames + const playerSine = Math.sin(playerPhase * 2 * Math.PI); + + // Map the sine wave (-1 to 1) to the available space between walls + // Add 0.5 to playerSine to map from [-1,1] to [-0.5,1.5], then clamp to [0,1] + const normalizedPos = Math.max(0, Math.min(1, playerSine * 0.5 + 0.5)); + + // Calculate player position, keeping a safe distance from walls + const safeLeftX = leftWallX + 2; + const safeRightX = rightWallX - 2; + const playerX = Math.round(safeLeftX + normalizedPos * (safeRightX - safeLeftX)); + + // Monster follows player with some lag and variation + // Use a different phase for the monster to create pursuing behavior + const monsterPhase = ((frame - 15) % 120) / 120; // 15 frames behind player + const monsterSine = Math.sin(monsterPhase * 2 * Math.PI); + const monsterNormPos = Math.max(0, Math.min(1, monsterSine * 0.5 + 0.5)); + + // Monster stays in leftmost part of screen, never going beyond middle + const monsterMaxX = Math.min(playerX - 2, Math.floor(width/2) - 2); + const monsterMinX = leftWallX + 2; + const monsterX = Math.round(monsterMinX + monsterNormPos * (monsterMaxX - monsterMinX)); + + // Draw player and monster with proper collision detection + _this._display.draw(playerX, centerY, '@', '#ff0', '#000'); + _this._display.draw(monsterX, centerY, '~', '#f00', '#000'); + + // Proper waiting text animation - first clear the line + const centerX = Math.floor(_this._screenWidth / 2); + for (let x = centerX-10; x <= centerX+10; x++) { + _this._display.draw(x, 10, ' ', '#fff', '#000'); + } + + // Now draw the waiting text with fixed position + const waitingText = "Waiting"; + const dotsText = ".".repeat(dots % 4); + const startX = centerX - Math.floor(waitingText.length / 2) - 2; + _this._display.drawText(startX, 10, "%c{white}" + waitingText + dotsText); + + // Update animation counters + dots++; + frame++; + }, 100); // Faster update rate for smoother animation + }, + + // Method to stop the waiting animation + stopWaitingScreen: function() { + if (this._waitingInterval) { + clearInterval(this._waitingInterval); + this._waitingInterval = null; + } + }, + + // Add these helper functions for text and animation + + // Helper function to draw centered text + _drawCenteredText: function(y, text, color) { + const centerX = Math.floor(this._screenWidth / 2); + const x = centerX - Math.floor(text.length / 2); + + // Handle color formatting + if (color) { + text = "%c{" + color + "}" + text; + } + + this._display.drawText(x, y, text); + return y + 1; // Return next line position } -}; + +}; // End of Game object socket.on('game_start', function(data) { console.log("๐ŸŽฎ Game start received with data:", data); @@ -564,12 +833,6 @@ socket.on('game_start', function(data) { } try { - // Make sure all game UI elements are visible - $('.container, .banner, .rotdis, .chat-container').css('display', ''); - - // Display what we received - $('#messages').append($('
  • ').text(`Entering the dungeon...`)); - // Start the game with received data var success = Game.startGame(data); console.log("Game start result:", success ? "SUCCESS" : "FAILED"); diff --git a/html/js/options.js b/html/js/options.js index 61b9019..6c70afd 100644 --- a/html/js/options.js +++ b/html/js/options.js @@ -163,3 +163,13 @@ var options = { " ": [992, 64] } }; + +// Update the tileset loading to properly set the global variables + +// Make sure the variables are properly set in the global scope +window.tileSet = tileSet; +window.tileMap = options.tileMap; + +// Add logging to confirm they're set +console.log("โœ… Tileset assigned to window.tileSet:", !!window.tileSet); +console.log("โœ… TileMap assigned to window.tileMap:", !!window.tileMap, "with keys:", window.tileMap ? Object.keys(window.tileMap).length : 0); diff --git a/index.js b/index.js index b485e4f..5ea4d3b 100644 --- a/index.js +++ b/index.js @@ -94,16 +94,13 @@ function startGamesForWaiting(blockHeight) { while (WAITING_PLAYERS.length > 0) { const playerEntry = WAITING_PLAYERS.shift(); const serverId = playerEntry.serverId; - const clientId = playerEntry.clientId; - console.log(`Processing player: server=${serverId}, client=${clientId}`); + console.log(`Processing player: server=${serverId}`); - // Get the user object const currentUser = getUserBySocket(serverId); if (currentUser) { currentUser.blockRec = blockHeight; - console.log(`Creating new game for user ${serverId}`); try { const game = currentUser.startGame(80, 40); @@ -113,20 +110,18 @@ function startGamesForWaiting(blockHeight) { gameState.blockHeight = blockHeight; console.log(`๐ŸŽฎ SENDING GAME_START to ${serverId}`); - console.log(`Player position: (${game.player.x}, ${game.player.y})`); - // Send a single game start message to chat - io.to(serverId).emit('message', `Entering the dungeon at position (0,0)...`); - - // Then send the actual game state + // Send ONLY ONE message - the game state itself io.to(serverId).emit('game_start', gameState); console.log(`Game started for player ${serverId}`); } catch (error) { console.error(`Error creating game:`, error); + io.to(serverId).emit('message', 'Error starting game: ' + error.message); } } else { console.error(`User not found for socket ${serverId}`); + io.to(serverId).emit('message', 'Error: User not found'); } } } @@ -179,8 +174,7 @@ io.on('connection', function(socket) { '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.'); } - // Update the 'enter' command handler to NOT start games immediately - + // Update the 'enter' command handler to send waiting_status if (msg.toLowerCase() == 'enter') { console.log(`Player ${socket.id} requested to enter the dungeon`); @@ -195,28 +189,42 @@ io.on('connection', function(socket) { WAITING_PLAYERS.push({ serverId: socket.id, clientId: socket.id, - user: currentUser + user: currentUser, + joinedAt: Date.now() }); - // Tell player they're in queue - io.to(socket.id).emit('message', 'Requesting to enter the dungeon...'); + // Send waiting status data for display - THIS IS THE KEY PART io.to(socket.id).emit('waiting_status', { status: 'waiting', message: 'Waiting for the next block to be found...', position: WAITING_PLAYERS.length, - currentBlock: debugBlockHeight || lastBlockHeight + currentBlock: debugBlockHeight || lastBlockHeight || 0, + joinTime: Date.now() }); - // Log the queue state console.log(`Added player to queue. Current queue: ${WAITING_PLAYERS.length} players`); - - // NO LONGER start game immediately - wait for next block - // startGamesForWaiting(lastBlockHeight); + } else { + io.to(socket.id).emit('message', 'You are already in the queue. Please wait.'); } } else { io.to(socket.id).emit('message', 'Error: Could not add you to the game queue. Please try again.'); } } + // Add a handler for the 'cancel' command + else if (msg.toLowerCase() == 'cancel') { + // Remove player from waiting queue + const index = WAITING_PLAYERS.findIndex(p => p.serverId === socket.id); + + if (index !== -1) { + WAITING_PLAYERS.splice(index, 1); + io.to(socket.id).emit('message', '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.'); + } + } else { io.emit('message', msg); }