Fix logic and start cleanup refactor

This commit is contained in:
jwinterm
2025-05-23 20:28:49 -04:00
parent 5c6342db6e
commit 8c0117ea88
11 changed files with 871 additions and 180 deletions
+2
View File
@@ -15,6 +15,8 @@ The game is built using Node.js for the backend, with Express and Socket.io for
- **Monster AI**: Evade monster as you navigate the dungeon
- **Treasure hunting**: Find treasure for bonus rewards
- **Wownero integration**: Players pay an entrance fee (10-100 WOW) and can win rewards
- **Chat system**: Players can communicate in real-time during gameplay
- **View other players' game states**: Spectate other players' dungeons in real-time
### Technology Stack:
- Backend: Node.js with Express and Socket.io
+14 -10
View File
@@ -157,7 +157,9 @@ class Game {
}
if (this.dungeon.treasure && newX === this.dungeon.treasure[0] && newY === this.dungeon.treasure[1] && !this.player.hasTreasure) {
this.player.hasTreasure = true;
// Remove treasure from map or mark as collected
// Remove treasure from dungeon so it won't be sent in future updates
this.dungeon.treasure = null;
console.log(`Player ${this.socketId} collected treasure! hasTreasure: ${this.player.hasTreasure}`);
return { status: 'moved', event: 'treasure_found', player: this.player, visibleTiles: this.visibleTiles };
}
@@ -214,19 +216,20 @@ class Game {
// So, no conversion to relative coordinates is needed here for visibleTiles.
// Just ensure this.visibleTiles is correctly populated by updateFOV.
// console.log("getState called. Player:", this.player, "Visible tiles keys:", Object.keys(this.visibleTiles || {}).length);
return {
const state = {
gameState: this.gameState,
player: { ...this.player }, // Send a copy of player object
monster: this.monster ? { ...this.monster } : null, // Send a copy if monster exists
// items: { ...this.items }, // If you have items, send them too
visibleTiles: { ...this.visibleTiles }, // Send a copy of visible tiles
// You might also want to send entrance/exit if they are always known or become known
// entrance: this.dungeon ? this.dungeon.entrance : null,
// exit: this.dungeon ? this.dungeon.exit : null,
// treasure: this.dungeon ? this.dungeon.treasure : null
entrance: this.dungeon ? this.dungeon.entrance : null,
exit: this.dungeon ? this.dungeon.exit : null,
treasure: this.dungeon ? this.dungeon.treasure : null,
};
// Debug logging to verify entity data
console.log("🎮 getState() sending entities - Monster:", state.monster, "Entrance:", state.entrance, "Exit:", state.exit, "Treasure:", state.treasure);
return state;
}
}
@@ -249,4 +252,5 @@ function checkMonsterKill(player, monster) {
return false; // Player is safe
}
module.exports = Game;
module.exports = Game;
module.exports.checkMonsterKill = checkMonsterKill;
+64 -56
View File
@@ -19,7 +19,16 @@
<script src="js/rot.min.js"></script>
<script src="js/options.js"></script>
<script src="js/teletype.js"></script> <!-- Load teletype.js -->
<!-- Use the original game.js with fixes -->
<script src="js/game.js"></script>
<!-- Modular game files - keeping as backup for now -->
<!-- <script src="js/displayManager.js"></script>
<script src="js/screenManager.js"></script>
<script src="js/gameState.js"></script>
<script src="js/inputHandler.js"></script>
<script src="js/game_refactored.js"></script> -->
</head>
<body>
<div class="container">
@@ -110,6 +119,21 @@
scrollChat();
});
// Handle waiting status updates
socket.on('waiting_status', function(data) {
console.log("🕒 WAITING STATUS received:", data);
if (data.status === 'waiting') {
$('#messages').append($('<li style="color:#ff0;">').text(data.message));
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
}
scrollChat();
});
socket.on('message', function(msg) {
$('#messages').append($('<li>').text(msg));
scrollChat();
@@ -120,31 +144,28 @@
console.log("🎮 Game start received with data:", data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
if (typeof Game !== 'undefined') {
if (typeof Game !== 'undefined' && Game.stopWaitingScreen) {
Game.stopWaitingScreen();
}
if (!data) {
$('#messages').append($('<li class="error">').text("Error: No game data received from server."));
if (typeof Game !== 'undefined') Game._drawWelcomeScreen();
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
return;
}
try {
// Pass player, map, monsters, items, and initialVisibleTiles if available
var success = Game.startGame(
data.player,
data.map,
data.monsters,
data.items,
data.visibleTiles // Pass initial visible tiles from server
);
// Use the original game.js structure
var success = Game.startGame(data.player, data.map, data.monster, data.items, data.visibleTiles);
console.log("Game start result:", success ? "SUCCESS" : "FAILED");
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed. Check console for details."));
if (typeof Game !== 'undefined') Game._drawWelcomeScreen();
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
} else {
// Draw the initial game screen - the game draws itself after startGame()
console.log("Game started successfully, initial screen should be drawn");
// Shift focus to the game display area after successful game start
$('#game-display').focus();
updateFocusIndicator();
@@ -160,20 +181,8 @@
socket.on('game_update', function(data) {
// console.log("🎮 GAME UPDATE received:", data); // Reduced verbosity
if (typeof Game !== 'undefined' && Game._gameActive) { // Ensure game is active
if (data.player) Game._player = data.player;
if (data.visibleTiles) {
Game._visibleTiles = data.visibleTiles;
Game._updateExploredTiles(); // Update explored based on new visibility
}
if (data.monster) Game._monster = data.monster; // Update monster data
if (data.items) Game._items = data.items; // Update items data
// Potentially update _entrance, _exit, _treasure if they are part of game_update
// Game._entrance = data.entrance;
// Game._exit = data.exit;
// Game._treasure = data.treasure;
Game._drawGameScreen();
if (typeof Game !== 'undefined' && Game._gameActive) { // Use Game._gameActive instead of gameState
Game.updateGameState(data); // Call updateGameState directly on Game object
} else {
// console.log("Game not active or Game undefined, skipping game_update draw.");
}
@@ -198,7 +207,7 @@
console.log("🔑 'ENTER' COMMAND DETECTED - expecting game_start response");
// Show waiting screen
if (typeof Game !== 'undefined') {
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
// Focus will be shifted in the 'game_start' handler after success
@@ -221,11 +230,11 @@
$('#game-display').attr('tabindex', '-1'); // Make it focusable
$(document).on('keydown', function(e) {
// console.log(`Keydown event: ${e.key}`, "Active element:", document.activeElement, "Game active?:", Game ? Game._gameActive : "Game undefined");
// console.log(`Keydown event: ${e.key}`, "Active element:", document.activeElement, "Game active?:", Game && Game.gameState ? Game.gameState.gameActive : "Game undefined");
if (document.activeElement === $('#game-display')[0]) {
// console.log("Game display has focus.");
if (Game && Game._gameActive) {
if (Game && Game._gameActive) { // Use Game._gameActive
// console.log("Game is active. Processing key:", e.key);
let dx = 0;
let dy = 0;
@@ -248,7 +257,7 @@
// console.log("Key pressed on game display, but not a movement key:", e.key);
}
} else {
console.log("Game display has focus, but Game is not active. Game object:", Game, "Game._gameActive:", Game ? Game._gameActive : "N/A");
console.log("Game display has focus, but Game is not active. Game object:", Game, "Game._gameActive:", Game && Game.gameState ? Game.gameState.gameActive : "N/A");
}
} else if (e.key === 'Enter' && document.activeElement !== $('#chatInput')[0]) {
// console.log("Enter pressed, focusing chat input.");
@@ -374,22 +383,10 @@ $(function() {
document.getElementById('toggle-mode').addEventListener('click', function() {
try {
var button = document.getElementById('toggle-mode');
// Check current mode and toggle accordingly
if (Game && Game._isTileMode) { // If currently in Tile Mode
console.log("Switching to ASCII mode...");
if (typeof Game.switchToAsciiMode === 'function') {
Game.switchToAsciiMode();
button.textContent = 'Switch to Tile Mode';
} else { console.error("Game.switchToAsciiMode is not defined"); }
} else if (Game) { // If currently in ASCII mode (or Game object exists and not in tile mode)
console.log("Switching to TILE mode...");
if (typeof Game.switchToTileMode === 'function') {
Game.switchToTileMode();
button.textContent = 'Switch to ASCII Mode';
} else { console.error("Game.switchToTileMode is not defined"); }
} else {
console.error("Game object not available to toggle mode.");
}
// Display mode switching has been removed - ASCII and tile modes are now equivalent
console.log("Display mode switching is no longer needed - modes are equivalent");
button.textContent = 'Mode switching disabled';
button.disabled = true;
} catch (e) {
console.error("Error switching display mode:", e);
}
@@ -402,15 +399,10 @@ document.getElementById('toggle-mode').addEventListener('click', function() {
socket.on('queue_cancelled', function() {
console.log("Queue cancelled, returning to welcome screen");
if (typeof Game !== 'undefined') {
// Try different naming patterns for compatibility
if (typeof Game._drawWelcomeScreen === 'function') {
Game._drawWelcomeScreen();
} else if (typeof Game.drawWelcomeScreen === 'function') {
Game.drawWelcomeScreen();
} else {
console.error("Welcome screen function not found");
}
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) {
Game._drawWelcomeScreen();
} else {
console.error("Game or _drawWelcomeScreen not available");
}
});
</script>
@@ -447,16 +439,32 @@ socket.on('game_over', function(data) {
$('#messages').append($('<li style="color:#f00; font-weight:bold;">').text(message));
// Draw lose screen
Game.drawLoseScreen(reason);
if (Game && Game.drawLoseScreen) {
Game.drawLoseScreen(reason);
}
}
// Set game as inactive to prevent further movement
Game._gameActive = false;
if (Game) {
Game._gameActive = false;
}
}
scrollChat();
});
</script>
<!-- Initialize the Game object -->
<script>
$(document).ready(function() {
console.log("Initializing Game...");
if (typeof Game !== 'undefined') {
Game.init();
} else {
console.error("Game object not found!");
}
});
</script>
</body>
</html>
+126
View File
@@ -0,0 +1,126 @@
/**
* DisplayManager - Handles all ROT.js display operations
*/
var DisplayManager = {
_display: null,
_screenWidth: 0,
_screenHeight: 0,
_displayReady: false,
_isTileMode: true,
// Visual representation constants
symbols: {
player: "@",
wall: "#",
floor: "'",
monster: "~",
exit: ">",
entrance: "<",
treasure: "$",
border: "+"
},
init: function(screenWidth, screenHeight, containerId) {
console.log("DisplayManager.init called");
this._screenWidth = screenWidth;
this._screenHeight = screenHeight;
var displayOptions = {
width: this._screenWidth,
height: this._screenHeight,
forceSquareRatio: true,
layout: "tile",
bg: window.options.bg,
fg: window.options.fg || "#FFF",
tileWidth: window.options.tileWidth,
tileHeight: window.options.tileHeight,
tileSet: window.options.tileSet,
tileMap: window.options.tileMap,
tileColorize: false
};
try {
var gameDisplayContainer = document.getElementById(containerId);
if (!gameDisplayContainer) {
console.error("Game display container not found:", containerId);
return false;
}
this._display = new ROT.Display(displayOptions);
gameDisplayContainer.innerHTML = '';
gameDisplayContainer.appendChild(this._display.getContainer());
console.log("ROT.Display initialized successfully");
this._displayReady = true;
return true;
} catch (e) {
console.error("Error initializing ROT.Display:", e);
this._displayReady = false;
return false;
}
},
isReady: function() {
return this._display && this._displayReady;
},
clear: function() {
if (!this.isReady()) return;
const display = this._display;
display._data = {};
display._dirty = {};
var canvas = display.getContainer();
if (canvas && typeof canvas.getContext === 'function') {
var ctx = canvas.getContext("2d");
if (ctx) {
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}
},
draw: function(x, y, char, fg, bg) {
if (!this.isReady()) return;
this._display.draw(x, y, char, fg, bg);
},
drawText: function(x, y, text, fg) {
if (!this.isReady()) return;
if (fg) {
this._display.drawText(x, y, `%c{${fg}}${text}`);
} else {
this._display.drawText(x, y, text);
}
},
drawCenteredText: function(y, text, fg) {
if (!this.isReady()) return y;
const x = Math.floor((this._screenWidth - text.length) / 2);
this.drawText(x, y, text, fg);
return y + 1;
},
drawBorder: function(fg) {
if (!this.isReady()) return;
const borderChar = this.symbols.border;
const color = fg || "#FFF";
for (let x = 0; x < this._screenWidth; x++) {
for (let y = 0; y < this._screenHeight; y++) {
if (x === 0 || y === 0 || x === this._screenWidth-1 || y === this._screenHeight-1) {
this.draw(x, y, borderChar, color, null);
}
}
}
},
getScreenWidth: function() {
return this._screenWidth;
},
getScreenHeight: function() {
return this._screenHeight;
}
};
+58 -79
View File
@@ -11,7 +11,6 @@ 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
@@ -28,15 +27,6 @@ var Game = {
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,
@@ -44,8 +34,6 @@ var Game = {
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
@@ -54,18 +42,6 @@ var Game = {
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");
@@ -90,47 +66,8 @@ var Game = {
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
},
}, // Removed .bind(this) - not needed for object literal methods
_ensureDisplay: function() {
if (!this._display || !this._displayReady) {
@@ -228,7 +165,12 @@ var Game = {
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._items = itemData; // Fixed: Use _items instead of _item
// Initialize entrance, exit, treasure from the game data
this._entrance = null;
this._exit = null;
this._treasure = null;
this._map = {};
this._visibleTiles = {}; // Clear/initialize visible tiles
@@ -416,6 +358,20 @@ var Game = {
this._items = data.items; // Ensure this is _items, not _item
needsRedraw = true;
}
// Update entrance, exit, treasure
if (data.entrance !== undefined) {
this._entrance = data.entrance;
needsRedraw = true;
}
if (data.exit !== undefined) {
this._exit = data.exit;
needsRedraw = true;
}
if (data.treasure !== undefined) {
this._treasure = data.treasure;
needsRedraw = true;
}
// Update visible tiles (most critical for movement feedback)
if (data.visibleTiles && typeof data.visibleTiles === 'object' && Object.keys(data.visibleTiles).length > 0) {
@@ -484,40 +440,63 @@ var Game = {
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 ? "'" : ".");
baseChar = (tileType === 1) ? '#' : "'"; // Fixed: Always use ' for floor
} 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 ? "'" : ".");
baseChar = (tileType === 1) ? '#' : "'"; // Fixed: Always use ' for floor
}
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];
// Render items (fix property name from _item to _items)
if (this._items) {
for (const itemKey in this._items) {
if (this._items.hasOwnProperty(itemKey)) {
const currentItem = this._items[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
charStack.push('$');
fgStack.push(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
// Render entrance
if (this._entrance && this._entrance[0] === wx && this._entrance[1] === wy && this._isVisible(wx, wy)) {
charStack.push('<');
fgStack.push('#0f0'); // Green for entrance
bgStack.push(null);
}
// Render exit
if (this._exit && this._exit[0] === wx && this._exit[1] === wy && this._isVisible(wx, wy)) {
charStack.push('>');
fgStack.push('#f0f'); // Magenta for exit
bgStack.push(null);
}
// Render treasure
if (this._treasure && this._treasure[0] === wx && this._treasure[1] === wy && this._isVisible(wx, wy)) {
charStack.push('$');
fgStack.push('#ff0'); // Yellow for treasure
bgStack.push(null);
}
// Render monster
if (this._monster && this._monster.x === wx && this._monster.y === wy && this._isVisible(wx, wy)) {
charStack.push('~');
fgStack.push('#f00'); // Red for monster
bgStack.push(null);
}
// Render player (always on top)
if (wx === playerWX && wy === playerWY) {
charStack.push('@');
fgStack.push(this.defaultFg); // Use this.defaultFg
fgStack.push(this.defaultFg);
bgStack.push(null);
}
+147
View File
@@ -0,0 +1,147 @@
/**
* GameState - Manages game state and data
*/
var GameState = {
player: null,
monster: null,
entrance: null,
exit: null,
treasure: null,
visibleTiles: {},
exploredTiles: {},
gameActive: false,
map: {},
items: {},
init: function() {
this.reset();
},
reset: function() {
this.player = null;
this.monster = null;
this.entrance = null;
this.exit = null;
this.treasure = null;
this.visibleTiles = {};
this.exploredTiles = {};
this.gameActive = false;
this.map = {};
this.items = {};
},
startGame: function(data) {
console.log("Starting game with data:", data);
if (!data) {
console.error("No game data received!");
return false;
}
try {
this.gameActive = true;
// Set player position
this.player = data.player || { x: 12, y: 12 };
this.monster = data.monster;
this.entrance = data.entrance;
this.exit = data.exit;
this.treasure = data.treasure;
// Set visible tiles
if (data.visibleTiles && typeof data.visibleTiles === 'object') {
this.visibleTiles = data.visibleTiles;
console.log("Visible tiles:", Object.keys(this.visibleTiles).length, "rows");
} else {
// Create a basic visible area around player
console.log("No visible tiles provided, creating basic view");
this.visibleTiles = {};
const px = this.player.x;
const py = this.player.y;
for (let y = py-2; y <= py+2; y++) {
if (!this.visibleTiles[y]) this.visibleTiles[y] = {};
for (let x = px-2; x <= px+2; x++) {
this.visibleTiles[y][x] = 0; // Floor tiles
}
}
}
// Update explored tiles
this.updateExploredTiles();
return true;
} catch (err) {
console.error("Error starting game:", err);
return false;
}
},
updateGameState: function(data) {
console.log("🎲 Updating game state with:", data);
if (!data) {
console.error("No update data received!");
return;
}
try {
// Update player position
if (data.player) {
console.log("Player position updated:", data.player);
this.player = data.player;
}
// Update monster position
if (data.monster !== undefined) {
this.monster = data.monster;
}
// Update visible tiles
if (data.visibleTiles && Object.keys(data.visibleTiles).length > 0) {
console.log("Visible tiles updated:", Object.keys(data.visibleTiles).length, "rows");
this.visibleTiles = data.visibleTiles;
this.updateExploredTiles();
}
return true;
} catch (err) {
console.error("Error updating game state:", err);
return false;
}
},
updateExploredTiles: function() {
// Add all currently visible tiles to explored tiles
for (var y in this.visibleTiles) {
if (!this.exploredTiles[y]) this.exploredTiles[y] = {};
for (var x in this.visibleTiles[y]) {
this.exploredTiles[y][x] = this.visibleTiles[y][x];
}
}
},
setGameActive: function(active) {
this.gameActive = active;
},
isGameActive: function() {
return this.gameActive;
},
getGameData: function() {
return {
player: this.player,
monster: this.monster,
entrance: this.entrance,
exit: this.exit,
treasure: this.treasure,
visibleTiles: this.visibleTiles,
exploredTiles: this.exploredTiles,
gameActive: this.gameActive,
map: this.map,
items: this.items
};
}
};
+139
View File
@@ -0,0 +1,139 @@
// 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);
}
};
}
/**
* Main Game object - coordinates all the managers
*/
var Game = {
// Managers
displayManager: DisplayManager,
screenManager: ScreenManager,
inputHandler: InputHandler,
gameState: GameState,
// Config
_screenWidth: options.width,
_screenHeight: options.height,
init: function() {
console.log("Game.init called");
// Initialize all managers
if (!this.displayManager.init(this._screenWidth, this._screenHeight, "game-display")) {
console.error("Failed to initialize display manager");
return false;
}
this.screenManager.init(this.displayManager);
this.gameState.init();
this.inputHandler.init();
// Set up input handler events
const self = this;
this.inputHandler.on('movement', function(direction) {
console.log("Movement input received:", direction);
});
// Draw welcome screen
this.screenManager.drawWelcomeScreen();
console.log("Game initialization complete");
return true;
},
startGame: function(data) {
console.log("🎮 Game start received with data:", data);
if (!this.gameState.startGame(data)) {
console.error("Failed to start game");
return false;
}
// Activate input handling
this.inputHandler.setGameActive(true);
this.gameState.setGameActive(true);
// Draw the game screen
this.screenManager.drawGameScreen(this.gameState.getGameData());
return true;
},
updateGameState: function(data) {
if (!this.gameState.updateGameState(data)) {
console.error("Failed to update game state");
return;
}
// Redraw the screen if game is active
if (this.gameState.isGameActive()) {
this.screenManager.drawGameScreen(this.gameState.getGameData());
}
},
showWinScreen: function(hasTreasure) {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawWinScreen(hasTreasure);
},
showLoseScreen: function(reason) {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawLoseScreen(reason);
},
showWaitingScreen: function() {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawWaitingScreen();
},
showWelcomeScreen: function() {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawWelcomeScreen();
},
clearDisplay: function() {
this.displayManager.clear();
}
};
// Socket event handlers
if (typeof window !== 'undefined' && window.socket) {
socket.on('game_start', function(data) {
console.log("🎮 Game start received with data:", data);
Game.startGame(data);
});
socket.on('game_update', function(data) {
Game.updateGameState(data);
});
socket.on('waiting_status', function(data) {
console.log("Waiting status received:", data);
Game.showWaitingScreen();
});
socket.on('queue_cancelled', function() {
console.log("Queue cancelled, returning to welcome screen");
Game.showWelcomeScreen();
});
socket.on('game_over', function(data) {
console.log("Game over received:", data);
if (data.status === 'won') {
Game.showWinScreen(data.hasTreasure);
} else {
Game.showLoseScreen(data.reason);
}
});
}
+85
View File
@@ -0,0 +1,85 @@
/**
* InputHandler - Manages keyboard input and translates to game actions
*/
var InputHandler = {
gameActive: false,
eventCallbacks: {},
init: function() {
console.log("Initializing keyboard handler");
const self = this;
window.addEventListener('keydown', function(e) {
self.handleKeyDown(e);
});
console.log("Keyboard handler initialized");
},
setGameActive: function(active) {
this.gameActive = active;
},
on: function(eventName, callback) {
if (!this.eventCallbacks[eventName]) {
this.eventCallbacks[eventName] = [];
}
this.eventCallbacks[eventName].push(callback);
},
emit: function(eventName, data) {
if (this.eventCallbacks[eventName]) {
this.eventCallbacks[eventName].forEach(function(callback) {
callback(data);
});
}
},
handleKeyDown: function(e) {
if (!this.gameActive) {
return;
}
let direction = null;
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:
return;
}
e.preventDefault();
console.log(`Key pressed: ${e.key}, sending direction: ${direction}`);
// Emit movement event
this.emit('movement', direction);
// Send to server via socket
if (window.socket && direction) {
window.socket.emit('move', direction);
}
}
};
+1 -32
View File
@@ -23,46 +23,15 @@ tileSet.onload = function() {
tileSet.onerror = function() {
console.error("❌ Failed to load tileset image!");
alert("Tileset image failed to load. Game will attempt to use ASCII mode.");
window.globalTileSetStatus = "error"; // Set a status for Game.js to check if needed
// Force ASCII mode when tileset fails to load
// REMOVED: if (typeof Game !== 'undefined' && Game._display) {
// REMOVED: Game.switchToAsciiMode();
// REMOVED: }
};
// Try different possible paths (to handle serving from different directories)
tileSet.src = "tiles.png"; // Try default path first
// Add fallback function if main path fails
setTimeout(function() {
if (!tileSetLoaded) {
console.warn("Tileset not loaded yet, trying alternate paths...");
tileSet.src = "./tiles.png"; // Try with explicit relative path
setTimeout(function() {
if (!tileSetLoaded) {
tileSet.src = "/tiles.png"; // Try from root
setTimeout(function() {
if (!tileSetLoaded && window.globalTileSetStatus !== "error") { // Check if error already handled
console.error("Failed to load tileset after trying multiple paths");
alert("Could not load tileset. Game will attempt to use ASCII mode.");
window.globalTileSetStatus = "error"; // Set a status
// REMOVED: if (typeof Game !== 'undefined' && Game._display) {
// REMOVED: Game.switchToAsciiMode();
// REMOVED: }
}
}, 1000);
}
}, 1000);
}
}, 1000);
var options = {
layout: "tile",
bg: "transparent", // Black background instead of transparent for better visibility
bg: "transparent",
// Adjusted dimensions for ~800x600 display
width: 25, // 25 tiles * 32px/tile = 800px
height: 19, // 19 tiles * 32px/tile = 608px
+195
View File
@@ -0,0 +1,195 @@
/**
* ScreenManager - Handles drawing different game screens
*/
var ScreenManager = {
displayManager: null,
defaultFg: "#FFF",
init: function(displayManager) {
this.displayManager = displayManager;
},
drawWelcomeScreen: function() {
if (!this.displayManager.isReady()) return;
console.log("Drawing welcome screen");
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
// Draw content with centered text
let y = 3;
y = this.displayManager.drawCenteredText(y, "WOWGUE", this.defaultFg);
y += 4;
// Draw instructions
y = this.displayManager.drawCenteredText(y, "Type enter to start!", this.defaultFg);
y = this.displayManager.drawCenteredText(y, "Or type help...", this.defaultFg);
y += 2;
// Draw legend
const centerX = Math.floor(this.displayManager.getScreenWidth() / 2) - 11;
const symbols = this.displayManager.symbols;
this.displayManager.draw(centerX, y, symbols.player, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- This is you", this.defaultFg);
y++;
this.displayManager.draw(centerX, y, symbols.exit, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- Escape the dungeon", this.defaultFg);
y++;
this.displayManager.draw(centerX, y, symbols.monster, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- Avoid the monster", this.defaultFg);
y++;
this.displayManager.draw(centerX, y, symbols.treasure, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- Secure the bag", this.defaultFg);
y++;
// Draw footer
y += 1;
this.displayManager.drawCenteredText(17, "Type enter in chat =", this.defaultFg);
},
drawWinScreen: function(hasTreasure) {
if (!this.displayManager.isReady()) return;
console.log("Drawing win screen, hasTreasure:", hasTreasure);
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
let y = 5;
y = this.displayManager.drawCenteredText(y, "YOU ESCAPED!", this.defaultFg);
y += 1;
const subtitle = hasTreasure ? "WITH THE TREASURE!" : "ALIVE!";
y = this.displayManager.drawCenteredText(y, subtitle, this.defaultFg);
y += 2;
if (hasTreasure) {
y = this.displayManager.drawCenteredText(y, "You won extra Wownero!", this.defaultFg);
y += 1;
}
y += 1;
this.displayManager.drawCenteredText(y, "Type ENTER to retry", this.defaultFg);
},
drawLoseScreen: function(reason) {
if (!this.displayManager.isReady()) return;
console.log("Drawing lose screen, reason:", reason);
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
let y = 5;
y = this.displayManager.drawCenteredText(y, "YOU DIED!", this.defaultFg);
y += 1;
let subtitle = "";
if (reason === 'monster') {
subtitle = "KILLED BY THE MONSTER";
} else if (reason === 'timeout') {
subtitle = "YOU RAN OUT OF TIME";
}
if (subtitle) {
y = this.displayManager.drawCenteredText(y, subtitle, this.defaultFg);
y += 1;
}
y += 2;
this.displayManager.drawCenteredText(y, "Type ENTER to retry", this.defaultFg);
},
drawWaitingScreen: function() {
if (!this.displayManager.isReady()) return;
console.log("Drawing waiting screen");
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
const centerY = Math.floor(this.displayManager.getScreenHeight() / 2);
this.displayManager.drawCenteredText(centerY, "Waiting for next block...", this.defaultFg);
},
drawGameScreen: function(gameState) {
if (!this.displayManager.isReady()) return;
console.log("Drawing game screen...");
this.displayManager.clear();
if (!gameState.player) {
console.error("No player data in game state!");
return;
}
const centerX = Math.floor(this.displayManager.getScreenWidth() / 2);
const centerY = Math.floor(this.displayManager.getScreenHeight() / 2);
const symbols = this.displayManager.symbols;
const playerX = gameState.player.x;
const playerY = gameState.player.y;
// Draw visible tiles centered on player (absolute to relative coordinate conversion)
for (let worldY in gameState.visibleTiles) {
worldY = parseInt(worldY);
for (let worldX in gameState.visibleTiles[worldY]) {
worldX = parseInt(worldX);
// Convert world coordinates to screen coordinates relative to player
const screenX = centerX + (worldX - playerX);
const screenY = centerY + (worldY - playerY);
if (screenX >= 0 && screenY >= 0 &&
screenX < this.displayManager.getScreenWidth() &&
screenY < this.displayManager.getScreenHeight()) {
const tileType = gameState.visibleTiles[worldY][worldX];
if (tileType === 1) { // Wall
this.displayManager.draw(screenX, screenY, symbols.wall, this.defaultFg, null);
} else { // Floor
this.displayManager.draw(screenX, screenY, symbols.floor, this.defaultFg, null);
}
}
}
}
// Draw entities with stacking (now with correct coordinate conversion)
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.entrance, symbols.entrance, gameState.visibleTiles);
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.exit, symbols.exit, gameState.visibleTiles);
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.treasure, symbols.treasure, gameState.visibleTiles);
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.monster, symbols.monster, gameState.visibleTiles);
// Always draw player at center
const baseTile = gameState.visibleTiles[playerY] && gameState.visibleTiles[playerY][playerX] === 1 ?
symbols.wall : symbols.floor;
this.displayManager.draw(centerX, centerY, [baseTile, symbols.player],
[this.defaultFg, this.defaultFg], [null, null]);
},
_drawEntityStacked: function(centerX, centerY, playerX, playerY, entity, symbol, visibleTiles) {
if (!entity) return;
const entityX = Array.isArray(entity) ? entity[0] : entity.x;
const entityY = Array.isArray(entity) ? entity[1] : entity.y;
// Convert world coordinates to screen coordinates relative to player
const screenX = centerX + (entityX - playerX);
const screenY = centerY + (entityY - playerY);
if (screenX >= 0 && screenY >= 0 &&
screenX < this.displayManager.getScreenWidth() &&
screenY < this.displayManager.getScreenHeight()) {
// Check if entity is visible
if (visibleTiles[entityY] && visibleTiles[entityY][entityX] !== undefined) {
const baseTile = visibleTiles[entityY][entityX] === 1 ?
this.displayManager.symbols.wall : this.displayManager.symbols.floor;
this.displayManager.draw(screenX, screenY, [baseTile, symbol],
[this.defaultFg, this.defaultFg], [null, null]);
}
}
}
};
+40 -3
View File
@@ -131,7 +131,11 @@ function checkGamesTimeout(currentHeight) {
activeGames.forEach((game, socketId) => {
const user = getUserBySocket(socketId);
if (user && user.blockRec && currentHeight > user.blockRec + 1) {
// Player must escape before the next block after their start block
// If they started on block 2, they must escape before block 3 is found
if (user && user.blockRec && currentHeight > user.blockRec) {
console.log(`Game timeout for player ${socketId}: started on block ${user.blockRec}, current block ${currentHeight}`);
// Game has timed out - player didn't escape in time
game.gameState = 'lost';
io.to(socketId).emit('game_over', {
@@ -241,8 +245,41 @@ io.on('connection', function(socket) {
const moveResult = game.movePlayer(moveData.dx, moveData.dy); // This should update player pos and FOV
if (moveResult && moveResult.status === 'moved') {
// Optional: Monster moves after player
// game.moveMonster();
// Move monster after player moves
game.moveMonster();
// Check if monster caught player
const GameModule = require('./game');
const checkMonsterKill = GameModule.checkMonsterKill ||
((player, monster) => monster.x === player.x && monster.y === player.y);
if (checkMonsterKill(game.player, game.monster)) {
game.gameState = 'lost';
io.to(socket.id).emit('game_over', {
status: 'lost',
reason: 'monster',
message: 'The monster caught you!'
});
activeGames.delete(socket.id);
return;
}
// Check for treasure pickup
if (moveResult.event === 'treasure_found') {
io.to(socket.id).emit('message', 'You found the treasure!');
}
// Check for escape
if (moveResult.event === 'escaped') {
game.gameState = 'won';
io.to(socket.id).emit('game_over', {
status: 'won',
reason: 'escaped',
message: 'Congratulations! You escaped the dungeon!'
});
activeGames.delete(socket.id);
return;
}
const updatedGameState = game.getState(); // Get the new state