Files
wownerogue/html/js/game.js
T
2025-05-17 06:42:50 -04:00

720 lines
35 KiB
JavaScript

// 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 <canvas> 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 = '<p style="color:white;">Switching to ASCII mode, display will re-initialize...</p>';
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 = '<p style="color:white;">Switching to TILE mode, display will re-initialize...</p>';
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