refactor: remove dead move handler + stale test scratch files

4.2 (partial): Delete the dead handlePlayerMove socket handler and its
orphaned playerMoveTimestamps map + cleanup. It was never registered (the live
move path is MovementManager.handleMove) but, if ever wired, it double-stepped
the monster and bypassed the escape/collision event handling — a loaded
footgun. No behaviour change.

4.5: Delete 14 stale non-jest files from test/ (5 scratch .js scripts + 9
.html debug pages) — they contain no jest constructs and are referenced
nowhere. test/ is now exclusively the 21 real jest suites.

Full suite: 21 suites / 155 tests.
This commit is contained in:
jwinterm
2026-05-31 14:31:10 -04:00
parent be8a620a83
commit d5df5dbd12
16 changed files with 4 additions and 1672 deletions
+3 -3
View File
@@ -43,11 +43,11 @@ Key: `S` ≤ half day · `M` ~1–3 days · `L` ~1 week+ · 🔴 launch-blocker
- [x] **3.3 S — Multi-step monster collision fix.** ✅ Collision is now checked after **each** monster sub-step, not just after the loop. With `movesPerPlayerMove > 1` (casino's 1.5× speed) the monster could previously step onto the player's tile and off again between checks, phasing through and missing the catch (player-favorable, eroded the house edge). New `monsterCollision.test.js`.
## Phase 4 — De-spaghetti / structural cleanup (🟡)
- [ ] **4.1 L — Break up `index.js`** into `app.js`, `routes/{public,user,admin,auth}.js`, `views/verify.js`, `server.js`.
- [ ] **4.2 M — Kill duplicates.** Merge `rpcService`+`rpccalls`; delete dead `handlePlayerMove` (`socketHandlers.js:244`); unify `payout_address` widths; remove `QueryValidator` theater (`databaseManager.js:21`).
- [x] **4.1 L — Break up `index.js` (substantially).** ✅ Extracted the verify HTML → `views/verifyPage.js` (now HTML-escaped), all 15 admin endpoints → `routes/admin.js`, and the 3 smirk endpoints → `routes/auth.js`, each as DI factories with smoke tests. **index.js: 2029 → 882 lines (-57%).** _Remaining user/public routes left inline — they're interleaved with bootstrap and have no route tests, so further slicing is diminishing-return/higher-risk; the structure + pattern are established for later._
- [~] **4.2 M — Kill duplicates (partial).** ✅ Deleted the dead `handlePlayerMove` handler (a footgun that double-stepped the monster + bypassed escape/collision detection) and its orphaned `playerMoveTimestamps` map/cleanup — the live path is `MovementManager`. _Remaining (lower-value/higher-effort): merge `rpcService`+`rpccalls`, unify `payout_address` column widths, address the `QueryValidator` false-positive — deferred._
- [ ] **4.3 M — Config as immutable snapshots;** stop mutating `process.env` (`gameModeManager.js:189`).
- [ ] **4.4 M — Frontend de-spaghetti.** Shared `escapeHtml`/`dom.js`; kill double-declares + dead `_appendMessage`; explicit init ordering; upgrade/drop jQuery; gate broadcast logging (`broadcastManager.js:33`).
- [ ] **4.5 S — Delete stale `test/` scratch files.**
- [x] **4.5 S — Delete stale `test/` scratch files.** ✅ Removed 14 non-jest leftovers (5 `.js` scratch scripts + 9 `.html` debug pages) — confirmed they contained no jest constructs and were referenced nowhere. `test/` is now exclusively the 21 real jest suites.
## Phase 5 — Test & ops overhaul (🟠 — the launch gate)
- [ ] **5.1 L — Real money-path tests** against Postgres (no stubbed `withTransaction`): under/overpay, double-confirm, batch dispatch, credit race, retry idempotency; property-test `money/atomic.js`.
+1 -95
View File
@@ -187,8 +187,7 @@ class SocketHandlers {
cleanupTimeoutMs: 300000 // 5 minutes to reconnect before game is lost
});
// Movement manager abstraction with memory cleanup
this.playerMoveTimestamps = new Map();
// Movement manager abstraction (owns its own move-cooldown state)
this.movementManager = new MovementManager({
activeGames: this.activeGames,
io: this.io,
@@ -206,32 +205,6 @@ class SocketHandlers {
* Register memory cleanup functions to prevent memory leaks
*/
_registerMemoryCleanups() {
// Cleanup player move timestamps (keep for 5 minutes)
this.memoryManager.registerCleanup(
'playerMoveTimestamps',
() => {
const now = Date.now();
const maxAge = 300000; // 5 minutes
let cleaned = 0;
const toDelete = [];
for (const [socketId, timestamp] of this.playerMoveTimestamps.entries()) {
if (now - timestamp > maxAge) {
toDelete.push(socketId);
}
}
for (const socketId of toDelete) {
if (this.playerMoveTimestamps.delete(socketId)) {
cleaned++;
}
}
return cleaned;
},
300000
);
// (mempool-notification dedup + TTL eviction lives in PaymentHandlers, which owns
// the actual dedup set; the old SocketHandlers copy here was unused and is removed.)
@@ -245,69 +218,6 @@ class SocketHandlers {
}
}
/**
* Handle player movement input from client.
* Expects moveData: { direction: 'up'|'down'|'left'|'right' }
*/
handlePlayerMove(socket, moveData) {
try {
if (!moveData || typeof moveData.direction !== 'string') return;
const now = Date.now();
const last = this.playerMoveTimestamps.get(socket.id) || 0;
if (now - last < this.MOVE_COOLDOWN) return; // rate limit moves
this.playerMoveTimestamps.set(socket.id, now);
const game = this.activeGames.get(socket.id);
if (!game) return; // not in a game
// Translate direction to delta
const dir = moveData.direction;
let dx = 0, dy = 0;
switch (dir) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
default: return; // ignore unknown
}
// Attempt move via game API (assuming game has movePlayer(dx,dy))
if (typeof game.movePlayer === 'function') {
game.movePlayer(dx, dy);
} else if (game.player) {
// Fallback direct mutation (legacy) with simple bounds check
const newX = game.player.x + dx;
const newY = game.player.y + dy;
if (newX >= 0 && newY >= 0) {
game.player.x = newX;
game.player.y = newY;
}
}
if (typeof game.moveMonster === 'function') {
game.moveMonster();
}
// Build updated state
let state;
if (typeof game.getState === 'function') {
state = game.getState();
} else {
state = { player: game.player };
}
// Add block height context
if (this.debugManager && typeof this.debugManager.getCurrentBlockHeight === 'function') {
state.blockHeight = this.debugManager.getCurrentBlockHeight();
}
this.io.to(socket.id).emit('game_update', state);
} catch (err) {
console.error('handlePlayerMove error:', err);
}
}
/**
* Called after a successful player move (before update is emitted) via MovementManager postMoveHook.
* Handles monster chasing logic and immediate game over if monster catches player.
@@ -816,9 +726,6 @@ class SocketHandlers {
// addressToUser entries must persist so checkPaymentStatus() can query the wallet
// RPC after session resumption. They get cleaned up when payments are confirmed or expired.
// Clean up movement timestamps
this.playerMoveTimestamps.delete(socketId);
// Preserve queue entry for users with session/payment
// The queue entry's socketId will be updated on reconnection
if (this.queueManager && typeof this.queueManager.removePlayer === 'function') {
@@ -1211,7 +1118,6 @@ class SocketHandlers {
// Clear remaining data structures
this.activeGames.clear();
this.playerMoveTimestamps.clear();
if (this.debugManager.CONSOLE_LOGGING) {
console.log('✅ SocketHandlers shutdown complete');
-163
View File
@@ -1,163 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Debug Client Lighting</title>
<style>
body { font-family: monospace; background: black; color: green; padding: 20px; }
.debug-box { border: 1px solid green; margin: 10px; padding: 10px; }
.lighting-tile { display: inline-block; width: 20px; height: 20px; margin: 1px; text-align: center; }
</style>
</head>
<body>
<h1>Client-Side Lighting Debug</h1>
<div class="debug-box">
<h3>Game State Lighting Data</h3>
<div id="lighting-status">No data received yet...</div>
</div>
<div class="debug-box">
<h3>Lighting Visualization</h3>
<div id="lighting-grid"></div>
</div>
<div class="debug-box">
<h3>Console Log</h3>
<div id="console-log" style="height: 300px; overflow-y: scroll; background: #111; padding: 10px;"></div>
</div>
<script src="html/js/jquery-3.4.1.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
// Capture console logs
const originalLog = console.log;
console.log = function(...args) {
originalLog.apply(console, args);
const logDiv = document.getElementById('console-log');
const logEntry = document.createElement('div');
logEntry.textContent = new Date().toLocaleTimeString() + ': ' + args.join(' ');
logDiv.appendChild(logEntry);
logDiv.scrollTop = logDiv.scrollHeight;
};
console.log("🔍 Debug client started");
socket.on('connect', function() {
console.log('Connected with socket ID:', socket.id);
socket.emit('register_client', {
clientId: socket.id,
userAgent: navigator.userAgent
});
});
socket.on('game_start', function(data) {
console.log('🎮 Game start received:', data);
if (data.lighting) {
console.log('🔥 Initial lighting data received with', Object.keys(data.lighting).length, 'rows');
updateLightingDisplay(data.lighting);
}
if (data.torches) {
console.log('🔦 Initial torch data received with', data.torches.length, 'torches');
}
});
socket.on('game_update', function(data) {
console.log('🎲 Game update received');
if (data.lighting) {
console.log('🔥 Lighting data in update with', Object.keys(data.lighting).length, 'rows');
updateLightingDisplay(data.lighting);
// Show detailed lighting values for debugging
let sampleCount = 0;
for (const y in data.lighting) {
for (const x in data.lighting[y]) {
if (sampleCount < 5) {
console.log(` Sample lighting[${y}][${x}] = ${data.lighting[y][x]}`);
sampleCount++;
}
}
}
} else {
console.log('⚠️ No lighting data in game update');
}
if (data.torches) {
console.log('🔦 Torch data in update with', data.torches.length, 'torches');
}
});
function updateLightingDisplay(lightingData) {
const statusDiv = document.getElementById('lighting-status');
const gridDiv = document.getElementById('lighting-grid');
const rowCount = Object.keys(lightingData).length;
let tileCount = 0;
for (const y in lightingData) {
tileCount += Object.keys(lightingData[y]).length;
}
statusDiv.innerHTML = `
<strong>Lighting Data Received:</strong><br>
Rows: ${rowCount}<br>
Total tiles: ${tileCount}<br>
Sample data: ${JSON.stringify(Object.keys(lightingData).slice(0, 3).map(y => `y=${y}: ${Object.keys(lightingData[y]).length} tiles`))}<br>
Last updated: ${new Date().toLocaleTimeString()}
`;
// Create a small visualization
gridDiv.innerHTML = '';
// Get min/max coordinates for visualization
let minY = Infinity, maxY = -Infinity, minX = Infinity, maxX = -Infinity;
for (const y in lightingData) {
const yNum = parseInt(y);
minY = Math.min(minY, yNum);
maxY = Math.max(maxY, yNum);
for (const x in lightingData[y]) {
const xNum = parseInt(x);
minX = Math.min(minX, xNum);
maxX = Math.max(maxX, xNum);
}
}
// Create grid (limit size for display)
const displayMaxSize = 20;
const yRange = Math.min(maxY - minY + 1, displayMaxSize);
const xRange = Math.min(maxX - minX + 1, displayMaxSize);
for (let y = minY; y < minY + yRange; y++) {
const rowDiv = document.createElement('div');
for (let x = minX; x < minX + xRange; x++) {
const tileDiv = document.createElement('div');
tileDiv.className = 'lighting-tile';
if (lightingData[y] && lightingData[y][x] !== undefined) {
const alpha = lightingData[y][x];
tileDiv.style.backgroundColor = `rgba(255, 255, 0, ${1 - alpha})`;
tileDiv.textContent = alpha.toFixed(2);
tileDiv.title = `(${x},${y}) = ${alpha}`;
} else {
tileDiv.style.backgroundColor = 'gray';
tileDiv.textContent = '?';
}
rowDiv.appendChild(tileDiv);
}
gridDiv.appendChild(rowDiv);
}
}
// Auto-start game for testing
setTimeout(() => {
console.log('🎮 Auto-starting game...');
socket.emit('message', 'enter');
}, 1000);
</script>
</body>
</html>
-186
View File
@@ -1,186 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Game Update Debugging</title>
<style>
body { font-family: monospace; padding: 20px; background: #222; color: #0f0; }
.log { margin: 5px 0; padding: 5px; background: #333; border-left: 3px solid #0f0; }
.error { border-left-color: #f00; color: #f99; }
.warning { border-left-color: #fa0; color: #ffa; }
.success { border-left-color: #0a0; color: #afa; }
#controls { margin-bottom: 20px; }
button { padding: 10px; margin: 5px; background: #444; color: #0f0; border: 1px solid #0f0; cursor: pointer; }
button:hover { background: #0f0; color: #000; }
</style>
</head>
<body>
<h1>Game Update Debugging</h1>
<div id="controls">
<button onclick="connectToServer()">Connect to Server</button>
<button onclick="startGame()">Start Game</button>
<button onclick="clearLogs()">Clear Logs</button>
<button onclick="testMove()">Test Move</button>
</div>
<div id="status">Not connected</div>
<div id="logs"></div>
<script src="/socket.io/socket.io.js"></script>
<script>
let socket;
let gameActive = false;
function log(message, type = 'log') {
const logDiv = document.createElement('div');
logDiv.className = `log ${type}`;
logDiv.innerHTML = `[${new Date().toLocaleTimeString()}] ${message}`;
document.getElementById('logs').appendChild(logDiv);
console.log(message);
// Auto scroll to bottom
logDiv.scrollIntoView();
}
function clearLogs() {
document.getElementById('logs').innerHTML = '';
}
function connectToServer() {
if (socket) {
socket.disconnect();
}
socket = io();
socket.on('connect', function() {
log('✅ Connected to server', 'success');
document.getElementById('status').innerHTML = '✅ Connected';
// Register client
socket.emit('register_client', {
clientId: socket.id,
userAgent: navigator.userAgent
});
});
socket.on('disconnect', function() {
log('❌ Disconnected from server', 'error');
document.getElementById('status').innerHTML = '❌ Disconnected';
gameActive = false;
});
socket.on('game_start', function(data) {
log('🎮 GAME_START received', 'success');
log(`📦 Game start data keys: ${Object.keys(data).join(', ')}`, 'log');
if (data.lighting) {
const lightingTileCount = Object.keys(data.lighting).reduce((acc, yKey) =>
acc + Object.keys(data.lighting[yKey] || {}).length, 0);
log(`🔥 GAME_START lighting: ${Object.keys(data.lighting).length} rows, ${lightingTileCount} tiles`, 'success');
} else {
log('⚠️ GAME_START: NO lighting data', 'warning');
}
if (data.torches) {
log(`🔦 GAME_START torches: ${data.torches.length} torches`, 'success');
} else {
log('⚠️ GAME_START: NO torch data', 'warning');
}
gameActive = true;
document.getElementById('status').innerHTML = '🎮 Game Active';
});
socket.on('game_update', function(data) {
log('🔄 GAME_UPDATE received', 'log');
log(`📦 Game update data keys: ${Object.keys(data).join(', ')}`, 'log');
if (data.lighting) {
const lightingTileCount = Object.keys(data.lighting).reduce((acc, yKey) =>
acc + Object.keys(data.lighting[yKey] || {}).length, 0);
log(`🔥 GAME_UPDATE lighting: ${Object.keys(data.lighting).length} rows, ${lightingTileCount} tiles`, 'success');
// Sample a few lighting values
let samples = [];
let count = 0;
for (const y in data.lighting) {
for (const x in data.lighting[y]) {
if (count < 3) {
samples.push(`(${x},${y}): ${data.lighting[y][x].toFixed(3)}`);
count++;
}
}
}
if (samples.length > 0) {
log(`💡 Sample lighting values: ${samples.join(', ')}`, 'log');
}
} else {
log('⚠️ GAME_UPDATE: NO lighting data', 'warning');
}
if (data.torches) {
log(`🔦 GAME_UPDATE torches: ${data.torches.length} torches`, 'success');
} else {
log('⚠️ GAME_UPDATE: NO torch data', 'warning');
}
if (data.player) {
log(`👤 Player position: (${data.player.x}, ${data.player.y})`, 'log');
}
if (data.visibleTiles) {
const visibleTileCount = Object.keys(data.visibleTiles).reduce((acc, yKey) =>
acc + Object.keys(data.visibleTiles[yKey] || {}).length, 0);
log(`👁️ Visible tiles: ${Object.keys(data.visibleTiles).length} rows, ${visibleTileCount} tiles`, 'log');
}
});
socket.on('game_over', function(data) {
log('🏁 GAME_OVER received', 'warning');
gameActive = false;
document.getElementById('status').innerHTML = '🏁 Game Over';
});
socket.on('message', function(msg) {
log(`💬 Server message: ${msg}`, 'log');
});
}
function startGame() {
if (!socket) {
log('❌ Not connected to server', 'error');
return;
}
log('🚀 Requesting game start...', 'log');
socket.emit('chat message', 'enter');
}
function testMove() {
if (!socket || !gameActive) {
log('❌ Game not active', 'error');
return;
}
// Send a random move
const moves = [
{ dx: 0, dy: 1 },
{ dx: 0, dy: -1 },
{ dx: 1, dy: 0 },
{ dx: -1, dy: 0 }
];
const move = moves[Math.floor(Math.random() * moves.length)];
log(`🎮 Sending move: dx=${move.dx}, dy=${move.dy}`, 'log');
socket.emit('player_move', move);
}
// Auto-connect on page load
window.addEventListener('load', function() {
log('🔌 Page loaded, auto-connecting...', 'log');
connectToServer();
});
</script>
</body>
</html>
-174
View File
@@ -1,174 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Lighting Debug - Wowngeon</title>
<style>
body {
font-family: monospace;
background: #222;
color: #fff;
padding: 20px;
}
.debug-section {
margin: 20px 0;
padding: 15px;
border: 1px solid #444;
background: #333;
}
.alpha-test {
display: inline-block;
width: 50px;
height: 50px;
margin: 5px;
text-align: center;
line-height: 50px;
border: 1px solid #666;
}
#game-container {
margin: 20px 0;
border: 2px solid #666;
}
.log-output {
background: #111;
padding: 10px;
max-height: 300px;
overflow-y: auto;
font-size: 12px;
}
</style>
</head>
<body>
<h1>Lighting System Debug</h1>
<div class="debug-section">
<h2>Alpha Value Tests</h2>
<p>Testing different alpha values to see how they render:</p>
<div id="alpha-tests"></div>
</div>
<div class="debug-section">
<h2>Game Display</h2>
<div id="game-container"></div>
<button onclick="startNewGame()">Start New Game</button>
<button onclick="captureCurrentLighting()">Capture Lighting Data</button>
</div>
<div class="debug-section">
<h2>Live Lighting Data</h2>
<div id="lighting-data">No data captured yet</div>
</div>
<div class="debug-section">
<h2>Console Log</h2>
<div id="log-output" class="log-output"></div>
</div>
<!-- Include game files -->
<script src="html/js/jquery-3.4.1.min.js"></script>
<script src="html/js/rot.min.js"></script>
<script src="html/js/options.js"></script>
<script src="html/js/displayManager.js"></script>
<script src="html/js/screenManager.js"></script>
<script src="html/js/gameState.js"></script>
<script src="html/js/renderEngine.js"></script>
<script src="html/js/game.js"></script>
<script src="html/js/socketHandlers.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
// Override console.log to capture output
const originalLog = console.log;
console.log = function(...args) {
originalLog.apply(console, args);
const logOutput = document.getElementById('log-output');
if (logOutput) {
logOutput.innerHTML += args.join(' ') + '<br>';
logOutput.scrollTop = logOutput.scrollHeight;
}
};
// Test different alpha values
function createAlphaTests() {
const container = document.getElementById('alpha-tests');
const alphas = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
alphas.forEach(alpha => {
const div = document.createElement('div');
div.className = 'alpha-test';
div.style.background = 'white';
div.style.color = `rgba(0, 0, 0, ${alpha})`;
div.textContent = alpha.toFixed(1);
div.title = `Alpha: ${alpha}`;
container.appendChild(div);
});
}
// Start a new game and capture data
function startNewGame() {
console.log('🎮 Starting new game for debug...');
if (window.Game) {
Game.init();
}
}
// Capture current lighting data from game state
function captureCurrentLighting() {
console.log('🔍 Capturing current lighting data...');
if (window.GameState) {
const gameState = GameState.getGameStateForRender();
const lighting = gameState.lighting || {};
const torches = gameState.torches || [];
let html = '<h3>Current Lighting State</h3>';
html += `<p><strong>Torches:</strong> ${torches.length} positions</p>`;
const lightingTileCount = Object.keys(lighting).reduce((acc, yKey) =>
acc + Object.keys(lighting[yKey] || {}).length, 0);
html += `<p><strong>Lighting Tiles:</strong> ${lightingTileCount} tiles</p>`;
if (lightingTileCount > 0) {
html += '<h4>Sample Lighting Values:</h4><ul>';
let samples = 0;
for (const y in lighting) {
for (const x in lighting[y]) {
if (samples >= 10) break;
const alpha = lighting[y][x];
html += `<li>(${x},${y}): ${alpha.toFixed(3)}</li>`;
samples++;
}
if (samples >= 10) break;
}
html += '</ul>';
}
document.getElementById('lighting-data').innerHTML = html;
} else {
document.getElementById('lighting-data').innerHTML = '<p>GameState not available</p>';
}
}
// Initialize when page loads
document.addEventListener('DOMContentLoaded', function() {
createAlphaTests();
// Initialize display manager
if (window.DisplayManager) {
DisplayManager.init('game-container');
}
console.log('🔧 Debug page initialized');
});
// Monitor game state changes
setInterval(() => {
if (window.GameState && window.GameState._lighting) {
const lightingKeys = Object.keys(window.GameState._lighting);
if (lightingKeys.length > 0) {
console.log(`💡 Current lighting state: ${lightingKeys.length} Y-coordinates with data`);
}
}
}, 5000);
</script>
</body>
</html>
-92
View File
@@ -1,92 +0,0 @@
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;
-172
View File
@@ -1,172 +0,0 @@
const ROT = require('./rot.js');
const Player = require('./player.js');
const Monster = require('./monster.js');
const DungeonGenerator = require('./dungeon.js');
class Game {
constructor(socketId, mapWidth, mapHeight) {
this.socketId = socketId;
this.width = mapWidth || 25; // Reduced default size to match client display
this.height = mapHeight || 19; // Reduced default size to match client display
this.gameState = 'waiting'; // waiting, active, won, lost
this.startBlock = null;
this.dungeon = null;
this.player = new Player();
this.monster = new Monster();
this.fee = 0; // Amount player paid
this.visibleTiles = {}; // Will store visible tiles
this.fov = null; // FOV calculator
this.generateDungeon();
}
generateDungeon() {
this.dungeon = DungeonGenerator.generate(this.width, this.height);
if (this.dungeon.entrance) {
this.player.moveTo(this.dungeon.entrance[0], this.dungeon.entrance[1]);
}
// Place monster far from entrance
if (this.dungeon.rooms && this.dungeon.rooms.length > 2) {
const monsterRoom = this.dungeon.rooms[Math.floor(this.dungeon.rooms.length * 0.7)];
const center = monsterRoom.getCenter();
this.monster.moveTo(center[0], center[1]);
}
// Initialize FOV calculator
this.fov = new ROT.FOV.PreciseShadowcasting(
(x, y) => {
// Return true if the tile is transparent (can see through it)
return this.dungeon.map[y] && this.dungeon.map[y][x] === 0;
}
);
// Calculate initial FOV
this.updateFOV();
// Set game state to active
this.gameState = 'active';
}
updateFOV() {
// Reset visible tiles
this.visibleTiles = {};
// Calculate new visible tiles
this.fov.compute(this.player.x, this.player.y, 10, (x, y, r, visibility) => { // Added r and visibility params
if (visibility > 0) { // Only add if tile is actually visible
if (!this.visibleTiles[y]) {
this.visibleTiles[y] = {};
}
// Store the actual tile type (0 for floor, 1 for wall) from the dungeon map
if (this.dungeon && this.dungeon.map && this.dungeon.map[y] && this.dungeon.map[y][x] !== undefined) {
this.visibleTiles[y][x] = this.dungeon.map[y][x];
} else {
// Should not happen if FOV is within map bounds, but as a fallback:
// this.visibleTiles[y][x] = 1; // Default to wall if outside known map
}
}
});
// console.log("FOV updated. Player:", this.player.x, this.player.y, "Visible tiles count:", Object.keys(this.visibleTiles).reduce((acc, k) => acc + Object.keys(this.visibleTiles[k]).length, 0));
}
movePlayer(dx, dy) {
const newX = this.player.x + dx;
const newY = this.player.y + dy;
// Debug logging for movement validation
console.log(`[DEBUG] Move attempt: from (${this.player.x},${this.player.y}) to (${newX},${newY}) with dx=${dx},dy=${dy}`);
console.log(`[DEBUG] Map bounds: width=${this.width}, height=${this.height}`);
console.log(`[DEBUG] Dungeon exists: ${!!this.dungeon}`);
if (this.dungeon && this.dungeon.map) {
console.log(`[DEBUG] Map row ${newY} exists: ${!!this.dungeon.map[newY]}`);
if (this.dungeon.map[newY]) {
console.log(`[DEBUG] Map cell [${newY}][${newX}] value: ${this.dungeon.map[newY][newX]} (0=floor, 1=wall)`);
}
}
// Check if the move is valid (not into a wall and within map bounds)
if (this.dungeon &&
this.dungeon.map[newY] &&
this.dungeon.map[newY][newX] !== undefined &&
this.dungeon.map[newY][newX] === 0) { // 0 is floor
this.player.moveTo(newX, newY);
console.log(`Player moved to ${newX},${newY} in game for socket ${this.socketId}`);
// Update FOV after moving
this.updateFOV();
// Check for game events like finding treasure or exit
if (this.dungeon.exit && this.player.isAt(this.dungeon.exit[0], this.dungeon.exit[1])) {
// Handle win condition (e.g., escaped)
this.gameState = 'won';
return { status: 'moved', event: 'escaped', player: this.player.getState(), visibleTiles: this.visibleTiles };
}
if (this.dungeon.treasure && this.player.isAt(this.dungeon.treasure[0], this.dungeon.treasure[1]) && !this.player.hasTreasure) {
this.player.hasTreasure = true;
// Remove treasure from dungeon so it won't be sent in future updates
this.dungeon.treasure = null;
console.log(`Player ${this.socketId} collected treasure! hasTreasure: ${this.player.hasTreasure}`);
return { status: 'moved', event: 'treasure_found', player: this.player.getState(), visibleTiles: this.visibleTiles };
}
return { status: 'moved', player: this.player.getState(), visibleTiles: this.visibleTiles };
}
console.log(`Player move to ${newX},${newY} for socket ${this.socketId} is invalid (wall or out of bounds).`);
// Debug: Show map around player
console.log(`[DEBUG] Map around player position:`);
for (let y = this.player.y - 2; y <= this.player.y + 2; y++) {
let row = '';
for (let x = this.player.x - 2; x <= this.player.x + 2; x++) {
if (x === this.player.x && y === this.player.y) {
row += '@'; // Player position
} else if (x === newX && y === newY) {
row += 'X'; // Attempted move position
} else if (this.dungeon && this.dungeon.map[y] && this.dungeon.map[y][x] !== undefined) {
row += this.dungeon.map[y][x] === 0 ? '.' : '#';
} else {
row += '?'; // Out of bounds or undefined
}
}
console.log(`[DEBUG] Y=${y}: ${row}`);
}
return { status: 'invalid' };
}
moveMonster() {
this.monster.moveTowardPlayer(this.player, this.dungeon);
}
// Update the getState() method to include all entities with relative positions
getState() {
const state = {
gameState: this.gameState,
player: this.player.getState(), // Use Player's getState method
monster: this.monster ? this.monster.getState() : null, // Use Monster's getState method
visibleTiles: { ...this.visibleTiles }, // Send a copy of visible tiles
entrance: this.dungeon ? this.dungeon.entrance : null,
exit: this.dungeon ? this.dungeon.exit : null,
treasure: this.dungeon ? this.dungeon.treasure : null,
};
// Debug logging to verify entity data
console.log("🎮 getState() sending entities - Monster:", state.monster, "Entrance:", state.entrance, "Exit:", state.exit, "Treasure:", state.treasure);
return state;
}
}
// Check if monster killed player
function checkMonsterKill(player, monster) {
// Use Monster's hasCaughtPlayer method
const monsterInstance = new Monster(monster.x, monster.y);
return monsterInstance.hasCaughtPlayer(player);
}
module.exports = Game;
module.exports.checkMonsterKill = checkMonsterKill;
-86
View File
@@ -1,86 +0,0 @@
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
return this.x === player.x && this.y === player.y;
}
// 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;
-48
View File
@@ -1,48 +0,0 @@
/**
* 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;
-109
View File
@@ -1,109 +0,0 @@
/**
* Test script for XMR/WOW address detection system
*/
// Test the regex patterns used in socketHandlers.js
function testAddressDetection() {
console.log('🧪 Testing XMR/WOW Address Detection System\n');
// XMR mainnet addresses start with 4, 8, A, or B and are 95 characters
// WOW addresses start with W and are 97 characters
const xmrRegex = /\b[48AB][1-9A-HJ-NP-Za-km-z]{94}\b/;
const wowRegex = /\bW[1-9A-HJ-NP-Za-km-z]{96}\b/;
function detectXMRAddress(message) {
const xmrMatch = message.match(xmrRegex);
const wowMatch = message.match(wowRegex);
if (xmrMatch) {
return { address: xmrMatch[0], type: 'XMR' };
}
if (wowMatch) {
return { address: wowMatch[0], type: 'WOW' };
}
return null;
}
// Test cases with real address formats
const testCases = [
{
name: 'Valid XMR Address (starts with 8)',
message: 'Here is my XMR address: 8Bqez9dvqY9bw3CgwMC9X1c8MX5KuRgKbiXRWg2F9UtYNYLevqjJXbQW4qYHUM1JF1792hhWYJLUufSUn9GvSC4G6wjMiRL',
expected: { address: '8Bqez9dvqY9bw3CgwMC9X1c8MX5KuRgKbiXRWg2F9UtYNYLevqjJXbQW4qYHUM1JF1792hhWYJLUufSUn9GvSC4G6wjMiRL', type: 'XMR' }
},
{
name: 'Valid XMR Address (starts with 4)',
message: 'Send to: 44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A',
expected: { address: '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A', type: 'XMR' }
},
{
name: 'Valid WOW Address (starts with WW)',
message: 'My WOW address is: WW35SBkcZDNGRkLroGNg4tKuJtyV8JhA95CREopLPCwrj9LgRU5pSnDb4D5BHKo9oBAZSjHXWnDwCJ4mRL5F6iJ81vSmNBuRC',
expected: { address: 'WW35SBkcZDNGRkLroGNg4tKuJtyV8JhA95CREopLPCwrj9LgRU5pSnDb4D5BHKo9oBAZSjHXWnDwCJ4mRL5F6iJ81vSmNBuRC', type: 'WOW' }
},
{
name: 'Valid WOW Address (starts with Wo)',
message: 'please send to Wo3MWeKwtA918DU4c69hVSNgejdWFCRCuWjShRY66mJkU2Hv58eygJWDJS1MNa2Ge5M1WjUkGHuLqHkweDxwZZU42d16v94mP thanks',
expected: { address: 'Wo3MWeKwtA918DU4c69hVSNgejdWFCRCuWjShRY66mJkU2Hv58eygJWDJS1MNa2Ge5M1WjUkGHuLqHkweDxwZZU42d16v94mP', type: 'WOW' }
},
{
name: 'Invalid XMR (too short)',
message: '4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx3skxNgYeYTRJ5zA4NJD9QhKCciJBcH9qPq8FgZKR8U8n',
expected: null
},
{
name: 'Invalid XMR (wrong prefix)',
message: '3AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx3skxNgYeYTRJ5zA4NJD9QhKCciJBcH9qPq8FgZKR8U8nZZZZ',
expected: null
},
{
name: 'Invalid WOW (wrong prefix)',
message: 'XW35SBkcZDNGRkLroGNg4tKuJtyV8JhA95CREopLPCwrj9LgRU5pSnDb4D5BHKo9oBAZSjHXWnDwCJ4mRL5F6iJ81vSmNBuRC',
expected: null
},
{
name: 'No address in message',
message: 'hello world this is just a chat message',
expected: null
},
{
name: 'Partial address in message',
message: 'my address starts with 4AdUnd but I wont share the full thing',
expected: null
}
];
let passed = 0;
let failed = 0;
testCases.forEach((testCase, index) => {
console.log(`Test ${index + 1}: ${testCase.name}`);
console.log(`Message: "${testCase.message}"`);
const result = detectXMRAddress(testCase.message);
if (JSON.stringify(result) === JSON.stringify(testCase.expected)) {
console.log(`✅ PASS - Detected: ${result ? `${result.type} ${result.address}` : 'null'}`);
passed++;
} else {
console.log(`❌ FAIL - Expected: ${JSON.stringify(testCase.expected)}`);
console.log(`❌ FAIL - Got: ${JSON.stringify(result)}`);
failed++;
}
console.log('');
});
console.log(`📊 Test Results: ${passed} passed, ${failed} failed`);
if (failed === 0) {
console.log('🎉 All tests passed! Address detection system is working correctly.');
} else {
console.log('⚠️ Some tests failed. Please review the regex patterns.');
}
}
// Run the tests
testAddressDetection();
module.exports = { testAddressDetection };
-79
View File
@@ -1,79 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowngeon Fixes Test</title>
<style>
body {
font-family: monospace;
background: #000;
color: #0f0;
padding: 20px;
}
.test-result {
margin: 10px 0;
padding: 10px;
border: 1px solid #333;
}
.pass { border-color: #0f0; }
.fail { border-color: #f00; }
iframe {
width: 100%;
height: 600px;
border: 1px solid #666;
margin: 20px 0;
}
</style>
</head>
<body>
<h1>🎮 Wowngeon Fixes Verification</h1>
<div class="test-result">
<h2>🔧 Fix #1: START Button Visibility</h2>
<p><strong>Issue:</strong> START button was invisible (cut off in game display)</p>
<p><strong>Fix:</strong> Moved to HTML status area with proper styling</p>
<p><strong>Test:</strong> Check if START button is visible in the status area below</p>
</div>
<div class="test-result">
<h2>⏱️ Fix #2: Auto-Entry Death Timing</h2>
<p><strong>Issue:</strong> Players died after 2+ blocks instead of 1 block in debug mode</p>
<p><strong>Fix:</strong> Set currentUser.blockRec = currentBlock + 1 for consistent timing</p>
<p><strong>Test:</strong> Use "enter" command and check death timing logs in console</p>
</div>
<h2>🎯 Live Game Test</h2>
<iframe src="http://localhost:3000"></iframe>
<div class="test-result">
<h3>📋 Test Instructions:</h3>
<ol>
<li><strong>START Button Test:</strong> Look for the green "🎮 START GAME" button in the status area (should be visible)</li>
<li><strong>Death Timing Test:</strong> Click START or type "enter" in chat, then check browser console for timing logs</li>
<li><strong>Expected Logs:</strong> Should see "🕒 AUTO-ENTRY: Player starts on block X, will die after block X+2"</li>
</ol>
</div>
<script>
// Test the START button visibility
window.addEventListener('load', function() {
setTimeout(function() {
const iframe = document.querySelector('iframe');
if (iframe && iframe.contentDocument) {
const startButton = iframe.contentDocument.getElementById('startButton');
if (startButton) {
console.log('✅ START button found in iframe');
const styles = window.getComputedStyle(startButton);
console.log('START button styles:', {
display: styles.display,
visibility: styles.visibility,
background: styles.backgroundColor
});
} else {
console.log('❌ START button not found in iframe');
}
}
}, 2000);
});
</script>
</body>
</html>
-132
View File
@@ -1,132 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Torch Lighting Test</title>
<style>
body {
background: #000;
color: #fff;
font-family: monospace;
padding: 20px;
}
#results {
white-space: pre-wrap;
border: 1px solid #333;
padding: 10px;
margin: 10px 0;
}
</style>
</head>
<body>
<h1>Torch Lighting System Test</h1>
<p>Open browser console to see detailed lighting data flow test.</p>
<div id="results"></div>
<script>
console.log("🔥 TORCH LIGHTING TEST - Starting comprehensive data flow check");
// Test 1: Check if lighting data is preserved through GameState
const testLightingData = {
"18": {"35": 0.3, "36": 0.15, "37": 0.45},
"19": {"35": 0.6, "36": 0.3, "37": 0.7},
"20": {"35": 0.8, "36": 0.6, "37": 0.9}
};
const testTorchData = [[36, 18], [42, 20], [30, 25]];
console.log("🧪 Test data created:", {
lighting: testLightingData,
torches: testTorchData
});
// Test 2: Simulate the data flow through GameState
console.log("⚡ Simulating GameState data storage...");
const mockGameState = {
_lighting: {},
_torches: [],
setLightingData: function(lightingData) {
this._lighting = lightingData;
console.log("✅ GameState: Lighting data stored:", Object.keys(this._lighting).length, "rows");
},
setTorchData: function(torchData) {
this._torches = torchData;
console.log("✅ GameState: Torch data stored:", this._torches.length, "torches");
},
getGameStateForRender: function() {
return {
lighting: this._lighting,
torches: this._torches,
player: {x: 36, y: 19},
visibleTiles: {
"18": {"35": "#", "36": "'1", "37": "#"},
"19": {"35": "'1", "36": "@", "37": "'1"},
"20": {"35": "#", "36": "'1", "37": "#"}
}
};
}
};
// Test the flow
mockGameState.setLightingData(testLightingData);
mockGameState.setTorchData(testTorchData);
const renderState = mockGameState.getGameStateForRender();
console.log("🎨 Render state includes lighting:", !!renderState.lighting);
console.log("🎨 Render state includes torches:", !!renderState.torches);
// Test 3: Simulate RenderEngine processing
console.log("🖼️ Simulating RenderEngine processing...");
const mockRenderEngine = {
applyLighting: function(x, y, lightingData) {
if (lightingData[y] && lightingData[y][x] !== undefined) {
const alpha = lightingData[y][x];
console.log(`🔍 Lighting at (${x},${y}): alpha=${alpha.toFixed(3)} -> rgba(0,0,0,${alpha})`);
return alpha;
} else {
console.log(`⭕ No lighting data for (${x},${y})`);
return 0;
}
}
};
// Test lighting application for visible tiles
for (const y in renderState.visibleTiles) {
for (const x in renderState.visibleTiles[y]) {
const lightingAlpha = mockRenderEngine.applyLighting(parseInt(x), parseInt(y), renderState.lighting);
}
}
// Test 4: Check if torch positions are available
console.log("🔦 Testing torch position access:");
renderState.torches.forEach((torch, i) => {
console.log(`Torch ${i + 1}: position [${torch[0]}, ${torch[1]}]`);
});
document.getElementById('results').textContent =
"✅ Torch lighting data flow test completed successfully!\n" +
"📊 Results:\n" +
` - Lighting data: ${Object.keys(testLightingData).length} rows\n` +
` - Torch positions: ${testTorchData.length} torches\n` +
` - Data flows correctly through GameState ✓\n` +
` - RenderEngine can access lighting data ✓\n` +
` - Alpha values generate proper rgba() backgrounds ✓\n\n` +
"🎮 The torch lighting system should now work in the actual game!";
console.log("🎯 CONCLUSION: Torch lighting data flow is working correctly!");
console.log("📋 Summary:");
console.log(" 1. ✅ Server calculates lighting data");
console.log(" 2. ✅ Socket handlers extract lighting/torch data");
console.log(" 3. ✅ Game.startGame receives lighting data");
console.log(" 4. ✅ GameState stores lighting data");
console.log(" 5. ✅ getGameStateForRender includes lighting data");
console.log(" 6. ✅ RenderEngine can apply lighting as rgba backgrounds");
console.log("🔥 Torch lighting should now be visible in the game!");
</script>
</body>
</html>
-216
View File
@@ -1,216 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Torch Lighting Verification Test</title>
<style>
body {
font-family: monospace;
background: black;
color: green;
padding: 20px;
}
.test-section {
margin: 20px 0;
padding: 10px;
border: 1px solid green;
}
.status {
padding: 5px;
margin: 5px 0;
}
.pass { background: darkgreen; }
.fail { background: darkred; }
.info { background: darkblue; }
#gameContainer {
margin-top: 20px;
border: 2px solid green;
padding: 10px;
}
</style>
</head>
<body>
<h1>🔥 Torch Lighting System Verification</h1>
<div class="test-section">
<h2>Connection Test</h2>
<div id="connectionStatus" class="status">Connecting...</div>
</div>
<div class="test-section">
<h2>Data Reception Test</h2>
<div id="lightingDataStatus" class="status">Waiting for lighting data...</div>
<div id="torchDataStatus" class="status">Waiting for torch data...</div>
<div id="gameStateStatus" class="status">Waiting for game state...</div>
</div>
<div class="test-section">
<h2>Rendering Test</h2>
<div id="renderEngineStatus" class="status">Waiting for render test...</div>
<div id="tileColorizeStatus" class="status">Checking tileColorize setting...</div>
</div>
<div class="test-section">
<h2>Live Data</h2>
<div id="liveData" style="font-size: 12px; max-height: 300px; overflow-y: auto;"></div>
</div>
<div id="gameContainer">
<div id="game-screen"></div>
</div>
<script src="html/js/jquery-3.4.1.min.js"></script>
<script src="html/js/rot.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="html/js/options.js"></script>
<script src="html/js/displayManager.js"></script>
<script src="html/js/screenManager.js"></script>
<script src="html/js/gameState.js"></script>
<script src="html/js/renderEngine.js"></script>
<script src="html/js/game.js"></script>
<script src="html/js/socketHandlers.js"></script>
<script>
// Test status tracking
let testResults = {
connection: false,
lightingData: false,
torchData: false,
gameState: false,
rendering: false,
tileColorize: false
};
function updateStatus(id, message, type = 'info') {
const element = document.getElementById(id);
element.textContent = message;
element.className = `status ${type}`;
}
function logLiveData(message) {
const liveData = document.getElementById('liveData');
const timestamp = new Date().toLocaleTimeString();
liveData.innerHTML += `<div>[${timestamp}] ${message}</div>`;
liveData.scrollTop = liveData.scrollHeight;
}
// Override console.log to capture debug messages
const originalConsoleLog = console.log;
console.log = function(...args) {
originalConsoleLog.apply(console, args);
if (args[0] && typeof args[0] === 'string') {
if (args[0].includes('🔥') || args[0].includes('💡') || args[0].includes('🔍')) {
logLiveData('CLIENT: ' + args.join(' '));
}
}
};
// Test tileColorize setting
if (window.options && window.options.tileColorize === true) {
updateStatus('tileColorizeStatus', '✅ tileColorize is enabled', 'pass');
testResults.tileColorize = true;
} else {
updateStatus('tileColorizeStatus', '❌ tileColorize is not enabled', 'fail');
}
// Initialize socket connection
const socket = io();
socket.on('connect', function() {
updateStatus('connectionStatus', '✅ Connected to server', 'pass');
testResults.connection = true;
logLiveData('Socket connected');
// Register user
socket.emit('register', {});
});
socket.on('disconnect', function() {
updateStatus('connectionStatus', '❌ Disconnected from server', 'fail');
testResults.connection = false;
});
// Test game data reception
socket.on('gameStart', function(data) {
logLiveData(`GAME_START received: ${Object.keys(data).join(', ')}`);
// Check for lighting data
if (data.lighting && Object.keys(data.lighting).length > 0) {
updateStatus('lightingDataStatus', `✅ Lighting data received (${Object.keys(data.lighting).length} tiles)`, 'pass');
testResults.lightingData = true;
logLiveData(`Lighting sample: ${JSON.stringify(Object.entries(data.lighting).slice(0, 3))}`);
} else {
updateStatus('lightingDataStatus', '❌ No lighting data received', 'fail');
}
// Check for torch data
if (data.torches && data.torches.length > 0) {
updateStatus('torchDataStatus', `✅ Torch data received (${data.torches.length} torches)`, 'pass');
testResults.torchData = true;
logLiveData(`Torch sample: ${JSON.stringify(data.torches.slice(0, 5))}`);
} else {
updateStatus('torchDataStatus', '❌ No torch data received', 'fail');
}
// Test game state storage
if (typeof window.Game !== 'undefined' && window.Game.startGame) {
try {
// Start the game with the received data
window.Game.startGame(data, data.lighting, data.torches);
updateStatus('gameStateStatus', '✅ Game state initialized successfully', 'pass');
testResults.gameState = true;
// Test rendering
setTimeout(() => {
testRendering();
}, 1000);
} catch (error) {
updateStatus('gameStateStatus', `❌ Game state error: ${error.message}`, 'fail');
logLiveData(`Game start error: ${error.message}`);
}
}
});
socket.on('gameUpdate', function(data) {
if (data.lighting) {
logLiveData(`GAME_UPDATE: lighting (${Object.keys(data.lighting).length} tiles)`);
}
});
function testRendering() {
try {
// Check if RenderEngine exists and has the right methods
if (typeof window.RenderEngine !== 'undefined') {
updateStatus('renderEngineStatus', '✅ RenderEngine available and lighting system active', 'pass');
testResults.rendering = true;
// Log overall test results
const passedTests = Object.values(testResults).filter(result => result).length;
const totalTests = Object.keys(testResults).length;
logLiveData(`🎯 FINAL RESULTS: ${passedTests}/${totalTests} tests passed`);
if (passedTests === totalTests) {
logLiveData('🎉 ALL TESTS PASSED - Torch lighting system should be working!');
} else {
logLiveData('⚠️ Some tests failed - check individual results above');
}
} else {
updateStatus('renderEngineStatus', '❌ RenderEngine not available', 'fail');
}
} catch (error) {
updateStatus('renderEngineStatus', `❌ Rendering test error: ${error.message}`, 'fail');
logLiveData(`Rendering error: ${error.message}`);
}
}
// Auto-start game after connection
setTimeout(() => {
if (testResults.connection) {
logLiveData('Auto-entering dungeon...');
socket.emit('message', 'enter');
}
}, 2000);
</script>
</body>
</html>
View File
View File
-117
View File
@@ -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 (15x12):");
const sampleDungeon = DungeonGenerator.generate(15, 12, defaultConfig);
console.log(" 012345678901234");
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");