fix transparency

This commit is contained in:
jwinterm
2025-05-17 06:42:50 -04:00
parent 52906cd893
commit 5c6342db6e
7 changed files with 837 additions and 1069 deletions
+3 -2
View File
@@ -4,14 +4,15 @@ A blockchain-based roguelike dungeon crawler built on the Wownero network.
## Project Overview
Wowngeon is a web-based roguelike game that integrates with the Wownero blockchain for a unique gaming experience. Players enter a randomly generated dungeon with the goal of escaping before the next block is discovered on the network.
Wowngeon is a web-based roguelike game that integrates with the Wownero blockchain for a unique gaming experience. Players enter a randomly generated dungeon with the goal of escaping before the next block is discovered on the network. The player requests to enter (and optionally pay an entrance fee), and then waits for the next block to be mined. When the next block is found, the player is placed in the dungeon and must escape before the next block is found. If they fail to escape, they die and lose their entrance fee. There is a singular monster and treasure in the dungeon. If the monster catches the player, they die. If the player finds the treasure and manages to escape, they could win 3x or 5x instead of just 2x for escaping sans treasure. The game is designed to be played in a web browser, making it accessible to a wide audience.
The game is built using Node.js for the backend, with Express and Socket.io for real-time communication. The frontend is developed using HTML5, CSS, and JavaScript, with ROT.js for rendering the dungeon and managing game mechanics. The Wownero blockchain is integrated via RPC calls to monitor block height and handle transactions.
### Key Features:
- **Blockchain-linked gameplay**: Players enter when one block is found and must escape before the next block (approximately 5 minutes in Wownero)
- **Permadeath mechanics**: If a player fails to escape before the next block is found, they die
- **Random dungeons**: Each game features procedurally generated maps using ROT.js
- **Monster AI**: Evade monsters as you navigate the dungeon
- **Monster AI**: Evade monster as you navigate the dungeon
- **Treasure hunting**: Find treasure for bonus rewards
- **Wownero integration**: Players pay an entrance fee (10-100 WOW) and can win rewards
+50 -96
View File
@@ -115,51 +115,56 @@ class Game {
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];
this.fov.compute(this.player.x, this.player.y, 10, (x, y, r, visibility) => { // Added r and visibility params
if (visibility > 0) { // Only add if tile is actually visible
if (!this.visibleTiles[y]) {
this.visibleTiles[y] = {};
}
// Store the actual tile type (0 for floor, 1 for wall) from the dungeon map
if (this.dungeon && this.dungeon.map && this.dungeon.map[y] && this.dungeon.map[y][x] !== undefined) {
this.visibleTiles[y][x] = this.dungeon.map[y][x];
} else {
// Should not happen if FOV is within map bounds, but as a fallback:
// this.visibleTiles[y][x] = 1; // Default to wall if outside known map
}
}
});
// console.log("FOV updated. Player:", this.player.x, this.player.y, "Visible tiles count:", Object.keys(this.visibleTiles).reduce((acc, k) => acc + Object.keys(this.visibleTiles[k]).length, 0));
}
movePlayer(dx, dy) {
const newX = this.player.x + dx;
const newY = this.player.y + dy;
// Check if the move is valid (not into a wall)
if (this.dungeon.map[newY] && this.dungeon.map[newY][newX] === 0) {
// Check if the move is valid (not into a wall and within map bounds)
if (this.dungeon &&
this.dungeon.map[newY] &&
this.dungeon.map[newY][newX] !== undefined &&
this.dungeon.map[newY][newX] === 0) { // 0 is floor
this.player.x = newX;
this.player.y = newY;
console.log(`Player moved to ${newX},${newY} in game for socket ${this.socketId}`);
// Update FOV after movement
// Update FOV after moving
this.updateFOV();
// Check if player found treasure
if (this.dungeon.treasure &&
newX === this.dungeon.treasure[0] &&
newY === this.dungeon.treasure[1]) {
// Check for game events like finding treasure or exit
if (this.dungeon.exit && newX === this.dungeon.exit[0] && newY === this.dungeon.exit[1]) {
// Handle win condition (e.g., escaped)
this.gameState = 'won'; // Or some other state
return { status: 'moved', event: 'escaped', player: this.player, visibleTiles: this.visibleTiles };
}
if (this.dungeon.treasure && newX === this.dungeon.treasure[0] && newY === this.dungeon.treasure[1] && !this.player.hasTreasure) {
this.player.hasTreasure = true;
// Remove treasure from map or mark as collected
return { status: 'moved', event: 'treasure_found', player: this.player, visibleTiles: this.visibleTiles };
}
// Check if player reached exit
if (this.dungeon.exit &&
newX === this.dungeon.exit[0] &&
newY === this.dungeon.exit[1]) {
this.gameState = 'won';
return { status: 'won', hasTreasure: this.player.hasTreasure };
}
// Move monster after player moves
this.moveMonster();
// Check if monster caught player
if (checkMonsterKill(this.player, this.monster)) {
this.gameState = 'lost';
return { status: 'lost', reason: 'caught' };
}
return { status: 'moved' };
return { status: 'moved', player: this.player, visibleTiles: this.visibleTiles };
}
console.log(`Player move to ${newX},${newY} for socket ${this.socketId} is invalid (wall or out of bounds).`);
return { status: 'invalid' };
}
@@ -202,77 +207,26 @@ class Game {
// Update the getState() method to include all entities with relative positions
getState() {
// Calculate relative position of all entities from player's perspective
const playerX = this.player.x;
const playerY = this.player.y;
// const playerX = this.player.x; // Not needed if sending absolute visibleTiles
// const playerY = this.player.y; // Not needed if sending absolute visibleTiles
// Convert all visible tiles to relative coordinates
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;
// Create row if needed
if (!relativeVisibleTiles[relY]) {
relativeVisibleTiles[relY] = {};
}
// Store tile type at relative position
relativeVisibleTiles[relY][relX] = this.visibleTiles[y][x];
}
}
// Create the state object
const state = {
// The client-side Game.js expects absolute coordinates for visibleTiles.
// So, no conversion to relative coordinates is needed here for visibleTiles.
// Just ensure this.visibleTiles is correctly populated by updateFOV.
// console.log("getState called. Player:", this.player, "Visible tiles keys:", Object.keys(this.visibleTiles || {}).length);
return {
gameState: this.gameState,
player: {
x: 0, // Player is always at (0,0) in relative coordinates
y: 0,
hasKey: this.player.hasKey || false,
hasTreasure: this.player.hasTreasure || false
},
visibleTiles: relativeVisibleTiles
player: { ...this.player }, // Send a copy of player object
monster: this.monster ? { ...this.monster } : null, // Send a copy if monster exists
// items: { ...this.items }, // If you have items, send them too
visibleTiles: { ...this.visibleTiles }, // Send a copy of visible tiles
// You might also want to send entrance/exit if they are always known or become known
// entrance: this.dungeon ? this.dungeon.entrance : null,
// exit: this.dungeon ? this.dungeon.exit : null,
// treasure: this.dungeon ? this.dungeon.treasure : null
};
// Add monster if in view
if (this.monster && this.visibleTiles[this.monster.y] &&
this.visibleTiles[this.monster.y][this.monster.x] !== undefined) {
state.monster = {
x: this.monster.x - playerX, // Relative position
y: this.monster.y - playerY
};
}
// Add entrance if defined
if (this.dungeon && this.dungeon.entrance) {
// Use array format for consistency
state.entrance = [
this.dungeon.entrance.x - playerX,
this.dungeon.entrance.y - playerY
];
}
// Add exit if in view
if (this.dungeon && this.dungeon.exit &&
this.visibleTiles[this.dungeon.exit.y] &&
this.visibleTiles[this.dungeon.exit.y][this.dungeon.exit.x] !== undefined) {
state.exit = [
this.dungeon.exit.x - playerX,
this.dungeon.exit.y - playerY
];
}
// Add treasure if in view
if (this.dungeon && this.dungeon.treasure &&
this.visibleTiles[this.dungeon.treasure.y] &&
this.visibleTiles[this.dungeon.treasure.y][this.dungeon.treasure.x] !== undefined) {
state.treasure = [
this.dungeon.treasure.x - playerX,
this.dungeon.treasure.y - playerY
];
}
return state;
}
}
+126 -135
View File
@@ -18,6 +18,7 @@
<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 -->
<script src="js/game.js"></script>
</head>
<body>
@@ -36,7 +37,7 @@
</div>
<!-- Game display -->
<div class="rotdis"></div>
<div id="game-display" class="rotdis"></div>
<!-- Chat messages area -->
<div class="chat-container">
@@ -59,89 +60,6 @@
</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() {
@@ -152,10 +70,10 @@
pause: 1500
});
$('#bannercursor').blink({
text: ['|', ' '],
delay: 500
});
// $(\'#bannercursor\').blink({ // <<< COMMENT OUT THIS BLOCK
// text: [\'|\', \' \'],
// delay: 500
// });
// After animations are complete, set static text
setTimeout(function() {
@@ -202,34 +120,36 @@
console.log("🎮 Game start received with data:", data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
// Stop waiting screen animation if it's running
if (typeof Game !== 'undefined') {
Game.stopWaitingScreen();
}
if (!data) {
$('#messages').append($('<li class="error">').text("Error: No game data received"));
$('#messages').append($('<li class="error">').text("Error: No game data received from server."));
if (typeof Game !== 'undefined') Game._drawWelcomeScreen();
return;
}
try {
// Start the game with received data
var success = Game.startGame(data);
// Pass player, map, monsters, items, and initialVisibleTiles if available
var success = Game.startGame(
data.player,
data.map,
data.monsters,
data.items,
data.visibleTiles // Pass initial visible tiles from server
);
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);
}
$('#messages').append($('<li class="error">').text("Game start failed. Check console for details."));
if (typeof Game !== 'undefined') Game._drawWelcomeScreen();
} else {
// Shift focus to the game display area after successful game start
$('#game-display').focus();
updateFocusIndicator();
}
window.focus();
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
@@ -238,19 +158,24 @@
// Game update handler
socket.on('game_update', function(data) {
console.log("🎮 GAME UPDATE received:", data);
// console.log("🎮 GAME UPDATE received:", data); // Reduced verbosity
if (typeof Game !== 'undefined') {
// Update all entity positions
Game._player = data.player;
Game._visibleTiles = data.visibleTiles;
Game._monster = data.monster;
Game._entrance = data.entrance;
Game._exit = data.exit;
Game._treasure = data.treasure;
if (typeof Game !== 'undefined' && Game._gameActive) { // Ensure game is active
if (data.player) Game._player = data.player;
if (data.visibleTiles) {
Game._visibleTiles = data.visibleTiles;
Game._updateExploredTiles(); // Update explored based on new visibility
}
if (data.monster) Game._monster = data.monster; // Update monster data
if (data.items) Game._items = data.items; // Update items data
// Potentially update _entrance, _exit, _treasure if they are part of game_update
// Game._entrance = data.entrance;
// Game._exit = data.exit;
// Game._treasure = data.treasure;
// Draw updated screen
Game._drawGameScreen();
} else {
// console.log("Game not active or Game undefined, skipping game_update draw.");
}
});
@@ -268,6 +193,7 @@
$('#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");
@@ -275,11 +201,85 @@
if (typeof Game !== 'undefined') {
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._gameActive : "Game undefined");
if (document.activeElement === $('#game-display')[0]) {
// console.log("Game display has focus.");
if (Game && 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._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 -->
@@ -373,34 +373,25 @@ $(function() {
<script>
document.getElementById('toggle-mode').addEventListener('click', function() {
try {
var button = document.getElementById('toggle-mode');
// 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("Game.switchToTileMode is not defined");
this.textContent = "Error: Tile Mode Not Available";
this.style.background = "#990000";
}
} else {
if (Game && Game._isTileMode) { // If currently in Tile Mode
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";
}
button.textContent = 'Switch to Tile Mode';
} else { console.error("Game.switchToAsciiMode is not defined"); }
} else if (Game) { // If currently in ASCII mode (or Game object exists and not in tile mode)
console.log("Switching to TILE mode...");
if (typeof Game.switchToTileMode === 'function') {
Game.switchToTileMode();
button.textContent = 'Switch to ASCII Mode';
} else { console.error("Game.switchToTileMode is not defined"); }
} else {
console.error("Game object not available to toggle mode.");
}
} catch (err) {
console.error("Error switching display mode:", err);
} catch (e) {
console.error("Error switching display mode:", e);
}
});
</script>
+611 -748
View File
File diff suppressed because it is too large Load Diff
+12 -10
View File
@@ -23,12 +23,13 @@ tileSet.onload = function() {
tileSet.onerror = function() {
console.error("❌ Failed to load tileset image!");
alert("Tileset image failed to load. Switching to ASCII mode.");
alert("Tileset image failed to load. Game will attempt to use ASCII mode.");
window.globalTileSetStatus = "error"; // Set a status for Game.js to check if needed
// Force ASCII mode when tileset fails to load
if (typeof Game !== 'undefined' && Game._display) {
Game.switchToAsciiMode();
}
// REMOVED: if (typeof Game !== 'undefined' && Game._display) {
// REMOVED: Game.switchToAsciiMode();
// REMOVED: }
};
// Try different possible paths (to handle serving from different directories)
@@ -45,12 +46,13 @@ setTimeout(function() {
tileSet.src = "/tiles.png"; // Try from root
setTimeout(function() {
if (!tileSetLoaded) {
if (!tileSetLoaded && window.globalTileSetStatus !== "error") { // Check if error already handled
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();
}
alert("Could not load tileset. Game will attempt to use ASCII mode.");
window.globalTileSetStatus = "error"; // Set a status
// REMOVED: if (typeof Game !== 'undefined' && Game._display) {
// REMOVED: Game.switchToAsciiMode();
// REMOVED: }
}
}, 1000);
}
@@ -60,7 +62,7 @@ setTimeout(function() {
var options = {
layout: "tile",
bg: "#000", // Black background instead of transparent for better visibility
bg: "transparent", // Black background instead of transparent for better visibility
// Adjusted dimensions for ~800x600 display
width: 25, // 25 tiles * 32px/tile = 800px
height: 19, // 19 tiles * 32px/tile = 608px
+8 -25
View File
@@ -59,39 +59,19 @@
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: #000;
border: 2px solid #444;
display: flex !important;
display: flex !important; /* Using flex to center canvas */
justify-content: center;
align-items: center;
min-height: 608px;
width: 100%;
/* min-height: 608px; */ /* REMOVED to allow shrinking */
width: 100%; /* Ensure it takes full width of its grid area */
height: 100%; /* Ensure it takes full height of its grid area */
position: relative;
overflow: hidden;
overflow: hidden; /* Important to clip canvas if it somehow overflows */
z-index: 5;
}
@@ -100,6 +80,9 @@
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
max-width: 100%; /* ADDED: Scale down to fit container width */
max-height: 100%; /* ADDED: Scale down to fit container height */
object-fit: contain; /* ADDED: Maintain aspect ratio when scaling */
}
/* Add debug overlay to see container bounds */
+27 -53
View File
@@ -231,62 +231,36 @@ io.on('connection', function(socket) {
});
// Handle player movement
socket.on('move', function(direction) {
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;
}
// 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}`);
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
});
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') {
// Optional: Monster moves after player
// game.moveMonster();
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 {
// 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);
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(`No active game found for player ${socket.id}`);
console.log(`Player move event from ${socket.id} ignored: No active game found for user.`);
}
});