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

252 lines
8.9 KiB
JavaScript

const ROT = require('./rot.js');
// Define the missing generateDungeon function
function generateDungeon(width, height) {
// Create dungeon using ROT.js Map.Digger
const digger = new ROT.Map.Digger(width, height, {
roomWidth: [3, 9],
roomHeight: [3, 7],
corridorLength: [2, 6],
dugPercentage: 0.2
});
// Initialize the empty map with walls (1)
const map = Array(height).fill().map(() => Array(width).fill(1));
// Fill in floors (0) using digger's callback
digger.create((x, y, value) => {
// value: 0 = floor, 1 = wall
map[y][x] = value;
});
// Get rooms that were created
const rooms = digger.getRooms();
// Place entrance in the first room
const entranceRoom = rooms[0];
const entranceCenter = entranceRoom.getCenter();
const entrance = [entranceCenter[0], entranceCenter[1]];
// Place exit in the last room
const exitRoom = rooms[rooms.length - 1];
const exitCenter = exitRoom.getCenter();
const exit = [exitCenter[0], exitCenter[1]];
// Place treasure in a middle room (not first or last)
let treasureRoom;
if (rooms.length > 2) {
treasureRoom = rooms[Math.floor(rooms.length / 2)];
} else {
// Fallback if we have fewer than 3 rooms
treasureRoom = rooms[0];
}
const treasureCenter = treasureRoom.getCenter();
// Place treasure slightly off center to make it more interesting
const treasure = [
treasureCenter[0] + Math.floor(Math.random() * 3) - 1,
treasureCenter[1] + Math.floor(Math.random() * 3) - 1
];
// Ensure the treasure is on a floor tile
if (map[treasure[1]][treasure[0]] !== 0) {
treasure[0] = treasureCenter[0];
treasure[1] = treasureCenter[1];
}
return {
map: map,
rooms: rooms,
entrance: entrance,
exit: exit,
treasure: treasure
};
}
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 = { x: 0, y: 0, hasKey: false, hasTreasure: false };
this.monster = { x: 0, y: 0 };
this.fee = 0; // Amount player paid
this.visibleTiles = {}; // Will store visible tiles
this.fov = null; // FOV calculator
this.generateDungeon();
}
generateDungeon() {
const dungeon = generateDungeon(this.width, this.height);
this.dungeon = dungeon;
if (dungeon.entrance) {
this.player.x = dungeon.entrance[0];
this.player.y = dungeon.entrance[1];
}
// Place monster far from entrance
if (dungeon.rooms && dungeon.rooms.length > 2) {
const monsterRoom = dungeon.rooms[Math.floor(dungeon.rooms.length * 0.7)];
const center = monsterRoom.getCenter();
this.monster.x = center[0];
this.monster.y = 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;
// 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.x = newX;
this.player.y = 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 && newX === this.dungeon.exit[0] && newY === this.dungeon.exit[1]) {
// Handle win condition (e.g., escaped)
this.gameState = 'won'; // Or some other state
return { status: 'moved', event: 'escaped', player: this.player, visibleTiles: this.visibleTiles };
}
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
return { status: 'moved', event: 'treasure_found', player: this.player, visibleTiles: this.visibleTiles };
}
return { status: 'moved', player: this.player, visibleTiles: this.visibleTiles };
}
console.log(`Player move to ${newX},${newY} for socket ${this.socketId} is invalid (wall or out of bounds).`);
return { status: 'invalid' };
}
moveMonster() {
// Simple monster AI to move toward player
const dx = Math.sign(this.player.x - this.monster.x);
const dy = Math.sign(this.player.y - this.monster.y);
// Try horizontal move
if (dx !== 0) {
if (this.dungeon.map[this.monster.y][this.monster.x + dx] === 0) {
this.monster.x += dx;
return;
}
}
// Try vertical move
if (dy !== 0) {
if (this.dungeon.map[this.monster.y + dy][this.monster.x] === 0) {
this.monster.y += dy;
return;
}
}
// Try random direction if direct path is blocked
const dirs = [[0,1],[1,0],[0,-1],[-1,0]];
const shuffledDirs = ROT.RNG.shuffle(dirs.slice());
for (const [dx, dy] of shuffledDirs) {
const nx = this.monster.x + dx;
const ny = this.monster.y + dy;
if (this.dungeon.map[ny] && this.dungeon.map[ny][nx] === 0) {
this.monster.x = nx;
this.monster.y = ny;
break;
}
}
}
// Update the getState() method to include all entities with relative positions
getState() {
// Calculate relative position of all entities from player's perspective
// const playerX = this.player.x; // Not needed if sending absolute visibleTiles
// const playerY = this.player.y; // Not needed if sending absolute visibleTiles
// The client-side Game.js expects absolute coordinates for visibleTiles.
// 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 {
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
};
}
}
// Check if monster killed player
function checkMonsterKill(player, monster) {
// Check for direct overlap - monster on same tile as player
if (monster.x === player.x && monster.y === player.y) {
return true; // Monster killed player
}
// Check for adjacent tiles (optional - if you want 1-tile proximity kills)
const dx = Math.abs(monster.x - player.x);
const dy = Math.abs(monster.y - player.y);
// If monster is adjacent (one tile away in any direction)
if ((dx <= 1 && dy === 0) || (dx === 0 && dy <= 1)) {
return true; // Monster killed player
}
return false; // Player is safe
}
module.exports = Game;