First kinda working version!

This commit is contained in:
jwinterm
2025-04-27 20:08:38 -04:00
parent 823b09e944
commit 03cc22ac4c
7 changed files with 1862 additions and 642 deletions
+106 -10
View File
@@ -65,14 +65,16 @@ function generateDungeon(width, height) {
class Game {
constructor(socketId, mapWidth, mapHeight) {
this.socketId = socketId;
this.width = mapWidth || 50;
this.height = mapHeight || 30;
this.width = mapWidth || 25; // Reduced default size to match client display
this.height = mapHeight || 19; // Reduced default size to match client display
this.gameState = 'waiting'; // waiting, active, won, lost
this.startBlock = null;
this.dungeon = null;
this.player = { x: 0, y: 0, hasKey: false, hasTreasure: false };
this.monster = { x: 0, y: 0 };
this.fee = 0; // Amount player paid
this.visibleTiles = {}; // Will store visible tiles
this.fov = null; // FOV calculator
this.generateDungeon();
}
@@ -92,6 +94,31 @@ class Game {
this.monster.x = center[0];
this.monster.y = center[1];
}
// Initialize FOV calculator
this.fov = new ROT.FOV.PreciseShadowcasting(
(x, y) => {
// Return true if the tile is transparent (can see through it)
return this.dungeon.map[y] && this.dungeon.map[y][x] === 0;
}
);
// Calculate initial FOV
this.updateFOV();
// Set game state to active
this.gameState = 'active';
}
updateFOV() {
// Reset visible tiles
this.visibleTiles = {};
// Calculate new visible tiles
this.fov.compute(this.player.x, this.player.y, 10, (x, y) => {
if (!this.visibleTiles[y]) this.visibleTiles[y] = {};
this.visibleTiles[y][x] = this.dungeon.map[y][x];
});
}
movePlayer(dx, dy) {
@@ -99,10 +126,13 @@ class Game {
const newY = this.player.y + dy;
// Check if the move is valid (not into a wall)
if (this.dungeon.map[newY][newX] === 0) {
if (this.dungeon.map[newY] && this.dungeon.map[newY][newX] === 0) {
this.player.x = newX;
this.player.y = newY;
// Update FOV after movement
this.updateFOV();
// Check if player found treasure
if (this.dungeon.treasure &&
newX === this.dungeon.treasure[0] &&
@@ -169,16 +199,82 @@ class Game {
}
}
// Replace getState() method with this relative-coordinates version
getState() {
// Only include visible information and translate to player-relative coordinates
const playerX = this.player.x;
const playerY = this.player.y;
// Create relative FOV map
const relativeVisibleTiles = {};
for (const y in this.visibleTiles) {
for (const x in this.visibleTiles[y]) {
const relX = parseInt(x) - playerX;
const relY = parseInt(y) - playerY;
// Initialize row if needed
if (!relativeVisibleTiles[relY]) {
relativeVisibleTiles[relY] = {};
}
// Add relative position tile
relativeVisibleTiles[relY][relX] = this.visibleTiles[y][x];
}
}
// Create relative entity positions only if visible in FOV
const entityInfo = {};
// Check if monster is visible
if (this.visibleTiles[this.monster.y] &&
this.visibleTiles[this.monster.y][this.monster.x] !== undefined) {
entityInfo.monster = {
x: this.monster.x - playerX,
y: this.monster.y - playerY
};
}
// Check if entrance is visible
if (this.dungeon.entrance &&
this.visibleTiles[this.dungeon.entrance[1]] &&
this.visibleTiles[this.dungeon.entrance[1]][this.dungeon.entrance[0]] !== undefined) {
entityInfo.entrance = [
this.dungeon.entrance[0] - playerX,
this.dungeon.entrance[1] - playerY
];
}
// Check if exit is visible
if (this.dungeon.exit &&
this.visibleTiles[this.dungeon.exit[1]] &&
this.visibleTiles[this.dungeon.exit[1]][this.dungeon.exit[0]] !== undefined) {
entityInfo.exit = [
this.dungeon.exit[0] - playerX,
this.dungeon.exit[1] - playerY
];
}
// Check if treasure is visible
if (this.dungeon.treasure &&
this.visibleTiles[this.dungeon.treasure[1]] &&
this.visibleTiles[this.dungeon.treasure[1]][this.dungeon.treasure[0]] !== undefined) {
entityInfo.treasure = [
this.dungeon.treasure[0] - playerX,
this.dungeon.treasure[1] - playerY
];
}
return {
gameState: this.gameState,
player: this.player,
monster: this.monster,
map: this.dungeon.map,
entrance: this.dungeon.entrance,
exit: this.dungeon.exit,
treasure: this.dungeon.treasure,
hasTreasure: this.player.hasTreasure
player: {
// Player is always at relative (0,0)
x: 0,
y: 0,
hasKey: this.player.hasKey || false,
hasTreasure: this.player.hasTreasure || false
},
visibleTiles: relativeVisibleTiles,
...entityInfo // Only include visible entities
};
}
}
+745 -197
View File
@@ -1,207 +1,755 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowgue</title>
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<div class='wrapper'>
<div class='header'>
<h1 id='staticbanner'><span id='banner'></span><span id='bannercursor'></span></h1>
</div>
<!-- Status area (block height) -->
<div class='status'>
Block height: <span id="blockheight">0</span>
</div>
<!-- Chat area -->
<div class='chat'>
<div id='messageBox'>
<ul id='messages'>
<li>Welcome to Wowgue!</li>
<li>Type 'hello' to begin</li>
</ul>
<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/game.js"></script>
</head>
<body>
<div class="container">
<!-- Header area -->
<header class="banner">
<div id="staticbanner">
<span id="banner"></span><span id="bannercursor">|</span>
</div>
</div>
<!-- Main game display -->
<div class='rotdis'></div>
<!-- Input area -->
<div id='messageInput'>
<form action="" id='chatForm'>
<input autocomplete="off" id='chatInput' />
<button>Send</button>
</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 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>
<div class="footer">
footer
</div>
</div>
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/options.js"></script>
<script src="js/game.js"></script>
<script src="js/rot.min.js"></script>
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<script src="js/teletype.js"></script>
<script>
// Initialize socket connection
var socket = io();
// Socket event listeners
$(function() {
// Welcome message - when server confirms connection
socket.on('welcome', function(msg) {
console.log('Received welcome:', msg);
$('#messages').append($('<li>').text("Connected to server!"));
scrollChat();
});
// Blockheight update
socket.on('blockheight', function(msg) {
$('#blockheight').text(msg);
});
// Regular chat messages
socket.on('message', function(msg) {
console.log('Received message:', msg);
$('#messages').append($('<li>').text(msg));
scrollChat();
});
// Payment request (we'll handle this later)
socket.on('payment_request', function(data) {
console.log('Payment request:', data);
$('#messages').append($('<li class="payment">').text(
"Send between " + data.minFee + " and " + data.maxFee + " WOW to: " + data.address
));
scrollChat();
});
// Game start event
socket.on('game_start', function(data) {
console.log('Game starting:', data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
scrollChat();
// Start the game with the received data
Game.startGame(data);
Game._drawGameScreen(); // Draw the initial game screen
});
// Game update event
socket.on('game_update', function(data) {
console.log('Game update:', data);
// Update game state
Game._player = data.player;
Game._monster = data.monster;
// Redraw the game screen
Game._drawGameScreen();
});
// Game over event
socket.on('game_over', function(data) {
console.log('Game over:', data);
// Handle game over state
if (data.status === 'won') {
Game._drawWinScreen(data.hasTreasure);
$('#messages').append($('<li class="win">').text(
"You won! " + (data.hasTreasure ? "You found the treasure!" : "")
));
} else {
Game._drawLoseScreen(data.reason);
$('#messages').append($('<li class="lose">').text(
"Game over: " + data.reason
));
}
scrollChat();
});
// Handle chat form submission
$('#chatForm').submit(function(e) {
e.preventDefault(); // Prevent page reload
var message = $('#chatInput').val();
if (message.trim() !== '') {
// Send message to server
socket.emit('chat message', message);
// Special handling for commands
if (message.toLowerCase() === 'enter') {
$('#messages').append($('<li class="self-command">').text(
"Requesting to enter the dungeon..."
));
}
// Clear input field
$('#chatInput').val('');
}
return false;
});
// Helper function to scroll chat to bottom
function scrollChat() {
var messageBox = $('#messageBox');
messageBox.scrollTop(messageBox.prop("scrollHeight"));
}
});
</script>
<!-- Game initialization -->
<script>
// Function to initialize the game and append its display
function initializeAndAppendDisplay() {
console.log("Initializing Game...");
</div>
<!-- Footer -->
<div class="footer">
Wownero Roguelike v0.1.0 | Use WASD or arrow keys to move
</div>
</div>
<!-- Add teletype JS implementation -->
<script>
// Simple teletype implementation
(function($) {
$.fn.teletype = function(options) {
var settings = $.extend({
text: [],
typeDelay: 100,
backDelay: 50,
pause: 1000,
loop: true,
cursor: ''
}, options);
return this.each(function() {
var $el = $(this);
var currentTextIndex = 0;
var currentCharIndex = 0;
var isTyping = true;
function type() {
var currentText = settings.text[currentTextIndex];
if (isTyping) {
// Typing forward
if (currentCharIndex < currentText.length) {
$el.text($el.text() + currentText.charAt(currentCharIndex));
currentCharIndex++;
setTimeout(type, settings.typeDelay);
} else {
// Reached end of text, pause before backspacing
isTyping = false;
setTimeout(type, settings.pause);
}
} else {
// Backspacing
if (currentCharIndex > 0) {
$el.text($el.text().slice(0, -1));
currentCharIndex--;
setTimeout(type, settings.backDelay);
} else {
// Finished backspacing, move to next text
isTyping = true;
currentTextIndex = (currentTextIndex + 1) % settings.text.length;
if (currentTextIndex === 0 && !settings.loop) {
// Stop if we've gone through all texts and not looping
return;
}
setTimeout(type, settings.typeDelay);
}
}
}
// Start typing
type();
});
};
})(jQuery);
// Cursor blink effect
(function($) {
$.fn.blink = function(options) {
var settings = $.extend({
text: ['|', ' '],
delay: 500
}, options);
return this.each(function() {
var $el = $(this);
var currentIndex = 0;
function blink() {
$el.text(settings.text[currentIndex]);
currentIndex = (currentIndex + 1) % settings.text.length;
setTimeout(blink, settings.delay);
}
blink();
});
};
})(jQuery);
</script>
<!-- Initialize teletype animation -->
<script>
$(function() {
$('#banner').teletype({
text: ["Welcome traveler...", "You have arrived at...", "The Wowngeon"],
typeDelay: 100,
backDelay: 50,
pause: 1500
});
$('#bannercursor').blink({
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();
});
socket.on('message', function(msg) {
$('#messages').append($('<li>').text(msg));
scrollChat();
});
// Critical game events
// Replace the game_start handler with this improved version
socket.on('game_start', function(data) {
console.log("🎮 Received game_start broadcast:", data);
// Log all socketIds for debugging
console.log("Current socket ID:", socket.id);
console.log("Target socket ID:", data.targetSocket);
// Check if this event is for us
const isForUs = !data.targetSocket || data.targetSocket === socket.id;
console.log("Is this event for us?", isForUs ? "YES" : "NO");
// Only process if directed at us or broadcast
if (!isForUs) {
console.log("Ignoring game_start message meant for another client");
return;
}
// Check for required data
if (!data) {
console.error("❌ Error: Received empty game data!");
$('#messages').append($('<li class="error">').text("Error: No game data received"));
return;
}
// Check if we have player data
if (!data.player || typeof data.player.x === 'undefined') {
console.error("❌ Error: Invalid player data in game_start");
$('#messages').append($('<li class="error">').text("Error: Invalid player data"));
return;
}
// Track visibleTiles
if (!data.visibleTiles) {
console.warn("⚠️ Warning: No visibleTiles in game data, creating empty object");
data.visibleTiles = {};
}
$('#messages').append($('<li class="game-start">').text("Starting game..."));
try {
// Display what we received
$('#messages').append($('<li>').text(`Entering the dungeon at position (${data.player.x},${data.player.y})...`));
// Start the game with received data (check if Game is available)
if (typeof Game !== 'undefined' && Game.startGame) {
var success = Game.startGame(data);
console.log("Game start result:", success ? "SUCCESS ✅" : "FAILED ❌");
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed - trying ASCII mode"));
// Try ASCII mode as fallback
if (typeof Game.switchToAsciiMode === 'function') {
Game.switchToAsciiMode();
success = Game.startGame(data);
}
}
} else {
console.error("Game object not available yet!");
// Try again in a moment
setTimeout(function() {
if (typeof Game !== 'undefined' && Game.startGame) {
Game.startGame(data);
}
}, 500);
}
window.focus();
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
}
// Ensure chat is scrolled
if (typeof scrollChat === 'function') {
scrollChat();
}
});
// Add game update handler
socket.on('game_update', function(data) {
console.log("🎮 Received game update:", data);
// Update game state
if (typeof Game !== 'undefined' && typeof Game.updateGameState === 'function') {
Game.updateGameState(data);
}
// Log player position
if (data.player) {
console.log(`Player now at: (${data.player.x}, ${data.player.y})`);
}
});
// Replace the chat form submission handler
$('#chatForm').submit(function(e) {
e.preventDefault();
var msg = $('#chatInput').val().trim();
if (msg) {
console.log(`📝 SENDING CHAT: "${msg}"`);
// Special handling for enter command
if (msg.toLowerCase() === 'enter') {
console.log("🔑 'ENTER' COMMAND DETECTED - expecting game_start response");
$('#messages').append($('<li style="color:#ff0">').text("Requesting to enter the dungeon..."));
}
socket.emit('chat message', msg);
$('#chatInput').val('');
}
return false;
});
});
</script>
<!-- Initialize the game when everything is ready -->
<script>
$(function() {
Game.init();
if (!Game.getDisplay()) {
console.error("Game display object not found after Game.init(). Cannot append.");
return;
}
var displayContainer = Game.getDisplay().getContainer();
var targetElement = $('.rotdis');
if (targetElement.length > 0) {
targetElement.append(displayContainer);
console.log("ROT display appended to .rotdis");
});
</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 {
// Check current mode and toggle accordingly
if (Game.isAsciiMode && Game.isAsciiMode()) {
console.log("Switching to TILE mode...");
if (typeof Game.switchToTileMode === 'function') {
Game.switchToTileMode();
this.textContent = "Switch to ASCII Mode";
this.style.background = "#333";
this.style.color = "white";
} else {
console.error("Target element '.rotdis' not found.");
document.body.appendChild(displayContainer);
console.error("Game.switchToTileMode is not defined");
this.textContent = "Error: Tile Mode Not Available";
this.style.background = "#990000";
}
}
$(function() {
// Wait for tileset to be loaded, then initialize game
if (tileSetLoaded) {
initializeAndAppendDisplay();
} else {
console.log("Tileset not loaded yet, waiting for onload...");
tileSet.addEventListener('load', function() {
initializeAndAppendDisplay();
});
}
});
</script>
<script>
$(function() {
// Initialize teletype only after DOM is ready
$('#banner').teletype({
text: ["Welcome traveler...", "You have arrived at...", "The Wowngeon"]
});
$('#bannercursor').teletype({
text: ['|', ' '],
delay: 0,
pause: 500
});
} else {
console.log("Switching to ASCII mode...");
if (typeof Game.switchToAsciiMode === 'function') {
Game.switchToAsciiMode();
this.textContent = "Switch to Tile Mode";
this.style.background = "#444";
this.style.color = "#0f0";
} else {
console.error("Game.switchToAsciiMode is not defined");
this.textContent = "Error: ASCII Mode Not Available";
this.style.background = "#990000";
}
}
} catch (err) {
console.error("Error switching display mode:", err);
}
});
</script>
<!-- Add this outside of any $(function() { ... }) block, similar to blockheight handler -->
<script>
// Listen for game start events from server
socket.on('game_start', function(data) {
console.log("🎮 GAME START EVENT received:", data);
// Check for required data
if (!data) {
console.error("❌ Error: Received empty game data!");
$('#messages').append($('<li class="error">').text("Error: No game data received"));
return;
}
// Check if we have player data
if (!data.player || typeof data.player.x === 'undefined') {
console.error("❌ Error: Invalid player data in game_start");
$('#messages').append($('<li class="error">').text("Error: Invalid player data"));
return;
}
// Track visibleTiles
if (!data.visibleTiles) {
console.warn("⚠️ Warning: No visibleTiles in game data, creating empty object");
data.visibleTiles = {};
}
$('#messages').append($('<li class="game-start">').text("Starting game..."));
try {
// Display what we received
$('#messages').append($('<li>').text(`Entering the dungeon at position (${data.player.x},${data.player.y})...`));
// Start the game with received data (check if Game is available)
if (typeof Game !== 'undefined' && Game.startGame) {
var success = Game.startGame(data);
console.log("Game start result:", success ? "SUCCESS ✅" : "FAILED ❌");
// Add timeout to change text after animation completes
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed - trying ASCII mode"));
// Try ASCII mode as fallback
if (typeof Game.switchToAsciiMode === 'function') {
Game.switchToAsciiMode();
success = Game.startGame(data);
}
}
} else {
console.error("Game object not available yet!");
// Try again in a moment
setTimeout(function() {
$('#banner, #bannercursor').remove();
$('#staticbanner').text("The Wowngeon");
}, 14340);
});
</script>
</body>
if (typeof Game !== 'undefined' && Game.startGame) {
Game.startGame(data);
}
}, 500);
}
window.focus();
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
}
// Ensure chat is scrolled
if (typeof scrollChat === 'function') {
scrollChat();
}
});
</script>
<!-- Enhanced socket logging wrapper -->
<script>
// Enhanced socket logging wrapper - FIXED VERSION
(function(socket) {
// Save original emit function
const originalEmit = socket.emit;
// Override emit to log outgoing events
socket.emit = function(event, data) {
console.log(`📤 SOCKET EMIT: ${event}`, data);
return originalEmit.apply(this, arguments);
};
// DON'T override the normal 'on' function - this is causing issues
// Instead, add a listener for all events
socket.onAny((event, ...args) => {
console.log(`📥 SOCKET RECEIVED EVENT: ${event}`, args);
// Special logging for game_start
if (event === 'game_start') {
const data = args[0];
console.log("🎮 GAME START DETAILS:");
console.log("- Player:", data && data.player ? `(${data.player.x},${data.player.y})` : "Missing");
console.log("- VisibleTiles:", data && data.visibleTiles ? Object.keys(data.visibleTiles).length + " rows" : "Missing");
}
});
})(socket);
// Force refresh socket connection status
setTimeout(() => {
console.log("Socket connection status check:", socket.connected ? "CONNECTED" : "DISCONNECTED");
if (!socket.connected) {
console.error("⚠️ Socket is not connected! Attempting reconnect...");
socket.connect();
}
}, 3000);
</script>
<!-- Add keyboard event logging -->
<script>
// Global keyboard event listener
document.addEventListener('keydown', function(e) {
// Only log when game is active
if (Game && Game._gameActive) {
console.log(`🔤 KEY: ${e.key} (code: ${e.keyCode})`);
// Log movement keys specifically
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'a', 's', 'd'].includes(e.key)) {
console.log(`🏃‍♂️ MOVEMENT: ${e.key}`);
}
}
});
</script>
<!-- Add game state debugging helper -->
<script>
// Function to log current game state
function debugGameState() {
if (!Game) {
console.error("Game object not available");
return;
}
console.log("🔍 GAME STATE:");
console.log("- Active:", Game._gameActive);
console.log("- Player:", Game._player ?
`(${Game._player.x},${Game._player.y})` : "Not set");
console.log("- Monster:", Game._monster ?
`(${Game._monster.x},${Game._monster.y})` : "Not present");
console.log("- Display:", Game._display ? "Initialized" : "Not initialized");
// Log visible tiles count
const visibleTileCount = Game._visibleTiles ?
Object.keys(Game._visibleTiles).reduce((sum, rowKey) => {
return sum + Object.keys(Game._visibleTiles[rowKey]).length;
}, 0) : 0;
console.log("- Visible tiles:", visibleTileCount);
// Run this every 10 seconds while game is active
if (Game._gameActive) {
setTimeout(debugGameState, 10000);
}
}
// Start debug logging when game activates
const originalStartGame = Game.startGame;
Game.startGame = function(data) {
const result = originalStartGame.call(this, data);
if (result) {
console.log("Game started successfully, initiating debug logging");
debugGameState();
}
return result;
};
</script>
<script>
// FOV debugging helper - add at end of page
function debugClientFOV() {
if (typeof Game === 'undefined') {
console.error("Game not initialized");
return;
}
console.log("🔍 CLIENT FOV DEBUG:");
console.log("- Game active:", Game._gameActive || false);
console.log("- Player:", Game._player ?
`(${Game._player.x},${Game._player.y})` : "Not set");
// Check visibleTiles
if (!Game._visibleTiles) {
console.error("❌ NO VISIBLE TILES DATA!");
return;
}
const rowKeys = Object.keys(Game._visibleTiles);
console.log(`- Client has ${rowKeys.length} FOV rows`);
if (rowKeys.length > 0) {
// Sample first row
const sampleRow = rowKeys[0];
const cellCount = Object.keys(Game._visibleTiles[sampleRow]).length;
console.log(` - Sample row ${sampleRow} has ${cellCount} cells`);
// Check if player is in visible tiles
if (Game._player && Game._visibleTiles[Game._player.y] &&
Game._visibleTiles[Game._player.y][Game._player.x] !== undefined) {
console.log(" ✅ Player position IS in visible tiles");
} else if (Game._player) {
console.log(` ❌ Player position (${Game._player.x},${Game._player.y}) NOT in visible tiles!`);
}
}
// Draw debug visualization
if (Game._display) {
console.log("Drawing debug visualization of FOV data...");
// Mark all visible tiles with a special character
for (const y in Game._visibleTiles) {
for (const x in Game._visibleTiles[y]) {
Game._display.draw(parseInt(x), parseInt(y), "·", "#ff0", "#008");
}
}
// Re-draw player on top
if (Game._player) {
Game._display.draw(Game._player.x, Game._player.y, "@", "#fff", "#000");
}
}
}
// Add keyboard shortcut: Ctrl+Shift+F
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.shiftKey && e.key === 'F') {
console.log("FOV debug triggered by keyboard shortcut");
debugClientFOV();
}
});
</script>
<!-- Add this function at the end of your debugging scripts -->
<script>
function sendTestMove(direction) {
console.log("📤 Sending test move:", direction);
socket.emit('move', direction);
}
// Then you can test with sendTestMove("right") in console
</script>
<!-- Add this to your index.html to show input focus state -->
<style>
.game-focus-indicator {
position: fixed;
bottom: 5px;
right: 5px;
background: rgba(0,0,0,0.5);
color: white;
padding: 3px 6px;
border-radius: 3px;
font-size: 12px;
pointer-events: none;
opacity: 0.7;
}
.chat-focused .game-focus-indicator {
color: #aaa;
}
</style>
<div class="game-focus-indicator">⌨️ Game Controls Active</div>
<script>
// Add this to monitor focus state
$(function() {
const chatInput = $('#chatInput');
const body = $('body');
chatInput.on('focus', function() {
body.addClass('chat-focused');
$('.game-focus-indicator').text('⌨️ Chat Input Active');
});
chatInput.on('blur', function() {
body.removeClass('chat-focused');
$('.game-focus-indicator').text('⌨️ Game Controls Active');
});
});
</script>
<!-- Add direct game_start_broadcast listener -->
<script>
socket.on('game_start_broadcast', function(data) {
console.log("🎮 Game start broadcast received:", data);
// Check if this message is for us
const isForUs = data.targetSocketId === socket.id;
console.log(`Game broadcast for socket ${data.targetSocketId}, our ID is ${socket.id}, match: ${isForUs}`);
if (isForUs) {
console.log("✅ This game broadcast is for us!");
// Process as if it were a direct game_start message
if (typeof Game !== 'undefined' && typeof Game.startGame === 'function') {
Game.startGame(data);
}
}
});
</script>
<!-- Add this handler to show waiting status -->
<script>
// Add waiting_status handler
socket.on('waiting_status', function(data) {
console.log("⏱️ Received waiting status:", data);
if (data.status === 'waiting') {
// Show waiting message
$('#messages').append($('<li style="color:#ff0">').text(data.message));
// Update game area with waiting screen
if (typeof Game !== 'undefined' && typeof Game.showWaitingScreen === 'function') {
Game.showWaitingScreen(data);
}
}
});
</script>
</body>
</html>
+453 -86
View File
@@ -1,3 +1,12 @@
// This provides a mock socket when the real one isn't available
if (typeof window !== 'undefined' && !window.socket) {
window.socket = {
emit: function(event, data) {
console.log("[Mock Socket] Would emit:", event, data);
}
};
}
var Game = {
// Change dimensions to match the display options (25 × 19 tiles)
_screenWidth: 25, // Used for drawing coordinates (columns)
@@ -13,21 +22,80 @@ var Game = {
_treasure: null,
_gameActive: false,
_message: "", // To display messages like 'waiting'
_visibleTiles: {}, // Store currently visible tiles
_exploredTiles: {}, // Store all tiles the player has seen
_isFirefox: function() {
return navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
},
init: function() {
// Assuming 'options' is defined globally or passed correctly
console.log("Creating ROT.Display with options:", options);
console.log("Initializing Game...");
// Check for Firefox
var isFirefox = this._isFirefox();
if (isFirefox) {
console.log("Firefox detected, applying special handling");
}
try {
this._display = new ROT.Display(options);
// Create a display with the defined options
var displayOptions = { ...options }; // Clone options
// Firefox-specific tweaks if needed
if (isFirefox) {
// Force numerical dimensions for Firefox
displayOptions.width = parseInt(displayOptions.width);
displayOptions.height = parseInt(displayOptions.height);
}
console.log("Creating ROT.Display with options:", displayOptions);
this._display = new ROT.Display(displayOptions);
console.log("ROT.Display created.");
// Do not append here; we use external logic to append container
// Get the DOM element and append display
var container = document.querySelector('.rotdis');
console.log("Found display container:", container);
if (container) {
// Clear any existing content
container.innerHTML = '';
// Add the new canvas
var canvas = this._display.getContainer();
// Firefox-specific attributes
if (isFirefox) {
canvas.style.width = 'auto';
canvas.style.height = 'auto';
canvas.style.display = 'block';
canvas.style.margin = 'auto';
}
container.appendChild(canvas);
console.log("Display container attached to DOM");
// Draw initial welcome screen
console.log("Drawing initial welcome screen");
this._drawWelcomeScreen();
// Extra check for Firefox
if (isFirefox) {
// Give Firefox a moment to render properly
setTimeout(() => {
console.log("Firefox re-render check");
this._drawWelcomeScreen();
}, 100);
}
} else {
console.error("Could not find .rotdis container");
}
} catch (e) {
console.error("Error creating ROT.Display:", e);
alert("Failed to initialize display. Check console for errors.");
return;
console.error("Error during game initialization:", e);
}
// Initialize empty map representation based on _mapWidth and _mapHeight
// Initialize empty map
this._map = [];
for (var i = 0; i < this._mapWidth; i++) {
this._map[i] = [];
@@ -38,141 +106,309 @@ var Game = {
}
var game = this;
// Set up keyboard input
// Set up keyboard input - ONLY FOR MOVEMENT, not for starting the game
window.addEventListener("keydown", function(e) {
var code = e.keyCode;
// If game isn't active, ignore all keys except Enter
if (!game._gameActive && code !== 13) {
// Check if an input/textarea has focus - if so, don't capture keys for game
const activeElement = document.activeElement;
const isInputFocused = activeElement && (
activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.id === 'm' // Your chat input id
);
// If input is focused or game isn't active, don't process game controls
if (isInputFocused || !game._gameActive) {
return;
}
// Enter key to start game
if (code === 13 && !game._gameActive) {
console.log("Enter pressed, sending 'enter' message to server.");
socket.emit('chat message', 'enter');
game._message = "Connecting...";
game._drawWelcomeScreen();
e.preventDefault();
return;
}
// Movement controls
// Movement controls (only when game is active AND no input has focus)
var direction = "";
switch (code) {
switch (e.keyCode) {
case 38: case 75: case 87: // up, k, w
direction = "up";
e.preventDefault(); // Prevent page scrolling
break;
case 40: case 74: case 83: // down, j, s
direction = "down";
e.preventDefault(); // Prevent page scrolling
break;
case 37: case 72: case 65: // left, h, a
direction = "left";
e.preventDefault(); // Prevent page scrolling
break;
case 39: case 76: case 68: // right, l, d
direction = "right";
e.preventDefault(); // Prevent page scrolling
break;
default:
return;
}
if (direction && game._gameActive) {
socket.emit('move', direction);
e.preventDefault();
// If a direction key was pressed, update local state and send to server
if (direction) {
console.log("Game state updated:", direction);
// Send the move command to the server
if (window.socket) {
console.log("Sending move command to server:", direction);
window.socket.emit('move', direction);
}
}
});
// Draw welcome screen
this._message = "Press Enter or type 'enter' to Play!";
// Force draw welcome screen
this._gameActive = false;
this._message = "Type 'enter' in chat to play!";
console.log("Drawing initial welcome screen");
this._drawWelcomeScreen();
// Verify welcome screen was drawn by checking for canvas content
setTimeout(() => {
const container = this._display.getContainer();
const canvas = container.querySelector("canvas");
if (canvas) {
console.log("Canvas dimensions:", canvas.width, canvas.height);
console.log("Canvas is visible:", canvas.style.display !== "none");
}
}, 500);
},
// Initialize game with data from server
startGame: function(data) {
console.log("Starting game with data:", data);
this._map = data.map;
this._player = data.player;
this._monster = data.monster;
this._entrance = data.entrance;
this._exit = data.exit;
this._treasure = data.treasure;
this._gameActive = true;
if (!data) {
console.error("No game data received!");
return false;
}
this._drawGameScreen();
try {
// Set game as active
this._gameActive = true;
// Set all game properties from data
this._player = data.player || { x: 12, y: 12 };
this._monster = data.monster;
this._entrance = data.entrance;
this._exit = data.exit;
this._treasure = data.treasure;
// Get visible tiles, ensuring they're valid
if (data.visibleTiles && typeof data.visibleTiles === 'object') {
// Store the visible tiles
this._visibleTiles = data.visibleTiles;
console.log("Visible tiles:", Object.keys(this._visibleTiles).length, "rows");
} else {
// If no tiles, create a basic visible area around player
console.log("No visible tiles provided, creating basic view");
this._visibleTiles = {};
const px = this._player.x;
const py = this._player.y;
// Create a 5x5 visible area around player
for (let y = py-2; y <= py+2; y++) {
if (!this._visibleTiles[y]) this._visibleTiles[y] = {};
for (let x = px-2; x <= px+2; x++) {
this._visibleTiles[y][x] = 0; // Floor tiles
}
}
}
// Update explored tiles
this._updateExploredTiles();
// Clear and redraw the screen
this.clearDisplay();
console.log("Drawing game screen for active game");
console.log("Player position:", this._player);
// Draw the game with explicit console logging after each step
this._drawGameScreen();
return true;
} catch (err) {
console.error("Error starting game:", err);
return false;
}
},
// Update game state with new data from server
updateGameState: function(data) {
console.log("🎲 Updating game state with:", data);
if (!data) {
console.error("No update data received!");
return;
}
try {
// Update player position
if (data.player) {
console.log("Player position updated:", data.player);
this._player = data.player;
}
// Update monster position
if (data.monster !== undefined) {
this._monster = data.monster;
}
// Update visible tiles
if (data.visibleTiles && Object.keys(data.visibleTiles).length > 0) {
console.log("Visible tiles updated:", Object.keys(data.visibleTiles).length, "rows");
this._visibleTiles = data.visibleTiles;
this._updateExploredTiles();
}
// Redraw the game screen
console.log("Redrawing game screen after update");
this._drawGameScreen();
} catch (err) {
console.error("Error updating game state:", err);
}
},
_updateExploredTiles: function() {
// Add all currently visible tiles to explored tiles
for (var y in this._visibleTiles) {
if (!this._exploredTiles[y]) this._exploredTiles[y] = {};
for (var x in this._visibleTiles[y]) {
this._exploredTiles[y][x] = this._visibleTiles[y][x];
}
}
},
clearDisplay: function() {
// Clear the entire display area
this._display.clear();
},
_drawGameScreen: function() {
console.log("Drawing game screen...");
// Clear the display
this.clearDisplay();
// Draw map
for (var x = 0; x < this._mapWidth; x++) {
for (var y = 0; y < this._mapHeight; y++) {
var tile = this._map[y] && this._map[y][x] ? this._map[y][x] : 1;
// Calculate screen center for display
const centerX = Math.floor(this._screenWidth / 2);
const centerY = Math.floor(this._screenHeight / 2);
console.log("Screen center:", centerX, centerY);
// Draw visible tiles centered on player
for (let relY in this._visibleTiles) {
relY = parseInt(relY);
for (let relX in this._visibleTiles[relY]) {
relX = parseInt(relX);
if (tile === 0) {
this._display.draw(x, y, "'", "#fff"); // Floor
} else {
this._display.draw(x, y, "#", "#777"); // Wall
// Convert to screen coordinates (centered)
const screenX = centerX + relX;
const screenY = centerY + relY;
// Only draw if on screen
if (screenX >= 0 && screenY >= 0 && screenX < this._screenWidth && screenY < this._screenHeight) {
const tileType = this._visibleTiles[relY][relX];
// Draw tile based on type
if (tileType === 1) { // Wall
this._display.draw(screenX, screenY, '#', '#fff', '#000');
} else { // Floor
this._display.draw(screenX, screenY, '.', '#888', '#000');
}
}
}
}
// Draw special features
if (this._entrance) {
this._display.draw(this._entrance[0], this._entrance[1], "<", "#0f0");
}
if (this._exit) {
this._display.draw(this._exit[0], this._exit[1], ">", "#0f0");
}
// Always draw player at center of screen
this._display.draw(centerX, centerY, '@', '#ff0', '#000');
console.log("Drew player at center:", centerX, centerY);
if (this._treasure && (!this._player || !this._player.hasTreasure)) {
this._display.draw(this._treasure[0], this._treasure[1], "$", "#ff0");
}
// Draw other entities if visible (treasure, exit, entrance)
// ... additional drawing code for other entities ...
console.log("Game screen drawing complete");
},
_isVisible: function(x, y) {
// Check if a tile is currently visible
return this._visibleTiles[y] && this._visibleTiles[y][x] !== undefined;
},
// Add these functions to your client-side Game object (not the server-side one)
// Draw player
if (this._player) {
this._display.draw(this._player.x, this._player.y, "@", "#fff");
// Function to switch to ASCII mode
switchToAsciiMode: function() {
console.log("Switching to ASCII mode");
var asciiOptions = {
width: 25,
height: 19,
fontSize: 16,
fontFamily: "monospace",
bg: "#000",
fg: "#fff"
};
try {
// Create new display with ASCII mode
var newDisplay = new ROT.Display(asciiOptions);
// Replace the existing display
var container = document.querySelector('.rotdis');
if (container) {
container.innerHTML = '';
container.appendChild(newDisplay.getContainer());
this._display = newDisplay;
// Redraw current game state
if (this._gameActive) {
this._drawGameScreen();
} else {
this._drawWelcomeScreen();
}
} else {
console.error("Could not find .rotdis container");
}
} catch (err) {
console.error("Error switching to ASCII mode:", err);
}
// Draw monster
if (this._monster) {
this._display.draw(this._monster.x, this._monster.y, "~", "#f00");
}
// Draw status line
var statusY = this._mapHeight; // Position below map
this._display.drawText(0, statusY, "--------------------------------------------------------------------------------"); // Separator
statusY++;
var statusLine = `Use Arrows/WASD/HJKL. Find ($) and reach exit (>). Avoid (~).`;
if (this._player && this._player.hasTreasure) {
statusLine = "You have the treasure ($)! Reach the exit (>)!";
}
this._display.drawText(1, statusY, statusLine);
},
// Fix the welcome screen drawing
_drawWelcomeScreen: function() {
this.clearDisplay();
console.log("Drawing welcome screen...");
this._display.clear();
// Draw borders
for (var x = 0; x < this._display.getOptions().width; x++) {
for (var y = 0; y < this._display.getOptions().height; y++) {
if (x === 0 || y === 0 || x === this._display.getOptions().width-1 || y === this._display.getOptions().height-1) {
this._display.draw(x, y, "#", "#666", "#000");
}
}
}
// Draw welcome text
var centerX = Math.floor(this._display.getOptions().width / 2);
var centerY = Math.floor(this._display.getOptions().height / 2);
// For ASCII mode, we can use drawText with colors
var y = 5;
this._display.drawText(this._screenWidth / 2 - 5, y++, "%c{yellow}WOWGUE");
y++;
this._display.drawText(this._screenWidth / 2 - 11, y++, "Type enter to start!");
this._display.drawText(this._screenWidth / 2 - 11, y++, "Or type help...");
y+=2;
this._display.drawText(this._screenWidth / 2 - 10, y++, "@ - This is you");
this._display.drawText(this._screenWidth / 2 - 10, y++, "> - Escape the dungeon");
this._display.drawText(this._screenWidth / 2 - 10, y++, "~ - Avoid the monster");
this._display.drawText(this._screenWidth / 2 - 10, y++, "$ - Find the treasure");
this._display.drawText(this._screenWidth / 2 - 10, y++, "~ - Avoid the wonster");
this._display.drawText(this._screenWidth / 2 - 10, y++, "$ - Secure the bag");
y+=2;
// Display dynamic message (like waiting or error)
if (this._message) {
this._display.drawText(this._screenWidth / 2 - this._message.length / 2, y++, `%c{yellow}${this._message}`);
}
// Draw instructions
this._display.drawText(2, this._display.getOptions().height - 2, "Type enter in chat =");
},
_drawWinScreen: function(hasTreasure) {
@@ -223,5 +459,136 @@ var Game = {
getScreenHeight: function() {
return this._screenHeight;
},
// Add these functions to your Game object
// Check if we're in ASCII mode
isAsciiMode: function() {
if (!this._display) return false;
var options = this._display.getOptions();
return !options.layout || options.layout !== "tile";
},
// Switch to ASCII mode
switchToAsciiMode: function() {
console.log("Switching to ASCII mode");
var asciiOptions = {
width: 25,
height: 19,
fontSize: 16,
fontFamily: "monospace",
bg: "#000",
fg: "#fff"
};
try {
// Create new display with ASCII mode
var newDisplay = new ROT.Display(asciiOptions);
// Replace the existing display in DOM
var container = document.querySelector('.rotdis');
if (container) {
container.innerHTML = '';
container.appendChild(newDisplay.getContainer());
// Update the game's display reference
this._display = newDisplay;
console.log("Switched to ASCII mode successfully");
// Redraw current game state
if (this._gameActive) {
this._drawGameScreen();
} else {
this._drawWelcomeScreen();
}
return true;
} else {
console.error("Could not find .rotdis container to replace display");
return false;
}
} catch (err) {
console.error("Error switching to ASCII mode:", err);
return false;
}
},
// Switch to tile mode
switchToTileMode: function() {
console.log("Switching to tile mode");
try {
// Create new display with tile mode
var newDisplay = new ROT.Display(options);
// Replace the existing display in DOM
var container = document.querySelector('.rotdis');
if (container) {
container.innerHTML = '';
container.appendChild(newDisplay.getContainer());
// Update the game's display reference
this._display = newDisplay;
console.log("Switched to tile mode successfully");
// Redraw current game state
if (this._gameActive) {
this._drawGameScreen();
} else {
this._drawWelcomeScreen();
}
return true;
} else {
console.error("Could not find .rotdis container to replace display");
return false;
}
} catch (err) {
console.error("Error switching to tile mode:", err);
return false;
}
}
};
};
socket.on('game_start', function(data) {
console.log("🎮 Game start received with data:", data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
if (!data) {
$('#messages').append($('<li class="error">').text("Error: No game data received"));
return;
}
try {
// Make sure all game UI elements are visible
$('.container, .banner, .rotdis, .chat-container').css('display', '');
// Display what we received
$('#messages').append($('<li>').text(`Entering the dungeon...`));
// Start the game with received data
var success = Game.startGame(data);
console.log("Game start result:", success ? "SUCCESS" : "FAILED");
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed - trying ASCII mode"));
// Try switching to ASCII mode
if (document.getElementById('toggle-mode')) {
document.getElementById('toggle-mode').click();
setTimeout(function() {
Game.startGame(data);
}, 100);
}
}
window.focus();
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
}
});
+52 -6
View File
@@ -1,16 +1,62 @@
var tileSet = document.createElement("img");
var tileSetLoaded = false; // Flag to track loading status
var tileSetLoaded = false;
// More robust error handling
tileSet.onload = function() {
console.log("Tileset image loaded successfully.");
console.log("✅ Tileset image loaded successfully!");
tileSetLoaded = true;
// If Game.init was waiting, maybe trigger it now (handled in index.html)
// 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!");
// Handle error appropriately - maybe fallback to a different layout?
console.error("❌ Failed to load tileset image!");
alert("Tileset image failed to load. Switching to ASCII mode.");
// Force ASCII mode when tileset fails to load
if (typeof Game !== 'undefined' && Game._display) {
Game.switchToAsciiMode();
}
};
tileSet.src = "tiles.png"; // Set src AFTER setting onload/onerror
// Try different possible paths (to handle serving from different directories)
tileSet.src = "tiles.png"; // Try default path first
// Add fallback function if main path fails
setTimeout(function() {
if (!tileSetLoaded) {
console.warn("Tileset not loaded yet, trying alternate paths...");
tileSet.src = "./tiles.png"; // Try with explicit relative path
setTimeout(function() {
if (!tileSetLoaded) {
tileSet.src = "/tiles.png"; // Try from root
setTimeout(function() {
if (!tileSetLoaded) {
console.error("Failed to load tileset after trying multiple paths");
alert("Could not load tileset. Switching to ASCII mode.");
if (typeof Game !== 'undefined' && Game._display) {
Game.switchToAsciiMode();
}
}
}, 1000);
}
}, 1000);
}
}, 1000);
var options = {
layout: "tile",
+155 -129
View File
@@ -1,199 +1,225 @@
/* Basic body styling */
body {
background-color: #222;
color: #eee;
font-family: sans-serif;
margin: 0;
padding: 0;
display: flex; /* Use flexbox to center wrapper */
justify-content: center;
align-items: flex-start; /* Align wrapper to top */
min-height: 100vh;
/* Font declarations */
@font-face {
font-family: 'PixelOperator';
src: url('fonts/pixel-operator/PixelOperator.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
/* Wrapper grid container */
.wrapper {
@font-face {
font-family: 'PixelOperator';
src: url('fonts/pixel-operator/PixelOperator-Bold.ttf') format('truetype');
font-weight: bold;
font-style: normal;
}
/* Updated layout with 5-row structure */
.container {
display: grid;
/* Define grid areas */
grid-template-areas:
"header header header"
"status game game"
"chat game game"
"chat game game"
"chat input input"
"input input input"
"footer footer footer";
/* Define column sizes: status/chat column, game columns */
grid-template-columns: 250px 1fr 1fr; /* Left sidebar fixed width, game takes remaining space */
/* Define row sizes */
grid-template-rows: auto auto 1fr 1fr auto auto; /* Header, status, chat rows, input, footer */
gap: 10px; /* Spacing between grid items */
padding: 10px;
max-width: 1400px;
width: 95%;
border: 1px solid #444;
background-color: #333;
height: 95vh; /* Set height to fill most of viewport */
grid-template-columns: 350px 1fr 1fr;
grid-template-rows: auto auto 1fr auto auto;
height: 100vh;
width: 100%;
max-width: 100vw;
overflow: hidden;
gap: 1px;
background-color: #222;
}
/* Assign elements to grid areas */
.header {
grid-area: header;
background-color: #444;
padding: 5px;
}
.status {
grid-area: status;
background-color: #444;
padding: 10px;
/* Header area */
.banner {
grid-area: header;
background-color: #111;
padding: 5px;
color: white;
text-align: left;
border-bottom: 1px solid #444;
font-family: 'PixelOperator', monospace;
font-weight: bold;
font-size: 24px;
letter-spacing: 1px;
}
.chat {
grid-area: chat;
background-color: #444;
/* Status area for block height display */
.status {
grid-area: status;
background-color: #222;
padding: 10px;
color: #0f0;
font-family: monospace;
display: flex;
flex-direction: column;
overflow: hidden; /* Prevent overflow */
justify-content: flex-start;
border-right: 1px solid #444;
}
/* Firefox-specific fixes */
@-moz-document url-prefix() {
.rotdis {
display: block !important;
position: relative;
height: 608px;
min-width: 800px;
}
.rotdis canvas {
position: absolute !important;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: auto !important;
height: auto !important;
}
}
/* Make sure rotdis is properly visible across all browsers */
.rotdis {
grid-area: game;
background-color: #111;
display: flex;
background-color: #000;
border: 2px solid #444;
display: flex !important;
justify-content: center;
align-items: center;
min-height: 608px;
width: 100%;
position: relative;
overflow: hidden;
z-index: 5;
}
/* Make ROT.js canvas display properly */
.rotdis canvas {
display: block;
max-width: 100%;
max-height: 100%;
object-fit: contain; /* Preserve aspect ratio */
display: block !important;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
}
#messageInput {
grid-area: input;
background-color: #444;
padding: 10px;
/* Add debug overlay to see container bounds */
.rotdis::before {
content: "GAME AREA";
position: absolute;
top: 5px;
left: 5px;
color: rgba(255,255,255,0.3);
font-family: monospace;
font-size: 12px;
z-index: 1;
pointer-events: none;
}
.footer {
grid-area: footer;
background-color: #444;
padding: 5px;
text-align: center;
/* Chat messages area */
.chat-container {
grid-area: chat;
background-color: #222;
display: flex;
flex-direction: column;
border-right: 1px solid #444;
overflow: hidden; /* Prevents chat from causing scrolling */
}
/* Header styling */
.header h1 {
margin: 0;
padding: 5px;
text-align: center;
}
/* Status styling */
#blockheight {
color: lightblue;
}
/* Chat box styling */
#messageBox {
flex-grow: 1; /* Allow message box to fill chat area */
border: 1px solid #555;
background-color: #2a2a2a;
overflow-y: auto; /* Enable vertical scrolling */
height: 100%; /* Fill available height */
margin-bottom: 5px;
flex-grow: 1;
overflow-y: auto;
padding: 10px;
height: 100%;
max-height: 100%;
}
#messages {
list-style-type: none;
margin: 0;
padding: 5px;
padding: 0;
color: #fff; /* Default text color */;
}
#messages li {
padding: 2px 0;
border-bottom: 1px dashed #444;
font-size: 0.9em;
padding: 3px 5px;
margin-bottom: 2px;
word-break: break-word;
}
/* Chat input area, now in its own row */
.input-container {
grid-area: input;
background-color: #333;
padding: 10px;
border-top: 1px solid #444;
border-bottom: 1px solid #444;
}
/* Chat input styling */
#chatForm {
display: flex;
width: 100%;
}
#chatInput {
flex-grow: 1;
padding: 5px;
border: 1px solid #555;
background-color: #eee;
color: #111;
padding: 8px;
border: none;
background-color: #444;
color: #fff;
border-radius: 3px 0 0 3px;
}
#chatForm button {
padding: 5px 10px;
border: 1px solid #555;
background-color: #666;
color: #eee;
padding: 0 20px;
background-color: #555;
color: white;
border: none;
cursor: pointer;
border-radius: 0 3px 3px 0;
}
#chatForm button:hover {
background-color: #777;
}
/* Teletype styling */
/* Add this to your stylesheet to use the font in other places */
#staticbanner {
margin: 0;
padding: 5px;
text-align: left;
font-size: 24px;
color: #fff;
}
#banner {
display: inline-block;
min-height: 1em;
}
#bannercursor {
display: inline-block;
color: #fff;
}
/* Add these styles for chat messages */
#messages li.self-command {
color: #aaf;
font-style: italic;
}
#messages li.payment {
color: #ff0;
font-family: 'PixelOperator', monospace;
font-weight: bold;
border-left: 3px solid #ff0;
padding-left: 5px;
margin: 5px 0;
letter-spacing: 1px;
padding: 10px;
}
#messages li.win {
.footer {
grid-area: footer;
background-color: #111;
color: #888;
padding: 5px;
text-align: center;
font-size: 0.8em;
font-family: 'PixelOperator', sans-serif;
}
/* Game messages styling */
li.game-start {
color: #0f0;
font-weight: bold;
text-align: center;
margin: 5px 0;
padding: 2px 0;
background: rgba(0,255,0,0.1);
}
#messages li.lose {
li.error {
color: #f00;
font-weight: bold;
font-family: monospace;
padding: 2px 5px;
background: rgba(255,0,0,0.1);
border-left: 2px solid #f00;
}
#messages li.game-start {
li.debug {
color: #0ff;
font-weight: bold;
margin: 5px 0;
font-family: monospace;
font-size: 0.9em;
padding-left: 10px;
border-left: 2px solid #0ff;
}
+310 -192
View File
@@ -4,73 +4,131 @@ 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.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
var users = [];
var players = [];
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');
});
// Function to get a user by socket ID
function getUserBySocket(socketId) {
return users.find(user => user.socketid === socketId);
}
// Modified block check function
setInterval(function() {
rpc.daemonCall("get_block_count", "", function(result) {
const currentHeight = result.result.count;
rpc.lastBlock.setHeight(currentHeight);
// 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);
// If new block found
if (currentHeight > lastBlockHeight) {
console.log(`New block found: ${currentHeight}`);
io.emit('blockheight', currentHeight);
// 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);
// Start games for waiting players
startGamesForWaiting(currentHeight);
// Check active games for timeout
checkGamesTimeout(currentHeight);
lastBlockHeight = currentHeight;
} else {
io.emit('blockheight', currentHeight);
}
});
}, 10000);
// 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) {
while (WAITING_PLAYERS.length > 0) {
const userId = WAITING_PLAYERS.shift();
const user = getUserBySocket(userId);
if (user) {
user.blockRec = blockHeight;
const game = user.startGame(80, 40); // Create a new game
activeGames.set(userId, game);
io.to(userId).emit('game_start', {
map: game.dungeon.map,
player: game.player,
monster: game.monster,
entrance: game.dungeon.entrance,
exit: game.dungeon.exit,
treasure: game.dungeon.treasure,
blockHeight: 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;
const clientId = playerEntry.clientId;
console.log(`Processing player: server=${serverId}, client=${clientId}`);
// Get the user object
const currentUser = getUserBySocket(serverId);
if (currentUser) {
currentUser.blockRec = blockHeight;
console.log(`Creating new game for user ${serverId}`);
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}`);
console.log(`Player position: (${game.player.x}, ${game.player.y})`);
// Send a single game start message to chat
io.to(serverId).emit('message', `Entering the dungeon at position (0,0)...`);
// Then send the actual game state
io.to(serverId).emit('game_start', gameState);
console.log(`Game started for player ${serverId}`);
} catch (error) {
console.error(`Error creating game:`, error);
}
} else {
console.error(`User not found for socket ${serverId}`);
}
}
}
}
// Check if any active games have timed out
@@ -98,33 +156,66 @@ io.on('connection', function(socket) {
console.log(socket.client.id);
console.log(socket.handshake.address);
db.insertVisitor(socket.client.id, socket.handshake.address, 0);
users.push(new user.User(socket.client.id, 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})`);
}
io.to(`${socket.client.id}`).emit('welcome', 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(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.');
}
else if (msg.toLowerCase() == 'enter') {
const currentUser = getUserBySocket(socket.client.id);
if (currentUser) {
// FOR TESTING: Skip payment and directly queue the player
currentUser.verifyPayment(10); // Minimal test fee
WAITING_PLAYERS.push(socket.client.id);
// Update the 'enter' command handler to NOT start games immediately
if (msg.toLowerCase() == 'enter') {
console.log(`Player ${socket.id} requested to enter the dungeon`);
io.to(`${socket.client.id}`).emit('message',
'For testing: You will enter the dungeon when the next block is found.');
// To test without waiting for a block, immediately start a game
startGameForPlayer(socket.client.id, lastBlockHeight || 1);
}
// 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
});
// Tell player they're in queue
io.to(socket.id).emit('message', 'Requesting to enter the dungeon...');
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
});
// Log the queue state
console.log(`Added player to queue. Current queue: ${WAITING_PLAYERS.length} players`);
// NO LONGER start game immediately - wait for next block
// startGamesForWaiting(lastBlockHeight);
}
} else {
io.to(socket.id).emit('message', 'Error: Could not add you to the game queue. Please try again.');
}
}
else {
io.emit('message', msg);
@@ -133,153 +224,180 @@ io.on('connection', function(socket) {
// Handle player movement
socket.on('move', function(direction) {
const game = activeGames.get(socket.client.id);
console.log(`Player ${socket.id} moved: ${direction}`);
// Find the active game
let game = activeGames.get(socket.id);
if (!game) {
// Try fallback methods to find the game
console.log(`Game not found for ${socket.id}, trying client.id: ${socket.client.id}`);
game = activeGames.get(socket.client.id);
}
if (!game) {
const mappedId = clientSocketMap.get(socket.id);
if (mappedId) {
console.log(`Game not found, trying mapped ID: ${mappedId}`);
game = activeGames.get(mappedId);
}
}
if (game) {
let dx = 0, dy = 0;
// Convert direction to dx,dy
switch(direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
const result = game.movePlayer(dx, dy);
// Send updated game state
socket.emit('game_update', {
player: game.player,
monster: game.monster,
status: result.status
});
// Handle win/lose conditions
if (result.status === 'won') {
const user = getUserBySocket(socket.client.id);
const reward = user.calculateReward();
let dx = 0, dy = 0;
socket.emit('game_over', {
status: 'won',
hasTreasure: game.player.hasTreasure,
reward: reward
});
// Convert direction to dx,dy
switch(direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
// TODO: Process payout
// Server still uses absolute coordinates internally
console.log(`Moving player ${dx},${dy} from (${game.player.x},${game.player.y})`);
const result = game.movePlayer(dx, dy);
console.log(`Move result: ${result.status}`);
activeGames.delete(socket.client.id);
}
else if (result.status === 'lost') {
socket.emit('game_over', {
status: 'lost',
reason: result.reason
});
activeGames.delete(socket.client.id);
}
if (result.status === 'won') {
io.to(socket.id).emit('game_over', {
status: 'won',
hasTreasure: game.player.hasTreasure
});
} else if (result.status === 'lost') {
io.to(socket.id).emit('game_over', {
status: 'lost',
reason: result.reason
});
} else {
// Get updated game state (now with relative coordinates)
const updatedState = game.getState();
// Send updated game state back to client
console.log(`Sending updated game state to ${socket.id}`);
io.to(socket.id).emit('game_update', updatedState);
}
} else {
console.log(`No active game found for player ${socket.id}`);
}
});
// Handle disconnection
socket.on('disconnect', function () {
console.log('A user disconnected');
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)`);
// Clean up any active games
if (activeGames.has(socket.client.id)) {
activeGames.delete(socket.client.id);
// 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;
}
users = spliceOut(socket.client.id, "socketid", users);
});
});
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');
});
// Remove objects from an array if one attribute matches
function spliceOut(target, field, array) {
for (var i = array.length - 1; i >= 0; --i) {
if (array[i][field] == target) {
array.splice(i,1);
}
}
return array;
}
// Check if objects are in an array if one attribute matches
function checkArray(target, field, array) {
for (var i = array.length - 1; i >= 0; --i) {
if (array[i][field] == target) {
return true;
}
}
return false;
}
// Add this function to generate a dungeon
function generateDungeon(width, height) {
// Create a new instance of ROT.Map.Digger
const digger = new ROT.Map.Digger(width, height, {
roomWidth: [3, 9],
roomHeight: [3, 5],
corridorLength: [2, 5],
dugPercentage: 0.2
});
// Create a map representation
const map = Array(height).fill().map(() => Array(width).fill(1)); // 1 = wall
// Callback to dig out rooms and corridors
digger.create((x, y, value) => {
map[y][x] = value; // 0 = floor, 1 = wall
});
// Get rooms and place special features
const rooms = digger.getRooms();
let entrance = null;
let exit = null;
let treasure = null;
if (rooms.length > 0) {
// Place entrance in the first room
entrance = rooms[0].getCenter();
// Place exit in the last room
exit = rooms[rooms.length-1].getCenter();
// Place treasure in a middle room
const treasureRoom = rooms[Math.floor(rooms.length/2)];
treasure = treasureRoom.getCenter();
}
return {
map: map,
entrance: entrance,
exit: exit,
treasure: treasure,
rooms: rooms
};
}
// Add this helper function to start a game for a specific player immediately:
// Make sure this function is defined in your file
function startGameForPlayer(socketId, blockHeight) {
const user = getUserBySocket(socketId);
console.log(`Starting game for player ${socketId} at block height ${blockHeight}`);
if (user) {
user.blockRec = blockHeight;
const game = user.startGame(25, 19); // Create a new game with dimensions matching our display
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);
io.to(socketId).emit('game_start', {
map: game.dungeon.map,
player: game.player,
// 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,
entrance: game.dungeon.entrance,
exit: game.dungeon.exit,
treasure: game.dungeon.treasure,
blockHeight: blockHeight
});
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)}...`);
}
+41 -22
View File
@@ -1,3 +1,8 @@
const Game = require('./game'); // Add this import at the top
// Global users registry
const userRegistry = new Map();
function User(socketid, ip) {
this.id = null;
this.socketid = socketid;
@@ -12,34 +17,48 @@ function User(socketid, ip) {
this.game = null; // Reference to current game
this.paymentAmount = 0;
this.paymentVerified = false;
// Auto-register in the registry
userRegistry.set(socketid, this);
console.log(`User registered with socket ID: ${socketid}`);
}
User.prototype.startGame = function(mapWidth, mapHeight) {
const Game = require('./game');
this.game = new Game(this.socketid, mapWidth, mapHeight);
return this.game;
};
// Add lookup function
function getUserBySocketId(socketId) {
const user = userRegistry.get(socketId);
console.log(`Looking up user ${socketId}: ${user ? "FOUND" : "NOT FOUND"}`);
return user;
}
User.prototype.verifyPayment = function(amount) {
this.paymentAmount = amount;
this.paymentVerified = true;
return true;
};
// Add function to remove user
function removeUser(socketId) {
const removed = userRegistry.delete(socketId);
console.log(`User ${socketId} removed: ${removed ? "YES" : "NO"}`);
return removed;
}
User.prototype.calculateReward = function() {
if (!this.game || this.game.gameState !== 'won') return 0;
// Base reward is 2x the entrance fee
let reward = this.paymentAmount * 2;
// Bonus for finding treasure
if (this.game.player.hasTreasure) {
reward = this.paymentAmount * 5;
// Get all users as array
function getAllUsers() {
return Array.from(userRegistry.values());
}
// Add this method to your User function/class
User.prototype.startGame = function(width, height) {
console.log(`Creating new game for user ${this.socketid} with dimensions ${width}x${height}`);
try {
this.game = new Game(this.socketid, width, height);
return this.game;
} catch (err) {
console.error("Error creating game:", err);
return null;
}
return reward;
};
// Rest of your existing code...
module.exports = {
User: User
User: User,
getUserBySocketId: getUserBySocketId,
removeUser: removeUser,
getAllUsers: getAllUsers
};