// This provides a mock socket when the real one isn't available if (typeof window !== 'undefined' && !window.socket) { window.socket = { emit: function(event, data) { console.log("[Mock Socket] Would emit:", event, data); } }; } var Game = { _display: null, _screenWidth: options.width, _screenHeight: options.height, _isTileMode: false, // Determined after checking tileset _displayReady: false, // Flag to indicate if _display is initialized _player: null, defaultFg: "#FFF", // Define defaultFg here // _defaultBg: null, // Standard for transparent/display default _monster: null, _map: {}, _items: {}, _gameInProgress: false, _inputEnabled: true, _messageLog: [], _maxLogMessages: 5, init: function() { console.log("Game.init called. 'this' is Game:", this === Game); console.log("Is this.checkTileset a function?", typeof this.checkTileset); if (typeof this.checkTileset !== 'function') { console.error("CRITICAL: Game.checkTileset is not a function. Halting initialization."); alert("Error: Game initialization failed (checkTileset not found). Please check game.js."); return; } this.checkTileset(function(tilesetAvailable) { console.log("Game.checkTileset callback. Tileset available: " + tilesetAvailable); this._isTileMode = tilesetAvailable; var displayOptions = { width: this._screenWidth, height: this._screenHeight, forceSquareRatio: true, // Good for both modes generally }; if (this._isTileMode) { console.log("Initializing display in TILE mode."); displayOptions.layout = "tile"; displayOptions.bg = window.options.bg; // Should be "transparent" displayOptions.fg = window.options.fg || "#FFF"; // Default foreground for text not in tileMap displayOptions.tileWidth = window.options.tileWidth; displayOptions.tileHeight = window.options.tileHeight; displayOptions.tileSet = window.options.tileSet; displayOptions.tileMap = window.options.tileMap; displayOptions.tileColorize = false; // Render tiles as-is from the tileset } else { console.log("Initializing display in ASCII mode."); displayOptions.layout = "rect"; displayOptions.bg = "#000000"; // Black background for ASCII displayOptions.fg = window.options.fg || "#FFF"; // Default foreground displayOptions.fontSize = 18; // Adjust as needed for your font/preference // Ensure no tile-specific options are passed if they might cause errors delete displayOptions.tileWidth; delete displayOptions.tileHeight; delete displayOptions.tileSet; delete displayOptions.tileMap; } try { var gameDisplayContainer = document.getElementById("game-display"); if (!gameDisplayContainer) { console.error("Game display container #game-display not found in HTML!"); alert("Error: Game display container #game-display not found. Cannot start game."); return; } this._display = new ROT.Display(displayOptions); gameDisplayContainer.innerHTML = ''; // Clear any previous content (e.g., loading messages) gameDisplayContainer.appendChild(this._display.getContainer()); console.log("ROT.Display initialized and appended. Display object:", this._display); this._displayReady = true; // Set flag AFTER successful initialization console.log("Game display initialized. _displayReady set to true."); // Now that the display is ready, other things can happen, // like drawing the welcome screen or enabling socket event handlers fully. this._drawWelcomeScreen(); // Example: draw initial screen } catch (e) { console.error("Error initializing ROT.Display:", e); alert("Failed to initialize game display. Error: " + e.message + ". Check console for details."); this._displayReady = false; // Explicitly false on error } }.bind(this)); // CRUCIAL: bind 'this' for the callback to refer to the Game object }, checkTileset: function(callback) { console.log("Game.checkTileset called to determine mode."); // This function relies on options.js to have attempted loading the tileset // and set window.tileSet, window.tileMap, and window.globalTileSetStatus. // Give a very short moment for options.js to potentially finish initial load attempts. setTimeout(function() { if (window.globalTileSetStatus === "error") { console.warn("Tileset loading previously failed (window.globalTileSetStatus is 'error'). Using ASCII mode."); callback(false); // Tileset not available } else if (window.tileSet && window.tileSet.complete && window.tileSet.naturalHeight !== 0 && window.tileMap && Object.keys(window.tileMap).length > 0) { console.log("Tileset and tileMap appear to be loaded and valid. Using TILE mode."); callback(true); // Tileset available } else { // If not immediately ready, and no definitive error, try waiting a bit longer. // options.js has its own timeouts. This is a secondary check. console.log("Tileset not immediately confirmed. Waiting up to 3 seconds for options.js to complete loading..."); let attempts = 0; const maxAttempts = 6; // Try for 3 seconds (6 * 500ms) const interval = setInterval(function() { attempts++; if (window.globalTileSetStatus === "error") { clearInterval(interval); console.warn("Tileset loading failed during interval check in Game.checkTileset. Using ASCII mode."); callback(false); } else if (window.tileSet && window.tileSet.complete && window.tileSet.naturalHeight !== 0 && window.tileMap && Object.keys(window.tileMap).length > 0) { clearInterval(interval); console.log("Tileset and tileMap confirmed after delay in Game.checkTileset. Using TILE mode."); callback(true); } else if (attempts >= maxAttempts) { clearInterval(interval); console.error("Tileset did not become available after extended wait in Game.checkTileset. Assuming not available. Using ASCII mode."); callback(false); // Assume not available } }.bind(this), 500); // Check every 500ms } }.bind(this), 50); // Small initial delay for options.js to run }, _ensureDisplay: function() { if (!this._display || !this._displayReady) { console.warn("Display not ready or not initialized. Aborting drawing/game operation."); return false; } return true; }, _forceClearToBlack: function() { if (!this._ensureDisplay()) { console.warn("Game._forceClearToBlack: Display not ready, aborting."); return; } // console.log("Game: _forceClearToBlack called at " + new Date().toLocaleTimeString()); const display = this._display; // Step 1: Clear ROT.js's internal data cache and dirty flags. // This ensures that ROT.js starts with a clean slate for what it thinks is on screen. display._data = {}; display._dirty = {}; // console.log("Game._forceClearToBlack: Cleared display._data and display._dirty."); // Step 2: Directly clear the visual canvas to black. // This bypasses ROT.js's drawing for the initial clear, ensuring it's black. var canvas = display.getContainer(); // This is typically the element if (canvas && typeof canvas.getContext === 'function') { var ctx = canvas.getContext("2d"); if (ctx) { // console.log(`Game._forceClearToBlack: Clearing canvas (${canvas.width}x${canvas.height}) to black.`); ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, canvas.width, canvas.height); } else { console.warn("Game._forceClearToBlack: Failed to get 2D context from canvas."); this._fallbackClear(display); } } else { console.warn("Game._forceClearToBlack: Display container is not a canvas or does not exist. Attempting fallback clear."); this._fallbackClear(display); } // After this, the canvas is black. ROT.js's _data and _dirty are empty. // Subsequent display.draw() calls (e.g., in _drawWelcomeScreen) will populate // _data and _dirty for the specific cells they affect. // The ROT.js _draw() loop will then only process these newly dirtied cells, // for which _data will exist. }, // Helper for fallback clear if direct canvas manipulation fails _fallbackClear: function(display) { console.warn("Game._forceClearToBlack: Using fallback clear method."); const originalBg = display.getOptions().bg; // Get current bg from display's options try { if (display._options) { // Ensure _options exists display._options.bg = "#000000"; // Set to black for the clear } display.clear(); // Use ROT.js's own clear method. // This will use the (now black) _options.bg. } catch (e) { console.error("Error during fallback clear:", e); } finally { if (display._options) { // Ensure _options exists before restoring display._options.bg = originalBg; // Restore original background } } }, // Add a helper for default colors to avoid errors if options.colors is not fully defined // _getColor: function(type, defaultColor) { // return (options.colors && options.colors[type]) ? options.colors[type] : defaultColor; // }, // REMOVED _isFirefox: function() { return navigator.userAgent.toLowerCase().indexOf('firefox') > -1; }, startGame: function(playerData, mapData, monsterData, itemData, initialVisibleTiles) { // Added initialVisibleTiles if (!this._ensureDisplay()) { console.error("Game: Cannot start game, display not ready."); return false; } console.log("Game: startGame called. PlayerData:", playerData, "MapData:", mapData, "InitialVisibleTiles:", initialVisibleTiles); this.clearDisplay(); try { this._gameActive = true; // Robust player initialization if (playerData && typeof playerData.x === 'number' && typeof playerData.y === 'number') { this._player = { x: playerData.x, y: playerData.y }; } else { console.warn("Invalid or missing playerData.x/y, using default player position.", playerData); this._player = { x: 12, y: 12 }; // Default position } console.log("Player initialized to:", this._player); this._monster = monsterData; // Assuming monsterData is structured correctly or null this._item = itemData; // Assuming itemData is structured correctly or null this._map = {}; this._visibleTiles = {}; // Clear/initialize visible tiles this._exploredTiles = this._exploredTiles || {}; // Initialize exploredTiles if it doesn't exist, or keep existing let mapInitialized = false; if (mapData && mapData.tiles && typeof mapData.tiles === 'object' && Object.keys(mapData.tiles).length > 0) { console.log("Initializing map from mapData.tiles:", mapData.tiles); for (var yKey in mapData.tiles) { const y = parseInt(yKey); this._map[y] = {}; for (var xKey in mapData.tiles[y]) { const x = parseInt(xKey); this._map[y][x] = mapData.tiles[y][x]; } } mapInitialized = true; console.log("Game map initialized from mapData.tiles:", this._map); } else if (mapData && Array.isArray(mapData) && mapData.length > 0) { console.warn("Received array-based mapData, attempting to convert."); for (let y = 0; y < mapData.length; y++) { this._map[y] = {}; if (mapData[y] && Array.isArray(mapData[y])) { for (let x = 0; x < mapData[y].length; x++) { this._map[y][x] = mapData[y][x]; } } } mapInitialized = true; console.log("Game map initialized from array-based mapData:", this._map); } else if (initialVisibleTiles && typeof initialVisibleTiles === 'object' && Object.keys(initialVisibleTiles).length > 0) { console.log("Initializing _visibleTiles from server-sent initialVisibleTiles and inferring _map for FOV."); this._visibleTiles = JSON.parse(JSON.stringify(initialVisibleTiles)); // Deep copy for (const yKey in this._visibleTiles) { const y = parseInt(yKey); this._map[y] = this._map[y] || {}; for (const xKey in this._visibleTiles[y]) { const x = parseInt(xKey); // Store the tile type (0 for floor, 1 for wall, etc.) into _map // This allows client-side FOV to know about passability. this._map[y][x] = this._visibleTiles[y][x]; } } mapInitialized = true; console.log("Game map partially initialized for FOV from initialVisibleTiles:", this._map); console.log("_visibleTiles set directly from initialVisibleTiles:", this._visibleTiles); } if (!mapInitialized) { console.warn("No valid mapData or initialVisibleTiles provided. Creating default map for testing. mapData:", mapData, "initialVisibleTiles:", initialVisibleTiles); const defaultMapWidth = this._screenWidth; const defaultMapHeight = this._screenHeight; for (let y = 0; y < defaultMapHeight; y++) { this._map[y] = {}; for (let x = 0; x < defaultMapWidth; x++) { this._map[y][x] = (x === 0 || x === defaultMapWidth - 1 || y === 0 || y === defaultMapHeight - 1) ? 1 : 0; } } // Adjust player position if it's in a wall of the default map if (this._map[this._player.y] && this._map[this._player.y][this._player.x] === 1) { this._player.x = Math.floor(defaultMapWidth / 2); this._player.y = Math.floor(defaultMapHeight / 2); if (this._map[this._player.y][this._player.x] === 1) { // If center is still wall (tiny map) this._map[this._player.y][this._player.x] = 0; // Force floor } } console.log("Default game map created. Player position:", this._player.x, this._player.y); } // Validate player position against the map if (!this._map[this._player.y] || this._map[this._player.y][this._player.x] === undefined) { console.error(`Player position (${this._player.x}, ${this._player.y}) is invalid or outside map boundaries AFTER map init. Attempting to find fallback.`); let foundFallback = false; for (let y_scan = 0; y_scan < this._screenHeight; y_scan++) { // Iterate within screen bounds for fallback if (this._map[y_scan]) { for (let x_scan = 0; x_scan < this._screenWidth; x_scan++) { if (this._map[y_scan][x_scan] === 0) { // Find first floor tile this._player.x = x_scan; this._player.y = y_scan; console.warn(`Player moved to fallback position: (${this._player.x}, ${this._player.y})`); foundFallback = true; break; } } } if (foundFallback) break; } if (!foundFallback) { console.error("Could not find a valid fallback starting position for the player on the map."); this._drawCenteredText(10, "Error: Map invalid / No player start!"); return false; } } // Initialize FOV // FOV requires a map where 0 is passable, other numbers are not. var fov = new ROT.FOV.PreciseShadowcasting(function(x, y) { return (this._map[y] && this._map[y][x] !== undefined) ? (this._map[y][x] === 0) : false; }.bind(this)); // If map was NOT built from initialVisibleTiles, compute FOV now. // If it WAS built from initialVisibleTiles, _visibleTiles is already set. // However, we run _updateExploredTiles and FOV compute to ensure consistency and explore. if (!(initialVisibleTiles && typeof initialVisibleTiles === 'object' && Object.keys(initialVisibleTiles).length > 0)) { this._visibleTiles = {}; // Clear if not set by server fov.compute(this._player.x, this._player.y, 10, function(x, y, r, visibility) { if (!this._visibleTiles[y]) this._visibleTiles[y] = {}; if (this._map[y] && this._map[y][x] !== undefined) { this._visibleTiles[y][x] = this._map[y][x]; } }.bind(this)); } this._updateExploredTiles(); // Add all currently visible tiles to explored tiles console.log("Initial FOV computed/processed. Visible tiles count:", Object.keys(this._visibleTiles).reduce((acc, yKey) => acc + Object.keys(this._visibleTiles[yKey]).length, 0)); if (!this._scheduler) this._scheduler = new ROT.Scheduler.Simple(); else this._scheduler.clear(); if (!this._engine) this._engine = new ROT.Engine(this._scheduler); this._scheduler.add(this._player, true); // this._engine.start(); // KEEP COMMENTED for now console.log("Player coordinates before drawing game screen:", this._player.x, this._player.y); if (!this._map[this._player.y] || this._map[this._player.y][this._player.x] === undefined) { console.error(`Player position (${this._player.x}, ${this._player.y}) is STILL invalid before drawing. Map issue?`); this._drawCenteredText(10, "Error: Player invalid for draw!"); return false; } this._drawGameScreen(); console.log("Game started successfully and initial screen drawn."); return true; } catch (err) { console.error("Error during Game.startGame:", err); console.error("Error name:", err.name, "Error message:", err.message, "Stack:", err.stack); if (err.message && (err.message.includes("_map") || err.message.includes("Cannot read properties of undefined"))) { console.error("Detailed error info: Player:", this._player, "Map keys:", Object.keys(this._map || {}).length); } try { this._drawCenteredText(10, "Error starting game!"); this._drawCenteredText(12, "Check console (F12) for details."); } catch (drawError) { console.error("Failed to draw error message on screen:", drawError); } return false; } }, // Update game state with new data from server updateGameState: function(data) { console.log("🎲 Game.updateGameState received data:", data); if (!this._gameActive) { console.warn("Game.updateGameState called, but game is not active. Ignoring update."); return; } if (!data) { console.error("Game.updateGameState: No update data received!"); return; } try { let needsRedraw = false; // Update player state if (data.player) { // console.log("Player state updated:", data.player); this._player = data.player; needsRedraw = true; } // Update monster state if (data.monster !== undefined) { // Check for undefined to allow null for no monster // console.log("Monster state updated:", data.monster); this._monster = data.monster; needsRedraw = true; } // Update items if (data.items !== undefined) { // Check for undefined to allow empty or null // console.log("Items state updated:", data.items); this._items = data.items; // Ensure this is _items, not _item needsRedraw = true; } // Update visible tiles (most critical for movement feedback) if (data.visibleTiles && typeof data.visibleTiles === 'object' && Object.keys(data.visibleTiles).length > 0) { console.log("Visible tiles updated. Number of rows:", Object.keys(data.visibleTiles).length); this._visibleTiles = data.visibleTiles; this._updateExploredTiles(); // This is important for fog of war needsRedraw = true; } else if (data.visibleTiles) { // console.log("Received visibleTiles, but it was empty or not an object:", data.visibleTiles); } // Redraw the game screen if any relevant data changed if (needsRedraw) { console.log("Redrawing game screen due to game update."); this._drawGameScreen(); } else { // console.log("Game.updateGameState: No relevant data changed that requires a redraw."); } } catch (err) { console.error("Error in Game.updateGameState:", err); } }, _updateExploredTiles: function() { // Add all currently visible tiles to explored tiles for (var yKey in this._visibleTiles) { const y = parseInt(yKey); if (!this._exploredTiles[y]) this._exploredTiles[y] = {}; for (var xKey in this._visibleTiles[y]) { const x = parseInt(xKey); this._exploredTiles[y][x] = this._visibleTiles[y][x]; } } }, clearDisplay: function() { if (!this._ensureDisplay()) return; this._forceClearToBlack(); // console.log("Display cleared via _forceClearToBlack"); }, _drawGameScreen: function() { if (!this._ensureDisplay()) return; const playerWX = this._player.x; const playerWY = this._player.y; const centerX = Math.floor(this._screenWidth / 2); const centerY = Math.floor(this._screenHeight / 2); const topLeftWX = playerWX - centerX; const topLeftWY = playerWY - centerY; for (let sy = 0; sy < this._screenHeight; sy++) { for (let sx = 0; sx < this._screenWidth; sx++) { const wx = topLeftWX + sx; const wy = topLeftWY + sy; let charStack = []; let fgStack = []; let bgStack = []; let baseChar = ' '; let baseFg = this.defaultFg; // Use this.defaultFg let tileType; if (this._visibleTiles[wy] && this._visibleTiles[wy][wx] !== undefined) { tileType = this._visibleTiles[wy][wx]; baseFg = this.defaultFg; // Use this.defaultFg baseChar = (tileType === 1) ? '#' : (this._isTileMode ? "'" : "."); } else if (this._exploredTiles[wy] && this._exploredTiles[wy][wx] !== undefined) { tileType = this._exploredTiles[wy][wx]; baseFg = this.defaultFg; // Use this.defaultFg baseChar = (tileType === 1) ? '#' : (this._isTileMode ? "'" : "."); } charStack.push(baseChar); fgStack.push(baseFg); bgStack.push(null); // Items, Monsters, Player drawing logic // Example for items (ensure _item and _isVisible are defined and work correctly) if (this._item) { // Check if _item exists for (const itemKey in this._item) { if (this._item.hasOwnProperty(itemKey)) { const currentItem = this._item[itemKey]; if (currentItem && currentItem.x === wx && currentItem.y === wy && this._isVisible(wx, wy)) { charStack.push('$'); // Assuming '$' for items fgStack.push(this.defaultFg); // Use this.defaultFg bgStack.push(null); } } } } if (this._monster && this._monster.x === wx && this._monster.y === wy && this._isVisible(wx, wy)) { charStack.push('~'); fgStack.push(this.defaultFg); // Use this.defaultFg bgStack.push(null); } if (wx === playerWX && wy === playerWY) { charStack.push('@'); fgStack.push(this.defaultFg); // Use this.defaultFg bgStack.push(null); } if (charStack.length > 0) { this._display.draw(sx, sy, charStack, fgStack, bgStack); } } } if (this._message) { this._drawCenteredText(this._screenHeight - 1, this._message); } }, _drawCenteredText: function(y, text, color) { // Restored color parameter if (!this._ensureDisplay()) return y; const x = Math.floor((this._screenWidth - text.length) / 2); const finalColor = color || this.defaultFg; // Use provided color or defaultFg this._display.drawText(x, y, `%c{${finalColor}}${text}`); // Use color in drawText return y + 1; }, _isVisible: function(x, y) { return this._visibleTiles[y] && this._visibleTiles[y][x] !== undefined; }, _drawBorder: function() { // NEW SIGNATURE if (!this._ensureDisplay()) return; // console.log("Drawing border. Tile mode: " + this._isTileMode); for (var i = 0; i < this._screenWidth; i++) { // this._display.draw(i, 0, "-", this._getColor("border_fg"), this._getColor("border_bg")); // OLD // this._display.draw(i, this._screenHeight - 1, "-", this._getColor("border_fg"), this._getColor("border_bg")); // OLD this._display.draw(i, 0, "-", this.defaultFg, null); // NEW this._display.draw(i, this._screenHeight - 1, "-", this.defaultFg, null); // NEW } for (var j = 1; j < this._screenHeight - 1; j++) { // this._display.draw(0, j, "|", this._getColor("border_fg"), this._getColor("border_bg")); // OLD // this._display.draw(this._screenWidth - 1, j, "|", this._getColor("border_fg"), this._getColor("border_bg")); // OLD this._display.draw(0, j, "|", this.defaultFg, null); // NEW this._display.draw(this._screenWidth - 1, j, "|", this.defaultFg, null); // NEW } }, _drawWelcomeScreen: function() { if (!this._ensureDisplay()) return; this.clearDisplay(); // console.log("Drawing welcome screen. Tile mode: " + this._isTileMode); let y = Math.floor(this._screenHeight / 4); y = this._drawCenteredText(y, "THE WOWNGEON", this.defaultFg); // Use this.defaultFg y = this._drawCenteredText(y + 2, "A Wownero Roguelike", this.defaultFg); // Use this.defaultFg // Original text regarding game start y = this._drawCenteredText(y + 3, "Type 'ENTER' in chat", this.defaultFg); y = this._drawCenteredText(y + 1, "to begin your descent...", this.defaultFg); this._drawCenteredText(y + 3, "Good luck, adventurer!", this.defaultFg); }, drawWinScreen: function(hasTreasure) { if (!this._ensureDisplay()) return; this.clearDisplay(); // console.log("Drawing win screen. Tile mode: " + this._isTileMode); let y = this._drawCenteredText(Math.floor(this._screenHeight / 3), "CONGRATULATIONS!"); if (hasTreasure) { y = this._drawCenteredText(y + 2, "You escaped with the treasure!"); } else { y = this._drawCenteredText(y + 2, "You escaped the dungeon!"); } this._drawCenteredText(y + 3, "A true hero of Wownero!"); }, drawLoseScreen: function(reason) { if (!this._ensureDisplay()) return; this.clearDisplay(); // console.log("Drawing lose screen. Tile mode: " + this._isTileMode); let y = this._drawCenteredText(Math.floor(this._screenHeight / 3), "YOU HAVE PERISHED"); if (reason === 'monster') { y = this._drawCenteredText(y + 2, "Slain by a fearsome beast."); } else if (reason === 'timeout') { y = this._drawCenteredText(y + 2, "The dungeon claimed you before the next block."); } else { y = this._drawCenteredText(y + 2, "Your adventure ends here."); } this._drawCenteredText(y + 3, "Better luck next time."); }, drawWaitingScreen: function() { if (!this._ensureDisplay()) return; this.clearDisplay(); // Clear it off // console.log("Drawing waiting screen"); var text = "Waiting for server..."; // Use _drawCenteredText for consistency and color handling this._drawCenteredText(Math.floor(this._screenHeight / 2), text, this.defaultFg); }, // Method to stop the waiting animation stopWaitingScreen: function() { if (!this._ensureDisplay()) return; this.clearDisplay(); // Clear it off // console.log("Stopping waiting screen (clearing display)"); // Typically, another screen (like game screen) will be drawn immediately after. }, // Helper function to draw centered text _drawCenteredText: function(y, text, color) { // Restored color parameter if (!this._ensureDisplay()) return y; const x = Math.floor((this._screenWidth - text.length) / 2); const finalColor = color || this.defaultFg; // Use provided color or defaultFg this._display.drawText(x, y, `%c{${finalColor}}${text}`); // Use color in drawText return y + 1; }, switchToAsciiMode: function() { console.log("Game.switchToAsciiMode called."); this._isTileMode = false; window.options.layout = "rect"; if (this._display) { console.log("Display exists, attempting to re-initialize for ASCII mode."); var gameDisplayContainer = document.getElementById("game-display"); if (gameDisplayContainer) gameDisplayContainer.innerHTML = '

Switching to ASCII mode, display will re-initialize...

'; this._display = null; this._displayReady = false; this.init(); // Re-trigger initialization } else { console.log("Display does not exist yet, init() will handle ASCII mode based on _isTileMode flag."); this.init(); // This will now initialize in ASCII mode due to the flag } }, switchToTileMode: function() { console.log("Game.switchToTileMode called."); this._isTileMode = true; window.options.layout = "tile"; if (this._display) { console.log("Display exists, attempting to re-initialize for TILE mode."); var gameDisplayContainer = document.getElementById("game-display"); if (gameDisplayContainer) gameDisplayContainer.innerHTML = '

Switching to TILE mode, display will re-initialize...

'; this._display = null; this._displayReady = false; this.init(); // Re-trigger initialization } else { console.log("Display does not exist yet, init() will handle TILE mode based on _isTileMode flag."); this.init(); // This will now initialize in TILE mode due to the flag } }, // New method to handle player movement movePlayer: function(dx, dy) { if (!this._ensureDisplay()) return; const newX = this._player.x + dx; const newY = this._player.y + dy; // Basic bounds checking if (newX < 0 || newY < 0 || newX >= this._screenWidth || newY >= this._screenHeight) { console.warn("Attempted to move player outside of bounds:", newX, newY); return; } // Check if the new position is a wall (1) or out of map bounds if (this._map[newY] && this._map[newY][newX] !== undefined && this._map[newY][newX] !== 1) { // Move the player this._player.x = newX; this._player.y = newY; console.log("Player moved to:", this._player.x, this._player.y); // Update visibility and redraw this._updateExploredTiles(); this._drawGameScreen(); } else { console.log("Move blocked by wall or invalid map data at:", newX, newY); } }, // Debug function to print the map to console debugPrintMap: function() { console.log("Current game map:"); for (let y = 0; y < this._screenHeight; y++) { let row = ""; for (let x = 0; x < this._screenWidth; x++) { if (this._map[y] && this._map[y][x] !== undefined) { row += this._map[y][x] + " "; } else { row += "? "; // Unknown/undefined area } } console.log(row); } } }; // Additional global or helper functions can be added here