diff --git a/README.md b/README.md
index 18d7b5a..40d7317 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,12 @@ The game is built using Node.js for the backend, with Express and Socket.io for
- **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
-- **Real-time multiplayer**: Multiple players can play simultaneously
-- **Chat system**: Players can communicate in real-time during gameplay
+- **Real-time multiplayer**: Multiple players can play simultaneously with efficient socket broadcasting
+- **Structured chat system**: Public chat broadcasts and private status updates with command separation
- **Field of view**: Limited visibility adds challenge and atmosphere
- **HTML START button**: Visible button in status area for game entry
+- **Auto-return system**: 30-second timeout returns players to title screen after game over
+- **Spectator-ready architecture**: Socket system designed for future spectator mode implementation
## Technology Stack
@@ -26,7 +28,27 @@ The game is built using Node.js for the backend, with Express and Socket.io for
- **Frontend**: HTML5, CSS, JavaScript with ROT.js for tile-based rendering
- **Blockchain**: Wownero RPC integration for block monitoring
- **Database**: SQLite for user data and game history
-- **Real-time Communication**: WebSocket connections via Socket.io
+- **Real-time Communication**: WebSocket connections via Socket.io with structured event broadcasting
+
+## Socket.io Real-Time Communication
+
+The game implements a sophisticated real-time communication system using Socket.io:
+
+### Broadcasting Strategy
+- **Block Height**: Broadcast to all clients every 5 seconds (not just on changes)
+- **Game Updates**: Player-specific with spectator support planned
+- **Chat Messages**: Public broadcasts vs private status updates
+- **Connection Status**: Immediate updates on connect/disconnect
+
+### Event Categories
+1. **Global Broadcasts** (`io.emit()`) - Block height, public chat
+2. **Player-Specific** (`io.to(socketId).emit()`) - Game updates, status messages
+3. **Future Spectator Support** - Multi-cast game updates to viewers
+
+### Production Scalability
+- Efficient event routing prevents unnecessary network traffic
+- Structured for adding spectator mode without performance impact
+- Clean separation between commands and actual chat messages
## Architecture
@@ -34,7 +56,7 @@ The project has been refactored into a clean, modular architecture with clear se
### Backend Structure (`src/backend/`)
-- **index.js** - Main server file handling HTTP requests, WebSocket connections, game lifecycle, and blockchain monitoring
+- **index.js** - Main server file handling HTTP requests, WebSocket connections, game lifecycle, blockchain monitoring, and structured socket event broadcasting
- **game.js** - Game class with dungeon generation, player movement, monster AI, and field-of-view calculations
- **user.js** - User registration, authentication, game association, and payment tracking
- **player.js** - Player state management and movement logic
@@ -43,6 +65,12 @@ The project has been refactored into a clean, modular architecture with clear se
- **dbcalls.js** - Database operations for user data and game history
- **rpccalls.js** - Wownero blockchain RPC integration
+#### Socket Event Broadcasting (index.js)
+- **Helper Functions**: `sendGameUpdate()`, `broadcastBlockHeight()`, `sendStatusUpdate()`
+- **Event Types**: Global broadcasts, player-specific updates, status messages
+- **Spectator Ready**: Architecture supports future multi-cast to spectators
+- **Regular Broadcasting**: Block height sent every 5 seconds to all clients
+
### Frontend Structure (`html/`)
#### Modular JavaScript Architecture:
@@ -79,8 +107,88 @@ The project has been refactored into a clean, modular architecture with clear se
- **socketHandlers.js** - WebSocket communication with initialization protection
- **ui.js** - User interface management and chat functionality
+## Socket Event Architecture
+
+The game uses a structured socket.io event system designed for real-time multiplayer gameplay with future spectator support:
+
+### Event Broadcasting Strategy
+
+#### **Block Height Broadcasting** (All Clients)
+- `blockheight` - Broadcast to ALL connected clients every 5 seconds
+- Ensures all players stay synchronized with blockchain state
+- Immediate emission on client connection
+- Regular updates prevent clients from missing block changes
+
+#### **Game State Updates** (Player + Future Spectators)
+- `game_update` - Currently sent to active player only
+- **Future Goal**: Broadcast to spectators watching specific games
+- Structured for easy expansion to multi-viewer support
+- Contains player position, FOV, monster state, items
+
+#### **Chat System** (Broadcast vs Player-Specific)
+- `chat_broadcast` - PUBLIC chat messages sent to ALL clients
+- `status_update` - PRIVATE status messages sent to individual players
+- Commands (like "enter", "hello") trigger status updates, not chat broadcasts
+- Real chat messages include username, timestamp, and message content
+
+#### **Connection & Status Management**
+- `status_update` - Player-specific notifications (errors, confirmations, help)
+- `welcome` - Initial connection acknowledgment
+- `game_start` / `game_over` - Game lifecycle events
+- Connection status immediately broadcast on client connect
+
+### Socket Event Types
+
+```javascript
+// Broadcast Events (All Clients)
+io.emit('blockheight', blockHeight);
+io.emit('chat_broadcast', { username, message, timestamp });
+
+// Player-Specific Events
+io.to(socketId).emit('status_update', { type, message, timestamp });
+io.to(socketId).emit('game_update', gameState);
+io.to(socketId).emit('game_start', gameState);
+io.to(socketId).emit('game_over', { status, reason, message });
+
+// Future Spectator Events (TODO)
+io.to(spectatorId).emit('spectator_update', { playerSocketId, gameState });
+```
+
+### Future Spectator System (Roadmap)
+
+The socket architecture is designed to support spectating with minimal changes:
+
+1. **Spectator Registration**: Players can request to watch active games
+2. **Multi-Cast Updates**: Game updates broadcast to player + all their spectators
+3. **Spectator-Specific Events**: Special events for spectator UI (player info, game stats)
+4. **Spectator Chat**: Separate chat channels for spectators vs players
+5. **Spectator Limits**: Configurable limits on spectators per game
+
+### Real-Time Communication Goals
+
+- **Blockchain Sync**: All clients receive block updates every few seconds (not just on block changes)
+- **Scalable Game Updates**: Player actions broadcast efficiently to relevant viewers only
+- **Rich Chat System**: Distinction between commands/status and actual chat communication
+- **Spectator Ready**: Architecture supports adding spectator features without major refactoring
+
## Recent Major Improvements
+### ๐ Socket Architecture Overhaul
+- **Implemented**: Structured event system for broadcast vs player-specific messages
+- **Features**: Block height broadcasting, chat system separation, spectator-ready design
+- **Helper Functions**: `sendGameUpdate()`, `broadcastBlockHeight()`, `sendStatusUpdate()`
+- **Result**: Clean separation of concerns and foundation for spectator mode
+
+### ๐ Death Logic Fix
+- **Fixed**: Players not dying at correct block timing in autostart/debug mode
+- **Solution**: Updated death logic to kill players the block after they enter
+- **Result**: Players die exactly when block advances after entry (block N+1 after entering on block N)
+
+### โฐ Auto-Return to Title Screen
+- **Added**: 30-second timeout after game over (win/lose/timeout)
+- **Features**: Automatic return to welcome screen, proper game state reset
+- **Result**: Seamless game flow without manual intervention
+
### ๐ฏ Movement System Overhaul
- **Fixed**: Players skipping over tiles in hallways
- **Solution**: Added movement throttling (100ms) and prevented duplicate event handlers
@@ -91,11 +199,6 @@ The project has been refactored into a clean, modular architecture with clear se
- **Solution**: Moved to HTML status area with proper styling and visibility control
- **Result**: Clearly visible green "๐ฎ START GAME" button
-### โฑ๏ธ Auto-Entry Timing Fix
-- **Fixed**: Players dying after 2+ blocks instead of 1 block in debug mode
-- **Solution**: Fixed backend timing logic to set `currentUser.blockRec = currentBlock + 1`
-- **Result**: Consistent 3-block timing for all entry methods
-
### ๐ Auto-Start System
- **Added**: Immediate game start for development and testing
- **Features**: Debug mode detection, 'D' key shortcut, proper SocketHandlers initialization
diff --git a/html/js/game.js b/html/js/game.js
index 1b8c572..e4f6ee3 100644
--- a/html/js/game.js
+++ b/html/js/game.js
@@ -47,12 +47,14 @@ var Game = {
return navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
},
- startGame: function(playerData, mapData, monsterData, itemData, initialVisibleTiles) {
+ startGame: function(playerData, mapData, monsterData, itemData, initialVisibleTiles, lightingData, torchData) {
if (!this._ensureDisplay()) {
console.error("Game: Cannot start game, display not ready.");
return false;
}
console.log("Game: startGame called. PlayerData:", playerData, "MapData:", mapData, "InitialVisibleTiles:", initialVisibleTiles);
+ console.log("๐ฅ LIGHTING DATA received in startGame:", lightingData);
+ console.log("๐ฆ TORCH DATA received in startGame:", torchData);
DisplayManager.clearDisplay();
@@ -92,6 +94,16 @@ var Game = {
GameState._exit = null;
GameState._treasure = null;
+ // Initialize lighting and torch data
+ if (lightingData) {
+ GameState._lighting = lightingData;
+ console.log("๐ฅ GameState: Lighting data stored with", Object.keys(lightingData).length, "rows");
+ }
+ if (torchData) {
+ GameState._torches = torchData;
+ console.log("๐ฆ GameState: Torch data stored with", torchData.length, "torches");
+ }
+
// Initialize map and validate player position
GameState.initializeMap(mapData, initialVisibleTiles, this._screenWidth, this._screenHeight);
@@ -155,6 +167,15 @@ var Game = {
_drawGameScreen: function() {
if (!this._ensureDisplay()) return;
const gameState = GameState.getGameStateForRender();
+
+ // Debug: Verify lighting data is included in render state
+ console.log("๐จ _drawGameScreen() passing lighting data:", {
+ hasLighting: !!gameState.lighting,
+ lightingKeys: gameState.lighting ? Object.keys(gameState.lighting).length : 0,
+ hasTorches: !!gameState.torches,
+ torchCount: gameState.torches ? gameState.torches.length : 0
+ });
+
RenderEngine.drawGameScreen(gameState);
},
diff --git a/html/js/gameState.js b/html/js/gameState.js
index fd13451..a1988f0 100644
--- a/html/js/gameState.js
+++ b/html/js/gameState.js
@@ -13,6 +13,8 @@ var GameState = {
_message: null,
_scheduler: null,
_engine: null,
+ _lighting: {},
+ _torches: [],
init: function() {
this.reset();
@@ -30,6 +32,8 @@ var GameState = {
this._exit = null;
this._treasure = null;
this._message = null;
+ this._lighting = {};
+ this._torches = [];
if (this._scheduler) this._scheduler.clear();
if (!this._scheduler) this._scheduler = new ROT.Scheduler.Simple();
@@ -249,6 +253,21 @@ var GameState = {
needsRedraw = true;
}
+ // Update lighting and torch data
+ if (data.lighting !== undefined) {
+ console.log("๐ฅ BEFORE UPDATE: this._lighting has", this._lighting ? Object.keys(this._lighting).length : 0, "rows");
+ console.log("๐ฅ NEW DATA: data.lighting has", Object.keys(data.lighting).length, "rows");
+ console.log("๐ฅ LIGHTING DATA SAMPLE:", JSON.stringify(data.lighting).substring(0, 200));
+ this._lighting = data.lighting;
+ console.log("๐ฅ AFTER UPDATE: this._lighting has", this._lighting ? Object.keys(this._lighting).length : 0, "rows");
+ needsRedraw = true;
+ }
+ if (data.torches !== undefined) {
+ this._torches = data.torches;
+ needsRedraw = true;
+ console.log("๐ฆ GameState: Updated torch data with", data.torches.length, "torches");
+ }
+
// Update visible tiles (most critical for movement feedback)
if (data.visibleTiles && typeof data.visibleTiles === 'object' && Object.keys(data.visibleTiles).length > 0) {
console.log("Visible tiles updated. Number of rows:", Object.keys(data.visibleTiles).length);
@@ -305,20 +324,6 @@ var GameState = {
}
},
- getGameStateForRender: function() {
- return {
- player: this._player,
- monster: this._monster,
- items: this._items,
- entrance: this._entrance,
- exit: this._exit,
- treasure: this._treasure,
- visibleTiles: this._visibleTiles,
- exploredTiles: this._exploredTiles,
- message: this._message
- };
- },
-
// Debug functions
debugPrintMap: function(screenWidth, screenHeight) {
console.log("Current game map:");
@@ -338,7 +343,7 @@ var GameState = {
debugTileMapping: function() {
const testValues = [0, 1, undefined, null];
for (const val of testValues) {
- const char = (val === 1) ? '#' : "'";
+ const char = (val === 1) ? '#' : "\'";
const debugMsg = `Test mapping: value=${val} -> char='${char}'`;
console.log(debugMsg);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(debugMsg);
@@ -376,75 +381,18 @@ var GameState = {
console.log("Visible tiles:", Object.keys(this._visibleTiles).length);
},
- // Update game state with new data from server
- updateGameState: function(data) {
- let needsRedraw = false;
-
- if (data.player && (data.player.x !== this._player.x || data.player.y !== this._player.y)) {
- this._player = data.player;
- needsRedraw = true;
- }
-
- if (data.monster) {
- this._monster = data.monster;
- needsRedraw = true;
- }
-
- if (data.items) {
- this._items = data.items;
- needsRedraw = true;
- }
-
- if (data.map) {
- this._map = data.map;
- needsRedraw = true;
- }
-
- // Handle visibleTiles from server (this is the FOV data)
- if (data.visibleTiles) {
- this._visibleTiles = data.visibleTiles;
-
- // Update explored tiles with newly visible areas
- for (const yKey in this._visibleTiles) {
- const y = parseInt(yKey);
- if (!this._exploredTiles[y]) this._exploredTiles[y] = {};
- for (const xKey in this._visibleTiles[y]) {
- const x = parseInt(xKey);
- this._exploredTiles[y][x] = this._visibleTiles[y][x];
- }
- }
-
- // console.log("Updated visibleTiles from server, visible tile count:",
- // Object.keys(this._visibleTiles).reduce((count, y) => count + Object.keys(this._visibleTiles[y]).length, 0));
- needsRedraw = true;
- }
-
- if (data.entrance) {
- this._entrance = data.entrance;
- needsRedraw = true;
- }
-
- if (data.exit) {
- this._exit = data.exit;
- needsRedraw = true;
- }
-
- if (data.treasure !== undefined) {
- this._treasure = data.treasure;
- needsRedraw = true;
- }
-
- if (data.message) {
- this._message = data.message;
- needsRedraw = true;
- }
-
- return needsRedraw;
- },
-
// Get game state data for rendering
getGameStateForRender: function() {
- return {
+ // Debug: Log the actual lighting data being returned
+ console.log("๐ getGameStateForRender() lighting check:", {
+ lightingExists: !!this._lighting,
+ lightingKeys: this._lighting ? Object.keys(this._lighting).length : 0,
+ lightingRef: this._lighting,
+ torchExists: !!this._torches,
+ torchCount: this._torches ? this._torches.length : 0
+ });
+
+ const renderState = {
map: this._map,
player: this._player,
monster: this._monster,
@@ -455,8 +403,13 @@ var GameState = {
visibleTiles: this._visibleTiles,
exploredTiles: this._exploredTiles,
message: this._message,
- gameActive: this._gameActive
+ gameActive: this._gameActive,
+ lighting: this._lighting,
+ torches: this._torches
};
+
+ console.log("๐ getGameStateForRender() RETURNING lighting with", renderState.lighting ? Object.keys(renderState.lighting).length : 0, "keys");
+ return renderState;
},
// Move player and update game state
@@ -485,9 +438,6 @@ var GameState = {
this._player.x = newX;
this._player.y = newY;
- // Recompute field of view
- this.computeFieldOfView();
-
// console.log("Player moved to:", this._player.x, this._player.y);
return true;
}
diff --git a/html/js/options.js b/html/js/options.js
index ddfeb80..d51b640 100644
--- a/html/js/options.js
+++ b/html/js/options.js
@@ -19,6 +19,7 @@ var options = {
height: 19, // 19 tiles * 32px/tile = 608px
tileWidth: 32, // Width of a single tile in pixels
tileHeight: 32, // Height of a single tile in pixels
+ tileColorize: true, // Enable colorization for tile stacks
tileSet: tileSet,
// GAME CONFIGURATION - Change these to select different assets
diff --git a/html/js/renderEngine.js b/html/js/renderEngine.js
index 00e8014..b428581 100644
--- a/html/js/renderEngine.js
+++ b/html/js/renderEngine.js
@@ -27,6 +27,7 @@ var RenderEngine = {
const torches = gameState.torches || []; // New torch positions
const visibleTiles = gameState.visibleTiles || {};
const exploredTiles = gameState.exploredTiles || {};
+ const lighting = gameState.lighting || {}; // Lighting data from server
const message = gameState.message;
// Get configured tile types from options
@@ -56,6 +57,12 @@ var RenderEngine = {
let baseFg = this.defaultFg;
let tileType;
+ // Debug log for a specific tile if needed - replace with actual test coordinates
+ // const wx_test = 10, wy_test = 10; // Example coordinates
+ // if (wx === wx_test && wy === wy_test) {
+ // console.log(`RenderEngine DEBUG: Tile (${wx},${wy}) raw lighting data: ${lighting[wy] ? lighting[wy][wx] : 'N/A'}`);
+ // }
+
// Render base terrain
if (this.isVisible(wx, wy, visibleTiles)) {
tileType = visibleTiles[wy][wx];
@@ -106,11 +113,28 @@ var RenderEngine = {
if (baseChar && baseChar !== ' ') {
charStack.push(baseChar);
- // Use black transparent overlay for explored tiles, normal colors for currently visible tiles
- if (this.isVisible(wx, wy, visibleTiles)) {
- fgStack.push("transparent"); // Currently visible - use normal tile colors
+ // Get lighting information for this tile
+ const lightingAlpha = (lighting[wy] && lighting[wy][wx]) ? lighting[wy][wx] : 0.0;
+
+ // Apply lighting shadows for terrain
+ const isCurrentlyVisible = this.isVisible(wx, wy, visibleTiles);
+
+ // Added log to inspect lightingAlpha for visible, lit tiles
+ if (isCurrentlyVisible && lightingAlpha > 0.01) { // Using 0.01 to catch small, non-zero alphas
+ console.log(`RenderEngine TRACE: Tile (${wx},${wy}), Visible: ${isCurrentlyVisible}, lightingAlpha: ${lightingAlpha.toFixed(3)}, from: ${lighting[wy] ? lighting[wy][wx].toFixed(3) : 'N/A'}`);
+ }
+
+ if (isCurrentlyVisible) {
+ if (lightingAlpha > 0) {
+ // Apply torch shadow using foreground overlay
+ fgStack.push(`rgba(0, 0, 0, ${Math.min(lightingAlpha, 0.8)})`);
+ } else {
+ // Normal tile with no shadow
+ fgStack.push("transparent");
+ }
} else {
- fgStack.push("rgba(0, 0, 0, 0.6)"); // Explored but not visible - use dark transparent overlay
+ // Explored but not currently visible - apply darker shadow
+ fgStack.push(`rgba(0, 0, 0, 0.7)`);
}
bgStack.push("transparent"); // Always transparent background for terrain
@@ -123,7 +147,13 @@ var RenderEngine = {
const currentItem = items[itemKey];
if (currentItem && currentItem.x === wx && currentItem.y === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push('$');
- fgStack.push("transparent"); // Use transparent for now
+ // Apply torch lighting to items
+ const lightingAlpha = (lighting[wy] && lighting[wy][wx]) ? lighting[wy][wx] : 0.0;
+ if (lightingAlpha > 0) {
+ fgStack.push(`rgba(0, 0, 0, ${Math.min(lightingAlpha, 0.8)})`);
+ } else {
+ fgStack.push("transparent");
+ }
bgStack.push("transparent");
}
}
@@ -133,42 +163,68 @@ var RenderEngine = {
// Render entrance
if (entrance && entrance[0] === wx && entrance[1] === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push('<');
- fgStack.push("transparent"); // Use transparent for now
+ // Apply torch lighting to entrance
+ const lightingAlpha = (lighting[wy] && lighting[wy][wx]) ? lighting[wy][wx] : 0.0;
+ if (lightingAlpha > 0) {
+ fgStack.push(`rgba(0, 0, 0, ${Math.min(lightingAlpha, 0.8)})`);
+ } else {
+ fgStack.push("transparent");
+ }
bgStack.push("transparent");
}
// Render exit
if (exit && exit[0] === wx && exit[1] === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push('>');
- fgStack.push("transparent"); // Use transparent for now
+ // Apply torch lighting to exit
+ const lightingAlpha = (lighting[wy] && lighting[wy][wx]) ? lighting[wy][wx] : 0.0;
+ if (lightingAlpha > 0) {
+ fgStack.push(`rgba(0, 0, 0, ${Math.min(lightingAlpha, 0.8)})`);
+ } else {
+ fgStack.push("transparent");
+ }
bgStack.push("transparent");
}
// Render treasure
if (treasure && treasure[0] === wx && treasure[1] === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push(treasureTile);
- fgStack.push("transparent"); // Use transparent for now
+ // Apply torch lighting to treasure
+ const lightingAlpha = (lighting[wy] && lighting[wy][wx]) ? lighting[wy][wx] : 0.0;
+ if (lightingAlpha > 0) {
+ fgStack.push(`rgba(0, 0, 0, ${Math.min(lightingAlpha, 0.8)})`);
+ } else {
+ fgStack.push("transparent");
+ }
bgStack.push("transparent");
}
// Render torches (check if this tile has a torch)
if (this.isVisible(wx, wy, visibleTiles) && visibleTiles[wy][wx] === 'torch') {
charStack.push(torchTile);
- fgStack.push("transparent"); // Use transparent for now
+ // Don't apply shadows to torches since they're light sources
+ fgStack.push("transparent");
bgStack.push("transparent");
}
// Render monster
if (monster && monster.x === wx && monster.y === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push(monsterTile);
- fgStack.push("transparent"); // Use transparent for now
+ // Apply torch lighting to monster
+ const lightingAlpha = (lighting[wy] && lighting[wy][wx]) ? lighting[wy][wx] : 0.0;
+ if (lightingAlpha > 0) {
+ fgStack.push(`rgba(0, 0, 0, ${Math.min(lightingAlpha, 0.8)})`);
+ } else {
+ fgStack.push("transparent");
+ }
bgStack.push("transparent");
}
// Render player (always on top)
if (wx === playerWX && wy === playerWY) {
charStack.push(playerTile);
- fgStack.push("transparent"); // Use transparent for now
+ // Don't apply shadows to player - keep them clearly visible
+ fgStack.push("transparent");
bgStack.push("transparent");
}
diff --git a/html/js/socketHandlers.js b/html/js/socketHandlers.js
index 650a2ec..e7dc1eb 100644
--- a/html/js/socketHandlers.js
+++ b/html/js/socketHandlers.js
@@ -104,6 +104,8 @@ const SocketHandlers = {
console.log(" - Monster:", data?.monster);
console.log(" - Items:", data?.items);
console.log(" - VisibleTiles keys:", data?.visibleTiles ? Object.keys(data.visibleTiles).length : "NO VISIBLE TILES");
+ console.log(" - Lighting keys:", data?.lighting ? Object.keys(data.lighting).length : "NO LIGHTING");
+ console.log(" - Torches:", data?.torches ? data.torches.length : "NO TORCHES (expected - lighting calculated server-side)");
$('#messages').append($('
').text("Starting game..."));
@@ -118,8 +120,8 @@ const SocketHandlers = {
}
try {
- // Use the original game.js structure
- var success = Game.startGame(data.player, data.map, data.monster, data.items, data.visibleTiles);
+ // Pass all game data including lighting and torches
+ var success = Game.startGame(data.player, data.map, data.monster, data.items, data.visibleTiles, data.lighting, data.torches);
console.log("Game start result:", success ? "SUCCESS" : "FAILED");
if (!success) {
@@ -143,6 +145,22 @@ const SocketHandlers = {
onGameUpdate: function(data) {
// Debug log to see what data we're receiving
+ console.log("๐ฎ GAME UPDATE received with keys:", Object.keys(data));
+
+ // Debug lighting data specifically
+ if (data.lighting && Object.keys(data.lighting).length > 0) {
+ console.log("๐ฅ GAME UPDATE: lighting data received with", Object.keys(data.lighting).length, "rows");
+ } else {
+ console.log("โ ๏ธ GAME UPDATE: NO lighting data in update");
+ }
+
+ // Debug torch data specifically
+ if (data.torches && data.torches.length > 0) {
+ console.log("๐ฆ GAME UPDATE: torch data received with", data.torches.length, "torches");
+ } else {
+ console.log("โน๏ธ GAME UPDATE: No torch data (expected - lighting calculated server-side)");
+ }
+
if (data.visibleTiles) {
const debugMsg = "๐ SOCKET: Received game update with visibleTiles";
console.log(debugMsg);
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..7d823a4
--- /dev/null
+++ b/index.js
@@ -0,0 +1,422 @@
+const express = require('express');
+var app = require('express')();
+var http = require('http').Server(app);
+var io = require('socket.io')(http);
+var rpc = require('./src/backend/rpccalls.js');
+var db = require('./src/backend/dbcalls.js');
+var user = require('./src/backend/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
+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');
+});
+
+// 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 has 3 blocks to complete the dungeon
+ // If they started on block 2, they must escape before block 5 is found
+ if (user && user.blockRec && currentHeight > user.blockRec + 2) {
+ console.log(`Game timeout for player ${socketId}: started on block ${user.blockRec}, current block ${currentHeight} (allowed until block ${user.blockRec + 2})`);
+
+ // 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 block time limit!'
+ });
+
+ 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 send waiting_status
+ if (msg.toLowerCase() == 'enter') {
+ console.log(`Player ${socket.id} requested to enter the dungeon`);
+
+ // Use the updated user lookup function
+ const currentUser = getUserBySocket(socket.id);
+
+ if (currentUser) {
+ console.log(`Found user for socket ${socket.id}, adding to queue...`);
+
+ // Add to waiting players queue
+ if (!WAITING_PLAYERS.some(p => p.serverId === socket.id)) {
+ WAITING_PLAYERS.push({
+ serverId: socket.id,
+ clientId: socket.id,
+ user: currentUser,
+ joinedAt: Date.now()
+ });
+
+ // Send waiting status data for display - THIS IS THE KEY PART
+ io.to(socket.id).emit('waiting_status', {
+ status: 'waiting',
+ message: 'Waiting for the next block to be found...',
+ position: WAITING_PLAYERS.length,
+ currentBlock: debugBlockHeight || lastBlockHeight || 0,
+ joinTime: Date.now()
+ });
+
+ console.log(`Added player to queue. Current queue: ${WAITING_PLAYERS.length} players`);
+ } else {
+ io.to(socket.id).emit('message', 'You are already in the queue. Please wait.');
+ }
+ } else {
+ io.to(socket.id).emit('message', 'Error: Could not add you to the game queue. 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') {
+ if (typeof moveData.dx === 'number' && typeof moveData.dy === 'number') {
+ 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);
+ 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)}...`);
+}
diff --git a/src/backend/game.js b/src/backend/game.js
index 6de2d80..1c5df7f 100644
--- a/src/backend/game.js
+++ b/src/backend/game.js
@@ -141,10 +141,11 @@ class Game {
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
+ lighting: this.calculateLighting(), // Add lighting data
entrance: this.dungeon ? this.dungeon.entrance : null,
exit: this.dungeon ? this.dungeon.exit : null,
treasure: this.dungeon ? this.dungeon.treasure : null,
- torches: this.dungeon ? this.dungeon.torches : [], // Include torch positions
+ // Don't send torch positions - client doesn't need them since lighting is calculated server-side
};
// Debug logging to verify entity data
@@ -155,6 +156,67 @@ class Game {
return state;
}
+
+ // Calculate lighting levels for all visible tiles based on distance from torches
+ calculateLighting() {
+ const lightingData = {};
+
+ // Get torch positions from dungeon
+ const torches = this.dungeon?.torches || [];
+ console.log("๐ฅ calculateLighting() - torch positions:", torches);
+
+ // For each visible tile, calculate the lighting level
+ for (const yKey in this.visibleTiles) {
+ const y = parseInt(yKey);
+ lightingData[y] = {};
+
+ for (const xKey in this.visibleTiles[y]) {
+ const x = parseInt(xKey);
+
+ // Calculate minimum distance to any torch
+ let minDistanceToTorch = Infinity;
+
+ for (const torch of torches) {
+ const distance = Math.max(Math.abs(x - torch[0]), Math.abs(y - torch[1])); // Chebyshev distance (diagonal movement)
+ minDistanceToTorch = Math.min(minDistanceToTorch, distance);
+ }
+
+ // Convert distance to alpha value for darkness overlay
+ let alpha = 0.0;
+
+ if (minDistanceToTorch === 0) {
+ // On a torch - no darkness
+ alpha = 0.0;
+ } else if (minDistanceToTorch === 1) {
+ // Adjacent to torch - light darkness with flickering effect
+ alpha = 0.15 + Math.random() * 0.15; // 0.15-0.3
+ } else if (minDistanceToTorch === 2) {
+ // Two tiles away - moderate darkness with flickering
+ alpha = 0.3 + Math.random() * 0.2; // 0.3-0.5
+ } else if (minDistanceToTorch === 3) {
+ // Three tiles away - heavier darkness with flickering
+ alpha = 0.5 + Math.random() * 0.2; // 0.5-0.7
+ } else {
+ // Far from torches - scale up to maximum darkness with flickering
+ const maxDistance = 6; // Beyond this distance, maximum darkness
+ const scaledDistance = Math.min(minDistanceToTorch, maxDistance);
+ alpha = 0.7 + (scaledDistance - 3) / (maxDistance - 3) * 0.2; // 0.7-0.9
+ }
+
+ // Clamp alpha to reasonable bounds
+ alpha = Math.max(0.0, Math.min(0.9, alpha));
+
+ lightingData[y][x] = alpha;
+ }
+ }
+
+ const lightingTileCount = Object.keys(lightingData).reduce((acc, yKey) => acc + Object.keys(lightingData[yKey]).length, 0);
+ console.log("๐ก calculateLighting() - generated lighting for", lightingTileCount, "tiles");
+ console.log("๐ก LIGHTING DATA SAMPLE:", JSON.stringify(lightingData).substring(0, 200));
+
+ return lightingData;
+ }
+
}
// Check if monster killed player
diff --git a/src/backend/index.js b/src/backend/index.js
index 0b858c7..2b16e0f 100644
--- a/src/backend/index.js
+++ b/src/backend/index.js
@@ -173,6 +173,11 @@ function checkGamesTimeout(currentHeight) {
* @param {object} gameState - The game state to broadcast
*/
function sendGameUpdate(playerSocketId, gameState) {
+ // Debug logging for lighting data in updates
+ console.log(`๐ก sendGameUpdate to ${playerSocketId}:`);
+ console.log(` - Lighting data: ${!!gameState.lighting} (${gameState.lighting ? Object.keys(gameState.lighting).length : 0} rows)`);
+ console.log(` - Torch data: ${!!gameState.torches} (${gameState.torches ? gameState.torches.length : 0} torches)`);
+
// Send to the player
io.to(playerSocketId).emit('game_update', gameState);
@@ -383,8 +388,23 @@ io.on('connection', function(socket) {
const updatedGameState = game.getState(); // Get the new state
+ // Debug logging for lighting data in game updates
+ console.log(`๐ GAME UPDATE DEBUG for ${socket.id}:`);
+ console.log(` - Player position: (${updatedGameState.player?.x}, ${updatedGameState.player?.y})`);
+ console.log(` - Visible tiles keys: ${Object.keys(updatedGameState.visibleTiles || {}).length} rows`);
+ console.log(` - Lighting data included: ${!!updatedGameState.lighting}`);
+ if (updatedGameState.lighting) {
+ const lightingTileCount = Object.keys(updatedGameState.lighting).reduce((acc, yKey) =>
+ acc + Object.keys(updatedGameState.lighting[yKey] || {}).length, 0);
+ console.log(` - Lighting tiles count: ${lightingTileCount}`);
+ }
+ console.log(` - Torch data included: ${!!updatedGameState.torches}`);
+ if (updatedGameState.torches) {
+ console.log(` - Torch count: ${updatedGameState.torches.length}`);
+ }
+
// 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 || {}));
+ console.log(`Sending game_update to ${socket.id} after player move.`);
// Send game update to player (current implementation)
// TODO: In future, also broadcast to spectators of this game
diff --git a/src/backend/monster.js b/src/backend/monster.js
index 0d048c7..5183381 100644
--- a/src/backend/monster.js
+++ b/src/backend/monster.js
@@ -27,10 +27,15 @@ class Monster {
const dx = Math.sign(player.x - this.x);
const dy = Math.sign(player.y - this.y);
+ // Check if a tile is passable (floor)
+ const isPassable = (tile) => {
+ return tile === "'1" || tile === "'2" || tile === 0; // Support both new and legacy floor types
+ };
+
// Try horizontal move first
if (dx !== 0) {
const newX = this.x + dx;
- if (dungeon.map[this.y] && dungeon.map[this.y][newX] === 0) {
+ if (dungeon.map[this.y] && isPassable(dungeon.map[this.y][newX])) {
this.x = newX;
return;
}
@@ -39,7 +44,7 @@ class Monster {
// Try vertical move
if (dy !== 0) {
const newY = this.y + dy;
- if (dungeon.map[newY] && dungeon.map[newY][this.x] === 0) {
+ if (dungeon.map[newY] && isPassable(dungeon.map[newY][this.x])) {
this.y = newY;
return;
}
@@ -52,7 +57,7 @@ class Monster {
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) {
+ if (dungeon.map[ny] && isPassable(dungeon.map[ny][nx])) {
this.x = nx;
this.y = ny;
break;
diff --git a/test/debug_client_lighting.html b/test/debug_client_lighting.html
new file mode 100644
index 0000000..61c2add
--- /dev/null
+++ b/test/debug_client_lighting.html
@@ -0,0 +1,163 @@
+
+
+
+ Debug Client Lighting
+
+
+
+ Client-Side Lighting Debug
+
+
+
Game State Lighting Data
+
No data received yet...
+
+
+
+
Lighting Visualization
+
+
+
+
+
+
+
+
+
+
diff --git a/test/debug_game_updates.html b/test/debug_game_updates.html
new file mode 100644
index 0000000..edeb27c
--- /dev/null
+++ b/test/debug_game_updates.html
@@ -0,0 +1,186 @@
+
+
+
+ Game Update Debugging
+
+
+
+ Game Update Debugging
+
+
+
+
+
+
+
+
+ Not connected
+
+
+
+
+
+
diff --git a/test/debug_lighting.html b/test/debug_lighting.html
new file mode 100644
index 0000000..575ac14
--- /dev/null
+++ b/test/debug_lighting.html
@@ -0,0 +1,174 @@
+
+
+
+ Lighting Debug - Wowngeon
+
+
+
+ Lighting System Debug
+
+
+
Alpha Value Tests
+
Testing different alpha values to see how they render:
+
+
+
+
+
Game Display
+
+
+
+
+
+
+
Live Lighting Data
+
No data captured yet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/test_lighting.html b/test/test_lighting.html
new file mode 100644
index 0000000..5d6f45c
--- /dev/null
+++ b/test/test_lighting.html
@@ -0,0 +1,132 @@
+
+
+
+ Torch Lighting Test
+
+
+
+ Torch Lighting System Test
+ Open browser console to see detailed lighting data flow test.
+
+
+
+
+
diff --git a/test/test_lighting_verification.html b/test/test_lighting_verification.html
new file mode 100644
index 0000000..915ae7d
--- /dev/null
+++ b/test/test_lighting_verification.html
@@ -0,0 +1,216 @@
+
+
+
+
+
+ Torch Lighting Verification Test
+
+
+
+ ๐ฅ Torch Lighting System Verification
+
+
+
Connection Test
+
Connecting...
+
+
+
+
Data Reception Test
+
Waiting for lighting data...
+
Waiting for torch data...
+
Waiting for game state...
+
+
+
+
Rendering Test
+
Waiting for render test...
+
Checking tileColorize setting...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test_tile_system.js b/test_tile_system.js
deleted file mode 100644
index 5eea13d..0000000
--- a/test_tile_system.js
+++ /dev/null
@@ -1,117 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Test script to demonstrate the new tile system
- * Shows floor variations, torch placement, and configurable assets
- */
-
-const DungeonGenerator = require('./src/backend/dungeon.js');
-
-console.log("๐๏ธ WOWNGEON TILE SYSTEM TEST");
-console.log("=" * 50);
-
-// Test 1: Default Configuration
-console.log("\n๐ง Test 1: Default Configuration");
-const defaultConfig = {
- playerType: "@",
- treasureType: "$W",
- monsterType: "~",
- primaryFloor: "'1",
- secondaryFloor: "'2",
- floorVariation: 0.01,
- torchEnabled: true,
- torchDensity: 0.15,
- torchTile: "torch"
-};
-
-const dungeon1 = DungeonGenerator.generate(20, 15, defaultConfig);
-console.log(`Generated dungeon: ${dungeon1.map.length}x${dungeon1.map[0].length}`);
-console.log(`Entrance: [${dungeon1.entrance}]`);
-console.log(`Exit: [${dungeon1.exit}]`);
-console.log(`Treasure: [${dungeon1.treasure}]`);
-console.log(`Torches: ${dungeon1.torches.length} placed`);
-
-// Count tile types
-const tileCounts = {};
-for (let y = 0; y < dungeon1.map.length; y++) {
- for (let x = 0; x < dungeon1.map[0].length; x++) {
- const tile = dungeon1.map[y][x];
- tileCounts[tile] = (tileCounts[tile] || 0) + 1;
- }
-}
-
-console.log("\n๐ Tile Distribution:");
-Object.keys(tileCounts).sort().forEach(tile => {
- console.log(` ${tile}: ${tileCounts[tile]} tiles`);
-});
-
-if (tileCounts["'1"] && tileCounts["'2"]) {
- const floorTotal = tileCounts["'1"] + tileCounts["'2"];
- const variationPercent = (tileCounts["'2"] / floorTotal * 100).toFixed(2);
- console.log(` Floor variation: ${variationPercent}% secondary floors`);
-}
-
-// Test 2: Custom Configuration
-console.log("\n๐ง Test 2: Custom Configuration (Bitcoin theme)");
-const customConfig = {
- playerType: "@2",
- treasureType: "$B",
- monsterType: "~2",
- primaryFloor: "'1",
- secondaryFloor: "'2",
- floorVariation: 0.05, // 5% variation
- torchEnabled: true,
- torchDensity: 0.25, // 25% more torches
- torchTile: "torch"
-};
-
-const dungeon2 = DungeonGenerator.generate(15, 10, customConfig);
-console.log(`Generated custom dungeon: ${dungeon2.map.length}x${dungeon2.map[0].length}`);
-console.log(`Torches in custom dungeon: ${dungeon2.torches.length}`);
-
-// Test 3: Show a small visual sample
-console.log("\n๐บ๏ธ Sample Dungeon (10x8):");
-const sampleDungeon = DungeonGenerator.generate(10, 8, defaultConfig);
-
-console.log(" 0123456789");
-for (let y = 0; y < sampleDungeon.map.length; y++) {
- let line = y.toString().padStart(2) + " ";
- for (let x = 0; x < sampleDungeon.map[0].length; x++) {
- let char = sampleDungeon.map[y][x];
-
- // Add entities for display
- if (sampleDungeon.entrance && sampleDungeon.entrance[0] === x && sampleDungeon.entrance[1] === y) {
- char = '<';
- } else if (sampleDungeon.exit && sampleDungeon.exit[0] === x && sampleDungeon.exit[1] === y) {
- char = '>';
- } else if (sampleDungeon.treasure && sampleDungeon.treasure[0] === x && sampleDungeon.treasure[1] === y) {
- char = '$';
- }
-
- // Display character mapping
- if (char === "'1") char = '.';
- else if (char === "'2") char = ':';
- else if (char === 'torch') char = 'T';
- else if (char === '#') char = '#';
-
- line += char;
- }
- console.log(line);
-}
-
-console.log("\nLegend:");
-console.log(" . = Primary floor ('1)");
-console.log(" : = Secondary floor ('2)");
-console.log(" # = Wall");
-console.log(" T = Torch");
-console.log(" < = Entrance");
-console.log(" > = Exit");
-console.log(" $ = Treasure");
-
-console.log("\nโ
Tile System Test Complete!");
-console.log("\nFeatures verified:");
-console.log(" โ Configurable player/treasure/monster types");
-console.log(" โ Floor tile variations (primary/secondary)");
-console.log(" โ Configurable torch placement");
-console.log(" โ Enhanced dungeon generation");
-console.log(" โ Torch position tracking");