const ROT = require('./rot.js'); const Player = require('./player.js'); const Monster = require('./monster.js'); const DungeonGenerator = require('./dungeon.js'); class Game { constructor(socketId, mapWidth, mapHeight) { this.socketId = socketId; this.width = mapWidth || 25; // Reduced default size to match client display this.height = mapHeight || 19; // Reduced default size to match client display this.gameState = 'waiting'; // waiting, active, won, lost this.startBlock = null; this.dungeon = null; this.player = new Player(); this.monster = new Monster(); this.fee = 0; // Amount player paid this.visibleTiles = {}; // Will store visible tiles this.fov = null; // FOV calculator this.generateDungeon(); } generateDungeon() { this.dungeon = DungeonGenerator.generate(this.width, this.height); if (this.dungeon.entrance) { this.player.moveTo(this.dungeon.entrance[0], this.dungeon.entrance[1]); } // Place monster far from entrance if (this.dungeon.rooms && this.dungeon.rooms.length > 2) { const monsterRoom = this.dungeon.rooms[Math.floor(this.dungeon.rooms.length * 0.7)]; const center = monsterRoom.getCenter(); this.monster.moveTo(center[0], center[1]); } // Initialize FOV calculator this.fov = new ROT.FOV.PreciseShadowcasting( (x, y) => { // Return true if the tile is transparent (can see through it) return this.dungeon.map[y] && this.dungeon.map[y][x] === 0; } ); // Calculate initial FOV this.updateFOV(); // Set game state to active this.gameState = 'active'; } updateFOV() { // Reset visible tiles this.visibleTiles = {}; // Calculate new visible tiles this.fov.compute(this.player.x, this.player.y, 10, (x, y, r, visibility) => { // Added r and visibility params if (visibility > 0) { // Only add if tile is actually visible if (!this.visibleTiles[y]) { this.visibleTiles[y] = {}; } // Store the actual tile type (0 for floor, 1 for wall) from the dungeon map if (this.dungeon && this.dungeon.map && this.dungeon.map[y] && this.dungeon.map[y][x] !== undefined) { this.visibleTiles[y][x] = this.dungeon.map[y][x]; } else { // Should not happen if FOV is within map bounds, but as a fallback: // this.visibleTiles[y][x] = 1; // Default to wall if outside known map } } }); // console.log("FOV updated. Player:", this.player.x, this.player.y, "Visible tiles count:", Object.keys(this.visibleTiles).reduce((acc, k) => acc + Object.keys(this.visibleTiles[k]).length, 0)); } movePlayer(dx, dy) { const newX = this.player.x + dx; const newY = this.player.y + dy; // Debug logging for movement validation console.log(`[DEBUG] Move attempt: from (${this.player.x},${this.player.y}) to (${newX},${newY}) with dx=${dx},dy=${dy}`); console.log(`[DEBUG] Map bounds: width=${this.width}, height=${this.height}`); console.log(`[DEBUG] Dungeon exists: ${!!this.dungeon}`); if (this.dungeon && this.dungeon.map) { console.log(`[DEBUG] Map row ${newY} exists: ${!!this.dungeon.map[newY]}`); if (this.dungeon.map[newY]) { console.log(`[DEBUG] Map cell [${newY}][${newX}] value: ${this.dungeon.map[newY][newX]} (0=floor, 1=wall)`); } } // Check if the move is valid (not into a wall and within map bounds) if (this.dungeon && this.dungeon.map[newY] && this.dungeon.map[newY][newX] !== undefined && this.dungeon.map[newY][newX] === 0) { // 0 is floor this.player.moveTo(newX, newY); console.log(`Player moved to ${newX},${newY} in game for socket ${this.socketId}`); // Update FOV after moving this.updateFOV(); // Check for game events like finding treasure or exit if (this.dungeon.exit && this.player.isAt(this.dungeon.exit[0], this.dungeon.exit[1])) { // Handle win condition (e.g., escaped) this.gameState = 'won'; return { status: 'moved', event: 'escaped', player: this.player.getState(), visibleTiles: this.visibleTiles }; } if (this.dungeon.treasure && this.player.isAt(this.dungeon.treasure[0], this.dungeon.treasure[1]) && !this.player.hasTreasure) { this.player.hasTreasure = true; // 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.getState(), visibleTiles: this.visibleTiles }; } return { status: 'moved', player: this.player.getState(), visibleTiles: this.visibleTiles }; } console.log(`Player move to ${newX},${newY} for socket ${this.socketId} is invalid (wall or out of bounds).`); // Debug: Show map around player console.log(`[DEBUG] Map around player position:`); for (let y = this.player.y - 2; y <= this.player.y + 2; y++) { let row = ''; for (let x = this.player.x - 2; x <= this.player.x + 2; x++) { if (x === this.player.x && y === this.player.y) { row += '@'; // Player position } else if (x === newX && y === newY) { row += 'X'; // Attempted move position } else if (this.dungeon && this.dungeon.map[y] && this.dungeon.map[y][x] !== undefined) { row += this.dungeon.map[y][x] === 0 ? '.' : '#'; } else { row += '?'; // Out of bounds or undefined } } console.log(`[DEBUG] Y=${y}: ${row}`); } return { status: 'invalid' }; } moveMonster() { this.monster.moveTowardPlayer(this.player, this.dungeon); } // Update the getState() method to include all entities with relative positions getState() { const state = { gameState: this.gameState, player: this.player.getState(), // Use Player's getState method monster: this.monster ? this.monster.getState() : null, // Use Monster's getState method visibleTiles: { ...this.visibleTiles }, // Send a copy of visible tiles 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; } } // Check if monster killed player function checkMonsterKill(player, monster) { // Use Monster's hasCaughtPlayer method const monsterInstance = new Monster(monster.x, monster.y); return monsterInstance.hasCaughtPlayer(player); } module.exports = Game; module.exports.checkMonsterKill = checkMonsterKill;