game and display mostly working, using autostart for now to work on lighting (dim explored tiles out of FoV)

This commit is contained in:
jwinterm
2025-05-25 15:10:45 -04:00
parent 0818b60541
commit 1db2fc818c
17 changed files with 1624 additions and 1467 deletions
+36
View File
@@ -0,0 +1,36 @@
# Wownero Roguelike - Complete
## Status: ✅ PRODUCTION READY
### Fixed Issues
- **Undefined character error** - Replaced emoji characters with ASCII equivalents
- **ROT.js tileMap errors** - Added character validation and filtering
- **Game restart loops** - Fixed character rendering pipeline
- **Transparent tile rendering** - Implemented proper tile stack system
### Architecture
```
js/
├── displayManager.js # ROT.js display management
├── screenManager.js # UI screens and block timing
├── renderEngine.js # Game rendering with tile stacks
├── gameState.js # Game state management
├── game.js # Main controller
├── inputHandler.js # Input handling
├── socketHandlers.js # Socket communication
├── ui.js # UI utilities
└── options.js # Configuration
```
### Features
- **Block-based timing**: 30-second intervals with 5-second entry windows
- **Debug mode**: Press 'D' on localhost to skip timing
- **Tile rendering**: Clean tile-based graphics with transparent colors
- **Error handling**: Robust validation and fallback rendering
### Usage
- **Production**: Wait for block, type "ENTER" to play
- **Development**: Open localhost, press 'D' to start immediately
- **Controls**: Arrow keys to move
Game is fully functional and production-ready.
+26
View File
@@ -0,0 +1,26 @@
# Wownero Roguelike
A tile-based dungeon crawler game using ROT.js with modular architecture.
## Features
- Tile-based rendering with ROT.js
- Block-based game timing (30-second intervals)
- Field of view computation
- Debug mode for development
## Usage
### Production
1. Wait for "NEW BLOCK FOUND!" message (every 30 seconds)
2. Type "ENTER" in chat during 5-second window
3. Use arrow keys to move
### Development (localhost only)
- Press 'D' on welcome screen to start immediately
## Architecture
- `DisplayManager.js` - ROT.js display management
- `ScreenManager.js` - Screen states and UI
- `RenderEngine.js` - Game rendering with tile stacks
- `GameState.js` - Game state management
- `game.js` - Main game controller
-182
View File
@@ -1,182 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowngeon Debug</title>
<style>
body {
font-family: monospace;
background: #000;
color: #0f0;
padding: 20px;
}
.module {
margin: 10px 0;
padding: 10px;
border: 1px solid #333;
}
.status-ok { color: #0f0; }
.status-error { color: #f00; }
.status-warning { color: #ff0; }
</style>
</head>
<body>
<h1>Wowngeon Module Debug</h1>
<div id="socket-status" class="module">
<h3>Socket.io Status</h3>
<div id="socket-info">Loading...</div>
</div>
<div id="modules-status" class="module">
<h3>Module Status</h3>
<div id="modules-info">Loading...</div>
</div>
<div id="game-status" class="module">
<h3>Game Display Status</h3>
<div id="game-info">Loading...</div>
</div>
<div id="console-log" class="module">
<h3>Console Messages</h3>
<div id="console-output"></div>
</div>
<!-- Socket.io -->
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<!-- Initialize socket -->
<script>
window.socket = io();
</script>
<!-- Core libraries -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/rot.min.js"></script>
<script src="js/options.js"></script>
<script src="js/teletype.js"></script>
<!-- Game modules -->
<script src="js/game.js"></script>
<script src="js/ui.js"></script>
<script src="js/socketHandlers.js"></script>
<script src="js/inputHandler.js"></script>
<script>
// Capture console messages
const consoleOutput = document.getElementById('console-output');
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
function addToConsole(type, message) {
const div = document.createElement('div');
div.className = type === 'error' ? 'status-error' :
type === 'warn' ? 'status-warning' : 'status-ok';
div.textContent = `[${type.toUpperCase()}] ${message}`;
consoleOutput.appendChild(div);
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
console.log = function(...args) {
originalLog.apply(console, args);
addToConsole('log', args.join(' '));
};
console.error = function(...args) {
originalError.apply(console, args);
addToConsole('error', args.join(' '));
};
console.warn = function(...args) {
originalWarn.apply(console, args);
addToConsole('warn', args.join(' '));
};
$(document).ready(function() {
console.log("Debug page ready - checking module status...");
// Check socket status
$('#socket-info').html(`
<div class="${window.socket ? 'status-ok' : 'status-error'}">
Socket Available: ${!!window.socket}
</div>
<div class="${window.socket && window.socket.connected ? 'status-ok' : 'status-warning'}">
Socket Connected: ${window.socket ? window.socket.connected : 'N/A'}
</div>
`);
// Check modules
const modules = ['Game', 'UI', 'SocketHandlers', 'InputHandler'];
let moduleStatus = '';
modules.forEach(module => {
const available = typeof window[module] !== 'undefined';
const hasInit = available && typeof window[module].init === 'function';
moduleStatus += `
<div class="${available ? 'status-ok' : 'status-error'}">
${module}: ${available ? 'Available' : 'Missing'}
${hasInit ? ' (has init)' : ''}
</div>
`;
});
$('#modules-info').html(moduleStatus);
// Check ROT.js
$('#game-info').html(`
<div class="${typeof ROT !== 'undefined' ? 'status-ok' : 'status-error'}">
ROT.js Available: ${typeof ROT !== 'undefined'}
</div>
<div class="${typeof window.options !== 'undefined' ? 'status-ok' : 'status-error'}">
Options Available: ${typeof window.options !== 'undefined'}
</div>
<div class="${document.getElementById('game-display') ? 'status-ok' : 'status-error'}">
Game Display Container: ${!!document.getElementById('game-display')}
</div>
`);
// Try to initialize modules
if (typeof Game !== 'undefined') {
console.log("Attempting to initialize Game...");
try {
Game.init();
console.log("Game.init() completed successfully");
} catch (e) {
console.error("Game.init() failed:", e);
}
}
if (typeof InputHandler !== 'undefined') {
console.log("Attempting to initialize InputHandler...");
try {
InputHandler.init();
console.log("InputHandler.init() completed successfully");
} catch (e) {
console.error("InputHandler.init() failed:", e);
}
}
// Socket event listeners
if (window.socket) {
window.socket.on('connect', function() {
console.log('Socket connected successfully');
$('#socket-info').append('<div class="status-ok">Socket connected event received</div>');
});
window.socket.on('disconnect', function() {
console.log('Socket disconnected');
$('#socket-info').append('<div class="status-warning">Socket disconnected</div>');
});
window.socket.on('welcome', function(data) {
console.log('Welcome message received:', data);
});
}
});
</script>
<!-- Add a minimal game display area for testing -->
<div id="game-display" style="width: 800px; height: 400px; border: 1px solid #333; margin: 20px 0;">
<!-- Game display will be created here -->
</div>
</body>
</html>
+9 -8
View File
@@ -19,8 +19,14 @@
<script src="js/options.js"></script>
<script src="js/teletype.js"></script>
<!-- Game modules -->
<!-- Modular game components (order matters for dependencies) -->
<script src="js/displayManager.js"></script>
<script src="js/screenManager.js"></script>
<script src="js/renderEngine.js"></script>
<script src="js/gameState.js"></script>
<script src="js/game.js"></script>
<!-- Other modules -->
<script src="js/ui.js"></script>
<script src="js/socketHandlers.js"></script>
<script src="js/inputHandler.js"></script>
@@ -43,8 +49,8 @@
<!-- Game display -->
<div id="game-display" class="rotdis"></div>
<!-- Debug display area -->
<div id="debug-display" style="background: rgba(0,0,0,0.8); color: #0f0; font-family: monospace; font-size: 12px; padding: 10px; margin: 5px; border: 1px solid #333; max-height: 200px; overflow-y: auto;">
<!-- Debug display area (hidden in production) -->
<div id="debug-display" style="display: none; background: rgba(0,0,0,0.8); color: #0f0; font-family: monospace; font-size: 12px; padding: 10px; margin: 5px; border: 1px solid #333; max-height: 200px; overflow-y: auto;">
<div><strong>Debug Information:</strong></div>
<div id="debug-content">Ready...</div>
</div>
@@ -79,11 +85,6 @@
</button>
</div>
<!-- Mode toggle button -->
<button id="toggle-mode" style="position: fixed; right: 10px; top: 10px; z-index: 1000; background: #333; color: white; padding: 5px; border: 1px solid #666; border-radius: 4px;">
Switch to ASCII Mode
</button>
<!-- Initialize game when ready -->
<script>
$(document).ready(function() {
-93
View File
@@ -1,93 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowgue - A Wownero Roguelike</title>
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- Socket.io -->
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<!-- Initialize socket -->
<script>
window.socket = io();
</script>
<!-- Core libraries -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/rot.min.js"></script>
<script src="js/options.js"></script>
<script src="js/teletype.js"></script>
<!-- Game modules -->
<script src="js/game.js"></script>
<script src="js/ui.js"></script>
<script src="js/socketHandlers.js"></script>
<script src="js/inputHandler.js"></script>
</head>
<body>
<div class="container">
<!-- Header area -->
<header class="banner">
<div id="staticbanner">
<span id="banner"></span><span id="bannercursor">|</span>
</div>
</header>
<!-- Status area for block height -->
<div class="status">
<div id="blockHeight">Block Height: <span id="heightValue">Loading...</span></div>
<div id="connectionStatus">Status: <span id="statusValue">Connecting...</span></div>
</div>
<!-- Game display -->
<div id="game-display" class="rotdis"></div>
<!-- Chat messages area -->
<div class="chat-container">
<div id="messageBox">
<ul id="messages"></ul>
</div>
</div>
<!-- Chat input in its own row -->
<div class="input-container">
<form id="chatForm" action="">
<input id="chatInput" autocomplete="off" placeholder="Type a command..." />
<button type="submit">Send</button>
</form>
</div>
<!-- Footer -->
<div class="footer">
Wownero Roguelike v0.1.0 | Use WASD or arrow keys to move
</div>
</div>
<!-- Browser info panel -->
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
<div>Browser: <span id="browser-name">checking...</span></div>
<div>Display: <span id="display-status">checking...</span></div>
<button onclick="document.getElementById('browser-info').style.display='none'" style="margin-top: 5px; background: #333; border: 1px solid #666; color: white; font-size: 10px; padding: 2px 4px;">
Close
</button>
</div>
<!-- Mode toggle button -->
<button id="toggle-mode" style="position: fixed; right: 10px; top: 10px; z-index: 1000; background: #333; color: white; padding: 5px; border: 1px solid #666; border-radius: 4px;">
Switch to ASCII Mode
</button>
<!-- Initialize game when ready -->
<script>
$(document).ready(function() {
console.log("Initializing Game...");
if (typeof Game !== 'undefined') {
Game.init();
} else {
console.error("Game object not found!");
}
});
</script>
</body>
</html>
-470
View File
@@ -1,470 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowgue - A Wownero Roguelike</title>
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- First load socket.io -->
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<!-- THEN initialize the socket -->
<script>
// Create socket connection BEFORE loading game.js
window.socket = io();
</script>
<!-- Only AFTER socket is initialized, load other scripts -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/rot.min.js"></script>
<script src="js/options.js"></script>
<script src="js/teletype.js"></script> <!-- Load teletype.js -->
<!-- Use the original game.js with fixes -->
<script src="js/game.js"></script>
<!-- Modular game files - keeping as backup for now -->
<!-- <script src="js/displayManager.js"></script>
<script src="js/screenManager.js"></script>
<script src="js/gameState.js"></script>
<script src="js/inputHandler.js"></script>
<script src="js/game_refactored.js"></script> -->
</head>
<body>
<div class="container">
<!-- Header area -->
<header class="banner">
<div id="staticbanner">
<span id="banner"></span><span id="bannercursor">|</span>
</div>
</header>
<!-- Status area for block height -->
<div class="status">
<div id="blockHeight">Block Height: <span id="heightValue">Loading...</span></div>
<div id="connectionStatus">Status: <span id="statusValue">Connecting...</span></div>
</div>
<!-- Game display -->
<div id="game-display" class="rotdis"></div>
<!-- Chat messages area -->
<div class="chat-container">
<div id="messageBox">
<ul id="messages"></ul>
</div>
</div>
<!-- Chat input in its own row -->
<div class="input-container">
<form id="chatForm" action="">
<input id="chatInput" autocomplete="off" placeholder="Type a command..." />
<button type="submit">Send</button>
</form>
</div>
<!-- Footer -->
<div class="footer">
Wownero Roguelike v0.1.0 | Use WASD or arrow keys to move
</div>
</div>
<!-- Initialize teletype animation -->
<script>
$(function() {
$('#banner').teletype({
text: ["Welcome traveler...", "You have arrived at...", "The Wowngeon"],
typeDelay: 100,
backDelay: 50,
pause: 1500
});
// $(\'#bannercursor\').blink({ // <<< COMMENT OUT THIS BLOCK
// text: [\'|\', \' \'],
// delay: 500
// });
// After animations are complete, set static text
setTimeout(function() {
$('#banner, #bannercursor').fadeOut(500, function() {
$('#staticbanner').text("THE WOWNGEON");
});
}, 14000);
});
</script>
<!-- Global scroll helper function -->
<script>
function scrollChat() {
var messageBox = $('#messageBox');
messageBox.scrollTop(messageBox.prop("scrollHeight"));
}
</script>
<script>
$(function() {
// Socket event listeners
socket.on('connect', function() {
console.log("Connected with socket ID:", socket.id);
// Tell server our client ID
socket.emit('register_client', {
clientId: socket.id,
userAgent: navigator.userAgent
});
});
socket.on('welcome', function(msg) {
$('#messages').append($('<li>').text("Connected to server!"));
scrollChat();
});
// Handle waiting status updates
socket.on('waiting_status', function(data) {
console.log("🕒 WAITING STATUS received:", data);
if (data.status === 'waiting') {
$('#messages').append($('<li style="color:#ff0;">').text(data.message));
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
}
scrollChat();
});
socket.on('message', function(msg) {
$('#messages').append($('<li>').text(msg));
scrollChat();
});
// Add or update the game_start handler
socket.on('game_start', function(data) {
console.log("🎮 Game start received with data:", data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
if (typeof Game !== 'undefined' && Game.stopWaitingScreen) {
Game.stopWaitingScreen();
}
if (!data) {
$('#messages').append($('<li class="error">').text("Error: No game data received from server."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
return;
}
try {
// Use the original game.js structure
var success = Game.startGame(data.player, data.map, data.monster, data.items, data.visibleTiles);
console.log("Game start result:", success ? "SUCCESS" : "FAILED");
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed. Check console for details."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
} else {
// Draw the initial game screen - the game draws itself after startGame()
console.log("Game started successfully, initial screen should be drawn");
// Shift focus to the game display area after successful game start
$('#game-display').focus();
updateFocusIndicator();
}
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
}
});
// Game update handler
socket.on('game_update', function(data) {
// console.log("🎮 GAME UPDATE received:", data); // Reduced verbosity
if (typeof Game !== 'undefined' && Game._gameActive) { // Use Game._gameActive instead of gameState
Game.updateGameState(data); // Call updateGameState directly on Game object
} else {
// console.log("Game not active or Game undefined, skipping game_update draw.");
}
});
// Update the chat form submission handler that processes the "enter" command
$('#chatForm').submit(function(e) {
e.preventDefault();
var msg = $('#chatInput').val().trim();
// Don't send empty messages
if (!msg) return false;
console.log("📝 SENDING CHAT:", msg);
socket.emit('chat message', msg);
$('#chatInput').val('');
// If the message is "enter", show the waiting screen
// AND shift focus to game display if game is about to start
if (msg.toLowerCase() === 'enter') {
console.log("🔑 'ENTER' COMMAND DETECTED - expecting game_start response");
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
// Focus will be shifted in the 'game_start' handler after success
}
return false;
});
// Set initial focus to chat input
$('#chatInput').focus();
updateFocusIndicator(); // Initial focus indicator update
// Add focus/blur listeners for visual indicator
$('#chatInput').on('focus', updateFocusIndicator).on('blur', updateFocusIndicator);
$('#game-display').on('focus', updateFocusIndicator).on('blur', updateFocusIndicator);
// Keyboard listener for game actions (WASD, Arrow Keys)
// Ensure this is attached to an element that can receive focus, like the game-display div.
// The game-display div needs a tabindex attribute to be focusable.
$('#game-display').attr('tabindex', '-1'); // Make it focusable
$(document).on('keydown', function(e) {
// console.log(`Keydown event: ${e.key}`, "Active element:", document.activeElement, "Game active?:", Game && Game.gameState ? Game.gameState.gameActive : "Game undefined");
if (document.activeElement === $('#game-display')[0]) {
// console.log("Game display has focus.");
if (Game && Game._gameActive) { // Use Game._gameActive
// console.log("Game is active. Processing key:", e.key);
let dx = 0;
let dy = 0;
let moved = false;
switch(e.key) {
case 'w': case 'ArrowUp': dy = -1; moved = true; break;
case 's': case 'ArrowDown': dy = 1; moved = true; break;
case 'a': case 'ArrowLeft': dx = -1; moved = true; break;
case 'd': case 'ArrowRight': dx = 1; moved = true; break;
}
if (moved) {
console.log(`Attempting to move: dx=${dx}, dy=${dy}. Key: ${e.key}`);
e.preventDefault(); // Prevent page scrolling
// Emit movement to server instead of directly calling Game.movePlayer
socket.emit('player_move', { dx: dx, dy: dy });
console.log(`Emitted player_move: dx=${dx}, dy=${dy}`);
} else {
// console.log("Key pressed on game display, but not a movement key:", e.key);
}
} else {
console.log("Game display has focus, but Game is not active. Game object:", Game, "Game._gameActive:", Game && Game.gameState ? Game.gameState.gameActive : "N/A");
}
} else if (e.key === 'Enter' && document.activeElement !== $('#chatInput')[0]) {
// console.log("Enter pressed, focusing chat input.");
// If Enter is pressed and chat is not focused, focus chat
$('#chatInput').focus();
updateFocusIndicator();
} else {
// console.log("Keydown event, but game display not focused or not Enter key for chat focus. Active element:", document.activeElement);
}
});
});
// Function to update visual focus indicator
function updateFocusIndicator() {
var gameDisplay = $('#game-display');
var chatInput = $('#chatInput');
if (document.activeElement === gameDisplay[0]) {
gameDisplay.css('outline', '2px solid yellow'); // Or some other visual cue
chatInput.css('outline', 'none');
console.log("Focus: Game Display");
} else if (document.activeElement === chatInput[0]) {
chatInput.css('outline', '2px solid cyan'); // Or some other visual cue
gameDisplay.css('outline', 'none');
console.log("Focus: Chat Input");
} else {
gameDisplay.css('outline', 'none');
chatInput.css('outline', 'none');
console.log("Focus: None or Other");
}
}
</script>
<!-- Initialize the game when everything is ready -->
<script>
$(function() {
Game.init();
});
</script>
<!-- Block height update script -->
<script>
// Function to update block height display
function updateBlockHeight(height) {
$('#heightValue').text(height);
}
// Listen for block updates from server
socket.on('blockheight', function(height) {
console.log(`📈 BLOCK HEIGHT: ${height}`);
updateBlockHeight(height);
$('#statusValue').text('Connected');
$('#statusValue').css('color', '#0f0');
});
// Set a test value if no response
setTimeout(function() {
if ($('#heightValue').text() === 'Loading...') {
updateBlockHeight('---');
$('#statusValue').text('Waiting...');
$('#statusValue').css('color', '#ff0');
}
}, 5000);
</script>
<!-- Add browser detection info -->
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
<div>Browser: <span id="browser-name">checking...</span></div>
<div>Display: <span id="display-status">checking...</span></div>
<button onclick="document.getElementById('browser-info').style.display='none'" style="margin-top: 5px; background: #333; border: 1px solid #666; color: white; font-size: 10px; padding: 2px 4px;">
Close
</button>
</div>
<script>
// Check browser and display status
$(function() {
setTimeout(function() {
var browserInfo = document.getElementById('browser-info');
browserInfo.style.display = 'block';
// Detect browser
var browserName = "Unknown";
if (navigator.userAgent.indexOf("Firefox") !== -1) {
browserName = "Firefox";
} else if (navigator.userAgent.indexOf("Chrome") !== -1) {
browserName = "Chrome/Chromium";
} else if (navigator.userAgent.indexOf("Safari") !== -1) {
browserName = "Safari";
} else if (navigator.userAgent.indexOf("Edge") !== -1) {
browserName = "Edge";
}
document.getElementById('browser-name').textContent = browserName;
// Check display status
var rotdis = document.querySelector('.rotdis');
var canvas = rotdis ? rotdis.querySelector('canvas') : null;
if (canvas) {
document.getElementById('display-status').textContent =
"OK (" + canvas.width + "x" + canvas.height + ")";
document.getElementById('display-status').style.color = "#0f0";
} else {
document.getElementById('display-status').textContent = "NOT FOUND";
document.getElementById('display-status').style.color = "#f00";
}
// Hide after 10 seconds
setTimeout(function() {
browserInfo.style.display = 'none';
}, 10000);
}, 2000);
});
</script>
<!-- Add ASCII Mode button at the top right -->
<button id="toggle-mode" style="position: fixed; right: 10px; top: 10px; z-index: 1000; background: #333; color: white; padding: 5px; border: 1px solid #666; border-radius: 4px;">
Switch to ASCII Mode
</button>
<script>
document.getElementById('toggle-mode').addEventListener('click', function() {
try {
var button = document.getElementById('toggle-mode');
// Display mode switching has been removed - ASCII and tile modes are now equivalent
console.log("Display mode switching is no longer needed - modes are equivalent");
button.textContent = 'Mode switching disabled';
button.disabled = true;
} catch (e) {
console.error("Error switching display mode:", e);
}
});
</script>
<!-- Replace the queue_cancelled handler -->
<script>
// Handle queue cancellation
socket.on('queue_cancelled', function() {
console.log("Queue cancelled, returning to welcome screen");
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) {
Game._drawWelcomeScreen();
} else {
console.error("Game or _drawWelcomeScreen not available");
}
});
</script>
<!-- Add this before the closing </body> tag -->
<script>
// Game over handler
socket.on('game_over', function(data) {
console.log("🎮 GAME OVER received:", data);
// Update game state based on outcome
if (typeof Game !== 'undefined') {
if (data.status === 'won') {
// Show win message
$('#messages').append($('<li style="color:#0f0; font-weight:bold;">').text(
data.hasTreasure ?
"YOU ESCAPED WITH THE TREASURE! YOU WON!" :
"You escaped the dungeon alive!"
));
// Draw win screen
Game.drawWinScreen(data.hasTreasure);
} else {
// Show loss message
const reason = data.reason || "unknown";
let message = "You died in the dungeon!";
if (reason === 'monster') {
message = "You were killed by the monster!";
} else if (reason === 'timeout') {
message = "You didn't escape before the next block was found!";
}
$('#messages').append($('<li style="color:#f00; font-weight:bold;">').text(message));
// Draw lose screen
if (Game && Game.drawLoseScreen) {
Game.drawLoseScreen(reason);
}
}
// Set game as inactive to prevent further movement
if (Game) {
Game._gameActive = false;
}
}
scrollChat();
});
</script>
<!-- Initialize the Game object -->
<script>
$(document).ready(function() {
console.log("Initializing Game...");
if (typeof Game !== 'undefined') {
Game.init();
} else {
console.error("Game object not found!");
}
});
</script>
</body>
</html>
+119
View File
@@ -0,0 +1,119 @@
// DisplayManager - Handles ROT.Display initialization and clearing
var DisplayManager = {
_display: null,
_displayReady: false,
_screenWidth: null,
_screenHeight: null,
init: function(screenWidth, screenHeight) {
this._screenWidth = screenWidth;
this._screenHeight = screenHeight;
var displayOptions = {
width: this._screenWidth,
height: this._screenHeight,
forceSquareRatio: true,
};
displayOptions.layout = "tile";
displayOptions.bg = window.options.bg;
displayOptions.fg = window.options.fg || "#FFF";
displayOptions.tileWidth = window.options.tileWidth;
displayOptions.tileHeight = window.options.tileHeight;
displayOptions.tileSet = window.options.tileSet;
displayOptions.tileMap = window.options.tileMap;
displayOptions.tileColorize = true; // Enable colorization for tile stacks
try {
var gameDisplayContainer = document.getElementById("game-display");
if (!gameDisplayContainer) {
console.error("Game display container #game-display not found in HTML!");
alert("Error: Game display container #game-display not found. Cannot start game.");
return false;
}
this._display = new ROT.Display(displayOptions);
gameDisplayContainer.innerHTML = '';
gameDisplayContainer.appendChild(this._display.getContainer());
this._displayReady = true;
return true;
} catch (e) {
console.error("Error initializing ROT.Display:", e);
alert("Failed to initialize game display. Error: " + e.message + ". Check console for details.");
this._displayReady = false;
return false;
}
},
getDisplay: function() {
return this._display;
},
isReady: function() {
return this._display && this._displayReady;
},
ensureDisplay: function() {
if (!this._display || !this._displayReady) {
return false;
}
return true;
},
forceClearToBlack: function() {
if (!this.ensureDisplay()) {
console.warn("DisplayManager.forceClearToBlack: Display not ready, aborting.");
return;
}
const display = this._display;
// Step 1: Clear ROT.js's internal data cache and dirty flags
display._data = {};
display._dirty = {};
// Step 2: Directly clear the visual canvas to black
var canvas = display.getContainer();
if (canvas && typeof canvas.getContext === 'function') {
var ctx = canvas.getContext("2d");
if (ctx) {
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
console.warn("DisplayManager.forceClearToBlack: Failed to get 2D context from canvas.");
this._fallbackClear(display);
}
} else {
console.warn("DisplayManager.forceClearToBlack: Display container is not a canvas or does not exist. Attempting fallback clear.");
this._fallbackClear(display);
}
},
_fallbackClear: function(display) {
console.warn("DisplayManager.forceClearToBlack: Using fallback clear method.");
const originalBg = display.getOptions().bg;
try {
if (display._options) {
display._options.bg = "#000000";
}
display.clear();
} catch (e) {
console.error("Error during fallback clear:", e);
} finally {
if (display._options) {
display._options.bg = originalBg;
}
}
},
clearDisplay: function() {
if (!this.ensureDisplay()) return;
this.forceClearToBlack();
}
};
// Make it available globally
if (typeof window !== 'undefined') {
window.DisplayManager = DisplayManager;
}
+109 -647
View File
@@ -1,3 +1,4 @@
// Refactored Game Object - Main game controller using modular architecture
// This provides a mock socket when the real one isn't available
if (typeof window !== 'undefined' && !window.socket) {
window.socket = {
@@ -8,313 +9,99 @@ if (typeof window !== 'undefined' && !window.socket) {
}
var Game = {
_display: null,
_screenWidth: options.width,
_screenHeight: options.height,
_displayReady: false, // Flag to indicate if _display is initialized
_player: null,
defaultFg: "#FFF", // Define defaultFg here
// _defaultBg: null, // Standard for transparent/display default
_monster: null,
_map: {},
_items: {},
_gameInProgress: false,
_screenWidth: 25, // Default values, will be updated from options
_screenHeight: 19,
_inputEnabled: true,
_messageLog: [],
_maxLogMessages: 5,
init: function() {
console.log("Game.init called. 'this' is Game:", this === Game);
console.log("Is this.checkTileset a function?", typeof this.checkTileset);
var displayOptions = {
width: this._screenWidth,
height: this._screenHeight,
forceSquareRatio: true, // Good for both modes generally
};
displayOptions.layout = "tile";
displayOptions.bg = window.options.bg; // Should be "transparent"
displayOptions.fg = window.options.fg || "#FFF"; // Default foreground for text not in tileMap
displayOptions.tileWidth = window.options.tileWidth;
displayOptions.tileHeight = window.options.tileHeight;
displayOptions.tileSet = window.options.tileSet;
displayOptions.tileMap = window.options.tileMap;
displayOptions.tileColorize = false; // Render tiles as-is from the tileset
try {
var gameDisplayContainer = document.getElementById("game-display");
if (!gameDisplayContainer) {
console.error("Game display container #game-display not found in HTML!");
alert("Error: Game display container #game-display not found. Cannot start game.");
return;
}
this._display = new ROT.Display(displayOptions);
gameDisplayContainer.innerHTML = ''; // Clear any previous content (e.g., loading messages)
gameDisplayContainer.appendChild(this._display.getContainer());
console.log("ROT.Display initialized and appended. Display object:", this._display);
this._displayReady = true; // Set flag AFTER successful initialization
console.log("Game display initialized. _displayReady set to true.");
// Now that the display is ready, other things can happen,
// like drawing the welcome screen or enabling socket event handlers fully.
this._drawWelcomeScreen(); // Example: draw initial screen
} catch (e) {
console.error("Error initializing ROT.Display:", e);
alert("Failed to initialize game display. Error: " + e.message + ". Check console for details.");
this._displayReady = false; // Explicitly false on error
}
}, // Removed .bind(this) - not needed for object literal methods
_ensureDisplay: function() {
if (!this._display || !this._displayReady) {
console.warn("Display not ready or not initialized. Aborting drawing/game operation.");
// Update screen dimensions from options
if (typeof window.options !== 'undefined') {
this._screenWidth = window.options.width || 25;
this._screenHeight = window.options.height || 19;
}
// Initialize all modules
if (!DisplayManager.init(this._screenWidth, this._screenHeight)) {
return false;
}
ScreenManager.init(this._screenWidth, this._screenHeight);
RenderEngine.init(this._screenWidth, this._screenHeight);
GameState.init();
// Start block simulation and draw welcome screen
ScreenManager.startBlockSimulation();
ScreenManager.drawWelcomeScreen();
return true;
},
_forceClearToBlack: function() {
if (!this._ensureDisplay()) {
console.warn("Game._forceClearToBlack: Display not ready, aborting.");
return;
}
// console.log("Game: _forceClearToBlack called at " + new Date().toLocaleTimeString());
const display = this._display;
// Step 1: Clear ROT.js's internal data cache and dirty flags.
// This ensures that ROT.js starts with a clean slate for what it thinks is on screen.
display._data = {};
display._dirty = {};
// console.log("Game._forceClearToBlack: Cleared display._data and display._dirty.");
// Step 2: Directly clear the visual canvas to black.
// This bypasses ROT.js's drawing for the initial clear, ensuring it's black.
var canvas = display.getContainer(); // This is typically the <canvas> element
if (canvas && typeof canvas.getContext === 'function') {
var ctx = canvas.getContext("2d");
if (ctx) {
// console.log(`Game._forceClearToBlack: Clearing canvas (${canvas.width}x${canvas.height}) to black.`);
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
console.warn("Game._forceClearToBlack: Failed to get 2D context from canvas.");
this._fallbackClear(display);
}
} else {
console.warn("Game._forceClearToBlack: Display container is not a canvas or does not exist. Attempting fallback clear.");
this._fallbackClear(display);
}
// After this, the canvas is black. ROT.js's _data and _dirty are empty.
// Subsequent display.draw() calls (e.g., in _drawWelcomeScreen) will populate
// _data and _dirty for the specific cells they affect.
// The ROT.js _draw() loop will then only process these newly dirtied cells,
// for which _data will exist.
_ensureDisplay: function() {
return DisplayManager.ensureDisplay();
},
// Helper for fallback clear if direct canvas manipulation fails
_fallbackClear: function(display) {
console.warn("Game._forceClearToBlack: Using fallback clear method.");
const originalBg = display.getOptions().bg; // Get current bg from display's options
try {
if (display._options) { // Ensure _options exists
display._options.bg = "#000000"; // Set to black for the clear
}
display.clear(); // Use ROT.js's own clear method.
// This will use the (now black) _options.bg.
} catch (e) {
console.error("Error during fallback clear:", e);
} finally {
if (display._options) { // Ensure _options exists before restoring
display._options.bg = originalBg; // Restore original background
}
}
},
// Add a helper for default colors to avoid errors if options.colors is not fully defined
// _getColor: function(type, defaultColor) {
// return (options.colors && options.colors[type]) ? options.colors[type] : defaultColor;
// }, // REMOVED
_isFirefox: function() {
return navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
},
startGame: function(playerData, mapData, monsterData, itemData, initialVisibleTiles) { // Added initialVisibleTiles
startGame: function(playerData, mapData, monsterData, itemData, initialVisibleTiles) {
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);
this.clearDisplay();
DisplayManager.clearDisplay();
try {
this._gameActive = true;
GameState.setGameActive(true);
// Robust player initialization
if (playerData && typeof playerData.x === 'number' && typeof playerData.y === 'number') {
this._player = { x: playerData.x, y: playerData.y };
} else {
console.warn("Invalid or missing playerData.x/y, using default player position.", playerData);
this._player = { x: 12, y: 12 }; // Default position
}
console.log("Player initialized to:", this._player);
// Initialize player
GameState.initializePlayer(playerData);
this._monster = monsterData; // Assuming monsterData is structured correctly or null
this._items = itemData; // Fixed: Use _items instead of _item
// Initialize other game objects
GameState._monster = monsterData;
GameState._items = itemData;
GameState._entrance = null;
GameState._exit = null;
GameState._treasure = null;
// Initialize map and validate player position
GameState.initializeMap(mapData, initialVisibleTiles, this._screenWidth, this._screenHeight);
// Initialize entrance, exit, treasure from the game data
this._entrance = null;
this._exit = null;
this._treasure = null;
this._map = {};
this._visibleTiles = {}; // Clear/initialize visible tiles
this._exploredTiles = this._exploredTiles || {}; // Initialize exploredTiles if it doesn't exist, or keep existing
let mapInitialized = false;
if (mapData && mapData.tiles && typeof mapData.tiles === 'object' && Object.keys(mapData.tiles).length > 0) {
console.log("Initializing map from mapData.tiles:", mapData.tiles);
for (var yKey in mapData.tiles) {
const y = parseInt(yKey);
this._map[y] = {};
for (var xKey in mapData.tiles[y]) {
const x = parseInt(xKey);
this._map[y][x] = mapData.tiles[y][x];
}
}
mapInitialized = true;
console.log("Game map initialized from mapData.tiles:", this._map);
} else if (mapData && Array.isArray(mapData) && mapData.length > 0) {
console.warn("Received array-based mapData, attempting to convert.");
for (let y = 0; y < mapData.length; y++) {
this._map[y] = {};
if (mapData[y] && Array.isArray(mapData[y])) {
for (let x = 0; x < mapData[y].length; x++) {
this._map[y][x] = mapData[y][x];
}
}
}
mapInitialized = true;
console.log("Game map initialized from array-based mapData:", this._map);
} else if (initialVisibleTiles && typeof initialVisibleTiles === 'object' && Object.keys(initialVisibleTiles).length > 0) {
console.log("Initializing _visibleTiles from server-sent initialVisibleTiles and inferring _map for FOV.");
this._visibleTiles = JSON.parse(JSON.stringify(initialVisibleTiles)); // Deep copy
for (const yKey in this._visibleTiles) {
const y = parseInt(yKey);
this._map[y] = this._map[y] || {};
for (const xKey in this._visibleTiles[y]) {
const x = parseInt(xKey);
// Store the tile type (0 for floor, 1 for wall, etc.) into _map
// This allows client-side FOV to know about passability.
this._map[y][x] = this._visibleTiles[y][x];
}
}
mapInitialized = true;
console.log("Game map partially initialized for FOV from initialVisibleTiles:", this._map);
console.log("_visibleTiles set directly from initialVisibleTiles:", this._visibleTiles);
if (!GameState.validatePlayerPosition(this._screenWidth, this._screenHeight)) {
ScreenManager.drawCenteredText(10, "Error: Map invalid / No player start!");
return false;
}
if (!mapInitialized) {
console.warn("No valid mapData or initialVisibleTiles provided. Creating default map for testing. mapData:", mapData, "initialVisibleTiles:", initialVisibleTiles);
const defaultMapWidth = this._screenWidth;
const defaultMapHeight = this._screenHeight;
for (let y = 0; y < defaultMapHeight; y++) {
this._map[y] = {};
for (let x = 0; x < defaultMapWidth; x++) {
this._map[y][x] = (x === 0 || x === defaultMapWidth - 1 || y === 0 || y === defaultMapHeight - 1) ? 1 : 0;
}
}
// Adjust player position if it's in a wall of the default map
if (this._map[this._player.y] && this._map[this._player.y][this._player.x] === 1) {
this._player.x = Math.floor(defaultMapWidth / 2);
this._player.y = Math.floor(defaultMapHeight / 2);
if (this._map[this._player.y][this._player.x] === 1) { // If center is still wall (tiny map)
this._map[this._player.y][this._player.x] = 0; // Force floor
}
}
console.log("Default game map created. Player position:", this._player.x, this._player.y);
}
// Compute field of view
GameState.computeFieldOfView();
// Validate player position against the map
if (!this._map[this._player.y] || this._map[this._player.y][this._player.x] === undefined) {
console.error(`Player position (${this._player.x}, ${this._player.y}) is invalid or outside map boundaries AFTER map init. Attempting to find fallback.`);
let foundFallback = false;
for (let y_scan = 0; y_scan < this._screenHeight; y_scan++) { // Iterate within screen bounds for fallback
if (this._map[y_scan]) {
for (let x_scan = 0; x_scan < this._screenWidth; x_scan++) {
if (this._map[y_scan][x_scan] === 0) { // Find first floor tile
this._player.x = x_scan;
this._player.y = y_scan;
console.warn(`Player moved to fallback position: (${this._player.x}, ${this._player.y})`);
foundFallback = true; break;
}
}
}
if (foundFallback) break;
}
if (!foundFallback) {
console.error("Could not find a valid fallback starting position for the player on the map.");
this._drawCenteredText(10, "Error: Map invalid / No player start!");
return false;
}
}
// Setup scheduler and engine
if (!GameState._scheduler) GameState._scheduler = new ROT.Scheduler.Simple();
else GameState._scheduler.clear();
if (!GameState._engine) GameState._engine = new ROT.Engine(GameState._scheduler);
// Initialize FOV
// FOV requires a map where 0 is passable, other numbers are not.
var fov = new ROT.FOV.PreciseShadowcasting(function(x, y) {
return (this._map[y] && this._map[y][x] !== undefined) ? (this._map[y][x] === 0) : false;
}.bind(this));
GameState._scheduler.add(GameState._player, true);
// If map was NOT built from initialVisibleTiles, compute FOV now.
// If it WAS built from initialVisibleTiles, _visibleTiles is already set.
// However, we run _updateExploredTiles and FOV compute to ensure consistency and explore.
if (!(initialVisibleTiles && typeof initialVisibleTiles === 'object' && Object.keys(initialVisibleTiles).length > 0)) {
this._visibleTiles = {}; // Clear if not set by server
fov.compute(this._player.x, this._player.y, 10, function(x, y, r, visibility) {
if (!this._visibleTiles[y]) this._visibleTiles[y] = {};
if (this._map[y] && this._map[y][x] !== undefined) {
this._visibleTiles[y][x] = this._map[y][x];
}
}.bind(this));
}
this._updateExploredTiles(); // Add all currently visible tiles to explored tiles
console.log("Initial FOV computed/processed. Visible tiles count:", Object.keys(this._visibleTiles).reduce((acc, yKey) => acc + Object.keys(this._visibleTiles[yKey]).length, 0));
if (!this._scheduler) this._scheduler = new ROT.Scheduler.Simple();
else this._scheduler.clear();
if (!this._engine) this._engine = new ROT.Engine(this._scheduler);
this._scheduler.add(this._player, true);
// this._engine.start(); // KEEP COMMENTED for now
console.log("Player coordinates before drawing game screen:", this._player.x, this._player.y);
if (!this._map[this._player.y] || this._map[this._player.y][this._player.x] === undefined) {
console.error(`Player position (${this._player.x}, ${this._player.y}) is STILL invalid before drawing. Map issue?`);
this._drawCenteredText(10, "Error: Player invalid for draw!");
console.log("Player coordinates before drawing game screen:", GameState._player.x, GameState._player.y);
if (!GameState._map[GameState._player.y] || GameState._map[GameState._player.y][GameState._player.x] === undefined) {
console.error(`Player position (${GameState._player.x}, ${GameState._player.y}) is STILL invalid before drawing. Map issue?`);
ScreenManager.drawCenteredText(10, "Error: Player invalid for draw!");
return false;
}
this._drawGameScreen();
console.log("Game started successfully and initial screen drawn.");
return true;
} catch (err) {
console.error("Error during Game.startGame:", err);
console.error("Error name:", err.name, "Error message:", err.message, "Stack:", err.stack);
if (err.message && (err.message.includes("_map") || err.message.includes("Cannot read properties of undefined"))) {
console.error("Detailed error info: Player:", this._player, "Map keys:", Object.keys(this._map || {}).length);
console.error("Detailed error info: Player:", GameState._player, "Map keys:", Object.keys(GameState._map || {}).length);
}
try {
this._drawCenteredText(10, "Error starting game!");
this._drawCenteredText(12, "Check console (F12) for details.");
ScreenManager.drawCenteredText(10, "Error starting game!");
ScreenManager.drawCenteredText(12, "Check console (F12) for details.");
} catch (drawError) {
console.error("Failed to draw error message on screen:", drawError);
}
@@ -324,420 +111,99 @@ var Game = {
// Update game state with new data from server
updateGameState: function(data) {
console.log("🎲 Game.updateGameState received data:", data);
const needsRedraw = GameState.updateGameState(data);
if (!this._gameActive) {
console.warn("Game.updateGameState called, but game is not active. Ignoring update.");
return;
}
if (!data) {
console.error("Game.updateGameState: No update data received!");
return;
}
try {
let needsRedraw = false;
// Update player state
if (data.player) {
// console.log("Player state updated:", data.player);
this._player = data.player;
needsRedraw = true;
}
// Update monster state
if (data.monster !== undefined) { // Check for undefined to allow null for no monster
// console.log("Monster state updated:", data.monster);
this._monster = data.monster;
needsRedraw = true;
}
// Update items
if (data.items !== undefined) { // Check for undefined to allow empty or null
// console.log("Items state updated:", data.items);
this._items = data.items; // Ensure this is _items, not _item
needsRedraw = true;
}
// Update entrance, exit, treasure
if (data.entrance !== undefined) {
this._entrance = data.entrance;
needsRedraw = true;
}
if (data.exit !== undefined) {
this._exit = data.exit;
needsRedraw = true;
}
if (data.treasure !== undefined) {
this._treasure = data.treasure;
needsRedraw = true;
}
// 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);
this._visibleTiles = data.visibleTiles;
// Debug log for specific problematic coordinates
if (this._visibleTiles[18] && (this._visibleTiles[18][36] !== undefined || this._visibleTiles[18][35] !== undefined)) {
const clientDebug = `🔍 CLIENT y=18: x=35: ${this._visibleTiles[18][35]}, x=36: ${this._visibleTiles[18][36]}`;
console.log(clientDebug);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(clientDebug);
}
if (this._visibleTiles[16] && (this._visibleTiles[16][36] !== undefined || this._visibleTiles[16][35] !== undefined)) {
const clientDebug = `🔍 CLIENT y=16: x=35: ${this._visibleTiles[16][35]}, x=36: ${this._visibleTiles[16][36]}`;
console.log(clientDebug);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(clientDebug);
}
this._updateExploredTiles(); // This is important for fog of war
needsRedraw = true;
} else if (data.visibleTiles) {
// console.log("Received visibleTiles, but it was empty or not an object:", data.visibleTiles);
}
// Redraw the game screen if any relevant data changed
if (needsRedraw) {
console.log("Redrawing game screen due to game update.");
this._drawGameScreen();
} else {
// console.log("Game.updateGameState: No relevant data changed that requires a redraw.");
}
} catch (err) {
console.error("Error in Game.updateGameState:", err);
// Redraw the game screen if any relevant data changed
if (needsRedraw) {
this._drawGameScreen();
}
},
_updateExploredTiles: function() {
// Add all currently visible tiles to explored tiles
for (var yKey in this._visibleTiles) {
const y = parseInt(yKey);
if (!this._exploredTiles[y]) this._exploredTiles[y] = {};
for (var xKey in this._visibleTiles[y]) {
const x = parseInt(xKey);
this._exploredTiles[y][x] = this._visibleTiles[y][x];
}
}
},
clearDisplay: function() {
if (!this._ensureDisplay()) return;
this._forceClearToBlack();
// console.log("Display cleared via _forceClearToBlack");
DisplayManager.clearDisplay();
},
_drawGameScreen: function() {
if (!this._ensureDisplay()) return;
const playerWX = this._player.x;
const playerWY = this._player.y;
const centerX = Math.floor(this._screenWidth / 2);
const centerY = Math.floor(this._screenHeight / 2);
const topLeftWX = playerWX - centerX;
const topLeftWY = playerWY - centerY;
for (let sy = 0; sy < this._screenHeight; sy++) {
for (let sx = 0; sx < this._screenWidth; sx++) {
const wx = topLeftWX + sx;
const wy = topLeftWY + sy;
let charStack = [];
let fgStack = [];
let bgStack = [];
let baseChar = ' ';
let baseFg = this.defaultFg; // Use this.defaultFg
let tileType;
if (this._visibleTiles[wy] && this._visibleTiles[wy][wx] !== undefined) {
tileType = this._visibleTiles[wy][wx];
baseFg = this.defaultFg; // Use this.defaultFg
baseChar = (tileType === 1) ? '#' : "'"; // Fixed: Always use ' for floor
// Debug for specific coordinates
if ((wx === 36 && (wy === 18 || wy === 16)) || (wx === 35 && (wy === 18 || wy === 16))) {
const renderDebug = `🔍 RENDER: (${wx},${wy}) tileType:${tileType} char:'${baseChar}' _visibleTiles[${wy}][${wx}]:${this._visibleTiles[wy][wx]}`;
console.log(renderDebug);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(renderDebug);
}
} else if (this._exploredTiles[wy] && this._exploredTiles[wy][wx] !== undefined) {
tileType = this._exploredTiles[wy][wx];
baseFg = this.defaultFg; // Use this.defaultFg
baseChar = (tileType === 1) ? '#' : "'"; // Fixed: Always use ' for floor
// Debug for specific coordinates in explored tiles
if ((wx === 36 && (wy === 18 || wy === 16)) || (wx === 35 && (wy === 18 || wy === 16))) {
const exploreDebug = `🔍 EXPLORED: (${wx},${wy}) tileType:${tileType} char:'${baseChar}' _exploredTiles[${wy}][${wx}]:${this._exploredTiles[wy][wx]}`;
console.log(exploreDebug);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(exploreDebug);
}
}
charStack.push(baseChar);
fgStack.push(baseFg);
bgStack.push(null);
// Items, Monsters, Player drawing logic
// Render items (fix property name from _item to _items)
if (this._items) {
for (const itemKey in this._items) {
if (this._items.hasOwnProperty(itemKey)) {
const currentItem = this._items[itemKey];
if (currentItem && currentItem.x === wx && currentItem.y === wy && this._isVisible(wx, wy)) {
charStack.push('$');
fgStack.push(this.defaultFg);
bgStack.push(null);
}
}
}
}
// Render entrance
if (this._entrance && this._entrance[0] === wx && this._entrance[1] === wy && this._isVisible(wx, wy)) {
charStack.push('<');
fgStack.push('#0f0'); // Green for entrance
bgStack.push(null);
}
// Render exit
if (this._exit && this._exit[0] === wx && this._exit[1] === wy && this._isVisible(wx, wy)) {
charStack.push('>');
fgStack.push('#f0f'); // Magenta for exit
bgStack.push(null);
}
// Render treasure
if (this._treasure && this._treasure[0] === wx && this._treasure[1] === wy && this._isVisible(wx, wy)) {
charStack.push('$');
fgStack.push('#ff0'); // Yellow for treasure
bgStack.push(null);
}
// Render monster
if (this._monster && this._monster.x === wx && this._monster.y === wy && this._isVisible(wx, wy)) {
charStack.push('~');
fgStack.push('#f00'); // Red for monster
bgStack.push(null);
}
// Render player (always on top)
if (wx === playerWX && wy === playerWY) {
charStack.push('@');
fgStack.push(this.defaultFg);
bgStack.push(null);
}
if (charStack.length > 0) {
this._display.draw(sx, sy, charStack, fgStack, bgStack);
}
}
}
if (this._message) {
this._drawCenteredText(this._screenHeight - 1, this._message);
}
},
_drawCenteredText: function(y, text, color) { // Restored color parameter
if (!this._ensureDisplay()) return y;
const x = Math.floor((this._screenWidth - text.length) / 2);
const finalColor = color || this.defaultFg; // Use provided color or defaultFg
this._display.drawText(x, y, `%c{${finalColor}}${text}`); // Use color in drawText
return y + 1;
},
_isVisible: function(x, y) {
return this._visibleTiles[y] && this._visibleTiles[y][x] !== undefined;
},
_drawBorder: function() { // NEW SIGNATURE
if (!this._ensureDisplay()) return;
// console.log("Drawing border. Tile mode: " + this._isTileMode);
for (var i = 0; i < this._screenWidth; i++) {
// this._display.draw(i, 0, "-", this._getColor("border_fg"), this._getColor("border_bg")); // OLD
// this._display.draw(i, this._screenHeight - 1, "-", this._getColor("border_fg"), this._getColor("border_bg")); // OLD
this._display.draw(i, 0, "-", this.defaultFg, null); // NEW
this._display.draw(i, this._screenHeight - 1, "-", this.defaultFg, null); // NEW
}
for (var j = 1; j < this._screenHeight - 1; j++) {
// this._display.draw(0, j, "|", this._getColor("border_fg"), this._getColor("border_bg")); // OLD
// this._display.draw(this._screenWidth - 1, j, "|", this._getColor("border_fg"), this._getColor("border_bg")); // OLD
this._display.draw(0, j, "|", this.defaultFg, null); // NEW
this._display.draw(this._screenWidth - 1, j, "|", this.defaultFg, null); // NEW
}
const gameState = GameState.getGameStateForRender();
RenderEngine.drawGameScreen(gameState);
},
// Screen drawing methods - delegate to ScreenManager
_drawWelcomeScreen: function() {
if (!this._ensureDisplay()) return;
this.clearDisplay();
// console.log("Drawing welcome screen. Tile mode: " + this._isTileMode);
let y = Math.floor(this._screenHeight / 4);
y = this._drawCenteredText(y, "THE WOWNGEON", this.defaultFg); // Use this.defaultFg
y = this._drawCenteredText(y + 2, "A Wownero Roguelike", this.defaultFg); // Use this.defaultFg
// Original text regarding game start
y = this._drawCenteredText(y + 3, "Type 'ENTER' in chat", this.defaultFg);
y = this._drawCenteredText(y + 1, "to begin your descent...", this.defaultFg);
this._drawCenteredText(y + 3, "Good luck, adventurer!", this.defaultFg);
ScreenManager.drawWelcomeScreen();
},
drawWinScreen: function(hasTreasure) {
if (!this._ensureDisplay()) return;
this.clearDisplay();
// console.log("Drawing win screen. Tile mode: " + this._isTileMode);
let y = this._drawCenteredText(Math.floor(this._screenHeight / 3), "CONGRATULATIONS!");
if (hasTreasure) {
y = this._drawCenteredText(y + 2, "You escaped with the treasure!");
} else {
y = this._drawCenteredText(y + 2, "You escaped the dungeon!");
}
this._drawCenteredText(y + 3, "A true hero of Wownero!");
ScreenManager.drawWinScreen(hasTreasure);
},
drawLoseScreen: function(reason) {
if (!this._ensureDisplay()) return;
this.clearDisplay();
// console.log("Drawing lose screen. Tile mode: " + this._isTileMode);
let y = this._drawCenteredText(Math.floor(this._screenHeight / 3), "YOU HAVE PERISHED");
if (reason === 'monster') {
y = this._drawCenteredText(y + 2, "Slain by a fearsome beast.");
} else if (reason === 'timeout') {
y = this._drawCenteredText(y + 2, "The dungeon claimed you before the next block.");
} else {
y = this._drawCenteredText(y + 2, "Your adventure ends here.");
}
this._drawCenteredText(y + 3, "Better luck next time.");
ScreenManager.drawLoseScreen(reason);
},
drawWaitingScreen: function() {
if (!this._ensureDisplay()) return;
this.clearDisplay(); // Clear it off
// console.log("Drawing waiting screen");
var text = "Waiting for server...";
// Use _drawCenteredText for consistency and color handling
this._drawCenteredText(Math.floor(this._screenHeight / 2), text, this.defaultFg);
ScreenManager.drawWaitingScreen();
},
// Method to stop the waiting animation
stopWaitingScreen: function() {
if (!this._ensureDisplay()) return;
this.clearDisplay(); // Clear it off
// console.log("Stopping waiting screen (clearing display)");
// Typically, another screen (like game screen) will be drawn immediately after.
ScreenManager.stopWaitingScreen();
},
// Helper function to draw centered text
_drawCenteredText: function(y, text, color) { // Restored color parameter
if (!this._ensureDisplay()) return y;
const x = Math.floor((this._screenWidth - text.length) / 2);
const finalColor = color || this.defaultFg; // Use provided color or defaultFg
this._display.drawText(x, y, `%c{${finalColor}}${text}`); // Use color in drawText
return y + 1;
},
switchToAsciiMode: function() {
console.log("Game.switchToAsciiMode called.");
this._isTileMode = false;
window.options.layout = "rect";
if (this._display) {
console.log("Display exists, attempting to re-initialize for ASCII mode.");
var gameDisplayContainer = document.getElementById("game-display");
if (gameDisplayContainer) gameDisplayContainer.innerHTML = '<p style="color:white;">Switching to ASCII mode, display will re-initialize...</p>';
this._display = null;
this._displayReady = false;
this.init(); // Re-trigger initialization
} else {
console.log("Display does not exist yet, init() will handle ASCII mode based on _isTileMode flag.");
this.init(); // This will now initialize in ASCII mode due to the flag
}
},
switchToTileMode: function() {
console.log("Game.switchToTileMode called.");
this._isTileMode = true;
window.options.layout = "tile";
if (this._display) {
console.log("Display exists, attempting to re-initialize for TILE mode.");
var gameDisplayContainer = document.getElementById("game-display");
if (gameDisplayContainer) gameDisplayContainer.innerHTML = '<p style="color:white;">Switching to TILE mode, display will re-initialize...</p>';
this._display = null;
this._displayReady = false;
this.init(); // Re-trigger initialization
} else {
console.log("Display does not exist yet, init() will handle TILE mode based on _isTileMode flag.");
this.init(); // This will now initialize in TILE mode due to the flag
}
},
// New method to handle player movement
// Player movement - delegate to GameState
movePlayer: function(dx, dy) {
if (!this._ensureDisplay()) return;
const newX = this._player.x + dx;
const newY = this._player.y + dy;
// Basic bounds checking
if (newX < 0 || newY < 0 || newX >= this._screenWidth || newY >= this._screenHeight) {
console.warn("Attempted to move player outside of bounds:", newX, newY);
return;
}
// Check if the new position is a wall (1) or out of map bounds
if (this._map[newY] && this._map[newY][newX] !== undefined && this._map[newY][newX] !== 1) {
// Move the player
this._player.x = newX;
this._player.y = newY;
console.log("Player moved to:", this._player.x, this._player.y);
// Update visibility and redraw
this._updateExploredTiles();
const moved = GameState.movePlayer(dx, dy, this._screenWidth, this._screenHeight);
if (moved) {
this._drawGameScreen();
} else {
console.log("Move blocked by wall or invalid map data at:", newX, newY);
}
},
// Debug function to print the map to console
// Debug functions - delegate to GameState
debugPrintMap: function() {
console.log("Current game map:");
for (let y = 0; y < this._screenHeight; y++) {
let row = "";
for (let x = 0; x < this._screenWidth; x++) {
if (this._map[y] && this._map[y][x] !== undefined) {
row += this._map[y][x] + " ";
} else {
row += "? "; // Unknown/undefined area
}
}
console.log(row);
}
GameState.debugPrintMap(this._screenWidth, this._screenHeight);
},
// Debugging function to test character mapping
debugTileMapping: function() {
const testValues = [0, 1, undefined, null];
for (const val of testValues) {
const char = (val === 1) ? '#' : "'";
const debugMsg = `Test mapping: value=${val} -> char='${char}'`;
console.log(debugMsg);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(debugMsg);
}
GameState.debugTileMapping();
},
// Properties for backward compatibility
get _gameActive() {
return GameState.isGameActive();
},
set _gameActive(value) {
GameState.setGameActive(value);
},
get _player() {
return GameState._player;
},
get _monster() {
return GameState._monster;
},
get _map() {
return GameState._map;
},
get _items() {
return GameState._items;
},
get _visibleTiles() {
return GameState._visibleTiles;
},
get _exploredTiles() {
return GameState._exploredTiles;
}
};
// Debug utility functions
// Debug utility functions (keep as-is for compatibility)
const GameDebug = {
log: function(message) {
console.log(message);
this.updateDebugDisplay(message);
this.sendToServer(message);
},
@@ -775,12 +241,8 @@ const GameDebug = {
}
};
// Make it available globally
window.GameDebug = GameDebug;
// Ensure Game is available globally
// Make them available globally
if (typeof window !== 'undefined') {
window.Game = Game;
window.GameDebug = GameDebug;
}
// Additional global or helper functions can be added here
+488
View File
@@ -0,0 +1,488 @@
// GameState - Handles game state management and updates
var GameState = {
_player: null,
_monster: null,
_map: {},
_items: {},
_gameActive: false,
_visibleTiles: {},
_exploredTiles: {},
_entrance: null,
_exit: null,
_treasure: null,
_message: null,
_scheduler: null,
_engine: null,
init: function() {
this.reset();
},
reset: function() {
this._player = null;
this._monster = null;
this._map = {};
this._items = {};
this._gameActive = false;
this._visibleTiles = {};
this._exploredTiles = {};
this._entrance = null;
this._exit = null;
this._treasure = null;
this._message = null;
if (this._scheduler) this._scheduler.clear();
if (!this._scheduler) this._scheduler = new ROT.Scheduler.Simple();
if (!this._engine) this._engine = new ROT.Engine(this._scheduler);
},
setGameActive: function(active) {
this._gameActive = active;
},
isGameActive: function() {
return this._gameActive;
},
initializePlayer: function(playerData) {
if (playerData && typeof playerData.x === 'number' && typeof playerData.y === 'number') {
this._player = { x: playerData.x, y: playerData.y };
} else {
console.warn("Invalid or missing playerData.x/y, using default player position.", playerData);
this._player = { x: 12, y: 12 }; // Default position
}
// console.log("Player initialized to:", this._player);
return this._player;
},
initializeMap: function(mapData, initialVisibleTiles, screenWidth, screenHeight) {
this._map = {};
this._visibleTiles = {};
this._exploredTiles = this._exploredTiles || {};
let mapInitialized = false;
// Try to initialize from mapData.tiles
if (mapData && mapData.tiles && typeof mapData.tiles === 'object' && Object.keys(mapData.tiles).length > 0) {
console.log("Initializing map from mapData.tiles:", mapData.tiles);
for (var yKey in mapData.tiles) {
const y = parseInt(yKey);
this._map[y] = {};
for (var xKey in mapData.tiles[y]) {
const x = parseInt(xKey);
this._map[y][x] = mapData.tiles[y][x];
}
}
mapInitialized = true;
console.log("Game map initialized from mapData.tiles:", this._map);
}
// Try array-based mapData
else if (mapData && Array.isArray(mapData) && mapData.length > 0) {
console.warn("Received array-based mapData, attempting to convert.");
for (let y = 0; y < mapData.length; y++) {
this._map[y] = {};
if (mapData[y] && Array.isArray(mapData[y])) {
for (let x = 0; x < mapData[y].length; x++) {
this._map[y][x] = mapData[y][x];
}
}
}
mapInitialized = true;
console.log("Game map initialized from array-based mapData:", this._map);
}
// Try initialVisibleTiles
else if (initialVisibleTiles && typeof initialVisibleTiles === 'object' && Object.keys(initialVisibleTiles).length > 0) {
console.log("Initializing _visibleTiles from server-sent initialVisibleTiles and inferring _map for FOV.");
this._visibleTiles = JSON.parse(JSON.stringify(initialVisibleTiles)); // Deep copy
for (const yKey in this._visibleTiles) {
const y = parseInt(yKey);
this._map[y] = this._map[y] || {};
for (const xKey in this._visibleTiles[y]) {
const x = parseInt(xKey);
this._map[y][x] = this._visibleTiles[y][x];
}
}
mapInitialized = true;
console.log("Game map partially initialized for FOV from initialVisibleTiles:", this._map);
console.log("_visibleTiles set directly from initialVisibleTiles:", this._visibleTiles);
}
// Create default map if nothing worked
if (!mapInitialized) {
console.warn("No valid mapData or initialVisibleTiles provided. Creating default map for testing.");
const defaultMapWidth = screenWidth;
const defaultMapHeight = screenHeight;
for (let y = 0; y < defaultMapHeight; y++) {
this._map[y] = {};
for (let x = 0; x < defaultMapWidth; x++) {
this._map[y][x] = (x === 0 || x === defaultMapWidth - 1 || y === 0 || y === defaultMapHeight - 1) ? 1 : 0;
}
}
// Adjust player position if it's in a wall of the default map
if (this._map[this._player.y] && this._map[this._player.y][this._player.x] === 1) {
this._player.x = Math.floor(defaultMapWidth / 2);
this._player.y = Math.floor(defaultMapHeight / 2);
if (this._map[this._player.y][this._player.x] === 1) {
this._map[this._player.y][this._player.x] = 0; // Force floor
}
}
console.log("Default game map created. Player position:", this._player.x, this._player.y);
}
return mapInitialized;
},
validatePlayerPosition: function(screenWidth, screenHeight) {
// Validate player position against the map
if (!this._map[this._player.y] || this._map[this._player.y][this._player.x] === undefined) {
console.error(`Player position (${this._player.x}, ${this._player.y}) is invalid or outside map boundaries. Attempting to find fallback.`);
let foundFallback = false;
for (let y_scan = 0; y_scan < screenHeight; y_scan++) {
if (this._map[y_scan]) {
for (let x_scan = 0; x_scan < screenWidth; x_scan++) {
if (this._map[y_scan][x_scan] === 0) { // Find first floor tile
this._player.x = x_scan;
this._player.y = y_scan;
console.warn(`Player moved to fallback position: (${this._player.x}, ${this._player.y})`);
foundFallback = true;
break;
}
}
}
if (foundFallback) break;
}
if (!foundFallback) {
console.error("Could not find a valid fallback starting position for the player on the map.");
return false;
}
}
return true;
},
computeFieldOfView: function() {
// Initialize FOV
var fov = new ROT.FOV.PreciseShadowcasting(function(x, y) {
return (this._map[y] && this._map[y][x] !== undefined) ? (this._map[y][x] === 0) : false;
}.bind(this));
// If map was NOT built from initialVisibleTiles, compute FOV now
if (!this._visibleTiles || Object.keys(this._visibleTiles).length === 0) {
this._visibleTiles = {};
fov.compute(this._player.x, this._player.y, 10, function(x, y, r, visibility) {
if (!this._visibleTiles[y]) this._visibleTiles[y] = {};
if (this._map[y] && this._map[y][x] !== undefined) {
this._visibleTiles[y][x] = this._map[y][x];
}
}.bind(this));
}
this.updateExploredTiles();
console.log("Initial FOV computed/processed. Visible tiles count:", Object.keys(this._visibleTiles).reduce((acc, yKey) => acc + Object.keys(this._visibleTiles[yKey]).length, 0));
},
updateExploredTiles: function() {
// Add all currently visible tiles to explored tiles
for (var yKey in this._visibleTiles) {
const y = parseInt(yKey);
if (!this._exploredTiles[y]) this._exploredTiles[y] = {};
for (var xKey in this._visibleTiles[y]) {
const x = parseInt(xKey);
this._exploredTiles[y][x] = this._visibleTiles[y][x];
}
}
},
updateGameState: function(data) {
console.log("🎲 GameState.updateGameState received data:", data);
if (!this._gameActive) {
console.warn("GameState.updateGameState called, but game is not active. Ignoring update.");
return false;
}
if (!data) {
console.error("GameState.updateGameState: No update data received!");
return false;
}
try {
let needsRedraw = false;
// Update player state
if (data.player) {
this._player = data.player;
needsRedraw = true;
}
// Update monster state
if (data.monster !== undefined) {
this._monster = data.monster;
needsRedraw = true;
}
// Update items
if (data.items !== undefined) {
this._items = data.items;
needsRedraw = true;
}
// Update entrance, exit, treasure
if (data.entrance !== undefined) {
this._entrance = data.entrance;
needsRedraw = true;
}
if (data.exit !== undefined) {
this._exit = data.exit;
needsRedraw = true;
}
if (data.treasure !== undefined) {
this._treasure = data.treasure;
needsRedraw = true;
}
// 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);
this._visibleTiles = data.visibleTiles;
// Debug log for specific problematic coordinates
if (this._visibleTiles[18] && (this._visibleTiles[18][36] !== undefined || this._visibleTiles[18][35] !== undefined)) {
const clientDebug = `🔍 CLIENT y=18: x=35: ${this._visibleTiles[18][35]}, x=36: ${this._visibleTiles[18][36]}`;
console.log(clientDebug);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(clientDebug);
}
if (this._visibleTiles[16] && (this._visibleTiles[16][36] !== undefined || this._visibleTiles[16][35] !== undefined)) {
const clientDebug = `🔍 CLIENT y=16: x=35: ${this._visibleTiles[16][35]}, x=36: ${this._visibleTiles[16][36]}`;
console.log(clientDebug);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(clientDebug);
}
this.updateExploredTiles();
needsRedraw = true;
}
return needsRedraw;
} catch (err) {
console.error("Error in GameState.updateGameState:", err);
return false;
}
},
movePlayer: function(dx, dy, screenWidth, screenHeight) {
if (!DisplayManager.ensureDisplay()) return;
const newX = this._player.x + dx;
const newY = this._player.y + dy;
// Basic bounds checking
if (newX < 0 || newY < 0 || newX >= screenWidth || newY >= screenHeight) {
console.warn("Attempted to move player outside of bounds:", newX, newY);
return;
}
// Check if the new position is a wall (1) or out of map bounds
if (this._map[newY] && this._map[newY][newX] !== undefined && this._map[newY][newX] !== 1) {
// Move the player
this._player.x = newX;
this._player.y = newY;
console.log("Player moved to:", this._player.x, this._player.y);
// Update visibility and redraw
this.updateExploredTiles();
return true;
} else {
console.log("Move blocked by wall or invalid map data at:", newX, newY);
return false;
}
},
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:");
for (let y = 0; y < screenHeight; y++) {
let row = "";
for (let x = 0; x < screenWidth; x++) {
if (this._map[y] && this._map[y][x] !== undefined) {
row += this._map[y][x] + " ";
} else {
row += "? "; // Unknown/undefined area
}
}
console.log(row);
}
},
debugTileMapping: function() {
const testValues = [0, 1, undefined, null];
for (const val of testValues) {
const char = (val === 1) ? '#' : "'";
const debugMsg = `Test mapping: value=${val} -> char='${char}'`;
console.log(debugMsg);
if (window.GameDebug) window.GameDebug.updateDebugDisplay(debugMsg);
}
},
// Compute field of view for the player (LOCAL TESTING ONLY - server handles FOV)
computeFieldOfView: function() {
if (!this._player) {
console.warn("Cannot compute FOV: player not initialized");
return;
}
console.log("⚠️ LOCAL FOV COMPUTATION (should only be used for testing without server)");
const oldVisibleTiles = JSON.parse(JSON.stringify(this._visibleTiles));
this._visibleTiles = {};
const fov = new ROT.FOV.PreciseShadowcasting(function(x, y) {
return this._map[y] && this._map[y][x] === 0; // 0 = passable, 1 = wall
}.bind(this));
fov.compute(this._player.x, this._player.y, 6, function(x, y, r, visibility) {
if (!this._visibleTiles[y]) this._visibleTiles[y] = {};
this._visibleTiles[y][x] = this._map[y] && this._map[y][x] !== undefined ? this._map[y][x] : 0;
// Mark as explored
if (!this._exploredTiles[y]) this._exploredTiles[y] = {};
this._exploredTiles[y][x] = this._map[y] && this._map[y][x] !== undefined ? this._map[y][x] : 0;
}.bind(this));
console.log("FOV computed for player at:", this._player.x, this._player.y);
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) {
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 {
map: this._map,
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,
gameActive: this._gameActive
};
},
// Move player and update game state
movePlayer: function(dx, dy, screenWidth, screenHeight) {
if (!this._player) {
console.warn("Cannot move player: player not initialized");
return false;
}
const newX = this._player.x + dx;
const newY = this._player.y + dy;
// Check bounds
if (newX < 0 || newX >= screenWidth || newY < 0 || newY >= screenHeight) {
// console.log("Player move blocked: out of bounds");
return false;
}
// Check if the new position is passable
if (this._map[newY] && this._map[newY][newX] === 1) {
// console.log("Player move blocked: wall at", newX, newY);
return false;
}
// Update player position
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;
}
};
// Make it available globally
if (typeof window !== 'undefined') {
window.GameState = GameState;
}
+51 -8
View File
@@ -30,19 +30,34 @@ const InputHandler = {
if (!msg) return false;
console.log("📝 SENDING CHAT:", msg);
socket.emit('chat message', msg);
$('#chatInput').val('');
// If the message is "enter", show the waiting screen
// If the message is "enter", always send it to server
// The server will handle queueing logic and timing
if (msg.toLowerCase() === 'enter') {
console.log("🔑 'ENTER' COMMAND DETECTED - expecting game_start response");
console.log("🔑 'ENTER' COMMAND DETECTED - sending to server");
socket.emit('chat message', msg);
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
// Show appropriate screen based on current state
const isDebugMode = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1' ||
window.location.protocol === 'file:';
if (isDebugMode || ScreenManager.canEnterGame()) {
console.log("✅ Can enter immediately - showing waiting screen");
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
} else {
console.log("⏳ Will be queued - showing queue message");
$('#messages').append($('<li style="color: #ff0;">').text("* You have been added to the queue! Game will start after next block."));
UI.scrollChat();
}
} else {
// Send other chat messages normally
socket.emit('chat message', msg);
}
$('#chatInput').val('');
return false;
});
@@ -104,7 +119,35 @@ const InputHandler = {
}
}
} else {
console.log("Game display has focus, but Game is not active.");
// Game display has focus but game is not active (welcome screen)
// Check for debug key 'D' to start immediately
if (e.key === 'D' || e.key === 'd') {
// Check if we're in debug mode (localhost)
const isDebugMode = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1' ||
window.location.protocol === 'file:';
if (isDebugMode) {
console.log("🔧 DEBUG: 'D' key pressed - starting game immediately");
e.preventDefault();
// Emit enter command to start game immediately
socket.emit('chat message', 'enter');
// Show waiting screen while game starts
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
// Add debug message to chat
$('#messages').append($('<li style="color: #0f0;">').text("🔧 DEBUG: Starting game immediately..."));
UI.scrollChat();
} else {
console.log("Debug key pressed but not in debug mode");
}
} else {
console.log("Game display has focus, but Game is not active.");
}
}
} else if (e.key === 'Enter' && document.activeElement !== $('#chatInput')[0]) {
// If Enter is pressed and chat is not focused, focus chat
+5 -28
View File
@@ -1,33 +1,15 @@
var tileSet = document.createElement("img");
var tileSetLoaded = false;
// More robust error handling
tileSet.onload = function() {
console.log("✅ Tileset image loaded successfully!");
tileSetLoaded = true;
// Add visible verification on the page
var statusElem = document.createElement('div');
statusElem.style.position = 'fixed';
statusElem.style.bottom = '40px';
statusElem.style.right = '10px';
statusElem.style.background = 'rgba(0,255,0,0.5)';
statusElem.style.color = 'white';
statusElem.style.padding = '5px';
statusElem.style.fontSize = '12px';
statusElem.style.zIndex = '1000';
statusElem.textContent = '✓ Tileset loaded';
document.body.appendChild(statusElem);
setTimeout(() => statusElem.remove(), 5000);
};
tileSet.onerror = function() {
console.error("❌ Failed to load tileset image!");
window.globalTileSetStatus = "error"; // Set a status for Game.js to check if needed
console.error("Failed to load tileset image!");
};
// Try different possible paths (to handle serving from different directories)
tileSet.src = "tiles.png"; // Try default path first
tileSet.src = "tiles.png";
var options = {
layout: "tile",
@@ -130,17 +112,12 @@ var options = {
"]": [832, 64],
"{": [864, 64],
"%": [896, 64],
"^": [928, 64], // Duplicate ^
"^": [928, 64],
" ": [992, 64]
}
};
// Update the tileset loading to properly set the global variables
// Make sure the variables are properly set in the global scope
// Make options globally available
window.tileSet = tileSet;
window.tileMap = options.tileMap;
// Add logging to confirm they're set
console.log("✅ Tileset assigned to window.tileSet:", !!window.tileSet);
console.log("✅ TileMap assigned to window.tileMap:", !!window.tileMap, "with keys:", window.tileMap ? Object.keys(window.tileMap).length : 0);
window.options = options;
+171
View File
@@ -0,0 +1,171 @@
// RenderEngine - Handles game screen rendering and drawing logic
var RenderEngine = {
defaultFg: "#FFF",
init: function(screenWidth, screenHeight) {
this._screenWidth = screenWidth;
this._screenHeight = screenHeight;
},
isVisible: function(x, y, visibleTiles) {
return visibleTiles[y] && visibleTiles[y][x] !== undefined;
},
drawGameScreen: function(gameState) {
if (!DisplayManager.ensureDisplay()) return;
const display = DisplayManager.getDisplay();
const player = gameState.player;
const monster = gameState.monster;
const items = gameState.items;
const entrance = gameState.entrance;
const exit = gameState.exit;
const treasure = gameState.treasure;
const visibleTiles = gameState.visibleTiles;
const exploredTiles = gameState.exploredTiles;
const message = gameState.message;
const playerWX = player.x;
const playerWY = player.y;
const centerX = Math.floor(this._screenWidth / 2);
const centerY = Math.floor(this._screenHeight / 2);
const topLeftWX = playerWX - centerX;
const topLeftWY = playerWY - centerY;
for (let sy = 0; sy < this._screenHeight; sy++) {
for (let sx = 0; sx < this._screenWidth; sx++) {
const wx = topLeftWX + sx;
const wy = topLeftWY + sy;
let charStack = [];
let fgStack = [];
let bgStack = [];
let baseChar = ' ';
let baseFg = this.defaultFg;
let tileType;
// Render base terrain
if (this.isVisible(wx, wy, visibleTiles)) {
tileType = visibleTiles[wy][wx];
baseFg = this.defaultFg;
// Fix: Handle all possible tile values explicitly and ensure they map to valid characters
if (tileType === 1) {
baseChar = '#'; // Wall
} else if (tileType === 0) {
baseChar = "'"; // Floor
} else {
baseChar = "'"; // Default to floor for safety
tileType = 0;
}
} else if (exploredTiles[wy] && exploredTiles[wy][wx] !== undefined) {
tileType = exploredTiles[wy][wx];
baseFg = this.defaultFg;
// Fix: Handle all possible tile values explicitly for explored tiles too
if (tileType === 1) {
baseChar = '#'; // Wall
} else if (tileType === 0) {
baseChar = "'"; // Floor
} else {
baseChar = "'"; // Default to floor for safety
tileType = 0;
}
} else {
// No visible or explored tile - render empty space
baseChar = ' ';
baseFg = this.defaultFg;
}
// Only add to stacks if we have a valid character
if (baseChar && baseChar !== ' ') {
charStack.push(baseChar);
fgStack.push("transparent"); // Use transparent for all base tiles
bgStack.push("transparent"); // Use transparent for all base tiles
}
// Render items
if (items) {
for (const itemKey in items) {
if (items.hasOwnProperty(itemKey)) {
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
bgStack.push("transparent");
}
}
}
}
// Render entrance
if (entrance && entrance[0] === wx && entrance[1] === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push('<');
fgStack.push("transparent"); // Use transparent for now
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
bgStack.push("transparent");
}
// Render treasure
if (treasure && treasure[0] === wx && treasure[1] === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push('$');
fgStack.push("transparent"); // Use transparent for now
bgStack.push("transparent");
}
// Render monster
if (monster && monster.x === wx && monster.y === wy && this.isVisible(wx, wy, visibleTiles)) {
charStack.push('~');
fgStack.push("transparent"); // Use transparent for now
bgStack.push("transparent");
}
// Render player (always on top)
if (wx === playerWX && wy === playerWY) {
charStack.push('@');
fgStack.push("transparent"); // Use transparent for now
bgStack.push("transparent");
}
if (charStack.length > 0) {
// Validate array lengths
while (fgStack.length < charStack.length) fgStack.push("transparent");
while (bgStack.length < charStack.length) bgStack.push("transparent");
// Validate characters are in tileMap
for (let i = 0; i < charStack.length; i++) {
if (!charStack[i] || (window.options?.tileMap && !window.options.tileMap.hasOwnProperty(charStack[i]))) {
charStack[i] = "'";
}
if (!fgStack[i]) fgStack[i] = "transparent";
if (!bgStack[i]) bgStack[i] = "transparent";
}
try {
display.draw(sx, sy, charStack, fgStack, bgStack);
} catch (error) {
display.draw(sx, sy, ["'"], ["transparent"], ["transparent"]);
}
}
}
}
// Render message at bottom if present
if (message) {
ScreenManager.drawCenteredText(this._screenHeight - 1, message);
}
}
};
// Make it available globally
if (typeof window !== 'undefined') {
window.RenderEngine = RenderEngine;
}
+181
View File
@@ -0,0 +1,181 @@
// ScreenManager - Handles different screen states and UI drawing
var ScreenManager = {
defaultFg: "#FFF",
_currentBlockHeight: 0,
_blockTimer: null,
_waitingForBlock: false,
init: function(screenWidth, screenHeight) {
this._screenWidth = screenWidth;
this._screenHeight = screenHeight;
this._currentBlockHeight = 0;
this._waitingForBlock = false;
},
drawCenteredText: function(y, text, color) {
if (!DisplayManager.ensureDisplay()) return y;
const display = DisplayManager.getDisplay();
// Filter text to only include valid characters
let filteredText = "";
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (window.options?.tileMap?.hasOwnProperty(char) || char === ' ') {
filteredText += char;
} else {
filteredText += "?";
}
}
const x = Math.floor((this._screenWidth - filteredText.length) / 2);
const finalColor = color || this.defaultFg;
display.drawText(x, y, `%c{${finalColor}}${filteredText}`);
return y + 1;
},
drawBorder: function() {
if (!DisplayManager.ensureDisplay()) return;
const display = DisplayManager.getDisplay();
for (var i = 0; i < this._screenWidth; i++) {
display.draw(i, 0, "-", this.defaultFg, null);
display.draw(i, this._screenHeight - 1, "-", this.defaultFg, null);
}
for (var j = 1; j < this._screenHeight - 1; j++) {
display.draw(0, j, "|", this.defaultFg, null);
display.draw(this._screenWidth - 1, j, "|", this.defaultFg, null);
}
},
drawWelcomeScreen: function() {
if (!DisplayManager.ensureDisplay()) return;
DisplayManager.clearDisplay();
let y = Math.floor(this._screenHeight / 4);
y = this.drawCenteredText(y, "THE WOWNGEON", this.defaultFg);
y = this.drawCenteredText(y + 2, "A Wownero Roguelike", this.defaultFg);
y = this.drawCenteredText(y + 3, "Waiting for next block...", this.defaultFg);
y = this.drawCenteredText(y + 1, `Block Height: ${this._currentBlockHeight}`, "#0f0");
if (this._waitingForBlock) {
y = this.drawCenteredText(y + 2, "Mining in progress...", "#ff0");
} else {
y = this.drawCenteredText(y + 2, "Type 'ENTER' to queue for game", this.defaultFg);
}
// Debug button (shown only in development)
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.protocol === 'file:') {
this.drawCenteredText(y + 3, "[DEBUG: Press 'D' to start immediately]", "#666");
}
this.drawCenteredText(y + 5, "Good luck, adventurer!", this.defaultFg);
},
// Start simulated block mining (30 second intervals)
startBlockSimulation: function() {
console.log("Starting block simulation...");
this._currentBlockHeight = Math.floor(Math.random() * 1000000) + 3200000; // Realistic Wownero block height
this._waitingForBlock = true;
// Update welcome screen every second during mining
const updateInterval = setInterval(() => {
if (this._waitingForBlock) {
this.drawWelcomeScreen();
} else {
clearInterval(updateInterval);
}
}, 1000);
// Simulate new block every 30 seconds
this._blockTimer = setInterval(() => {
this._currentBlockHeight++;
this._waitingForBlock = false;
this.drawWelcomeScreen();
// Show block found message briefly
setTimeout(() => {
if (!DisplayManager.ensureDisplay()) return;
let y = Math.floor(this._screenHeight / 2);
this.drawCenteredText(y, "🎉 NEW BLOCK FOUND! 🎉", "#0f0");
this.drawCenteredText(y + 1, "You may now enter the dungeon!", "#0f0");
// Allow entry for 5 seconds
setTimeout(() => {
this._waitingForBlock = true;
this.drawWelcomeScreen();
}, 5000);
}, 500);
}, 30000); // 30 second intervals
},
// Stop block simulation
stopBlockSimulation: function() {
if (this._blockTimer) {
clearInterval(this._blockTimer);
this._blockTimer = null;
}
this._waitingForBlock = false;
},
// Check if player can enter (not waiting for block or debug mode)
canEnterGame: function() {
const isDebugMode = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1' ||
window.location.protocol === 'file:';
return !this._waitingForBlock || isDebugMode;
},
drawWinScreen: function(hasTreasure) {
if (!DisplayManager.ensureDisplay()) return;
DisplayManager.clearDisplay();
let y = this.drawCenteredText(Math.floor(this._screenHeight / 3), "CONGRATULATIONS!");
if (hasTreasure) {
y = this.drawCenteredText(y + 2, "You escaped with the treasure!");
} else {
y = this.drawCenteredText(y + 2, "You escaped the dungeon!");
}
this.drawCenteredText(y + 3, "A true hero of Wownero!");
},
drawLoseScreen: function(reason) {
if (!DisplayManager.ensureDisplay()) return;
DisplayManager.clearDisplay();
let y = this.drawCenteredText(Math.floor(this._screenHeight / 3), "YOU HAVE PERISHED");
if (reason === 'monster') {
y = this.drawCenteredText(y + 2, "Slain by a fearsome beast.");
} else if (reason === 'timeout') {
y = this.drawCenteredText(y + 2, "The dungeon claimed you before the next block.");
} else {
y = this.drawCenteredText(y + 2, "Your adventure ends here.");
}
this.drawCenteredText(y + 3, "Better luck next time.");
},
drawWaitingScreen: function() {
if (!DisplayManager.ensureDisplay()) return;
DisplayManager.clearDisplay();
let y = Math.floor(this._screenHeight / 2) - 2;
y = this.drawCenteredText(y, "* MINING IN PROGRESS *", "#ff0");
y = this.drawCenteredText(y + 2, `Current Block: ${this._currentBlockHeight}`, "#0f0");
y = this.drawCenteredText(y + 1, "Waiting for next block to enter...", this.defaultFg);
// Animated dots
const dots = ".".repeat((Math.floor(Date.now() / 500) % 4));
this.drawCenteredText(y + 2, `Mining${dots}`, "#666");
},
stopWaitingScreen: function() {
if (!DisplayManager.ensureDisplay()) return;
DisplayManager.clearDisplay();
}
};
// Make it available globally
if (typeof window !== 'undefined') {
window.ScreenManager = ScreenManager;
}
+2 -2
View File
@@ -46,11 +46,11 @@ const SocketHandlers = {
userAgent: navigator.userAgent
});
// Auto-start game for faster testing
// Auto-start for testing (timeout issue has been fixed)
setTimeout(() => {
console.log("🚀 AUTO-STARTING GAME for faster testing");
socket.emit('chat message', 'enter');
}, 500); // Small delay to ensure connection is fully established
}, 500);
},
onWelcome: function(msg) {
-24
View File
@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowgue</title>
<link rel="stylesheet" type="text/css" href="styles/test.css">
</head>
<body>
<div class="header">
<h1>Welcome traveler...</h1>
</div>
<div class="wrapper">
<div id="item1">1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6sssssssssssssssssssssssss</div>
</div>
</body>
</html>
+422
View File
@@ -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('./rpccalls.js');
var db = require('./dbcalls.js');
var user = require('./user');
// Function to get a user by socket ID
// Enhance the getUserBySocket function to try both IDs
function getUserBySocket(socketId) {
console.log(`Looking up user with socketId: ${socketId}`);
// Try direct lookup first
let foundUser = user.getUserBySocketId(socketId);
// If not found but we have a socket map, try the mapped ID
if (!foundUser && clientSocketMap.has(socketId)) {
const mappedId = clientSocketMap.get(socketId);
console.log(`Socket ID ${socketId} not found directly, trying mapped ID: ${mappedId}`);
foundUser = user.getUserBySocketId(mappedId);
}
console.log(`User lookup result for ${socketId}: ${foundUser ? "FOUND" : "NOT FOUND"}`);
return foundUser;
}
const Game = require('./game');
const activeGames = new Map(); // Maps socketId to Game objects
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)}...`);
}
+5 -5
View File
@@ -133,17 +133,17 @@ function checkGamesTimeout(currentHeight) {
activeGames.forEach((game, socketId) => {
const user = getUserBySocket(socketId);
// Player must escape before the next block after their start block
// If they started on block 2, they must escape before block 3 is found
if (user && user.blockRec && currentHeight > user.blockRec) {
console.log(`Game timeout for player ${socketId}: started on block ${user.blockRec}, current block ${currentHeight}`);
// 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 next block was found!'
message: 'You didn\'t escape before the block time limit!'
});
activeGames.delete(socketId);