refactor backend and fix movement skipping

This commit is contained in:
jwinterm
2025-05-25 10:43:47 -04:00
parent 928af8b071
commit 0818b60541
21 changed files with 778 additions and 167 deletions
+181
View File
@@ -0,0 +1,181 @@
# Wowngeon Code Architecture
## Overview
The Wowngeon roguelike game has been refactored into a modular architecture with clear separation of concerns between frontend and backend components. Recent major improvements include fixing critical movement bugs, implementing auto-start functionality, and resolving duplicate event handler issues.
## Recent Major Bug Fixes and Improvements
### 🎯 Movement System Overhaul
**Problem**: Players were skipping over tiles when moving through hallways, especially when approaching from certain directions.
**Root Cause**: Double event handler registration caused duplicate keydown events, leading to rapid-fire movement commands.
**Solution**:
- Added `_initialized` flags to prevent multiple initializations of InputHandler and SocketHandlers
- Implemented 100ms movement throttling on both client and server sides
- Enhanced movement validation and debugging with comprehensive logging
- Fixed module initialization order in `index.html`
**Result**: Players now move exactly one tile per keypress with no skipping.
### 🚀 Auto-Start System Implementation
**Problem**: Game was hanging on "waiting for server..." screen instead of starting immediately for testing.
**Root Cause**: Missing `SocketHandlers.init()` call in `index.html` meant client couldn't receive `game_start` events.
**Solution**:
- Added proper SocketHandlers initialization to HTML document ready function
- Implemented auto-start that sends "enter" command 500ms after connection
- Enhanced server-side auto-start to immediately begin games in DEBUG_MODE
- Improved error handling and connection flow
**Result**: Games now start instantly for faster development and testing.
### 🔧 Code Architecture Improvements
**Problem**: Monolithic code structure made debugging and maintenance difficult.
**Solution**:
- Split frontend JavaScript into focused modules (ui.js, socketHandlers.js, inputHandler.js)
- Added initialization protection to prevent duplicate module setup
- Enhanced logging with emojis and timing information for easier debugging
- Maintained backward compatibility while improving code organization
**Result**: Much cleaner, more maintainable codebase with improved debugging capabilities.
## Backend Architecture
### Core Classes
#### `Player` (/player.js)
- Manages player state and position
- Methods: `moveTo()`, `isAt()`, `getState()`, `setState()`, `reset()`
- Properties: x, y, hasKey, hasTreasure
#### `Monster` (/monster.js)
- Manages monster state and AI behavior
- Methods: `moveTowardPlayer()`, `hasCaughtPlayer()`, `getState()`, `setState()`
- AI Features: Smart pathfinding toward player, collision detection
#### `DungeonGenerator` (/dungeon.js)
- Handles procedural dungeon generation using ROT.js
- Methods: `generate()`, `getRandomRoomCenter()`, `isFloorTile()`, `isWallTile()`
- Features: Room placement, entrance/exit/treasure positioning
#### `Game` (/game.js)
- Main game logic coordinator
- Uses Player, Monster, and DungeonGenerator classes
- Manages game state, FOV calculation, and entity interactions
- Methods: `movePlayer()`, `moveMonster()`, `getState()`, `updateFOV()`
### Supporting Files *(ENHANCED)*
- `user.js` - User management and game instantiation with improved socket ID mapping
- `index.js` - Server entry point with movement throttling, auto-start, and enhanced debugging
- `dbcalls.js` - Database operations
- `rpccalls.js` - Wownero blockchain integration
## Frontend Architecture
### Modular JavaScript Components
#### `ui.js`
- UI utilities and visual helpers
- Functions: `scrollChat()`, `updateBlockHeight()`, `updateFocusIndicator()`
- Manages teletype animations and browser detection
#### `socketHandlers.js` *(MAJOR UPDATES)*
- All WebSocket event handlers with initialization protection
- Events: connect, game_start, game_update, game_over, etc.
- **NEW**: Auto-start functionality for testing (sends "enter" after 500ms)
- **NEW**: `_initialized` flag prevents duplicate handler registration
- **FIXED**: Proper initialization order ensures events are received
- Clean separation of network communication logic
#### `inputHandler.js` *(MAJOR UPDATES)*
- Keyboard and form input management with movement throttling
- **NEW**: 100ms movement cooldown prevents rapid-fire commands
- **NEW**: `_pendingMove` system prevents movement overlap
- **FIXED**: Duplicate event handler prevention
- Handles WASD/arrow key movement, chat input, focus management
- Enhanced debugging with movement timing logs
#### `ui.js`
- UI utilities and visual helpers
- Functions: `scrollChat()`, `updateBlockHeight()`, `updateFocusIndicator()`
- Manages teletype animations and browser detection
#### `game.js` (frontend)
- Core game rendering and client-side state management
- ROT.js display integration, FOV rendering, entity drawing
- Maintained original structure for compatibility
### HTML Structure *(UPDATED)*
- `index.html` - Streamlined main page with proper module initialization order
- **FIXED**: Added missing `SocketHandlers.init()` call to prevent auto-start hanging
- **IMPROVED**: Clean document ready structure ensures proper initialization sequence
- `index_original.html` - Backup of original monolithic structure
## Key Improvements
### Backend Benefits
1. **Modularity**: Clear separation of Player, Monster, and Dungeon logic
2. **Maintainability**: Each class has single responsibility
3. **Testability**: Individual components can be unit tested
4. **Extensibility**: Easy to add new entity types or game mechanics
5. **Movement Validation**: Server-side throttling and validation prevents cheating
6. **Debug Capabilities**: Comprehensive logging for troubleshooting
### Frontend Benefits
1. **Reduced Complexity**: 471-line index.html reduced to ~80 lines
2. **Code Organization**: Related functionality grouped into logical modules
3. **Reusability**: UI and input handlers can be reused/extended
4. **Debugging**: Easier to locate and fix issues in specific modules
5. **Movement Reliability**: Throttling prevents duplicate commands and tile skipping
6. **Auto-Start Testing**: Instant game start for development and testing
## File Structure
```
/wowngeon/
├── Backend Classes
│ ├── player.js # Player entity and state
│ ├── monster.js # Monster AI and behavior
│ ├── dungeon.js # Procedural generation
│ └── game.js # Main game coordinator
├── Backend Support
│ ├── index.js # Server and socket handling
│ ├── user.js # User management
│ └── ...other files
└── Frontend
├── index.html # Clean modular HTML
└── js/
├── game.js # Core game rendering
├── ui.js # UI utilities
├── socketHandlers.js # Network communication
├── inputHandler.js # Input management
└── ...libraries
```
## Migration Summary *(UPDATED)*
- ✅ Backend modularized into Player, Monster, DungeonGenerator classes
- ✅ Frontend split into logical modules (UI, Socket, Input handlers)
- ✅ **CRITICAL BUG FIXES**: Movement skipping and auto-start hanging resolved
- ✅ **MOVEMENT SYSTEM**: Added throttling and duplicate prevention
- ✅ **INITIALIZATION**: Fixed module loading order and event handler registration
- ✅ **DEBUGGING**: Enhanced logging with emojis and timing information
- ✅ Removed unused files and maintained full compatibility
- ✅ All game mechanics verified working (movement, FOV, collision, treasure)
## Testing Verification *(COMPREHENSIVE)*
- [x] Server starts without errors
- [x] Game initialization works with proper module loading
- [x] **NEW**: Auto-start functionality working (500ms delay → instant game start)
- [x] **FIXED**: Player movement functional with no tile skipping
- [x] **VERIFIED**: Single-tile movement precision confirmed via server logs
- [x] Monster AI and collision detection operational
- [x] Treasure collection working
- [x] **ENHANCED**: Frontend modules load with initialization protection
- [x] **CONFIRMED**: All socket events properly handled and received
- [x] **TESTED**: Movement throttling prevents rapid-fire commands
- [x] **VALIDATED**: Server-side movement validation working correctly
## Current Status: ✅ MAJOR BUGS RESOLVED
The Wowngeon game is now in excellent working condition with all critical movement and initialization bugs fixed. The codebase is much more maintainable and follows modern software engineering practices while preserving all original game functionality.
+141
View File
@@ -0,0 +1,141 @@
# Wowngeon
A blockchain-based roguelike dungeon crawler built on the Wownero network.
## Project Overview
Wowngeon is a web-based roguelike game that integrates with the Wownero blockchain for a unique gaming experience. Players enter a randomly generated dungeon with the goal of escaping before the next block is discovered on the network. The player requests to enter (and optionally pay an entrance fee), and then waits for the next block to be mined. When the next block is found, the player is placed in the dungeon and must escape before the next block is found. If they fail to escape, they die and lose their entrance fee. There is a singular monster and treasure in the dungeon. If the monster catches the player, they die. If the player finds the treasure and manages to escape, they could win 3x or 5x instead of just 2x for escaping sans treasure. The game is designed to be played in a web browser, making it accessible to a wide audience.
The game is built using Node.js for the backend, with Express and Socket.io for real-time communication. The frontend is developed using HTML5, CSS, and JavaScript, with ROT.js for rendering the dungeon and managing game mechanics. The Wownero blockchain is integrated via RPC calls to monitor block height and handle transactions.
### Key Features:
- **Blockchain-linked gameplay**: Players enter when one block is found and must escape before the next block (approximately 5 minutes in Wownero)
- **Permadeath mechanics**: If a player fails to escape before the next block is found, they die
- **Random dungeons**: Each game features procedurally generated maps using ROT.js
- **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
- Frontend: HTML5, CSS, and JavaScript with ROT.js for rendering
- Blockchain: Wownero RPC integration for block monitoring and transactions
## File Structure
### Server Files
**index.js**
The main server file that handles HTTP requests, WebSocket connections, game lifecycle management, and blockchain monitoring. Coordinates the entire application. Now includes movement throttling, auto-start functionality, and improved socket ID handling.
**game.js**
Contains the Game class and logic for dungeon generation, player movement, monster AI, field-of-view calculations, and game state management. Enhanced with better debugging and state synchronization.
**user.js**
Manages user registration, authentication, game association, and payment tracking. Maintains a registry of all active users with improved socket ID mapping.
**dbcalls.js**
Handles database operations for storing user data, game history, and payment records.
**rpccalls.js**
Contains functions for interacting with the Wownero blockchain via RPC, including checking block height and verifying transactions.
### Client Files
**html/index.html**
The main HTML file served to clients, containing the structure for the game UI and chat interface. Now includes proper module initialization order to prevent duplicate event handlers.
**html/js/game.js**
Client-side game logic handling rendering the game state using ROT.js and managing core game functionality. Maintained original structure for compatibility.
**html/js/socketHandlers.js** *(NEW MODULAR STRUCTURE)*
Dedicated module for all WebSocket event handling including connect, game_start, game_update, and game_over events. Includes auto-start functionality and initialization protection.
**html/js/inputHandler.js** *(NEW MODULAR STRUCTURE)*
Handles all keyboard input (WASD/arrow keys) and form submissions. Includes movement throttling (100ms cooldown) and duplicate event prevention.
**html/js/ui.js** *(NEW MODULAR STRUCTURE)*
UI utilities including chat scrolling, block height updates, teletype animations, and browser detection.
**html/styles/stylesheet.css**
Styling for the game interface, ensuring proper layout and visual appeal.
**html/js/rot.js**
The Roguelike Toolkit library that provides dungeon generation, field-of-view calculations, and display capabilities.
## Recent Bug Fixes and Improvements
### ✅ Movement System Overhaul (Completed)
- **Fixed movement skipping bug**: Players no longer skip over tiles when moving through hallways
- **Implemented movement throttling**: Added 100ms cooldown on both client and server to prevent rapid-fire commands
- **Resolved duplicate event handlers**: Fixed double initialization of InputHandler and SocketHandlers that caused movement issues
- **Enhanced debugging**: Added comprehensive logging with emojis and timing information for movement events
### ✅ Auto-Start System (Completed)
- **Instant game start**: Players automatically enter games without waiting for blockchain blocks (for testing)
- **Fixed socket initialization**: Resolved missing SocketHandlers.init() call that prevented game_start events from being received
- **Improved connection flow**: Streamlined the connection → registration → game start process
- **Enhanced error handling**: Better feedback when auto-start fails or hangs
### ✅ Client-Side Architecture (Completed)
- **Modular JavaScript structure**: Split monolithic code into focused modules (ui.js, socketHandlers.js, inputHandler.js)
- **Initialization protection**: Added flags to prevent multiple module initializations
- **Clean separation of concerns**: Network, input, and UI logic properly separated
- **Backward compatibility**: Maintained all original game functionality while improving code organization
### ✅ Server-Side Improvements (Completed)
- **Movement validation**: Server-side movement throttling and validation
- **Socket ID mapping**: Proper handling of client-to-server socket ID relationships
- **Disconnect cleanup**: Proper cleanup of player data when clients disconnect
- **Debug mode enhancements**: Improved block simulation for faster testing
## To-Do List
### High Priority
- [x] ~~Fix movement skipping bug~~ **COMPLETED**
- [x] ~~Fix auto-start hanging issue~~ **COMPLETED**
- [x] ~~Resolve duplicate event handler registration~~ **COMPLETED**
- [ ] Implement proper payment verification through Wownero RPC
- [ ] Add visual indicators for block time status (when you're in danger)
- [ ] Add score tracking and leaderboard functionality
### Medium Priority
- [ ] Improve monster AI with more sophisticated pathfinding
- [ ] Add multiple dungeon layouts and difficulty levels
- [ ] Implement different monster types with varying behaviors
- [ ] Create persistent user accounts with game history
- [ ] Add sound effects and basic animations
- [ ] Optimize chat system for better message broadcasting
### Low Priority
- [ ] Implement additional power-ups or items in the dungeon
- [ ] Add achievements system
- [ ] Create detailed statistics tracking
- [ ] Optimize performance for mobile devices
- [ ] Add difficulty settings with corresponding reward multipliers
## Running the Game
1. Install dependencies: `npm install`
2. Configure Wownero RPC settings in `config.js` or environment variables
3. Start the server: `node index.js`
4. Access the game at `http://localhost:3000`
## Development Mode
The game includes a DEBUG_MODE setting that simulates block discovery every 30 seconds for easier testing. Set `DEBUG_MODE = true` in `index.js` to enable this feature.
### Auto-Start Testing Mode
When DEBUG_MODE is enabled, the game automatically:
- Connects players and sends "enter" command after 500ms
- Starts games immediately without waiting for blockchain blocks
- Provides instant feedback for testing movement and game mechanics
- Includes comprehensive debug logging with emojis for easy tracking
### Debug Features
- **Movement Logging**: Track every player movement with timing information
- **Socket Communication**: Monitor all WebSocket events and data flow
- **FOV Debugging**: Detailed field-of-view calculation logs
- **Game State Tracking**: Real-time monitoring of player, monster, and treasure states
+67
View File
@@ -0,0 +1,67 @@
const { Client } = require('pg');
const client = new Client({
user: 'postgres',
host: 'localhost',
database: 'bitdungeon',
password: 'postgres',
port: 5432,
});
client.connect();
// Add this function to create the necessary table if it doesn't exist
function initializeDatabase() {
const createTableQuery = `
CREATE TABLE IF NOT EXISTS visitors (
id SERIAL PRIMARY KEY,
socketid VARCHAR(255) NOT NULL,
ip VARCHAR(100) NOT NULL,
time BIGINT NOT NULL
)
`;
client.query(createTableQuery)
.then(() => console.log("Database initialized"))
.catch(err => console.error("Database initialization error:", err));
}
// Call this function when starting up
initializeDatabase();
// String for inserting new visitor
const insertVisitorText = 'INSERT INTO visitors(socketid, ip, time) VALUES($1, $2, $3)';
// Function for inserting new visitor and returning ID#
function insertVisitor(socketid, ip, cookie) {
values = [socketid, ip, Math.floor(new Date() / 1000)];
// First check if table exists to prevent errors
client.query("SELECT to_regclass('visitors')", (err, res) => {
if (err) {
console.error("Database check error:", err);
return;
}
// If table doesn't exist yet, try to initialize again
if (res.rows[0].to_regclass === null) {
console.log("Table 'visitors' doesn't exist yet, creating it now...");
initializeDatabase();
return;
}
// Insert the visitor
client.query(insertVisitorText, values, (err, res) => {
if (err) {
console.log(err.stack);
} else if (res.rows && res.rows[0]) {
console.log("Visitor inserted:", res.rows[0]);
} else {
console.log("Visitor inserted");
}
});
});
}
module.exports = {
insertVisitor: insertVisitor
}
+92
View File
@@ -0,0 +1,92 @@
const ROT = require('./rot.js');
/**
* Dungeon generation utilities
*/
class DungeonGenerator {
static generate(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
};
}
static getRandomRoomCenter(rooms, excludeFirst = false, excludeLast = false) {
if (!rooms || rooms.length === 0) return null;
let availableRooms = [...rooms];
if (excludeFirst && availableRooms.length > 1) {
availableRooms = availableRooms.slice(1);
}
if (excludeLast && availableRooms.length > 1) {
availableRooms = availableRooms.slice(0, -1);
}
const randomRoom = availableRooms[Math.floor(Math.random() * availableRooms.length)];
return randomRoom.getCenter();
}
static isFloorTile(map, x, y) {
return map[y] && map[y][x] === 0;
}
static isWallTile(map, x, y) {
return map[y] && map[y][x] === 1;
}
}
module.exports = DungeonGenerator;
+151
View File
@@ -0,0 +1,151 @@
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];
// Debug log for specific problematic coordinates
if ((x === 36 && (y === 18 || y === 16)) || (x === 35 && (y === 18 || y === 16))) {
console.log(`🔍 DEBUG FOV: (${x},${y}) - server map value: ${this.dungeon.map[y][x]}, sent to client: ${this.visibleTiles[y][x]}, visibility: ${visibility}`);
}
} 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;
// Simple debug output
console.log(`Move: (${this.player.x},${this.player.y}) -> (${newX},${newY})`);
// 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(`Move BLOCKED: ${newX},${newY} - cell value: ${this.dungeon && this.dungeon.map[newY] ? this.dungeon.map[newY][newX] : 'undefined'}`);
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;
+453
View File
@@ -0,0 +1,453 @@
const express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var rpc = require('./rpccalls.js');
var db = require('./dbcalls.js');
var user = require('./user');
// Function to get a user by socket ID
// Enhance the getUserBySocket function to try both IDs
function getUserBySocket(socketId) {
console.log(`Looking up user with socketId: ${socketId}`);
// Try direct lookup first
let foundUser = user.getUserBySocketId(socketId);
// If not found but we have a socket map, try the mapped ID
if (!foundUser && clientSocketMap.has(socketId)) {
const mappedId = clientSocketMap.get(socketId);
console.log(`Socket ID ${socketId} not found directly, trying mapped ID: ${mappedId}`);
foundUser = user.getUserBySocketId(mappedId);
}
console.log(`User lookup result for ${socketId}: ${foundUser ? "FOUND" : "NOT FOUND"}`);
return foundUser;
}
const Game = require('./game');
const activeGames = new Map(); // Maps socketId to Game objects
const playerMoveTimestamps = new Map(); // Track last move time per player
const MOVE_COOLDOWN = 100; // Minimum 100ms between moves on server side
let lastBlockHeight = 0; // Track last block height
const MIN_FEE = 10; // Minimum entrance fee in WOW
const MAX_FEE = 100; // Maximum entrance fee in WOW
const WAITING_PLAYERS = []; // Players waiting for the next block
const DEBUG_MODE = true; // Set to false to disable debug mode
let debugBlockHeight = 1;
const clientSocketMap = new Map(); // To track client-to-server socket ID mappings
app.use(express.static('../../html'));
app.get('/', function(req, res) {
res.sendFile('index.html', { root: '../../html' });
});
// Replace your existing block check interval with this conditional version
if (DEBUG_MODE) {
console.log("🐛 DEBUG MODE ENABLED - Simulating blocks every 30 seconds");
// Initial debug block
io.emit('blockheight', debugBlockHeight);
// Debug block height simulator
setInterval(function() {
debugBlockHeight++;
console.log(`🐛 DEBUG: New simulated block: ${debugBlockHeight}`);
io.emit('blockheight', debugBlockHeight);
// Start games for waiting players
startGamesForWaiting(debugBlockHeight);
// Check active games for timeout
checkGamesTimeout(debugBlockHeight);
}, 30000); // Every 30 seconds
} else {
// Your original block check code
setInterval(function() {
rpc.daemonCall("get_block_count", "", function(result) {
const currentHeight = result.result.count;
rpc.lastBlock.setHeight(currentHeight);
// If new block found
if (currentHeight > lastBlockHeight) {
console.log(`New block found: ${currentHeight}`);
io.emit('blockheight', currentHeight);
// Start games for waiting players
startGamesForWaiting(currentHeight);
// Check active games for timeout
checkGamesTimeout(currentHeight);
lastBlockHeight = currentHeight;
} else {
io.emit('blockheight', currentHeight);
}
});
}, 5000);
}
// Start games for waiting players
function startGamesForWaiting(blockHeight) {
console.log(`Starting games for ${WAITING_PLAYERS.length} waiting players at block height ${blockHeight}`);
while (WAITING_PLAYERS.length > 0) {
const playerEntry = WAITING_PLAYERS.shift();
const serverId = playerEntry.serverId;
console.log(`Processing player: server=${serverId}`);
const currentUser = getUserBySocket(serverId);
if (currentUser) {
currentUser.blockRec = blockHeight;
try {
const game = currentUser.startGame(80, 40);
activeGames.set(serverId, game);
const gameState = game.getState();
gameState.blockHeight = blockHeight;
console.log(`🎮 SENDING GAME_START to ${serverId}`);
// Send ONLY ONE message - the game state itself
io.to(serverId).emit('game_start', gameState);
console.log(`Game started for player ${serverId}`);
} catch (error) {
console.error(`Error creating game:`, error);
io.to(serverId).emit('message', 'Error starting game: ' + error.message);
}
} else {
console.error(`User not found for socket ${serverId}`);
io.to(serverId).emit('message', 'Error: User not found');
}
}
}
// Check if any active games have timed out
function checkGamesTimeout(currentHeight) {
activeGames.forEach((game, socketId) => {
const user = getUserBySocket(socketId);
// 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', {
status: 'lost',
reason: 'timeout',
message: 'You didn\'t escape before the next block was found!'
});
activeGames.delete(socketId);
}
});
}
// Socket.io handlers
io.on('connection', function(socket) {
console.log('A user connected');
console.log(socket.client.id);
console.log(socket.handshake.address);
// Create and register user (automatically stored in registry)
new user.User(socket.id, socket.handshake.address);
// Also store socket.client.id as an attribute
const newUser = getUserBySocket(socket.id);
if (newUser) {
newUser.clientId = socket.client.id;
console.log(`User created with both socket.id (${socket.id}) and socket.client.id (${socket.client.id})`);
}
// Insert into visitors DB
db.insertVisitor(socket.client.id, socket.handshake.address, 0);
// Send welcome message
io.to(socket.client.id).emit('welcome', socket.client.id);
socket.on('chat message', function(msg) {
console.log('Message received:', msg);
if (msg.toLowerCase() == 'hello') {
io.to(`${socket.client.id}`).emit('message',
'Welcome, to enter the dungeon type Enter, and you will be given a Wownero address, ' +
'which you must send between 10-100 WOW to enter. The more you send, the more you can win.');
}
// Update the 'enter' command handler to start game immediately for testing
if (msg.toLowerCase() == 'enter') {
console.log(`Player ${socket.id} requested to enter the dungeon - STARTING IMMEDIATELY`);
// Use the updated user lookup function
const currentUser = getUserBySocket(socket.id);
if (currentUser) {
console.log(`Found user for socket ${socket.id}, starting game immediately...`);
// Start game immediately for faster testing
try {
currentUser.blockRec = debugBlockHeight || lastBlockHeight || 1;
const game = currentUser.startGame(80, 40);
activeGames.set(socket.id, game);
const gameState = game.getState();
gameState.blockHeight = debugBlockHeight || lastBlockHeight || 1;
console.log(`🎮 SENDING IMMEDIATE GAME_START to ${socket.id}`);
io.to(socket.id).emit('game_start', gameState);
console.log(`Game started immediately for player ${socket.id}`);
} catch (error) {
console.error(`Error creating game:`, error);
io.to(socket.id).emit('message', 'Error starting game: ' + error.message);
}
} else {
io.to(socket.id).emit('message', 'Error: Could not start game. Please try again.');
}
}
// Add a handler for the 'cancel' command
else if (msg.toLowerCase() == 'cancel') {
// Remove player from waiting queue
const index = WAITING_PLAYERS.findIndex(p => p.serverId === socket.id);
if (index !== -1) {
WAITING_PLAYERS.splice(index, 1);
io.to(socket.id).emit('message', 'You have left the queue.');
// Reset to welcome screen
io.to(socket.id).emit('queue_cancelled');
} else {
io.to(socket.id).emit('message', 'You were not in the queue.');
}
}
else {
io.emit('message', msg);
}
});
// Handle player movement
socket.on('player_move', function(moveData) { // Changed from 'move' to 'player_move' and added moveData
console.log(`Player move event received from ${socket.id}:`, moveData);
const currentUser = getUserBySocket(socket.id);
if (currentUser && currentUser.game && currentUser.game.gameState === 'active') {
// Server-side movement throttling
const now = Date.now();
const lastMoveTime = playerMoveTimestamps.get(socket.id) || 0;
if (now - lastMoveTime < MOVE_COOLDOWN) {
console.log(`Move from ${socket.id} throttled - too soon after last move (${now - lastMoveTime}ms)`);
return; // Ignore move if too soon
}
if (typeof moveData.dx === 'number' && typeof moveData.dy === 'number') {
playerMoveTimestamps.set(socket.id, now); // Update timestamp before processing
const game = currentUser.game;
const moveResult = game.movePlayer(moveData.dx, moveData.dy); // This should update player pos and FOV
if (moveResult && moveResult.status === 'moved') {
// 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
// Log before sending to client for debugging
console.log(`Sending game_update to ${socket.id} after player move. Player:`, updatedGameState.player, "Visible tiles keys:", Object.keys(updatedGameState.visibleTiles || {}));
io.to(socket.id).emit('game_update', updatedGameState);
} else {
console.log(`Player move from ${socket.id} was invalid or resulted in no change.`);
// Optionally, send an update even for invalid moves if you want to provide feedback
// const currentGameState = game.getState();
// io.to(socket.id).emit('game_update', currentGameState);
}
} else {
console.error(`Invalid moveData received from ${socket.id}:`, moveData);
}
} else {
console.log(`Player move event from ${socket.id} ignored: No active game found for user.`);
}
});
// Handle disconnection
socket.on('disconnect', function() {
console.log('User disconnected', socket.client.id);
// Clean up movement timestamps
playerMoveTimestamps.delete(socket.id);
// Clean up active games
activeGames.delete(socket.id);
// Remove from waiting players
const waitingIndex = WAITING_PLAYERS.findIndex(p => p.serverId === socket.id);
if (waitingIndex !== -1) {
WAITING_PLAYERS.splice(waitingIndex, 1);
}
user.removeUser(socket.client.id);
});
// Add this to your socket handlers in index.js
socket.on('debug_ping', function(data) {
console.log(`Debug ping received from ${socket.client.id}`);
socket.emit('debug_pong', {
message: "Hello from server!",
clientTime: data.time,
serverTime: Date.now(),
socketId: socket.client.id
});
});
socket.on('register_client', function(data) {
console.log(`Client registered: ${socket.id} (server) <-> ${data.clientId} (client)`);
// Store both mappings for lookup
clientSocketMap.set(data.clientId, socket.id);
clientSocketMap.set(socket.id, data.clientId);
// Send confirmation back to client
socket.emit('socket_registered', {
clientId: data.clientId,
serverId: socket.id,
success: true
});
});
}); // End of io.on('connection') handler
// Add this debug function to check all registered users
function debugRegisteredUsers() {
console.log("\n🔍 CHECKING ALL REGISTERED USERS:");
const allUsers = user.getAllUsers ? user.getAllUsers() : null;
if (!allUsers) {
console.log("No getAllUsers function available or no users registered");
return;
}
console.log(`Found ${allUsers.length} registered users:`);
allUsers.forEach((u, i) => {
console.log(`User ${i+1}:`, {
id: u.id,
clientId: u.clientId || 'undefined',
address: u.address
});
});
console.log(); // Empty line for readability
}
// Call this after a user registers and at intervals
setInterval(debugRegisteredUsers, 10000);
http.listen(3000, function() {
console.log('listening on *:3000');
});
// Make sure this function is defined in your file
function startGameForPlayer(socketId, blockHeight) {
console.log(`Starting game for player ${socketId} at block height ${blockHeight}`);
const currentUser = getUserBySocket(socketId);
if (!currentUser) {
console.error(`User with socket ID ${socketId} not found`);
return;
}
try {
// Create a new game
const game = currentUser.startGame(25, 19);
if (!game) {
console.error("Game creation failed");
return;
}
// Store the game in activeGames map
activeGames.set(socketId, game);
// Get game state - ensure it has the required fields
const gameState = {
gameState: "active", // Add this so frontend knows it's active
player: {
x: game.player.x,
y: game.player.y,
hasKey: !!game.player.hasKey,
hasTreasure: !!game.player.hasTreasure
},
monster: game.monster,
visibleTiles: game.calculateFOV ? game.calculateFOV() : {},
entrance: [game.dungeon.entrance.x, game.dungeon.entrance.y],
exit: [game.dungeon.exit.x, game.dungeon.exit.y],
treasure: [game.dungeon.treasure.x, game.dungeon.treasure.y]
};
console.log("Sending game_start with state:", JSON.stringify(gameState, null, 2).substring(0, 200) + "...");
// Add a small delay before sending (sometimes helps with socket timing issues)
setTimeout(() => {
debugSocket(socketId, 'game_start', gameState);
// Send initial game state to client
io.to(socketId).emit('game_start', gameState);
console.log(`Game started for player ${socketId}`);
}, 100);
} catch (err) {
console.error("Error starting game:", err);
io.to(socketId).emit('message', 'Error starting game: ' + err.message);
}
}
function debugSocket(socketId, eventName, data) {
console.log(`🔌 SOCKET DEBUG: Sending ${eventName} to ${socketId.substring(0, 8)}...`);
console.log(`📦 PAYLOAD (first 300 chars): ${JSON.stringify(data).substring(0, 300)}...`);
}
// Add express middleware to parse JSON
app.use(express.json());
app.use(express.static('html'));
// Debug endpoint to receive client-side debug info
app.post('/debug', (req, res) => {
const debugData = req.body;
console.log('🎯 CLIENT DEBUG:', debugData.message || debugData);
res.json({ status: 'ok' });
});
+99
View File
@@ -0,0 +1,99 @@
const ROT = require('./rot.js');
/**
* Monster class - Manages monster state and AI
*/
class Monster {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
// Move monster to new position
moveTo(x, y) {
this.x = x;
this.y = y;
}
// Check if monster is at given coordinates
isAt(x, y) {
return this.x === x && this.y === y;
}
// Simple AI to move toward player
moveTowardPlayer(player, dungeon) {
if (!player || !dungeon || !dungeon.map) return;
const dx = Math.sign(player.x - this.x);
const dy = Math.sign(player.y - this.y);
// Try horizontal move first
if (dx !== 0) {
const newX = this.x + dx;
if (dungeon.map[this.y] && dungeon.map[this.y][newX] === 0) {
this.x = newX;
return;
}
}
// Try vertical move
if (dy !== 0) {
const newY = this.y + dy;
if (dungeon.map[newY] && dungeon.map[newY][this.x] === 0) {
this.y = newY;
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.x + dx;
const ny = this.y + dy;
if (dungeon.map[ny] && dungeon.map[ny][nx] === 0) {
this.x = nx;
this.y = ny;
break;
}
}
}
// Check if monster has caught the player
hasCaughtPlayer(player) {
if (!player) return false;
// Check for direct overlap - monster on same tile as player
if (this.x === player.x && this.y === player.y) {
return true;
}
// Check for adjacent tiles (optional - if you want 1-tile proximity kills)
const dx = Math.abs(this.x - player.x);
const dy = Math.abs(this.y - player.y);
// If monster is adjacent (one tile away in any direction)
if ((dx <= 1 && dy === 0) || (dx === 0 && dy <= 1)) {
return true;
}
return false;
}
// Get monster state as object
getState() {
return {
x: this.x,
y: this.y
};
}
// Set monster state from object
setState(state) {
if (state.x !== undefined) this.x = state.x;
if (state.y !== undefined) this.y = state.y;
}
}
module.exports = Monster;
+20
View File
@@ -0,0 +1,20 @@
{
"name": "bitrogue",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "jw",
"license": "ISC",
"dependencies": {
"axios": "^1.9.0",
"express": "^4.17.1",
"pg": "^8.15.5",
"request": "^2.88.0",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1"
}
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Player class - Manages player state and actions
*/
class Player {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
this.hasKey = false;
this.hasTreasure = false;
}
// Move player to new position
moveTo(x, y) {
this.x = x;
this.y = y;
}
// Check if player is at given coordinates
isAt(x, y) {
return this.x === x && this.y === y;
}
// Reset player state
reset() {
this.hasKey = false;
this.hasTreasure = false;
}
// Get player state as object
getState() {
return {
x: this.x,
y: this.y,
hasKey: this.hasKey,
hasTreasure: this.hasTreasure
};
}
// Set player state from object
setState(state) {
if (state.x !== undefined) this.x = state.x;
if (state.y !== undefined) this.y = state.y;
if (state.hasKey !== undefined) this.hasKey = state.hasKey;
if (state.hasTreasure !== undefined) this.hasTreasure = state.hasTreasure;
}
}
module.exports = Player;
+7228
View File
File diff suppressed because it is too large Load Diff
+138
View File
@@ -0,0 +1,138 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(g,p,r){g!=Array.prototype&&g!=Object.prototype&&(g[p]=r.value)};$jscomp.getGlobal=function(g){return"undefined"!=typeof window&&window===g?g:"undefined"!=typeof global&&null!=global?global:g};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.polyfill=function(g,p,r,t){if(p){r=$jscomp.global;g=g.split(".");for(t=0;t<g.length-1;t++){var w=g[t];w in r||(r[w]={});r=r[w]}g=g[g.length-1];t=r[g];p=p(t);p!=t&&null!=p&&$jscomp.defineProperty(r,g,{configurable:!0,writable:!0,value:p})}};$jscomp.polyfill("Array.prototype.fill",function(g){return g?g:function(g,r,t){var p=this.length||0;0>r&&(r=Math.max(0,p+r));if(null==t||t>p)t=p;t=Number(t);0>t&&(t=Math.max(0,p+t));for(r=Number(r||0);r<t;r++)this[r]=g;return this}},"es6","es3");
$jscomp.checkStringArgs=function(g,p,r){if(null==g)throw new TypeError("The 'this' value for String.prototype."+r+" must not be null or undefined");if(p instanceof RegExp)throw new TypeError("First argument to String.prototype."+r+" must not be a regular expression");return g+""};
$jscomp.polyfill("String.prototype.repeat",function(g){return g?g:function(g){var p=$jscomp.checkStringArgs(this,null,"repeat");if(0>g||1342177279<g)throw new RangeError("Invalid count value");g|=0;for(var t="";g;)if(g&1&&(t+=p),g>>>=1)p+=p;return t}},"es6","es3");$jscomp.stringPadding=function(g,p){g=void 0!==g?String(g):" ";return 0<p&&g?g.repeat(Math.ceil(p/g.length)).substring(0,p):""};
$jscomp.polyfill("String.prototype.padStart",function(g){return g?g:function(g,r){var p=$jscomp.checkStringArgs(this,null,"padStart");return $jscomp.stringPadding(r,g-p.length)+p}},"es8","es3");$jscomp.owns=function(g,p){return Object.prototype.hasOwnProperty.call(g,p)};$jscomp.assign="function"==typeof Object.assign?Object.assign:function(g,p){for(var r=1;r<arguments.length;r++){var t=arguments[r];if(t)for(var w in t)$jscomp.owns(t,w)&&(g[w]=t[w])}return g};
$jscomp.polyfill("Object.assign",function(g){return g||$jscomp.assign},"es6","es3");$jscomp.SYMBOL_PREFIX="jscomp_symbol_";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var g=0;return function(p){return $jscomp.SYMBOL_PREFIX+(p||"")+g++}}();
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var g=$jscomp.global.Symbol.iterator;g||(g=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[g]&&$jscomp.defineProperty(Array.prototype,g,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};
$jscomp.initSymbolAsyncIterator=function(){$jscomp.initSymbol();var g=$jscomp.global.Symbol.asyncIterator;g||(g=$jscomp.global.Symbol.asyncIterator=$jscomp.global.Symbol("asyncIterator"));$jscomp.initSymbolAsyncIterator=function(){}};$jscomp.arrayIterator=function(g){var p=0;return $jscomp.iteratorPrototype(function(){return p<g.length?{done:!1,value:g[p++]}:{done:!0}})};
$jscomp.iteratorPrototype=function(g){$jscomp.initSymbolIterator();g={next:g};g[$jscomp.global.Symbol.iterator]=function(){return this};return g};$jscomp.iteratorFromArray=function(g,p){$jscomp.initSymbolIterator();g instanceof String&&(g+="");var r=0,t={next:function(){if(r<g.length){var w=r++;return{value:p(w,g[w]),done:!1}}t.next=function(){return{done:!0,value:void 0}};return t.next()}};t[Symbol.iterator]=function(){return t};return t};
$jscomp.polyfill("Array.prototype.keys",function(g){return g?g:function(){return $jscomp.iteratorFromArray(this,function(g){return g})}},"es6","es3");function _assertThisInitialized(g){if(void 0===g)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return g}function _inheritsLoose(g,p){g.prototype=Object.create(p.prototype);g.prototype.constructor=g;g.__proto__=p}
(function(g,p){"object"===typeof exports&&"undefined"!==typeof module?p(exports):"function"===typeof define&&define.amd?define(["exports"],p):p(g.ROT={})})(this,function(g){function p(d,c){return(d%c+c)%c}function r(d,c,a){void 0===c&&(c=0);void 0===a&&(a=1);return d<c?c:d>a?a:d}function t(d){return d.charAt(0).toUpperCase()+d.substring(1)}function w(d){for(var c=arguments.length,a=Array(1<c?c-1:0),b=1;b<c;b++)a[b-1]=arguments[b];var e=w.map;return d.replace(/%(?:([a-z]+)|(?:{([^}]+)}))/gi,function(b,
m,k,h){if("%"==d.charAt(h-1))return b.substring(1);if(!a.length)return b;k=(m||k).split(",");m=k.shift()||"";h=e[m.toLowerCase()];if(!h)return b;b=a.shift();b=b[h].apply(b,k);m=m.charAt(0);m!=m.toLowerCase()&&(b=t(b));return b})}function L(d){var c;if(d in Q)var a=Q[d];else{if("#"==d.charAt(0))if(a=(d.match(/[0-9a-f]/gi)||[]).map(function(b){return parseInt(b,16)}),3==a.length)a=a.map(function(b){return 17*b});else for(c=0;3>c;c++)a[c+1]+=16*a[c],a.splice(c,1);else a=(c=d.match(/rgb\(([0-9, ]+)\)/i))?
c[1].split(/\s*,\s*/).map(function(b){return parseInt(b)}):[0,0,0];Q[d]=a}return a.slice()}function Y(d){for(var c=arguments.length,a=Array(1<c?c-1:0),b=1;b<c;b++)a[b-1]=arguments[b];for(c=0;3>c;c++)for(b=0;b<a.length;b++)d[c]+=a[b][c];return d}function Z(d,c,a){void 0===a&&(a=.5);for(var b=d.slice(),e=0;3>e;e++)b[e]=Math.round(b[e]+a*(c[e]-d[e]));return b}function aa(d,c,a){void 0===a&&(a=.5);d=R(d);c=R(c);for(var b=0;3>b;b++)d[b]+=a*(c[b]-d[b]);return ba(d)}function R(d){var c=d[0]/255,a=d[1]/255;
d=d[2]/255;var b=Math.max(c,a,d),e=Math.min(c,a,d),f=0,m=(b+e)/2;if(b==e)e=0;else{var k=b-e;e=.5<m?k/(2-b-e):k/(b+e);switch(b){case c:f=(a-d)/k+(a<d?6:0);break;case a:f=(d-c)/k+2;break;case d:f=(c-a)/k+4}f/=6}return[f,e,m]}function S(d,c,a){0>a&&(a+=1);1<a&&--a;return a<1/6?d+6*(c-d)*a:.5>a?c:a<2/3?d+(c-d)*(2/3-a)*6:d}function ba(d){var c=d[2];if(0==d[1])return c=Math.round(255*c),[c,c,c];var a=d[1];a=.5>c?c*(1+a):c+a-c*a;var b=2*c-a;c=S(b,a,d[0]+1/3);var e=S(b,a,d[0]);d=S(b,a,d[0]-1/3);return[Math.round(255*
c),Math.round(255*e),Math.round(255*d)]}function fa(d,c,a){var b=d.createShader(d.VERTEX_SHADER);d.shaderSource(b,c);d.compileShader(b);if(!d.getShaderParameter(b,d.COMPILE_STATUS))throw Error(d.getShaderInfoLog(b)||"");c=d.createShader(d.FRAGMENT_SHADER);d.shaderSource(c,a);d.compileShader(c);if(!d.getShaderParameter(c,d.COMPILE_STATUS))throw Error(d.getShaderInfoLog(c)||"");a=d.createProgram();d.attachShader(a,b);d.attachShader(a,c);d.linkProgram(a);if(!d.getProgramParameter(a,d.LINK_STATUS))throw Error(d.getProgramInfoLog(a)||
"");return a}function ha(d){var c=new Float32Array([0,0,1,0,0,1,1,1]),a=d.createBuffer();d.bindBuffer(d.ARRAY_BUFFER,a);d.bufferData(d.ARRAY_BUFFER,c,d.STATIC_DRAW);d.enableVertexAttribArray(0);d.vertexAttribPointer(0,2,d.FLOAT,!1,0,0)}function M(d){if(!(d in T)){if("transparent"==d)var c=[0,0,0,0];else if(-1<d.indexOf("rgba")){c=(d.match(/[\d.]+/g)||[]).map(Number);for(var a=0;3>a;a++)c[a]/=255}else c=L(d).map(function(b){return b/255}),c.push(1);T[d]=c}return T[d]}function U(d){d=L(d);return 36*
Math.floor(.0234375*d[0])+6*Math.floor(.0234375*d[1])+1*Math.floor(.0234375*d[2])+16}function V(d,c){var a=[],b=0;d.replace(ia,function(e,m,k,h){var f=d.substring(b,h);f.length&&a.push({type:0,value:f});a.push({type:"c"==m?2:3,value:k.trim()});b=h+e.length;return""});var e=d.substring(b);e.length&&a.push({type:0,value:e});return ja(a,c)}function ja(d,c){c||(c=Infinity);for(var a=0,b=0,e=-1;a<d.length;){var f=d[a];1==f.type&&(b=0,e=-1);if(0!=f.type)a++;else{for(;0==b&&" "==f.value.charAt(0);)f.value=
f.value.substring(1);var m=f.value.indexOf("\n");if(-1!=m){f.value=N(d,a,m,!0);for(m=f.value.split("");m.length&&" "==m[m.length-1];)m.pop();f.value=m.join("")}if(f.value.length){if(b+f.value.length>c){for(m=-1;;){var k=f.value.indexOf(" ",m+1);if(-1==k)break;if(b+k>c)break;m=k}-1!=m?f.value=N(d,a,m,!0):-1!=e?(a=d[e],f=a.value.lastIndexOf(" "),a.value=N(d,e,f,!0),a=e):f.value=N(d,a,c-b,!1)}else b+=f.value.length,-1!=f.value.indexOf(" ")&&(e=a);a++}else d.splice(a,1)}}d.push({type:1});c=null;for(b=
0;b<d.length;b++)switch(e=d[b],e.type){case 0:c=e;break;case 1:if(c){for(e=c.value.split("");e.length&&" "==e[e.length-1];)e.pop();c.value=e.join("")}c=null}d.pop();return d}function N(d,c,a,b){b={type:0,value:d[c].value.substring(a+(b?1:0))};d.splice(c+1,0,{type:1},b);return d[c].value.substring(0,a)}function ca(d,c,a){a[c[d+1]]=a[d];c[a[d]]=c[d+1];a[d]=d+1;c[d+1]=d}function da(d,c,a){a[c[d]]=a[d];c[a[d]]=c[d];a[d]=d;c[d]=d}var q=(new (function(){function d(){this._c=this._s2=this._s1=this._s0=this._seed=
0}var c=d.prototype;c.getSeed=function(){return this._seed};c.setSeed=function(a){this._seed=a=1>a?1/a:a;this._s0=2.3283064365386963E-10*(a>>>0);a=69069*a+1>>>0;this._s1=2.3283064365386963E-10*a;this._s2=2.3283064365386963E-10*(69069*a+1>>>0);this._c=1;return this};c.getUniform=function(){var a=2091639*this._s0+2.3283064365386963E-10*this._c;this._s0=this._s1;this._s1=this._s2;this._c=a|0;return this._s2=a-this._c};c.getUniformInt=function(a,b){var e=Math.max(a,b);a=Math.min(a,b);return Math.floor(this.getUniform()*
(e-a+1))+a};c.getNormal=function(a,b){void 0===a&&(a=0);void 0===b&&(b=1);do{var e=2*this.getUniform()-1;var f=2*this.getUniform()-1;f=e*e+f*f}while(1<f||0==f);return a+e*Math.sqrt(-2*Math.log(f)/f)*b};c.getPercentage=function(){return 1+Math.floor(100*this.getUniform())};c.getItem=function(a){return a.length?a[Math.floor(this.getUniform()*a.length)]:null};c.shuffle=function(a){var b=[];for(a=a.slice();a.length;){var e=a.indexOf(this.getItem(a));b.push(a.splice(e,1)[0])}return b};c.getWeightedValue=
function(a){var b=0;for(f in a)b+=a[f];b*=this.getUniform();var e;var f=0;for(e in a)if(f+=a[e],b<f)break;return e};c.getState=function(){return[this._s0,this._s1,this._s2,this._c]};c.setState=function(a){this._s0=a[0];this._s1=a[1];this._s2=a[2];this._c=a[3];return this};c.clone=function(){return(new d).setState(this.getState())};return d}())).setSeed(Date.now()),I=function(){function d(){}var c=d.prototype;c.getContainer=function(){return null};c.setOptions=function(a){this._options=a};return d}(),
J=function(d){function c(){var b=d.call(this)||this;b._ctx=document.createElement("canvas").getContext("2d");return b}_inheritsLoose(c,d);var a=c.prototype;a.schedule=function(b){requestAnimationFrame(b)};a.getContainer=function(){return this._ctx.canvas};a.setOptions=function(b){d.prototype.setOptions.call(this,b);b=(b.fontStyle?b.fontStyle+" ":"")+" "+b.fontSize+"px "+b.fontFamily;this._ctx.font=b;this._updateSize();this._ctx.font=b;this._ctx.textAlign="center";this._ctx.textBaseline="middle"};
a.clear=function(){this._ctx.fillStyle=this._options.bg;this._ctx.fillRect(0,0,this._ctx.canvas.width,this._ctx.canvas.height)};a.eventToPosition=function(b,e){var f=this._ctx.canvas,a=f.getBoundingClientRect();b-=a.left;e-=a.top;b*=f.width/a.width;e*=f.height/a.height;return 0>b||0>e||b>=f.width||e>=f.height?[-1,-1]:this._normalizedEventToPosition(b,e)};return c}(I);w.map={s:"toString"};var ka=Object.freeze({mod:p,clamp:r,capitalize:t,format:w}),O=function(d){function c(){var b=d.call(this)||this;
b._spacingX=0;b._spacingY=0;b._hexSize=0;return b}_inheritsLoose(c,d);var a=c.prototype;a.draw=function(b,e){var f=b[2],a=b[3],k=b[4];b=[(b[0]+1)*this._spacingX,b[1]*this._spacingY+this._hexSize];this._options.transpose&&b.reverse();e&&(this._ctx.fillStyle=k,this._fill(b[0],b[1]));if(f)for(this._ctx.fillStyle=a,e=[].concat(f),f=0;f<e.length;f++)this._ctx.fillText(e[f],b[0],Math.ceil(b[1]))};a.computeSize=function(b,e){this._options.transpose&&(b+=e,e=b-e,b-=e);return[Math.floor(b/this._spacingX)-
1,Math.floor((e-2*this._hexSize)/this._spacingY+1)]};a.computeFontSize=function(b,e){this._options.transpose&&(b+=e,e=b-e,b-=e);b=Math.min(2*b/((this._options.width+1)*Math.sqrt(3))-1,e/(2+1.5*(this._options.height-1)));e=this._ctx.font;this._ctx.font="100px "+this._options.fontFamily;var f=Math.ceil(this._ctx.measureText("W").width);this._ctx.font=e;b=Math.floor(b)+1;return Math.ceil(2*b/(this._options.spacing*(1+f/100/Math.sqrt(3))))-1};a._normalizedEventToPosition=function(b,e){if(this._options.transpose){b+=
e;e=b-e;b-=e;var f=this._ctx.canvas.width}else f=this._ctx.canvas.height;e=Math.floor(e/(f/this._options.height));p(e,2)?(b-=this._spacingX,b=1+2*Math.floor(b/(2*this._spacingX))):b=2*Math.floor(b/(2*this._spacingX));return[b,e]};a._fill=function(b,e){var f=this._hexSize,a=this._options.border,k=this._ctx;k.beginPath();this._options.transpose?(k.moveTo(b-f+a,e),k.lineTo(b-f/2+a,e+this._spacingX-a),k.lineTo(b+f/2-a,e+this._spacingX-a),k.lineTo(b+f-a,e),k.lineTo(b+f/2-a,e-this._spacingX+a),k.lineTo(b-
f/2+a,e-this._spacingX+a),k.lineTo(b-f+a,e)):(k.moveTo(b,e-f+a),k.lineTo(b+this._spacingX-a,e-f/2+a),k.lineTo(b+this._spacingX-a,e+f/2-a),k.lineTo(b,e+f-a),k.lineTo(b-this._spacingX+a,e+f/2-a),k.lineTo(b-this._spacingX+a,e-f/2+a),k.lineTo(b,e-f+a));k.fill()};a._updateSize=function(){var b=this._options,e=Math.ceil(this._ctx.measureText("W").width);this._hexSize=Math.floor(b.spacing*(b.fontSize+e/Math.sqrt(3))/2);this._spacingX=this._hexSize*Math.sqrt(3)/2;this._spacingY=1.5*this._hexSize;if(b.transpose){e=
"height";var f="width"}else e="width",f="height";this._ctx.canvas[e]=Math.ceil((b.width+1)*this._spacingX);this._ctx.canvas[f]=Math.ceil((b.height-1)*this._spacingY+2*this._hexSize)};return c}(J),F=function(d){function c(){var b=d.call(this)||this;b._spacingX=0;b._spacingY=0;b._canvasCache={};return b}_inheritsLoose(c,d);var a=c.prototype;a.setOptions=function(b){d.prototype.setOptions.call(this,b);this._canvasCache={}};a.draw=function(b,e){c.cache?this._drawWithCache(b):this._drawNoCache(b,e)};a._drawWithCache=
function(b){var e=b[0],f=b[1],a=b[2],k=b[3],h=b[4];b=""+a+k+h;if(b in this._canvasCache)var c=this._canvasCache[b];else{var d=this._options.border;c=document.createElement("canvas");var g=c.getContext("2d");c.width=this._spacingX;c.height=this._spacingY;g.fillStyle=h;g.fillRect(d,d,c.width-d,c.height-d);if(a)for(g.fillStyle=k,g.font=this._ctx.font,g.textAlign="center",g.textBaseline="middle",a=[].concat(a),k=0;k<a.length;k++)g.fillText(a[k],this._spacingX/2,Math.ceil(this._spacingY/2));this._canvasCache[b]=
c}this._ctx.drawImage(c,e*this._spacingX,f*this._spacingY)};a._drawNoCache=function(b,e){var f=b[0],a=b[1],k=b[2],h=b[3];b=b[4];e&&(e=this._options.border,this._ctx.fillStyle=b,this._ctx.fillRect(f*this._spacingX+e,a*this._spacingY+e,this._spacingX-e,this._spacingY-e));if(k)for(this._ctx.fillStyle=h,k=[].concat(k),h=0;h<k.length;h++)this._ctx.fillText(k[h],(f+.5)*this._spacingX,Math.ceil((a+.5)*this._spacingY))};a.computeSize=function(b,e){return[Math.floor(b/this._spacingX),Math.floor(e/this._spacingY)]};
a.computeFontSize=function(b,e){b=Math.floor(b/this._options.width);e=Math.floor(e/this._options.height);var f=this._ctx.font;this._ctx.font="100px "+this._options.fontFamily;var a=Math.ceil(this._ctx.measureText("W").width);this._ctx.font=f;b=a/100*e/b;1<b&&(e=Math.floor(e/b));return Math.floor(e/this._options.spacing)};a._normalizedEventToPosition=function(b,e){return[Math.floor(b/this._spacingX),Math.floor(e/this._spacingY)]};a._updateSize=function(){var b=this._options,e=Math.ceil(this._ctx.measureText("W").width);
this._spacingX=Math.ceil(b.spacing*e);this._spacingY=Math.ceil(b.spacing*b.fontSize);b.forceSquareRatio&&(this._spacingX=this._spacingY=Math.max(this._spacingX,this._spacingY));this._ctx.canvas.width=b.width*this._spacingX;this._ctx.canvas.height=b.height*this._spacingY};return c}(J);F.cache=!1;var B=function(d){function c(){var b=d.call(this)||this;b._colorCanvas=document.createElement("canvas");return b}_inheritsLoose(c,d);var a=c.prototype;a.draw=function(b,e){var f=b[0],a=b[1],k=b[2],h=b[3],c=
b[4];b=this._options.tileWidth;var d=this._options.tileHeight;e&&(this._options.tileColorize?this._ctx.clearRect(f*b,a*d,b,d):(this._ctx.fillStyle=c,this._ctx.fillRect(f*b,a*d,b,d)));if(k)for(e=[].concat(k),h=[].concat(h),c=[].concat(c),k=0;k<e.length;k++){var g=this._options.tileMap[e[k]];if(!g)throw Error('Char "'+e[k]+'" not found in tileMap');if(this._options.tileColorize){var z=this._colorCanvas,y=z.getContext("2d");y.globalCompositeOperation="source-over";y.clearRect(0,0,b,d);var q=h[k],p=c[k];
y.drawImage(this._options.tileSet,g[0],g[1],b,d,0,0,b,d);"transparent"!=q&&(y.fillStyle=q,y.globalCompositeOperation="source-atop",y.fillRect(0,0,b,d));"transparent"!=p&&(y.fillStyle=p,y.globalCompositeOperation="destination-over",y.fillRect(0,0,b,d));this._ctx.drawImage(z,f*b,a*d,b,d)}else this._ctx.drawImage(this._options.tileSet,g[0],g[1],b,d,f*b,a*d,b,d)}};a.computeSize=function(b,e){return[Math.floor(b/this._options.tileWidth),Math.floor(e/this._options.tileHeight)]};a.computeFontSize=function(){throw Error("Tile backend does not understand font size");
};a._normalizedEventToPosition=function(b,e){return[Math.floor(b/this._options.tileWidth),Math.floor(e/this._options.tileHeight)]};a._updateSize=function(){var b=this._options;this._ctx.canvas.width=b.width*b.tileWidth;this._ctx.canvas.height=b.height*b.tileHeight;this._colorCanvas.width=b.tileWidth;this._colorCanvas.height=b.tileHeight};return c}(J),Q={black:[0,0,0],navy:[0,0,128],darkblue:[0,0,139],mediumblue:[0,0,205],blue:[0,0,255],darkgreen:[0,100,0],green:[0,128,0],teal:[0,128,128],darkcyan:[0,
139,139],deepskyblue:[0,191,255],darkturquoise:[0,206,209],mediumspringgreen:[0,250,154],lime:[0,255,0],springgreen:[0,255,127],aqua:[0,255,255],cyan:[0,255,255],midnightblue:[25,25,112],dodgerblue:[30,144,255],forestgreen:[34,139,34],seagreen:[46,139,87],darkslategray:[47,79,79],darkslategrey:[47,79,79],limegreen:[50,205,50],mediumseagreen:[60,179,113],turquoise:[64,224,208],royalblue:[65,105,225],steelblue:[70,130,180],darkslateblue:[72,61,139],mediumturquoise:[72,209,204],indigo:[75,0,130],darkolivegreen:[85,
107,47],cadetblue:[95,158,160],cornflowerblue:[100,149,237],mediumaquamarine:[102,205,170],dimgray:[105,105,105],dimgrey:[105,105,105],slateblue:[106,90,205],olivedrab:[107,142,35],slategray:[112,128,144],slategrey:[112,128,144],lightslategray:[119,136,153],lightslategrey:[119,136,153],mediumslateblue:[123,104,238],lawngreen:[124,252,0],chartreuse:[127,255,0],aquamarine:[127,255,212],maroon:[128,0,0],purple:[128,0,128],olive:[128,128,0],gray:[128,128,128],grey:[128,128,128],skyblue:[135,206,235],
lightskyblue:[135,206,250],blueviolet:[138,43,226],darkred:[139,0,0],darkmagenta:[139,0,139],saddlebrown:[139,69,19],darkseagreen:[143,188,143],lightgreen:[144,238,144],mediumpurple:[147,112,216],darkviolet:[148,0,211],palegreen:[152,251,152],darkorchid:[153,50,204],yellowgreen:[154,205,50],sienna:[160,82,45],brown:[165,42,42],darkgray:[169,169,169],darkgrey:[169,169,169],lightblue:[173,216,230],greenyellow:[173,255,47],paleturquoise:[175,238,238],lightsteelblue:[176,196,222],powderblue:[176,224,
230],firebrick:[178,34,34],darkgoldenrod:[184,134,11],mediumorchid:[186,85,211],rosybrown:[188,143,143],darkkhaki:[189,183,107],silver:[192,192,192],mediumvioletred:[199,21,133],indianred:[205,92,92],peru:[205,133,63],chocolate:[210,105,30],tan:[210,180,140],lightgray:[211,211,211],lightgrey:[211,211,211],palevioletred:[216,112,147],thistle:[216,191,216],orchid:[218,112,214],goldenrod:[218,165,32],crimson:[220,20,60],gainsboro:[220,220,220],plum:[221,160,221],burlywood:[222,184,135],lightcyan:[224,
255,255],lavender:[230,230,250],darksalmon:[233,150,122],violet:[238,130,238],palegoldenrod:[238,232,170],lightcoral:[240,128,128],khaki:[240,230,140],aliceblue:[240,248,255],honeydew:[240,255,240],azure:[240,255,255],sandybrown:[244,164,96],wheat:[245,222,179],beige:[245,245,220],whitesmoke:[245,245,245],mintcream:[245,255,250],ghostwhite:[248,248,255],salmon:[250,128,114],antiquewhite:[250,235,215],linen:[250,240,230],lightgoldenrodyellow:[250,250,210],oldlace:[253,245,230],red:[255,0,0],fuchsia:[255,
0,255],magenta:[255,0,255],deeppink:[255,20,147],orangered:[255,69,0],tomato:[255,99,71],hotpink:[255,105,180],coral:[255,127,80],darkorange:[255,140,0],lightsalmon:[255,160,122],orange:[255,165,0],lightpink:[255,182,193],pink:[255,192,203],gold:[255,215,0],peachpuff:[255,218,185],navajowhite:[255,222,173],moccasin:[255,228,181],bisque:[255,228,196],mistyrose:[255,228,225],blanchedalmond:[255,235,205],papayawhip:[255,239,213],lavenderblush:[255,240,245],seashell:[255,245,238],cornsilk:[255,248,220],
lemonchiffon:[255,250,205],floralwhite:[255,250,240],snow:[255,250,250],yellow:[255,255,0],lightyellow:[255,255,224],ivory:[255,255,240],white:[255,255,255]};J=Object.freeze({fromString:L,add:function(d){for(var c=d.slice(),a=arguments.length,b=Array(1<a?a-1:0),e=1;e<a;e++)b[e-1]=arguments[e];for(a=0;3>a;a++)for(e=0;e<b.length;e++)c[a]+=b[e][a];return c},add_:Y,multiply:function(d){for(var c=d.slice(),a=arguments.length,b=Array(1<a?a-1:0),e=1;e<a;e++)b[e-1]=arguments[e];for(a=0;3>a;a++){for(e=0;e<
b.length;e++)c[a]*=b[e][a]/255;c[a]=Math.round(c[a])}return c},multiply_:function(d){for(var c=arguments.length,a=Array(1<c?c-1:0),b=1;b<c;b++)a[b-1]=arguments[b];for(c=0;3>c;c++){for(b=0;b<a.length;b++)d[c]*=a[b][c]/255;d[c]=Math.round(d[c])}return d},interpolate:Z,lerp:Z,interpolateHSL:aa,lerpHSL:aa,randomize:function(d,c){c instanceof Array||(c=Math.round(q.getNormal(0,c)));d=d.slice();for(var a=0;3>a;a++)d[a]+=c instanceof Array?Math.round(q.getNormal(0,c[a])):c;return d},rgb2hsl:R,hsl2rgb:ba,
toRGB:function(d){return"rgb("+d.map(function(c){return r(c,0,255)}).join(",")+")"},toHex:function(d){return"#"+d.map(function(c){return r(c,0,255).toString(16).padStart(2,"0")}).join("")}});var x=function(d){function c(){var b=d.call(this)||this;b._uniforms={};try{b._gl=b._initWebGL()}catch(e){alert(e.message)}return b}_inheritsLoose(c,d);c.isSupported=function(){return!!document.createElement("canvas").getContext("webgl2",{preserveDrawingBuffer:!0})};var a=c.prototype;a.schedule=function(b){requestAnimationFrame(b)};
a.getContainer=function(){return this._gl.canvas};a.setOptions=function(b){var e=this;d.prototype.setOptions.call(this,b);this._updateSize();var f=this._options.tileSet;f&&"complete"in f&&!f.complete?f.addEventListener("load",function(){return e._updateTexture(f)}):this._updateTexture(f)};a.draw=function(b,e){var f=this._gl,a=this._options,k=b[0],h=b[1],c=b[2],d=b[3];b=b[4];f.scissor(k*a.tileWidth,f.canvas.height-(h+1)*a.tileHeight,a.tileWidth,a.tileHeight);e&&(a.tileColorize?f.clearColor(0,0,0,0):
f.clearColor.apply(f,M(b)),f.clear(f.COLOR_BUFFER_BIT));if(c)for(e=[].concat(c),c=[].concat(b),d=[].concat(d),f.uniform2fv(this._uniforms.targetPosRel,[k,h]),k=0;k<e.length;k++){h=this._options.tileMap[e[k]];if(!h)throw Error('Char "'+e[k]+'" not found in tileMap');f.uniform1f(this._uniforms.colorize,a.tileColorize?1:0);f.uniform2fv(this._uniforms.tilesetPosAbs,h);a.tileColorize&&(f.uniform4fv(this._uniforms.tint,M(d[k])),f.uniform4fv(this._uniforms.bg,M(c[k])));f.drawArrays(f.TRIANGLE_STRIP,0,4)}};
a.clear=function(){var b=this._gl;b.clearColor.apply(b,M(this._options.bg));b.scissor(0,0,b.canvas.width,b.canvas.height);b.clear(b.COLOR_BUFFER_BIT)};a.computeSize=function(b,e){return[Math.floor(b/this._options.tileWidth),Math.floor(e/this._options.tileHeight)]};a.computeFontSize=function(){throw Error("Tile backend does not understand font size");};a.eventToPosition=function(b,e){var f=this._gl.canvas,a=f.getBoundingClientRect();b-=a.left;e-=a.top;b*=f.width/a.width;e*=f.height/a.height;return 0>
b||0>e||b>=f.width||e>=f.height?[-1,-1]:this._normalizedEventToPosition(b,e)};a._initWebGL=function(){var b=this,e=document.createElement("canvas").getContext("webgl2",{preserveDrawingBuffer:!0});window.gl=e;var f=fa(e,la,ma);e.useProgram(f);ha(e);na.forEach(function(a){return b._uniforms[a]=e.getUniformLocation(f,a)});this._program=f;e.enable(e.BLEND);e.blendFuncSeparate(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA,e.ONE,e.ONE_MINUS_SRC_ALPHA);e.enable(e.SCISSOR_TEST);return e};a._normalizedEventToPosition=
function(b,e){return[Math.floor(b/this._options.tileWidth),Math.floor(e/this._options.tileHeight)]};a._updateSize=function(){var b=this._gl,e=this._options,a=[e.width*e.tileWidth,e.height*e.tileHeight];b.canvas.width=a[0];b.canvas.height=a[1];b.viewport(0,0,a[0],a[1]);b.uniform2fv(this._uniforms.tileSize,[e.tileWidth,e.tileHeight]);b.uniform2fv(this._uniforms.targetSize,a)};a._updateTexture=function(b){var e=this._gl,a=e.createTexture();e.bindTexture(e.TEXTURE_2D,a);e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,
e.NEAREST);e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST);e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.REPEAT);e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.REPEAT);e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,0);e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,b)};return c}(I),na="targetPosRel tilesetPosAbs tileSize targetSize colorize bg tint".split(" "),la="#version 300 es\n\nin vec2 tilePosRel;\nout vec2 tilesetPosPx;\n\nuniform vec2 tilesetPosAbs;\nuniform vec2 tileSize;\nuniform vec2 targetSize;\nuniform vec2 targetPosRel;\n\nvoid main() {\n\tvec2 targetPosPx = (targetPosRel + tilePosRel) * tileSize;\n\tvec2 targetPosNdc = ((targetPosPx / targetSize)-0.5)*2.0;\n\ttargetPosNdc.y *= -1.0;\n\n\tgl_Position = vec4(targetPosNdc, 0.0, 1.0);\n\ttilesetPosPx = tilesetPosAbs + tilePosRel * tileSize;\n}",
ma="#version 300 es\nprecision highp float;\n\nin vec2 tilesetPosPx;\nout vec4 fragColor;\nuniform sampler2D image;\nuniform bool colorize;\nuniform vec4 bg;\nuniform vec4 tint;\n\nvoid main() {\n\tfragColor = vec4(0, 0, 0, 1);\n\n\tvec4 texel = texelFetch(image, ivec2(tilesetPosPx), 0);\n\n\tif (colorize) {\n\t\ttexel.rgb = tint.a * tint.rgb + (1.0-tint.a) * texel.rgb;\n\t\tfragColor.rgb = texel.a*texel.rgb + (1.0-texel.a)*bg.rgb;\n\t\tfragColor.a = texel.a + (1.0-texel.a)*bg.a;\n\t} else {\n\t\tfragColor = texel;\n\t}\n}",
T={},u=function(d){function c(){var b=d.call(this)||this;b._offset=[0,0];b._cursor=[-1,-1];b._lastColor="";return b}_inheritsLoose(c,d);var a=c.prototype;a.schedule=function(b){setTimeout(b,1E3/60)};a.setOptions=function(b){d.prototype.setOptions.call(this,b);var e=[b.width,b.height];this._offset=this.computeSize().map(function(b,a){return Math.floor((b-e[a])/2)})};a.clear=function(){process.stdout.write("\u001b[0;48;5;"+U(this._options.bg)+"m\u001b[2J")};a.draw=function(b,e){var a=b[2],m=b[3],k=
b[4],c=this._offset[0]+b[0],d=this._offset[1]+b[1];b=this.computeSize();if(!(0>c||c>=b[0]||0>d||d>=b[1])){if(c!==this._cursor[0]||d!==this._cursor[1])process.stdout.write("\u001b["+(d+1)+";"+(c+1)+"H"),this._cursor[0]=c,this._cursor[1]=d;e&&(a||(a=" "));a&&(e="\u001b[0;38;5;"+U(m)+";48;5;"+U(k)+"m",e!==this._lastColor&&(process.stdout.write(e),this._lastColor=e),a=[].concat(a),process.stdout.write(a[0]),this._cursor[0]++,this._cursor[0]>=b[0]&&(this._cursor[0]=0,this._cursor[1]++))}};a.computeFontSize=
function(){throw Error("Terminal backend has no notion of font size");};a.eventToPosition=function(b,e){return[b,e]};a.computeSize=function(){return[process.stdout.columns,process.stdout.rows]};return c}(I),ia=/%([bc]){([^}]*)}/g;I=Object.freeze({TYPE_TEXT:0,TYPE_NEWLINE:1,TYPE_FG:2,TYPE_BG:3,measure:function(d,c){var a={width:0,height:1};d=V(d,c);for(var b=c=0;b<d.length;b++){var e=d[b];switch(e.type){case 0:c+=e.value.length;break;case 1:a.height++,a.width=Math.max(a.width,c),c=0}}a.width=Math.max(a.width,
c);return a},tokenize:V});var v={4:[[0,-1],[1,0],[0,1],[-1,0]],8:[[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]],6:[[-1,-1],[1,-1],[2,0],[1,1],[-1,1],[-2,0]]},oa={hex:O,rect:F,tile:B,"tile-gl":x,term:u},pa={width:80,height:25,transpose:!1,layout:"rect",fontSize:15,spacing:1,border:0,forceSquareRatio:!1,fontFamily:"monospace",fontStyle:"",fg:"#ccc",bg:"#000",tileWidth:32,tileHeight:32,tileMap:{},tileSet:null,tileColorize:!1},G=function(){function d(a){void 0===a&&(a={});this._data={};this._dirty=
!1;this._options={};a=Object.assign({},pa,a);this.setOptions(a);this.DEBUG=this.DEBUG.bind(this);this._tick=this._tick.bind(this);this._backend.schedule(this._tick)}var c=d.prototype;c.DEBUG=function(a,b,e){var f=[this._options.bg,this._options.fg];this.draw(a,b,null,null,f[e%f.length])};c.clear=function(){this._data={};this._dirty=!0};c.setOptions=function(a){Object.assign(this._options,a);if(a.width||a.height||a.fontSize||a.fontFamily||a.spacing||a.layout)a.layout&&(this._backend=new oa[a.layout]),
this._backend.setOptions(this._options),this._dirty=!0;return this};c.getOptions=function(){return this._options};c.getContainer=function(){return this._backend.getContainer()};c.computeSize=function(a,b){return this._backend.computeSize(a,b)};c.computeFontSize=function(a,b){return this._backend.computeFontSize(a,b)};c.computeTileSize=function(a,b){return[Math.floor(a/this._options.width),Math.floor(b/this._options.height)]};c.eventToPosition=function(a){if("touches"in a){var b=a.touches[0].clientX;
a=a.touches[0].clientY}else b=a.clientX,a=a.clientY;return this._backend.eventToPosition(b,a)};c.draw=function(a,b,e,f,m){f||(f=this._options.fg);m||(m=this._options.bg);var k=a+","+b;this._data[k]=[a,b,e,f,m];!0!==this._dirty&&(this._dirty||(this._dirty={}),this._dirty[k]=!0)};c.drawText=function(a,b,e,f){var m=null,k=null,c=a,d=1;f||(f=this._options.width-a);for(e=V(e,f);e.length;)switch(f=e.shift(),f.type){case 0:for(var l,g=!1,z,y=!1,q=0;q<f.value.length;q++){l=f.value.charCodeAt(q);var p=f.value.charAt(q);
z=65280<l&&65377>l||65500<l&&65512>l||65518<l;l=32==p.charCodeAt(0)||12288==p.charCodeAt(0);!y||z||l||c++;z&&!g&&c++;this.draw(c++,b,p,m,k);g=l;y=z}break;case 2:m=f.value||null;break;case 3:k=f.value||null;break;case 1:c=a,b++,d++}return d};c._tick=function(){this._backend.schedule(this._tick);if(this._dirty){if(!0===this._dirty){this._backend.clear();for(var a in this._data)this._draw(a,!1)}else for(var b in this._dirty)this._draw(b,!0);this._dirty=!1}};c._draw=function(a,b){a=this._data[a];a[4]!=
this._options.bg&&(b=!0);this._backend.draw(a,b)};return d}();G.Rect=F;G.Hex=O;G.Tile=B;G.TileGL=x;G.Term=u;O=function(){function d(a){this._options={words:!1,order:3,prior:.001};Object.assign(this._options,a);this._suffix=this._boundary=String.fromCharCode(0);this._prefix=[];for(a=0;a<this._options.order;a++)this._prefix.push(this._boundary);this._priorValues={};this._priorValues[this._boundary]=this._options.prior;this._data={}}var c=d.prototype;c.clear=function(){this._data={};this._priorValues=
{}};c.generate=function(){for(var a=[this._sample(this._prefix)];a[a.length-1]!=this._boundary;)a.push(this._sample(a));return this._join(a.slice(0,-1))};c.observe=function(a){a=this._split(a);for(var b=0;b<a.length;b++)this._priorValues[a[b]]=this._options.prior;a=this._prefix.concat(a).concat(this._suffix);for(b=this._options.order;b<a.length;b++)for(var e=a.slice(b-this._options.order,b),f=a[b],m=0;m<e.length;m++){var k=e.slice(m);this._observeEvent(k,f)}};c.getStats=function(){var a=[],b=Object.keys(this._priorValues).length;
b--;a.push("distinct samples: "+b);b=Object.keys(this._data).length;var e=0,f;for(f in this._data)e+=Object.keys(this._data[f]).length;a.push("dictionary size (contexts): "+b);a.push("dictionary size (events): "+e);return a.join(", ")};c._split=function(a){return a.split(this._options.words?/\s+/:"")};c._join=function(a){return a.join(this._options.words?" ":"")};c._observeEvent=function(a,b){a=this._join(a);a in this._data||(this._data[a]={});a=this._data[a];b in a||(a[b]=0);a[b]++};c._sample=function(a){a=
this._backoff(a);a=this._join(a);a=this._data[a];var b={};if(this._options.prior){for(var e in this._priorValues)b[e]=this._priorValues[e];for(var f in a)b[f]+=a[f]}else b=a;return q.getWeightedValue(b)};c._backoff=function(a){for(a.length>this._options.order?a=a.slice(-this._options.order):a.length<this._options.order&&(a=this._prefix.slice(0,this._options.order-a.length).concat(a));!(this._join(a)in this._data)&&0<a.length;)a=a.slice(1);return a};return d}();var ea=function(){function d(){this._time=
0;this._events=[];this._eventTimes=[]}var c=d.prototype;c.getTime=function(){return this._time};c.clear=function(){this._events=[];this._eventTimes=[];return this};c.add=function(a,b){for(var e=this._events.length,f=0;f<this._eventTimes.length;f++)if(this._eventTimes[f]>b){e=f;break}this._events.splice(e,0,a);this._eventTimes.splice(e,0,b)};c.get=function(){if(!this._events.length)return null;var a=this._eventTimes.splice(0,1)[0];if(0<a){this._time+=a;for(var b=0;b<this._eventTimes.length;b++)this._eventTimes[b]-=
a}return this._events.splice(0,1)[0]};c.getEventTime=function(a){a=this._events.indexOf(a);if(-1!=a)return this._eventTimes[a]};c.remove=function(a){a=this._events.indexOf(a);if(-1==a)return!1;this._remove(a);return!0};c._remove=function(a){this._events.splice(a,1);this._eventTimes.splice(a,1)};return d}();x=function(){function d(){this._queue=new ea;this._repeat=[];this._current=null}var c=d.prototype;c.getTime=function(){return this._queue.getTime()};c.add=function(a,b){b&&this._repeat.push(a);
return this};c.getTimeOf=function(a){return this._queue.getEventTime(a)};c.clear=function(){this._queue.clear();this._repeat=[];this._current=null;return this};c.remove=function(a){var b=this._queue.remove(a),e=this._repeat.indexOf(a);-1!=e&&this._repeat.splice(e,1);this._current==a&&(this._current=null);return b};c.next=function(){return this._current=this._queue.get()};return d}();F=function(d){function c(){return d.apply(this,arguments)||this}_inheritsLoose(c,d);var a=c.prototype;a.add=function(b,
e){this._queue.add(b,0);return d.prototype.add.call(this,b,e)};a.next=function(){null!==this._current&&-1!=this._repeat.indexOf(this._current)&&this._queue.add(this._current,0);return d.prototype.next.call(this)};return c}(x);B=function(d){function c(){return d.apply(this,arguments)||this}_inheritsLoose(c,d);var a=c.prototype;a.add=function(b,e,a){this._queue.add(b,void 0!==a?a:1/b.getSpeed());return d.prototype.add.call(this,b,e)};a.next=function(){this._current&&-1!=this._repeat.indexOf(this._current)&&
this._queue.add(this._current,1/this._current.getSpeed());return d.prototype.next.call(this)};return c}(x);x=function(d){function c(){var b=d.call(this)||this;b._defaultDuration=1;b._duration=b._defaultDuration;return b}_inheritsLoose(c,d);var a=c.prototype;a.add=function(b,e,a){this._queue.add(b,a||this._defaultDuration);return d.prototype.add.call(this,b,e)};a.clear=function(){this._duration=this._defaultDuration;return d.prototype.clear.call(this)};a.remove=function(b){b==this._current&&(this._duration=
this._defaultDuration);return d.prototype.remove.call(this,b)};a.next=function(){null!==this._current&&-1!=this._repeat.indexOf(this._current)&&(this._queue.add(this._current,this._duration||this._defaultDuration),this._duration=this._defaultDuration);return d.prototype.next.call(this)};a.setDuration=function(b){this._current&&(this._duration=b);return this};return c}(x);F={Simple:F,Speed:B,Action:x};u=function(){function d(c,a){void 0===a&&(a={});this._lightPasses=c;this._options=Object.assign({topology:8},
a)}d.prototype._getCircle=function(c,a,b){var e=[];switch(this._options.topology){case 4:var f=1;var m=[0,1];var k=[v[8][7],v[8][1],v[8][3],v[8][5]];break;case 6:k=v[6];f=1;m=[-1,1];break;case 8:k=v[4];f=2;m=[-1,1];break;default:throw Error("Incorrect topology for FOV computation");}c+=m[0]*b;a+=m[1]*b;for(m=0;m<k.length;m++)for(var h=0;h<b*f;h++)e.push([c,a]),c+=k[m][0],a+=k[m][1];return e};return d}();B=function(d){function c(){return d.apply(this,arguments)||this}_inheritsLoose(c,d);var a=c.prototype;
a.compute=function(b,e,a,m){m(b,e,0,1);if(this._lightPasses(b,e))for(var f=[],c,d,l,g,z,y=1;y<=a;y++)for(var q=this._getCircle(b,e,y),p=360/q.length,r=0;r<q.length;r++)if(l=q[r][0],g=q[r][1],c=p*(r-.5),d=c+p,z=!this._lightPasses(l,g),this._visibleCoords(Math.floor(c),Math.ceil(d),z,f)&&m(l,g,y,1),2==f.length&&0==f[0]&&360==f[1])return};a._visibleCoords=function(b,e,a,m){if(0>b)return e=this._visibleCoords(0,e,a,m),b=this._visibleCoords(360+b,360,a,m),e||b;for(var f=0;f<m.length&&m[f]<b;)f++;if(f==
m.length)return a&&m.push(b,e),!0;var c=0;if(f%2){for(;f<m.length&&m[f]<e;)f++,c++;if(0==c)return!1;a&&(c%2?m.splice(f-c,c,e):m.splice(f-c,c))}else{for(;f<m.length&&m[f]<e;)f++,c++;if(b==m[f-c]&&1==c)return!1;a&&(c%2?m.splice(f-c,c,b):m.splice(f-c,c,b,e))}return!0};return c}(u);x=function(d){function c(){return d.apply(this,arguments)||this}_inheritsLoose(c,d);var a=c.prototype;a.compute=function(b,e,a,c){c(b,e,0,1);if(this._lightPasses(b,e))for(var f=[],m,d,l,g,z,q=1;q<=a;q++)for(var p=this._getCircle(b,
e,q),r=p.length,t=0;t<r;t++)if(m=p[t][0],d=p[t][1],g=[t?2*t-1:2*r-1,2*r],z=[2*t+1,2*r],l=!this._lightPasses(m,d),(l=this._checkVisibility(g,z,l,f))&&c(m,d,q,l),2==f.length&&0==f[0][0]&&f[1][0]==f[1][1])return};a._checkVisibility=function(b,e,a,c){if(b[0]>e[0])return b=this._checkVisibility(b,[b[1],b[1]],a,c),e=this._checkVisibility([0,1],e,a,c),(b+e)/2;for(var f=0,m=!1;f<c.length;){var d=c[f];d=d[0]*b[1]-b[0]*d[1];if(0<=d){0!=d||f%2||(m=!0);break}f++}d=c.length;for(var l=!1;d--;){var g=c[d];g=e[0]*
g[1]-g[0]*e[1];if(0<=g){0==g&&d%2&&(l=!0);break}}g=!0;f==d&&(m||l)?g=!1:m&&l&&f+1==d&&d%2?g=!1:f>d&&f%2&&(g=!1);if(!g)return 0;m=d-f+1;if(m%2)f%2?(d=c[f],d=(e[0]*d[1]-d[0]*e[1])/(d[1]*e[1]),a&&c.splice(f,m,e)):(d=c[d],d=(d[0]*b[1]-b[0]*d[1])/(b[1]*d[1]),a&&c.splice(f,m,b));else if(f%2)l=c[f],d=c[d],d=(d[0]*l[1]-l[0]*d[1])/(l[1]*d[1]),a&&c.splice(f,m);else return a&&c.splice(f,m,b,e),1;return d/((e[0]*b[1]-b[0]*e[1])/(b[1]*e[1]))};return c}(u);var C=[[-1,0,0,1],[0,-1,1,0],[0,-1,-1,0],[-1,0,0,-1],[1,
0,0,-1],[0,1,-1,0],[0,1,1,0],[1,0,0,1]];u=function(d){function c(){return d.apply(this,arguments)||this}_inheritsLoose(c,d);var a=c.prototype;a.compute=function(b,e,a,c){c(b,e,0,1);for(var f=0;f<C.length;f++)this._renderOctant(b,e,C[f],a,c)};a.compute180=function(b,e,a,c,k){k(b,e,0,1);var f=(c-1+8)%8,m=(c+1+8)%8;this._renderOctant(b,e,C[(c-2+8)%8],a,k);this._renderOctant(b,e,C[f],a,k);this._renderOctant(b,e,C[c],a,k);this._renderOctant(b,e,C[m],a,k)};a.compute90=function(b,e,a,c,k){k(b,e,0,1);var f=
(c-1+8)%8;this._renderOctant(b,e,C[c],a,k);this._renderOctant(b,e,C[f],a,k)};a._renderOctant=function(b,e,a,c,k){this._castVisibility(b,e,1,1,0,c+1,a[0],a[1],a[2],a[3],k)};a._castVisibility=function(b,e,a,c,k,d,n,l,g,q,p){if(!(c<k))for(;a<=d;a++){for(var f=-a-1,m=-a,h=!1,W=0;0>=f;){f+=1;var z=b+f*n+m*l,y=e+f*g+m*q,r=(f-.5)/(m+.5),t=(f+.5)/(m-.5);if(!(t>c)){if(r<k)break;f*f+m*m<d*d&&p(z,y,a,1);h?this._lightPasses(z,y)?(h=!1,c=W):W=t:!this._lightPasses(z,y)&&a<d&&(h=!0,this._castVisibility(b,e,a+1,
c,r,d,n,l,g,q,p),W=t)}}if(h)break}};return c}(u);B={DiscreteShadowcasting:B,PreciseShadowcasting:x,RecursiveShadowcasting:u};u=function(){function d(c,a){void 0===c&&(c=80);void 0===a&&(a=25);this._width=c;this._height=a}d.prototype._fillMap=function(c){for(var a=[],b=0;b<this._width;b++){a.push([]);for(var e=0;e<this._height;e++)a[b].push(c)}return a};return d}();x=function(d){function c(){return d.apply(this,arguments)||this}_inheritsLoose(c,d);c.prototype.create=function(a){for(var b=this._width-
1,e=this._height-1,f=0;f<=b;f++)for(var c=0;c<=e;c++)a(f,c,f&&c&&f<b&&c<e?0:1);return this};return c}(u);var H=function(d){function c(b,e){b=d.call(this,b,e)||this;b._rooms=[];b._corridors=[];return b}_inheritsLoose(c,d);var a=c.prototype;a.getRooms=function(){return this._rooms};a.getCorridors=function(){return this._corridors};return c}(u),D=function(){},P=function(d){function c(b,e,a,c,k,h){var f=d.call(this)||this;f._x1=b;f._y1=e;f._x2=a;f._y2=c;f._doors={};void 0!==k&&void 0!==h&&f.addDoor(k,
h);return f}_inheritsLoose(c,d);c.createRandomAt=function(b,e,a,c,k){var f=k.roomWidth[0],d=k.roomWidth[1],m=q.getUniformInt(f,d);f=k.roomHeight[0];d=k.roomHeight[1];k=q.getUniformInt(f,d);if(1==a)return a=e-Math.floor(q.getUniform()*k),new this(b+1,a,b+m,a+k-1,b,e);if(-1==a)return a=e-Math.floor(q.getUniform()*k),new this(b-m,a,b-1,a+k-1,b,e);if(1==c)return a=b-Math.floor(q.getUniform()*m),new this(a,e+1,a+m-1,e+k,b,e);if(-1==c)return a=b-Math.floor(q.getUniform()*m),new this(a,e-k,a+m-1,e-1,b,e);
throw Error("dx or dy must be 1 or -1");};c.createRandomCenter=function(b,e,a){var f=a.roomWidth[0],c=a.roomWidth[1],d=q.getUniformInt(f,c);f=a.roomHeight[0];c=a.roomHeight[1];a=q.getUniformInt(f,c);b-=Math.floor(q.getUniform()*d);e-=Math.floor(q.getUniform()*a);return new this(b,e,b+d-1,e+a-1)};c.createRandom=function(b,a,f){var e=f.roomWidth[0],c=f.roomWidth[1],d=q.getUniformInt(e,c);e=f.roomHeight[0];c=f.roomHeight[1];f=q.getUniformInt(e,c);b=b-d-1;a=a-f-1;b=1+Math.floor(q.getUniform()*b);a=1+
Math.floor(q.getUniform()*a);return new this(b,a,b+d-1,a+f-1)};var a=c.prototype;a.addDoor=function(b,a){this._doors[b+","+a]=1;return this};a.getDoors=function(b){for(var a in this._doors){var f=a.split(",");b(parseInt(f[0]),parseInt(f[1]))}return this};a.clearDoors=function(){this._doors={};return this};a.addDoors=function(b){for(var a=this._x1-1,f=this._x2+1,c=this._y1-1,d=this._y2+1,h=a;h<=f;h++)for(var n=c;n<=d;n++)if(h==a||h==f||n==c||n==d)b(h,n)||this.addDoor(h,n);return this};a.debug=function(){console.log("room",
this._x1,this._y1,this._x2,this._y2)};a.isValid=function(b,a){for(var e=this._x1-1,c=this._x2+1,d=this._y1-1,h=this._y2+1,n=e;n<=c;n++)for(var l=d;l<=h;l++)if(n==e||n==c||l==d||l==h){if(!b(n,l))return!1}else if(!a(n,l))return!1;return!0};a.create=function(b){for(var a=this._x1-1,f=this._x2+1,c=this._y1-1,d=this._y2+1,h,n=a;n<=f;n++)for(var l=c;l<=d;l++)h=n+","+l in this._doors?2:n==a||n==f||l==c||l==d?1:0,b(n,l,h)};a.getCenter=function(){return[Math.round((this._x1+this._x2)/2),Math.round((this._y1+
this._y2)/2)]};a.getLeft=function(){return this._x1};a.getRight=function(){return this._x2};a.getTop=function(){return this._y1};a.getBottom=function(){return this._y2};return c}(D),X=function(d){function c(b,a,f,c){var e=d.call(this)||this;e._startX=b;e._startY=a;e._endX=f;e._endY=c;e._endsWithAWall=!0;return e}_inheritsLoose(c,d);c.createRandomAt=function(b,a,f,c,d){d=q.getUniformInt(d.corridorLength[0],d.corridorLength[1]);return new this(b,a,b+f*d,a+c*d)};var a=c.prototype;a.debug=function(){console.log("corridor",
this._startX,this._startY,this._endX,this._endY)};a.isValid=function(b,a){var e=this._startX,c=this._startY,d=this._endX-e,h=this._endY-c,n=1+Math.max(Math.abs(d),Math.abs(h));d&&(d/=Math.abs(d));h&&(h/=Math.abs(h));for(var l=h,g=-d,q=!0,p=0;p<n;p++){var r=e+p*d,t=c+p*h;a(r,t)||(q=!1);b(r+l,t+g)||(q=!1);b(r-l,t-g)||(q=!1);if(!q){n=p;this._endX=r-d;this._endY=t-h;break}}if(0==n||1==n&&b(this._endX+d,this._endY+h))return!1;a=!b(this._endX+d+l,this._endY+h+g);l=!b(this._endX+d-l,this._endY+h-g);this._endsWithAWall=
b(this._endX+d,this._endY+h);return(a||l)&&this._endsWithAWall?!1:!0};a.create=function(b){var a=this._startX,f=this._startY,c=this._endX-a,d=this._endY-f,h=1+Math.max(Math.abs(c),Math.abs(d));c&&(c/=Math.abs(c));d&&(d/=Math.abs(d));for(var n=0;n<h;n++)b(a+n*c,f+n*d,0);return!0};a.createPriorityWalls=function(b){if(this._endsWithAWall){var a=this._endX-this._startX,f=this._endY-this._startY;a&&(a/=Math.abs(a));f&&(f/=Math.abs(f));var c=f,d=-a;b(this._endX+a,this._endY+f);b(this._endX+c,this._endY+
d);b(this._endX-c,this._endY-d)}};return c}(D);D=function(d){function c(b,a,f){b=d.call(this,b,a)||this;b._options={roomWidth:[3,9],roomHeight:[3,5],roomDugPercentage:.1,timeLimit:1E3};Object.assign(b._options,f);b._map=[];b._dug=0;b._roomAttempts=20;b._corridorAttempts=20;b._connected=[];b._unconnected=[];b._digCallback=b._digCallback.bind(_assertThisInitialized(_assertThisInitialized(b)));b._canBeDugCallback=b._canBeDugCallback.bind(_assertThisInitialized(_assertThisInitialized(b)));b._isWallCallback=
b._isWallCallback.bind(_assertThisInitialized(_assertThisInitialized(b)));return b}_inheritsLoose(c,d);var a=c.prototype;a.create=function(b){for(var a=Date.now();;){if(Date.now()-a>this._options.timeLimit)return null;this._map=this._fillMap(1);this._dug=0;this._rooms=[];this._unconnected=[];this._generateRooms();if(!(2>this._rooms.length)&&this._generateCorridors())break}if(b)for(a=0;a<this._width;a++)for(var f=0;f<this._height;f++)b(a,f,this._map[a][f]);return this};a._generateRooms=function(){var b=
this._width-2,a=this._height-2;do{var f=this._generateRoom();if(this._dug/(b*a)>this._options.roomDugPercentage)break}while(f)};a._generateRoom=function(){for(var b=0;b<this._roomAttempts;){b++;var a=P.createRandom(this._width,this._height,this._options);if(a.isValid(this._isWallCallback,this._canBeDugCallback))return a.create(this._digCallback),this._rooms.push(a),a}return null};a._generateCorridors=function(){for(var b=0;b<this._corridorAttempts;){b++;this._corridors=[];this._map=this._fillMap(1);
for(var a=0;a<this._rooms.length;a++){var f=this._rooms[a];f.clearDoors();f.create(this._digCallback)}this._unconnected=q.shuffle(this._rooms.slice());this._connected=[];for(this._unconnected.length&&this._connected.push(this._unconnected.pop());;){a=q.getItem(this._connected);if(!a)break;a=this._closestRoom(this._unconnected,a);if(!a)break;f=this._closestRoom(this._connected,a);if(!f)break;if(!this._connectRooms(a,f))break;if(!this._unconnected.length)return!0}}return!1};a._closestRoom=function(b,
a){var e=Infinity;a=a.getCenter();for(var c=null,d=0;d<b.length;d++){var h=b[d],n=h.getCenter(),l=n[0]-a[0];n=n[1]-a[1];l=l*l+n*n;l<e&&(e=l,c=h)}return c};a._connectRooms=function(b,a){var e=b.getCenter(),c=a.getCenter(),d=c[0]-e[0];e=c[1]-e[1];if(Math.abs(d)<Math.abs(e)){d=0<e?2:0;var h=(d+2)%4;var n=a.getLeft();var l=a.getRight();e=0}else d=0<d?1:3,h=(d+2)%4,n=a.getTop(),l=a.getBottom(),e=1;d=this._placeInWall(b,d);if(!d)return!1;if(d[e]>=n&&d[e]<=l){c=d.slice();n=0;switch(h){case 0:n=a.getTop()-
1;break;case 1:n=a.getRight()+1;break;case 2:n=a.getBottom()+1;break;case 3:n=a.getLeft()-1}c[(e+1)%2]=n;this._digLine([d,c])}else if(d[e]<n-1||d[e]>l+1){c=d[e]-c[e];n=0;switch(h){case 0:case 1:n=0>c?3:1;break;case 2:case 3:n=0>c?1:3}c=this._placeInWall(a,(h+n)%4);if(!c)return!1;h=[0,0];h[e]=d[e];e=(e+1)%2;h[e]=c[e];this._digLine([d,h,c])}else{n=(e+1)%2;c=this._placeInWall(a,h);if(!c)return!1;h=Math.round((c[n]+d[n])/2);l=[0,0];var g=[0,0];l[e]=d[e];l[n]=h;g[e]=c[e];g[n]=h;this._digLine([d,l,g,c])}b.addDoor(d[0],
d[1]);a.addDoor(c[0],c[1]);e=this._unconnected.indexOf(b);-1!=e&&(this._unconnected.splice(e,1),this._connected.push(b));e=this._unconnected.indexOf(a);-1!=e&&(this._unconnected.splice(e,1),this._connected.push(a));return!0};a._placeInWall=function(b,a){var e=[0,0],c=[0,0],d=0;switch(a){case 0:c=[1,0];e=[b.getLeft(),b.getTop()-1];d=b.getRight()-b.getLeft()+1;break;case 1:c=[0,1];e=[b.getRight()+1,b.getTop()];d=b.getBottom()-b.getTop()+1;break;case 2:c=[1,0];e=[b.getLeft(),b.getBottom()+1];d=b.getRight()-
b.getLeft()+1;break;case 3:c=[0,1],e=[b.getLeft()-1,b.getTop()],d=b.getBottom()-b.getTop()+1}b=[];a=-2;for(var h=0;h<d;h++){var n=e[0]+h*c[0],l=e[1]+h*c[1];b.push(null);1==this._map[n][l]?a!=h-1&&(b[h]=[n,l]):(a=h)&&(b[h-1]=null)}for(e=b.length-1;0<=e;e--)b[e]||b.splice(e,1);return b.length?q.getItem(b):null};a._digLine=function(b){for(var a=1;a<b.length;a++){var f=b[a-1],c=b[a];f=new X(f[0],f[1],c[0],c[1]);f.create(this._digCallback);this._corridors.push(f)}};a._digCallback=function(b,a,f){this._map[b][a]=
f;0==f&&this._dug++};a._isWallCallback=function(b,a){return 0>b||0>a||b>=this._width||a>=this._height?!1:1==this._map[b][a]};a._canBeDugCallback=function(b,a){return 1>b||1>a||b+1>=this._width||a+1>=this._height?!1:1==this._map[b][a]};return c}(H);var E=function(d){function c(b,a,f){void 0===f&&(f={});b=d.call(this,b,a)||this;b._options={born:[5,6,7,8],survive:[4,5,6,7,8],topology:8};b.setOptions(f);b._dirs=v[b._options.topology];b._map=b._fillMap(0);return b}_inheritsLoose(c,d);var a=c.prototype;
a.randomize=function(b){for(var a=0;a<this._width;a++)for(var f=0;f<this._height;f++)this._map[a][f]=q.getUniform()<b?1:0;return this};a.setOptions=function(b){Object.assign(this._options,b)};a.set=function(b,a,f){this._map[b][a]=f};a.create=function(b){for(var a=this._fillMap(0),f=this._options.born,c=this._options.survive,d=0;d<this._height;d++){var h=1,n=0;6==this._options.topology&&(h=2,n=d%2);for(;n<this._width;n+=h){var l=this._map[n][d],g=this._getNeighbors(n,d);l&&-1!=c.indexOf(g)?a[n][d]=
1:l||-1==f.indexOf(g)||(a[n][d]=1)}}this._map=a;b&&this._serviceCallback(b)};a._serviceCallback=function(b){for(var a=0;a<this._height;a++){var f=1,c=0;6==this._options.topology&&(f=2,c=a%2);for(;c<this._width;c+=f)b(c,a,this._map[c][a])}};a._getNeighbors=function(b,a){for(var e=0,c=0;c<this._dirs.length;c++){var d=this._dirs[c],h=b+d[0];d=a+d[1];0>h||h>=this._width||0>d||d>=this._height||(e+=1==this._map[h][d]?1:0)}return e};a.connect=function(b,a,f){a||(a=0);var e=[],c={},d=1,n=[0,0];6==this._options.topology&&
(d=2,n=[0,1]);for(var l=0;l<this._height;l++)for(var g=n[l%2];g<this._width;g+=d)if(this._freeSpace(g,l,a)){var p=[g,l];c[this._pointKey(p)]=p;e.push([g,l])}d=e[q.getUniformInt(0,e.length-1)];n=this._pointKey(d);e={};e[n]=d;delete c[n];for(this._findConnected(e,c,[d],!1,a);0<Object.keys(c).length;){n=this._getFromTo(e,c);d=n[0];l=n[1];n={};n[this._pointKey(d)]=d;this._findConnected(n,c,[d],!0,a);(6==this._options.topology?this._tunnelToConnected6:this._tunnelToConnected).call(this,l,d,e,c,a,f);for(var r in n)d=
n[r],this._map[d[0]][d[1]]=a,e[r]=d,delete c[r]}b&&this._serviceCallback(b)};a._getFromTo=function(b,a){for(var e=[0,0],c=[0,0],d,h=Object.keys(b),n=Object.keys(a),l=0;5>l&&!(h.length<n.length?(e=h,c=b[e[q.getUniformInt(0,e.length-1)]],e=this._getClosest(c,a)):(e=n,e=a[e[q.getUniformInt(0,e.length-1)]],c=this._getClosest(e,b)),d=(e[0]-c[0])*(e[0]-c[0])+(e[1]-c[1])*(e[1]-c[1]),64>d);l++);return[e,c]};a._getClosest=function(b,a){var e=null,c=null,d;for(d in a){var h=a[d],n=(h[0]-b[0])*(h[0]-b[0])+(h[1]-
b[1])*(h[1]-b[1]);if(null==c||n<c)c=n,e=h}return e};a._findConnected=function(b,a,f,c,d){for(;0<f.length;){var e=f.splice(0,1)[0];e=6==this._options.topology?[[e[0]+2,e[1]],[e[0]+1,e[1]-1],[e[0]-1,e[1]-1],[e[0]-2,e[1]],[e[0]-1,e[1]+1],[e[0]+1,e[1]+1]]:[[e[0]+1,e[1]],[e[0]-1,e[1]],[e[0],e[1]+1],[e[0],e[1]-1]];for(var m=0;m<e.length;m++){var k=this._pointKey(e[m]);null==b[k]&&this._freeSpace(e[m][0],e[m][1],d)&&(b[k]=e[m],c||delete a[k],f.push(e[m]))}}};a._tunnelToConnected=function(b,a,f,c,d,h){if(a[0]<
b[0]){var e=a;var m=b}else e=b,m=a;for(var k=e[0];k<=m[0];k++){this._map[k][e[1]]=d;var g=[k,e[1]],q=this._pointKey(g);f[q]=g;delete c[q]}h&&e[0]<m[0]&&h(e,[m[0],e[1]]);k=m[0];a[1]<b[1]?(e=a,m=b):(e=b,m=a);for(b=e[1];b<m[1];b++)this._map[k][b]=d,a=[k,b],g=this._pointKey(a),f[g]=a,delete c[g];h&&e[1]<m[1]&&h([m[0],e[1]],[m[0],m[1]])};a._tunnelToConnected6=function(b,a,c,d,k,h){if(a[0]<b[0]){var e=a;var f=b}else e=b,f=a;var m=e[0];for(e=e[1];m!=f[0]||e!=f[1];){var g=2;e<f[1]?(e++,g=1):e>f[1]&&(e--,
g=1);m=m<f[0]?m+g:m>f[0]?m-g:f[1]%2?m-g:m+g;this._map[m][e]=k;g=[m,e];var q=this._pointKey(g);c[q]=g;delete d[q]}h&&h(a,b)};a._freeSpace=function(b,a,f){return 0<=b&&b<this._width&&0<=a&&a<this._height&&this._map[b][a]==f};a._pointKey=function(b){return b[0]+"."+b[1]};return c}(u),qa={room:P,corridor:X};H=function(d){function c(b,a,f){void 0===f&&(f={});b=d.call(this,b,a)||this;b._options=Object.assign({roomWidth:[3,9],roomHeight:[3,5],corridorLength:[3,10],dugPercentage:.2,timeLimit:1E3},f);b._features=
{room:4,corridor:4};b._map=[];b._featureAttempts=20;b._walls={};b._dug=0;b._digCallback=b._digCallback.bind(_assertThisInitialized(_assertThisInitialized(b)));b._canBeDugCallback=b._canBeDugCallback.bind(_assertThisInitialized(_assertThisInitialized(b)));b._isWallCallback=b._isWallCallback.bind(_assertThisInitialized(_assertThisInitialized(b)));b._priorityWallCallback=b._priorityWallCallback.bind(_assertThisInitialized(_assertThisInitialized(b)));return b}_inheritsLoose(c,d);var a=c.prototype;a.create=
function(b){this._rooms=[];this._corridors=[];this._map=this._fillMap(1);this._walls={};this._dug=0;var a=(this._width-2)*(this._height-2);this._firstRoom();var f=Date.now();do{var c=0;if(Date.now()-f>this._options.timeLimit)break;var d=this._findWall();if(!d)break;var h=d.split(",");d=parseInt(h[0]);h=parseInt(h[1]);var g=this._getDiggingDirection(d,h);if(g){var l=0;do if(l++,this._tryFeature(d,h,g[0],g[1])){this._removeSurroundingWalls(d,h);this._removeSurroundingWalls(d-g[0],h-g[1]);break}while(l<
this._featureAttempts);for(var q in this._walls)1<this._walls[q]&&c++}}while(this._dug/a<this._options.dugPercentage||c);this._addDoors();if(b)for(a=0;a<this._width;a++)for(f=0;f<this._height;f++)b(a,f,this._map[a][f]);this._walls={};this._map=[];return this};a._digCallback=function(b,a,f){0==f||2==f?(this._map[b][a]=0,this._dug++):this._walls[b+","+a]=1};a._isWallCallback=function(b,a){return 0>b||0>a||b>=this._width||a>=this._height?!1:1==this._map[b][a]};a._canBeDugCallback=function(b,a){return 1>
b||1>a||b+1>=this._width||a+1>=this._height?!1:1==this._map[b][a]};a._priorityWallCallback=function(b,a){this._walls[b+","+a]=2};a._firstRoom=function(){var b=P.createRandomCenter(Math.floor(this._width/2),Math.floor(this._height/2),this._options);this._rooms.push(b);b.create(this._digCallback)};a._findWall=function(){var b=[],a=[],f;for(f in this._walls)2==this._walls[f]?a.push(f):b.push(f);b=a.length?a:b;if(!b.length)return null;b=q.getItem(b.sort());delete this._walls[b];return b};a._tryFeature=
function(b,a,f,c){var e=q.getWeightedValue(this._features);b=qa[e].createRandomAt(b,a,f,c,this._options);if(!b.isValid(this._isWallCallback,this._canBeDugCallback))return!1;b.create(this._digCallback);b instanceof P&&this._rooms.push(b);b instanceof X&&(b.createPriorityWalls(this._priorityWallCallback),this._corridors.push(b));return!0};a._removeSurroundingWalls=function(b,a){for(var e=v[4],c=0;c<e.length;c++){var d=e[c],h=b+d[0],g=a+d[1];delete this._walls[h+","+g];h=b+2*d[0];g=a+2*d[1];delete this._walls[h+
","+g]}};a._getDiggingDirection=function(b,a){if(0>=b||0>=a||b>=this._width-1||a>=this._height-1)return null;for(var e=null,c=v[4],d=0;d<c.length;d++){var h=c[d];if(!this._map[b+h[0]][a+h[1]]){if(e)return null;e=h}}return e?[-e[0],-e[1]]:null};a._addDoors=function(){function b(b,e){return 1==a[b][e]}for(var a=this._map,c=0;c<this._rooms.length;c++){var d=this._rooms[c];d.clearDoors();d.addDoors(b)}};return c}(H);var ra=function(d){function c(){return d.apply(this,arguments)||this}_inheritsLoose(c,
d);c.prototype.create=function(a){for(var b=this._fillMap(1),e=Math.ceil((this._width-2)/2),c=[],d=[],k=0;k<e;k++)c.push(k),d.push(k);c.push(e-1);for(k=1;k+3<this._height;k+=2)for(var h=0;h<e;h++){var g=2*h+1,l=k;b[g][l]=0;h!=c[h+1]&&.375<q.getUniform()&&(ca(h,c,d),b[g+1][l]=0);h!=c[h]&&.375<q.getUniform()?da(h,c,d):b[g][l+1]=0}for(h=0;h<e;h++)g=2*h+1,l=k,b[g][l]=0,h!=c[h+1]&&(h==c[h]||.375<q.getUniform())&&(ca(h,c,d),b[g+1][l]=0),da(h,c,d);for(e=0;e<this._width;e++)for(c=0;c<this._height;c++)a(e,
c,b[e][c]);return this};return c}(u),sa=function(d){function c(){var b=d.apply(this,arguments)||this;b._stack=[];b._map=[];return b}_inheritsLoose(c,d);var a=c.prototype;a.create=function(b){var a=this._width,c=this._height;this._map=[];for(var d=0;d<a;d++){this._map.push([]);for(var k=0;k<c;k++)this._map[d].push(0==d||0==k||d+1==a||k+1==c?1:0)}this._stack=[[1,1,a-2,c-2]];this._process();for(d=0;d<a;d++)for(k=0;k<c;k++)b(d,k,this._map[d][k]);this._map=[];return this};a._process=function(){for(;this._stack.length;){var b=
this._stack.shift();this._partitionRoom(b)}};a._partitionRoom=function(b){for(var a=[],c=[],d=b[0]+1;d<b[2];d++){var k=this._map[d][b[3]+1];!this._map[d][b[1]-1]||!k||d%2||a.push(d)}for(d=b[1]+1;d<b[3];d++)k=this._map[b[2]+1][d],!this._map[b[0]-1][d]||!k||d%2||c.push(d);if(a.length&&c.length){a=q.getItem(a);c=q.getItem(c);this._map[a][c]=1;d=[];k=[];d.push(k);for(var h=b[0];h<a;h++)this._map[h][c]=1,k.push([h,c]);k=[];d.push(k);for(h=a+1;h<=b[2];h++)this._map[h][c]=1,k.push([h,c]);k=[];d.push(k);
for(h=b[1];h<c;h++)this._map[a][h]=1,k.push([a,h]);k=[];d.push(k);for(h=c+1;h<=b[3];h++)this._map[a][h]=1,k.push([a,h]);k=q.getItem(d);for(h=0;h<d.length;h++){var g=d[h];g!=k&&(g=q.getItem(g),this._map[g[0]][g[1]]=0)}this._stack.push([b[0],b[1],a-1,c-1]);this._stack.push([a+1,b[1],b[2],c-1]);this._stack.push([b[0],c+1,a-1,b[3]]);this._stack.push([a+1,c+1,b[2],b[3]])}};return c}(u),ta=function(d){function c(b,a,c){void 0===c&&(c=0);b=d.call(this,b,a)||this;b._regularity=c;b._map=[];return b}_inheritsLoose(c,
d);var a=c.prototype;a.create=function(b){var a=this._width,c=this._height,d=this._fillMap(1);a-=a%2?1:2;c-=c%2?1:2;var k=0,h=[[0,0],[0,0],[0,0],[0,0]];do{var g=1+2*Math.floor(q.getUniform()*(a-1)/2);var l=1+2*Math.floor(q.getUniform()*(c-1)/2);k||(d[g][l]=0);if(!d[g][l]){this._randomize(h);do{0==Math.floor(q.getUniform()*(this._regularity+1))&&this._randomize(h);var p=!0;for(var r=0;4>r;r++){var t=g+2*h[r][0];var A=l+2*h[r][1];if(this._isFree(d,t,A,a,c)){d[t][A]=0;d[g+h[r][0]][l+h[r][1]]=0;g=t;l=
A;p=!1;k++;break}}}while(!p)}}while(k+1<a*c/4);for(a=0;a<this._width;a++)for(c=0;c<this._height;c++)b(a,c,d[a][c]);this._map=[];return this};a._randomize=function(b){for(var a=0;4>a;a++)b[a][0]=0,b[a][1]=0;switch(Math.floor(4*q.getUniform())){case 0:b[0][0]=-1;b[1][0]=1;b[2][1]=-1;b[3][1]=1;break;case 1:b[3][0]=-1;b[2][0]=1;b[1][1]=-1;b[0][1]=1;break;case 2:b[2][0]=-1;b[3][0]=1;b[0][1]=-1;b[1][1]=1;break;case 3:b[1][0]=-1,b[0][0]=1,b[3][1]=-1,b[2][1]=1}};a._isFree=function(b,a,c,d,k){return 1>a||
1>c||a>=d||c>=k?!1:b[a][c]};return c}(u);u=function(d){function c(b,a,c){b=d.call(this,b,a)||this;b.map=[];b.rooms=[];b.connectedCells=[];c=Object.assign({cellWidth:3,cellHeight:3},c);c.hasOwnProperty("roomWidth")||(c.roomWidth=b._calculateRoomSize(b._width,c.cellWidth));c.hasOwnProperty("roomHeight")||(c.roomHeight=b._calculateRoomSize(b._height,c.cellHeight));b._options=c;return b}_inheritsLoose(c,d);var a=c.prototype;a.create=function(b){this.map=this._fillMap(1);this.rooms=[];this.connectedCells=
[];this._initRooms();this._connectRooms();this._connectUnconnectedRooms();this._createRandomRoomConnections();this._createRooms();this._createCorridors();if(b)for(var a=0;a<this._width;a++)for(var c=0;c<this._height;c++)b(a,c,this.map[a][c]);return this};a._calculateRoomSize=function(b,a){var c=Math.floor(b/a*.8);b=Math.floor(b/a*.25);2>b&&(b=2);2>c&&(c=2);return[b,c]};a._initRooms=function(){for(var b=0;b<this._options.cellWidth;b++){this.rooms.push([]);for(var a=0;a<this._options.cellHeight;a++)this.rooms[b].push({x:0,
y:0,width:0,height:0,connections:[],cellx:b,celly:a})}};a._connectRooms=function(){var b=q.getUniformInt(0,this._options.cellWidth-1),a=q.getUniformInt(0,this._options.cellHeight-1);do{var c=[0,2,4,6];c=q.shuffle(c);do{var d=!1;var k=c.pop();var h=b+v[8][k][0];k=a+v[8][k][1];if(!(0>h||h>=this._options.cellWidth||0>k||k>=this._options.cellHeight)){var g=this.rooms[b][a];if(0<g.connections.length&&g.connections[0][0]==h&&g.connections[0][1]==k)break;g=this.rooms[h][k];0==g.connections.length&&(g.connections.push([b,
a]),this.connectedCells.push([h,k]),b=h,a=k,d=!0)}}while(0<c.length&&0==d)}while(0<c.length)};a._connectUnconnectedRooms=function(){var b=this._options.cellWidth,a=this._options.cellHeight;this.connectedCells=q.shuffle(this.connectedCells);for(var c,d,k,h=0;h<this._options.cellWidth;h++)for(var g=0;g<this._options.cellHeight;g++)if(c=this.rooms[h][g],0==c.connections.length){var l=[0,2,4,6];l=q.shuffle(l);k=!1;do{var p=l.pop(),r=h+v[8][p][0];p=g+v[8][p][1];if(!(0>r||r>=b||0>p||p>=a)){d=this.rooms[r][p];
k=!0;if(0==d.connections.length)break;for(r=0;r<d.connections.length;r++)if(d.connections[r][0]==h&&d.connections[r][1]==g){k=!1;break}if(k)break}}while(l.length);k?c.connections.push([d.cellx,d.celly]):console.log("-- Unable to connect room.")}};a._createRandomRoomConnections=function(){};a._createRooms=function(){for(var b=this._width,a=this._height,c=this._options.cellWidth,d=this._options.cellHeight,k=Math.floor(this._width/c),h=Math.floor(this._height/d),g,l,p=this._options.roomWidth,r=this._options.roomHeight,
t,A,u,v=0;v<c;v++)for(var w=0;w<d;w++){t=k*v;A=h*w;0==t&&(t=1);0==A&&(A=1);g=q.getUniformInt(p[0],p[1]);l=q.getUniformInt(r[0],r[1]);if(0<w)for(u=this.rooms[v][w-1];3>A-(u.y+u.height);)A++;if(0<v)for(u=this.rooms[v-1][w];3>t-(u.x+u.width);)t++;u=Math.round(q.getUniformInt(0,k-g)/2);for(var x=Math.round(q.getUniformInt(0,h-l)/2);t+u+g>=b;)u?u--:g--;for(;A+x+l>=a;)x?x--:l--;t+=u;A+=x;this.rooms[v][w].x=t;this.rooms[v][w].y=A;this.rooms[v][w].width=g;this.rooms[v][w].height=l;for(u=t;u<t+g;u++)for(x=
A;x<A+l;x++)this.map[u][x]=0}};a._getWallPosition=function(b,a){if(1==a||3==a){var c=q.getUniformInt(b.x+1,b.x+b.width-2);if(1==a){var e=b.y-2;b=e+1}else e=b.y+b.height+1,b=e-1;this.map[c][b]=0}else e=q.getUniformInt(b.y+1,b.y+b.height-2),2==a?(c=b.x+b.width+1,b=c-1):(c=b.x-2,b=c+1),this.map[b][e]=0;return[c,e]};a._drawCorridor=function(b,a){var c=a[0]-b[0],e=a[1]-b[1];a=b[0];b=b[1];var d=[];var h=Math.abs(c);var g=Math.abs(e);var l=q.getUniform();var p=1-l;c=0<c?2:6;e=0<e?4:0;h<g?(l=Math.ceil(g*
l),d.push([e,l]),d.push([c,h]),l=Math.floor(g*p),d.push([e,l])):(l=Math.ceil(h*l),d.push([c,l]),d.push([e,g]),l=Math.floor(h*p),d.push([c,l]));for(this.map[a][b]=0;0<d.length;)for(h=d.pop();0<h[1];)a+=v[8][h[0]][0],b+=v[8][h[0]][1],this.map[a][b]=0,--h[1]};a._createCorridors=function(){for(var a=this._options.cellWidth,c=this._options.cellHeight,d,g,k,h,n=0;n<a;n++)for(var l=0;l<c;l++){d=this.rooms[n][l];for(var p=0;p<d.connections.length;p++)g=d.connections[p],g=this.rooms[g[0]][g[1]],g.cellx>d.cellx?
(k=2,h=4):g.cellx<d.cellx?(k=4,h=2):g.celly>d.celly?(k=3,h=1):(k=1,h=3),this._drawCorridor(this._getWallPosition(d,k),this._getWallPosition(g,h))}};return c}(u);x={Arena:x,Uniform:D,Cellular:E,Digger:H,EllerMaze:ra,DividedMaze:sa,IceyMaze:ta,Rogue:u};var ua=.5*(Math.sqrt(3)-1),K=(3-Math.sqrt(3))/6;u={Simplex:function(d){function c(a){void 0===a&&(a=256);var b=d.call(this)||this;b._gradients=[[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]];for(var c=[],f=0;f<a;f++)c.push(f);c=q.shuffle(c);b._perms=
[];b._indexes=[];for(f=0;f<2*a;f++)b._perms.push(c[f%a]),b._indexes.push(b._perms[f]%b._gradients.length);return b}_inheritsLoose(c,d);c.prototype.get=function(a,b){var c=this._perms,d=this._indexes,g=c.length/2,k=0,h=0,n=0,l=(a+b)*ua,q=Math.floor(a+l);var r=Math.floor(b+l);var t=(q+r)*K;l=a-(q-t);var u=b-(r-t);if(l>u){var v=1;var w=0}else v=0,w=1;t=l-v+K;var x=u-w+K;b=l-1+2*K;a=u-1+2*K;q=p(q,g);g=p(r,g);var B=.5-l*l-u*u;0<=B&&(B*=B,r=d[q+c[g]],k=this._gradients[r],k=B*B*(k[0]*l+k[1]*u));l=.5-t*t-
x*x;0<=l&&(l*=l,r=d[q+v+c[g+w]],h=this._gradients[r],h=l*l*(h[0]*t+h[1]*x));l=.5-b*b-a*a;0<=l&&(l*=l,r=d[q+1+c[g+1]],c=this._gradients[r],n=l*l*(c[0]*b+c[1]*a));return 70*(k+h+n)};return c}(function(){})};E=function(){function d(c,a,b,d){void 0===d&&(d={});this._toX=c;this._toY=a;this._passableCallback=b;this._options=Object.assign({topology:8},d);this._dirs=v[this._options.topology];8==this._options.topology&&(this._dirs=[this._dirs[0],this._dirs[2],this._dirs[4],this._dirs[6],this._dirs[1],this._dirs[3],
this._dirs[5],this._dirs[7]])}d.prototype._getNeighbors=function(c,a){for(var b=[],d=0;d<this._dirs.length;d++){var f=this._dirs[d],g=c+f[0];f=a+f[1];this._passableCallback(g,f)&&b.push([g,f])}return b};return d}();D=function(d){function c(a,c,f,g){f=d.call(this,a,c,f,g)||this;f._computed={};f._todo=[];f._add(a,c,null);return f}_inheritsLoose(c,d);var a=c.prototype;a.compute=function(a,c,d){var b=a+","+c;b in this._computed||this._compute(a,c);if(b in this._computed)for(a=this._computed[b];a;)d(a.x,
a.y),a=a.prev};a._compute=function(a,c){for(;this._todo.length;){var b=this._todo.shift();if(b.x==a&&b.y==c)break;for(var d=this._getNeighbors(b.x,b.y),e=0;e<d.length;e++){var g=d[e],n=g[0];g=g[1];n+","+g in this._computed||this._add(n,g,b)}}};a._add=function(a,c,d){d={x:a,y:c,prev:d};this._computed[a+","+c]=d;this._todo.push(d)};return c}(E);E=function(d){function c(a,c,f,g){void 0===g&&(g={});a=d.call(this,a,c,f,g)||this;a._todo=[];a._done={};return a}_inheritsLoose(c,d);var a=c.prototype;a.compute=
function(a,c,d){this._todo=[];this._done={};this._fromX=a;this._fromY=c;for(this._add(this._toX,this._toY,null);this._todo.length;){var b=this._todo.shift(),e=b.x+","+b.y;if(!(e in this._done)){this._done[e]=b;if(b.x==a&&b.y==c)break;e=this._getNeighbors(b.x,b.y);for(var f=0;f<e.length;f++){var g=e[f],l=g[0];g=g[1];l+","+g in this._done||this._add(l,g,b)}}}if(a=this._done[a+","+c])for(;a;)d(a.x,a.y),a=a.prev};a._add=function(a,c,d){var b=this._distance(a,c);a={x:a,y:c,prev:d,g:d?d.g+1:0,h:b};c=a.g+
a.h;for(d=0;d<this._todo.length;d++){var e=this._todo[d],f=e.g+e.h;if(c<f||c==f&&b<e.h){this._todo.splice(d,0,a);return}}this._todo.push(a)};a._distance=function(a,c){switch(this._options.topology){case 4:return Math.abs(a-this._fromX)+Math.abs(c-this._fromY);case 6:return c=Math.abs(c-this._fromY),c+Math.max(0,(Math.abs(a-this._fromX)-c)/2);case 8:return Math.max(Math.abs(a-this._fromX),Math.abs(c-this._fromY))}};return c}(E);D={Dijkstra:D,AStar:E};E=function(){function d(a){this._scheduler=a;this._lock=
1}var c=d.prototype;c.start=function(){return this.unlock()};c.lock=function(){this._lock++;return this};c.unlock=function(){if(!this._lock)throw Error("Cannot unlock unlocked engine");for(this._lock--;!this._lock;){var a=this._scheduler.next();if(!a)return this.lock();(a=a.act())&&a.then&&(this.lock(),a.then(this.unlock.bind(this)))}return this};return d}();H=function(){function d(a,b){void 0===b&&(b={});this._reflectivityCallback=a;this._options={};b=Object.assign({passes:1,emissionThreshold:100,
range:10},b);this._lights={};this._reflectivityCache={};this._fovCache={};this.setOptions(b)}var c=d.prototype;c.setOptions=function(a){Object.assign(this._options,a);a&&a.range&&this.reset();return this};c.setFOV=function(a){this._fov=a;this._fovCache={};return this};c.setLight=function(a,b,c){a=a+","+b;c?this._lights[a]="string"==typeof c?L(c):c:delete this._lights[a];return this};c.clearLights=function(){this._lights={}};c.reset=function(){this._reflectivityCache={};this._fovCache={};return this};
c.compute=function(a){var b={},c={},d={},g;for(g in this._lights){var k=this._lights[g];c[g]=[0,0,0];Y(c[g],k)}for(g=0;g<this._options.passes;g++)this._emitLight(c,d,b),g+1!=this._options.passes&&(c=this._computeEmitters(d,b));for(var h in d)c=h.split(","),b=parseInt(c[0]),c=parseInt(c[1]),a(b,c,d[h]);return this};c._emitLight=function(a,b,c){for(var d in a){var e=d.split(","),g=parseInt(e[0]);e=parseInt(e[1]);this._emitLightFromCell(g,e,a[d],b);c[d]=1}return this};c._computeEmitters=function(a,b){var c=
{},d;for(d in a)if(!(d in b)){var g=a[d];if(d in this._reflectivityCache)var k=this._reflectivityCache[d];else{var h=d.split(",");k=parseInt(h[0]);h=parseInt(h[1]);k=this._reflectivityCallback(k,h);this._reflectivityCache[d]=k}if(0!=k){h=[0,0,0];for(var n=0,l=0;3>l;l++){var p=Math.round(g[l]*k);h[l]=p;n+=p}n>this._options.emissionThreshold&&(c[d]=h)}}return c};c._emitLightFromCell=function(a,b,c,d){var e=a+","+b;a=e in this._fovCache?this._fovCache[e]:this._updateFOV(a,b);for(var f in a){b=a[f];f in
d?e=d[f]:(e=[0,0,0],d[f]=e);for(var g=0;3>g;g++)e[g]+=Math.round(c[g]*b)}return this};c._updateFOV=function(a,b){var c={};this._fovCache[a+","+b]=c;var d=this._options.range;this._fov.compute(a,b,d,function(a,b,e,f){e=f*(1-e/d);0!=e&&(c[a+","+b]=e)}.bind(this));return c};return d}();g.Util=ka;g.Color=J;g.Text=I;g.RNG=q;g.Display=G;g.StringGenerator=O;g.EventQueue=ea;g.Scheduler=F;g.FOV=B;g.Map=x;g.Noise=u;g.Path=D;g.Engine=E;g.Lighting=H;g.DEFAULT_WIDTH=80;g.DEFAULT_HEIGHT=25;g.DIRS=v;g.KEYS={VK_CANCEL:3,
VK_HELP:6,VK_BACK_SPACE:8,VK_TAB:9,VK_CLEAR:12,VK_RETURN:13,VK_ENTER:14,VK_SHIFT:16,VK_CONTROL:17,VK_ALT:18,VK_PAUSE:19,VK_CAPS_LOCK:20,VK_ESCAPE:27,VK_SPACE:32,VK_PAGE_UP:33,VK_PAGE_DOWN:34,VK_END:35,VK_HOME:36,VK_LEFT:37,VK_UP:38,VK_RIGHT:39,VK_DOWN:40,VK_PRINTSCREEN:44,VK_INSERT:45,VK_DELETE:46,VK_0:48,VK_1:49,VK_2:50,VK_3:51,VK_4:52,VK_5:53,VK_6:54,VK_7:55,VK_8:56,VK_9:57,VK_COLON:58,VK_SEMICOLON:59,VK_LESS_THAN:60,VK_EQUALS:61,VK_GREATER_THAN:62,VK_QUESTION_MARK:63,VK_AT:64,VK_A:65,VK_B:66,VK_C:67,
VK_D:68,VK_E:69,VK_F:70,VK_G:71,VK_H:72,VK_I:73,VK_J:74,VK_K:75,VK_L:76,VK_M:77,VK_N:78,VK_O:79,VK_P:80,VK_Q:81,VK_R:82,VK_S:83,VK_T:84,VK_U:85,VK_V:86,VK_W:87,VK_X:88,VK_Y:89,VK_Z:90,VK_CONTEXT_MENU:93,VK_NUMPAD0:96,VK_NUMPAD1:97,VK_NUMPAD2:98,VK_NUMPAD3:99,VK_NUMPAD4:100,VK_NUMPAD5:101,VK_NUMPAD6:102,VK_NUMPAD7:103,VK_NUMPAD8:104,VK_NUMPAD9:105,VK_MULTIPLY:106,VK_ADD:107,VK_SEPARATOR:108,VK_SUBTRACT:109,VK_DECIMAL:110,VK_DIVIDE:111,VK_F1:112,VK_F2:113,VK_F3:114,VK_F4:115,VK_F5:116,VK_F6:117,VK_F7:118,
VK_F8:119,VK_F9:120,VK_F10:121,VK_F11:122,VK_F12:123,VK_F13:124,VK_F14:125,VK_F15:126,VK_F16:127,VK_F17:128,VK_F18:129,VK_F19:130,VK_F20:131,VK_F21:132,VK_F22:133,VK_F23:134,VK_F24:135,VK_NUM_LOCK:144,VK_SCROLL_LOCK:145,VK_CIRCUMFLEX:160,VK_EXCLAMATION:161,VK_DOUBLE_QUOTE:162,VK_HASH:163,VK_DOLLAR:164,VK_PERCENT:165,VK_AMPERSAND:166,VK_UNDERSCORE:167,VK_OPEN_PAREN:168,VK_CLOSE_PAREN:169,VK_ASTERISK:170,VK_PLUS:171,VK_PIPE:172,VK_HYPHEN_MINUS:173,VK_OPEN_CURLY_BRACKET:174,VK_CLOSE_CURLY_BRACKET:175,
VK_TILDE:176,VK_COMMA:188,VK_PERIOD:190,VK_SLASH:191,VK_BACK_QUOTE:192,VK_OPEN_BRACKET:219,VK_BACK_SLASH:220,VK_CLOSE_BRACKET:221,VK_QUOTE:222,VK_META:224,VK_ALTGR:225,VK_WIN:91,VK_KANA:21,VK_HANGUL:21,VK_EISU:22,VK_JUNJA:23,VK_FINAL:24,VK_HANJA:25,VK_KANJI:25,VK_CONVERT:28,VK_NONCONVERT:29,VK_ACCEPT:30,VK_MODECHANGE:31,VK_SELECT:41,VK_PRINT:42,VK_EXECUTE:43,VK_SLEEP:95};Object.defineProperty(g,"__esModule",{value:!0})});
+57
View File
@@ -0,0 +1,57 @@
var axios = require('axios');
var request = require('request');
/*List of RPC calls
Daemon -
Get blockheight: "get_block_count"
*/
function constructCall(method, params) {
var body = {
"jsonrpc": "2.0",
"id": "0",
"method": method,
"params": params
}
return body;
}
var lastBlock = {
height: 0,
hash: 0,
setHeight: function(newHeight) {this.height = newHeight}
}
var daemonURL = 'http://localhost:34568/json_rpc';
function daemonCall(method, params, callback, io) {
// Real blockchain call (now used for all cases)
var body = constructCall(method, params);
request({
url: daemonURL,
method: 'POST',
headers: {"content-type": "application/json"},
json: body
}, function (error, res, body) {
if (error) {
console.log("Daemon connection error:", error.message);
callback(null);
return;
}
callback(body);
});
}
function getBlockHeight(body) {
if (body == null || body.result == null || body.result.count == null) {
return null;
}
return body.result.count;
}
// Export the functions
module.exports = {
daemonCall: daemonCall,
getBlockHeight: getBlockHeight,
lastBlock: lastBlock,
};
+64
View File
@@ -0,0 +1,64 @@
const Game = require('./game'); // Add this import at the top
// Global users registry
const userRegistry = new Map();
function User(socketid, ip) {
this.id = null;
this.socketid = socketid;
this.ip = ip;
this.nick = null;
this.receiveAddy = "";
this.payoutAddy = "";
this.blockRec = null;
this.map = null;
this.player = false;
this.won = false;
this.game = null; // Reference to current game
this.paymentAmount = 0;
this.paymentVerified = false;
// Auto-register in the registry
userRegistry.set(socketid, this);
console.log(`User registered with socket ID: ${socketid}`);
}
// Add lookup function
function getUserBySocketId(socketId) {
const user = userRegistry.get(socketId);
console.log(`Looking up user ${socketId}: ${user ? "FOUND" : "NOT FOUND"}`);
return user;
}
// Add function to remove user
function removeUser(socketId) {
const removed = userRegistry.delete(socketId);
console.log(`User ${socketId} removed: ${removed ? "YES" : "NO"}`);
return removed;
}
// Get all users as array
function getAllUsers() {
return Array.from(userRegistry.values());
}
// Add this method to your User function/class
User.prototype.startGame = function(width, height) {
console.log(`Creating new game for user ${this.socketid} with dimensions ${width}x${height}`);
try {
this.game = new Game(this.socketid, width, height);
return this.game;
} catch (err) {
console.error("Error creating game:", err);
return null;
}
};
// Rest of your existing code...
module.exports = {
User: User,
getUserBySocketId: getUserBySocketId,
removeUser: removeUser,
getAllUsers: getAllUsers
};