mostly working basics wow

This commit is contained in:
jwinterm
2025-05-23 20:43:46 -04:00
parent 8c0117ea88
commit 928af8b071
15 changed files with 1345 additions and 1224 deletions
+117
View File
@@ -0,0 +1,117 @@
# Wowngeon Code Architecture
## Overview
The Wowngeon roguelike game has been refactored into a modular architecture with clear separation of concerns between frontend and backend components.
## Backend Architecture
### Core Classes
#### `Player` (/player.js)
- Manages player state and position
- Methods: `moveTo()`, `isAt()`, `getState()`, `setState()`, `reset()`
- Properties: x, y, hasKey, hasTreasure
#### `Monster` (/monster.js)
- Manages monster state and AI behavior
- Methods: `moveTowardPlayer()`, `hasCaughtPlayer()`, `getState()`, `setState()`
- AI Features: Smart pathfinding toward player, collision detection
#### `DungeonGenerator` (/dungeon.js)
- Handles procedural dungeon generation using ROT.js
- Methods: `generate()`, `getRandomRoomCenter()`, `isFloorTile()`, `isWallTile()`
- Features: Room placement, entrance/exit/treasure positioning
#### `Game` (/game.js)
- Main game logic coordinator
- Uses Player, Monster, and DungeonGenerator classes
- Manages game state, FOV calculation, and entity interactions
- Methods: `movePlayer()`, `moveMonster()`, `getState()`, `updateFOV()`
### Supporting Files
- `user.js` - User management and game instantiation
- `index.js` - Server entry point and socket handling
- `dbcalls.js` - Database operations
- `rpccalls.js` - Wownero blockchain integration
## Frontend Architecture
### Modular JavaScript Components
#### `ui.js`
- UI utilities and visual helpers
- Functions: `scrollChat()`, `updateBlockHeight()`, `updateFocusIndicator()`
- Manages teletype animations and browser detection
#### `socketHandlers.js`
- All WebSocket event handlers
- Events: connect, game_start, game_update, game_over, etc.
- Clean separation of network communication logic
#### `inputHandler.js`
- Keyboard and form input management
- Handles WASD/arrow key movement, chat input, focus management
- Event delegation for game controls
#### `game.js` (frontend)
- Core game rendering and client-side state management
- ROT.js display integration, FOV rendering, entity drawing
- Maintained original structure for compatibility
### HTML Structure
- `index.html` - Streamlined main page that loads modular components
- `index_original.html` - Backup of original monolithic structure
## Key Improvements
### Backend Benefits
1. **Modularity**: Clear separation of Player, Monster, and Dungeon logic
2. **Maintainability**: Each class has single responsibility
3. **Testability**: Individual components can be unit tested
4. **Extensibility**: Easy to add new entity types or game mechanics
### Frontend Benefits
1. **Reduced Complexity**: 471-line index.html reduced to ~80 lines
2. **Code Organization**: Related functionality grouped into logical modules
3. **Reusability**: UI and input handlers can be reused/extended
4. **Debugging**: Easier to locate and fix issues in specific modules
## File Structure
```
/wowngeon/
├── Backend Classes
│ ├── player.js # Player entity and state
│ ├── monster.js # Monster AI and behavior
│ ├── dungeon.js # Procedural generation
│ └── game.js # Main game coordinator
├── Backend Support
│ ├── index.js # Server and socket handling
│ ├── user.js # User management
│ └── ...other files
└── Frontend
├── index.html # Clean modular HTML
└── js/
├── game.js # Core game rendering
├── ui.js # UI utilities
├── socketHandlers.js # Network communication
├── inputHandler.js # Input management
└── ...libraries
```
## Migration Summary
- ✅ Backend modularized into Player, Monster, DungeonGenerator classes
- ✅ Frontend split into logical modules (UI, Socket, Input handlers)
- ✅ Removed unused files (game_refactored.js, displayManager.js, etc.)
- ✅ Maintained full game functionality and compatibility
- ✅ All game mechanics verified working (movement, FOV, collision, treasure)
## Testing Verification
- [x] Server starts without errors
- [x] Game initialization works
- [x] Player movement functional
- [x] Monster AI and collision detection operational
- [x] Treasure collection working
- [x] Frontend modules load correctly
- [x] All socket events properly handled
The refactored codebase is now much more maintainable and follows modern software engineering practices while preserving all original game functionality.
+92
View File
@@ -0,0 +1,92 @@
const ROT = require('./rot.js');
/**
* Dungeon generation utilities
*/
class DungeonGenerator {
static generate(width, height) {
// Create dungeon using ROT.js Map.Digger
const digger = new ROT.Map.Digger(width, height, {
roomWidth: [3, 9],
roomHeight: [3, 7],
corridorLength: [2, 6],
dugPercentage: 0.2
});
// Initialize the empty map with walls (1)
const map = Array(height).fill().map(() => Array(width).fill(1));
// Fill in floors (0) using digger's callback
digger.create((x, y, value) => {
// value: 0 = floor, 1 = wall
map[y][x] = value;
});
// Get rooms that were created
const rooms = digger.getRooms();
// Place entrance in the first room
const entranceRoom = rooms[0];
const entranceCenter = entranceRoom.getCenter();
const entrance = [entranceCenter[0], entranceCenter[1]];
// Place exit in the last room
const exitRoom = rooms[rooms.length - 1];
const exitCenter = exitRoom.getCenter();
const exit = [exitCenter[0], exitCenter[1]];
// Place treasure in a middle room (not first or last)
let treasureRoom;
if (rooms.length > 2) {
treasureRoom = rooms[Math.floor(rooms.length / 2)];
} else {
// Fallback if we have fewer than 3 rooms
treasureRoom = rooms[0];
}
const treasureCenter = treasureRoom.getCenter();
// Place treasure slightly off center to make it more interesting
const treasure = [
treasureCenter[0] + Math.floor(Math.random() * 3) - 1,
treasureCenter[1] + Math.floor(Math.random() * 3) - 1
];
// Ensure the treasure is on a floor tile
if (map[treasure[1]][treasure[0]] !== 0) {
treasure[0] = treasureCenter[0];
treasure[1] = treasureCenter[1];
}
return {
map: map,
rooms: rooms,
entrance: entrance,
exit: exit,
treasure: treasure
};
}
static getRandomRoomCenter(rooms, excludeFirst = false, excludeLast = false) {
if (!rooms || rooms.length === 0) return null;
let availableRooms = [...rooms];
if (excludeFirst && availableRooms.length > 1) {
availableRooms = availableRooms.slice(1);
}
if (excludeLast && availableRooms.length > 1) {
availableRooms = availableRooms.slice(0, -1);
}
const randomRoom = availableRooms[Math.floor(Math.random() * availableRooms.length)];
return randomRoom.getCenter();
}
static isFloorTile(map, x, y) {
return map[y] && map[y][x] === 0;
}
static isWallTile(map, x, y) {
return map[y] && map[y][x] === 1;
}
}
module.exports = DungeonGenerator;
+24 -139
View File
@@ -1,66 +1,7 @@
const ROT = require('./rot.js');
// Define the missing generateDungeon function
function generateDungeon(width, height) {
// Create dungeon using ROT.js Map.Digger
const digger = new ROT.Map.Digger(width, height, {
roomWidth: [3, 9],
roomHeight: [3, 7],
corridorLength: [2, 6],
dugPercentage: 0.2
});
// Initialize the empty map with walls (1)
const map = Array(height).fill().map(() => Array(width).fill(1));
// Fill in floors (0) using digger's callback
digger.create((x, y, value) => {
// value: 0 = floor, 1 = wall
map[y][x] = value;
});
// Get rooms that were created
const rooms = digger.getRooms();
// Place entrance in the first room
const entranceRoom = rooms[0];
const entranceCenter = entranceRoom.getCenter();
const entrance = [entranceCenter[0], entranceCenter[1]];
// Place exit in the last room
const exitRoom = rooms[rooms.length - 1];
const exitCenter = exitRoom.getCenter();
const exit = [exitCenter[0], exitCenter[1]];
// Place treasure in a middle room (not first or last)
let treasureRoom;
if (rooms.length > 2) {
treasureRoom = rooms[Math.floor(rooms.length / 2)];
} else {
// Fallback if we have fewer than 3 rooms
treasureRoom = rooms[0];
}
const treasureCenter = treasureRoom.getCenter();
// Place treasure slightly off center to make it more interesting
const treasure = [
treasureCenter[0] + Math.floor(Math.random() * 3) - 1,
treasureCenter[1] + Math.floor(Math.random() * 3) - 1
];
// Ensure the treasure is on a floor tile
if (map[treasure[1]][treasure[0]] !== 0) {
treasure[0] = treasureCenter[0];
treasure[1] = treasureCenter[1];
}
return {
map: map,
rooms: rooms,
entrance: entrance,
exit: exit,
treasure: treasure
};
}
const Player = require('./player.js');
const Monster = require('./monster.js');
const DungeonGenerator = require('./dungeon.js');
class Game {
constructor(socketId, mapWidth, mapHeight) {
@@ -70,8 +11,8 @@ class Game {
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.player = new Player();
this.monster = new Monster();
this.fee = 0; // Amount player paid
this.visibleTiles = {}; // Will store visible tiles
this.fov = null; // FOV calculator
@@ -79,20 +20,17 @@ class Game {
}
generateDungeon() {
const dungeon = generateDungeon(this.width, this.height);
this.dungeon = dungeon;
this.dungeon = DungeonGenerator.generate(this.width, this.height);
if (dungeon.entrance) {
this.player.x = dungeon.entrance[0];
this.player.y = dungeon.entrance[1];
if (this.dungeon.entrance) {
this.player.moveTo(this.dungeon.entrance[0], this.dungeon.entrance[1]);
}
// Place monster far from entrance
if (dungeon.rooms && dungeon.rooms.length > 2) {
const monsterRoom = dungeon.rooms[Math.floor(dungeon.rooms.length * 0.7)];
if (this.dungeon.rooms && this.dungeon.rooms.length > 2) {
const monsterRoom = this.dungeon.rooms[Math.floor(this.dungeon.rooms.length * 0.7)];
const center = monsterRoom.getCenter();
this.monster.x = center[0];
this.monster.y = center[1];
this.monster.moveTo(center[0], center[1]);
}
// Initialize FOV calculator
@@ -142,28 +80,27 @@ class Game {
this.dungeon.map[newY][newX] !== undefined &&
this.dungeon.map[newY][newX] === 0) { // 0 is floor
this.player.x = newX;
this.player.y = newY;
this.player.moveTo(newX, newY);
console.log(`Player moved to ${newX},${newY} in game for socket ${this.socketId}`);
// Update FOV after moving
this.updateFOV();
// Check for game events like finding treasure or exit
if (this.dungeon.exit && newX === this.dungeon.exit[0] && newY === this.dungeon.exit[1]) {
if (this.dungeon.exit && this.player.isAt(this.dungeon.exit[0], 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 };
this.gameState = 'won';
return { status: 'moved', event: 'escaped', player: this.player.getState(), visibleTiles: this.visibleTiles };
}
if (this.dungeon.treasure && newX === this.dungeon.treasure[0] && newY === this.dungeon.treasure[1] && !this.player.hasTreasure) {
if (this.dungeon.treasure && this.player.isAt(this.dungeon.treasure[0], this.dungeon.treasure[1]) && !this.player.hasTreasure) {
this.player.hasTreasure = true;
// Remove treasure from dungeon so it won't be sent in future updates
this.dungeon.treasure = null;
console.log(`Player ${this.socketId} collected treasure! hasTreasure: ${this.player.hasTreasure}`);
return { status: 'moved', event: 'treasure_found', player: this.player, visibleTiles: this.visibleTiles };
return { status: 'moved', event: 'treasure_found', player: this.player.getState(), visibleTiles: this.visibleTiles };
}
return { status: 'moved', player: this.player, visibleTiles: this.visibleTiles };
return { status: 'moved', player: this.player.getState(), visibleTiles: this.visibleTiles };
}
console.log(`Player move to ${newX},${newY} for socket ${this.socketId} is invalid (wall or out of bounds).`);
@@ -171,55 +108,15 @@ class Game {
}
moveMonster() {
// Simple monster AI to move toward player
const dx = Math.sign(this.player.x - this.monster.x);
const dy = Math.sign(this.player.y - this.monster.y);
// Try horizontal move
if (dx !== 0) {
if (this.dungeon.map[this.monster.y][this.monster.x + dx] === 0) {
this.monster.x += dx;
return;
}
}
// Try vertical move
if (dy !== 0) {
if (this.dungeon.map[this.monster.y + dy][this.monster.x] === 0) {
this.monster.y += dy;
return;
}
}
// Try random direction if direct path is blocked
const dirs = [[0,1],[1,0],[0,-1],[-1,0]];
const shuffledDirs = ROT.RNG.shuffle(dirs.slice());
for (const [dx, dy] of shuffledDirs) {
const nx = this.monster.x + dx;
const ny = this.monster.y + dy;
if (this.dungeon.map[ny] && this.dungeon.map[ny][nx] === 0) {
this.monster.x = nx;
this.monster.y = ny;
break;
}
}
this.monster.moveTowardPlayer(this.player, this.dungeon);
}
// 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; // Not needed if sending absolute visibleTiles
// const playerY = this.player.y; // Not needed if sending absolute visibleTiles
// 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.
const state = {
gameState: this.gameState,
player: { ...this.player }, // Send a copy of player object
monster: this.monster ? { ...this.monster } : null, // Send a copy if monster exists
player: this.player.getState(), // Use Player's getState method
monster: this.monster ? this.monster.getState() : null, // Use Monster's getState method
visibleTiles: { ...this.visibleTiles }, // Send a copy of visible tiles
entrance: this.dungeon ? this.dungeon.entrance : null,
exit: this.dungeon ? this.dungeon.exit : null,
@@ -235,21 +132,9 @@ class Game {
// Check if monster killed player
function checkMonsterKill(player, monster) {
// Check for direct overlap - monster on same tile as player
if (monster.x === player.x && monster.y === player.y) {
return true; // Monster killed player
}
// Check for adjacent tiles (optional - if you want 1-tile proximity kills)
const dx = Math.abs(monster.x - player.x);
const dy = Math.abs(monster.y - player.y);
// If monster is adjacent (one tile away in any direction)
if ((dx <= 1 && dy === 0) || (dx === 0 && dy <= 1)) {
return true; // Monster killed player
}
return false; // Player is safe
// Use Monster's hasCaughtPlayer method
const monsterInstance = new Monster(monster.x, monster.y);
return monsterInstance.hasCaughtPlayer(player);
}
module.exports = Game;
+30 -407
View File
@@ -5,30 +5,25 @@
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- First load socket.io -->
<!-- Socket.io -->
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<!-- THEN initialize the socket -->
<!-- Initialize socket -->
<script>
// Create socket connection BEFORE loading game.js
window.socket = io();
window.socket = io();
</script>
<!-- Only AFTER socket is initialized, load other scripts -->
<!-- Core libraries -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/rot.min.js"></script>
<script src="js/options.js"></script>
<script src="js/teletype.js"></script> <!-- Load teletype.js -->
<script src="js/teletype.js"></script>
<!-- Use the original game.js with fixes -->
<!-- Game modules -->
<script src="js/game.js"></script>
<!-- Modular game files - keeping as backup for now -->
<!-- <script src="js/displayManager.js"></script>
<script src="js/screenManager.js"></script>
<script src="js/gameState.js"></script>
<script src="js/ui.js"></script>
<script src="js/socketHandlers.js"></script>
<script src="js/inputHandler.js"></script>
<script src="js/game_refactored.js"></script> -->
</head>
<body>
<div class="container">
@@ -69,402 +64,30 @@
</div>
</div>
<!-- Initialize teletype animation -->
<script>
$(function() {
$('#banner').teletype({
text: ["Welcome traveler...", "You have arrived at...", "The Wowngeon"],
typeDelay: 100,
backDelay: 50,
pause: 1500
});
// $(\'#bannercursor\').blink({ // <<< COMMENT OUT THIS BLOCK
// text: [\'|\', \' \'],
// delay: 500
// });
// After animations are complete, set static text
setTimeout(function() {
$('#banner, #bannercursor').fadeOut(500, function() {
$('#staticbanner').text("THE WOWNGEON");
});
}, 14000);
});
</script>
<!-- Browser info panel -->
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
<div>Browser: <span id="browser-name">checking...</span></div>
<div>Display: <span id="display-status">checking...</span></div>
<button onclick="document.getElementById('browser-info').style.display='none'" style="margin-top: 5px; background: #333; border: 1px solid #666; color: white; font-size: 10px; padding: 2px 4px;">
Close
</button>
</div>
<!-- Global scroll helper function -->
<script>
function scrollChat() {
var messageBox = $('#messageBox');
messageBox.scrollTop(messageBox.prop("scrollHeight"));
}
</script>
<script>
$(function() {
// Socket event listeners
socket.on('connect', function() {
console.log("Connected with socket ID:", socket.id);
// Tell server our client ID
socket.emit('register_client', {
clientId: socket.id,
userAgent: navigator.userAgent
});
});
socket.on('welcome', function(msg) {
$('#messages').append($('<li>').text("Connected to server!"));
scrollChat();
});
// Handle waiting status updates
socket.on('waiting_status', function(data) {
console.log("🕒 WAITING STATUS received:", data);
if (data.status === 'waiting') {
$('#messages').append($('<li style="color:#ff0;">').text(data.message));
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
}
scrollChat();
});
socket.on('message', function(msg) {
$('#messages').append($('<li>').text(msg));
scrollChat();
});
// Add or update the game_start handler
socket.on('game_start', function(data) {
console.log("🎮 Game start received with data:", data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
if (typeof Game !== 'undefined' && Game.stopWaitingScreen) {
Game.stopWaitingScreen();
}
if (!data) {
$('#messages').append($('<li class="error">').text("Error: No game data received from server."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
return;
}
try {
// Use the original game.js structure
var success = Game.startGame(data.player, data.map, data.monster, data.items, data.visibleTiles);
console.log("Game start result:", success ? "SUCCESS" : "FAILED");
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed. Check console for details."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
} else {
// Draw the initial game screen - the game draws itself after startGame()
console.log("Game started successfully, initial screen should be drawn");
// Shift focus to the game display area after successful game start
$('#game-display').focus();
updateFocusIndicator();
}
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
}
});
// Game update handler
socket.on('game_update', function(data) {
// console.log("🎮 GAME UPDATE received:", data); // Reduced verbosity
if (typeof Game !== 'undefined' && Game._gameActive) { // Use Game._gameActive instead of gameState
Game.updateGameState(data); // Call updateGameState directly on Game object
} else {
// console.log("Game not active or Game undefined, skipping game_update draw.");
}
});
// Update the chat form submission handler that processes the "enter" command
$('#chatForm').submit(function(e) {
e.preventDefault();
var msg = $('#chatInput').val().trim();
// Don't send empty messages
if (!msg) return false;
console.log("📝 SENDING CHAT:", msg);
socket.emit('chat message', msg);
$('#chatInput').val('');
// If the message is "enter", show the waiting screen
// AND shift focus to game display if game is about to start
if (msg.toLowerCase() === 'enter') {
console.log("🔑 'ENTER' COMMAND DETECTED - expecting game_start response");
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
// Focus will be shifted in the 'game_start' handler after success
}
return false;
});
// Set initial focus to chat input
$('#chatInput').focus();
updateFocusIndicator(); // Initial focus indicator update
// Add focus/blur listeners for visual indicator
$('#chatInput').on('focus', updateFocusIndicator).on('blur', updateFocusIndicator);
$('#game-display').on('focus', updateFocusIndicator).on('blur', updateFocusIndicator);
// Keyboard listener for game actions (WASD, Arrow Keys)
// Ensure this is attached to an element that can receive focus, like the game-display div.
// The game-display div needs a tabindex attribute to be focusable.
$('#game-display').attr('tabindex', '-1'); // Make it focusable
$(document).on('keydown', function(e) {
// console.log(`Keydown event: ${e.key}`, "Active element:", document.activeElement, "Game active?:", Game && Game.gameState ? Game.gameState.gameActive : "Game undefined");
if (document.activeElement === $('#game-display')[0]) {
// console.log("Game display has focus.");
if (Game && Game._gameActive) { // Use Game._gameActive
// console.log("Game is active. Processing key:", e.key);
let dx = 0;
let dy = 0;
let moved = false;
switch(e.key) {
case 'w': case 'ArrowUp': dy = -1; moved = true; break;
case 's': case 'ArrowDown': dy = 1; moved = true; break;
case 'a': case 'ArrowLeft': dx = -1; moved = true; break;
case 'd': case 'ArrowRight': dx = 1; moved = true; break;
}
if (moved) {
console.log(`Attempting to move: dx=${dx}, dy=${dy}. Key: ${e.key}`);
e.preventDefault(); // Prevent page scrolling
// Emit movement to server instead of directly calling Game.movePlayer
socket.emit('player_move', { dx: dx, dy: dy });
console.log(`Emitted player_move: dx=${dx}, dy=${dy}`);
} else {
// console.log("Key pressed on game display, but not a movement key:", e.key);
}
} else {
console.log("Game display has focus, but Game is not active. Game object:", Game, "Game._gameActive:", Game && Game.gameState ? Game.gameState.gameActive : "N/A");
}
} else if (e.key === 'Enter' && document.activeElement !== $('#chatInput')[0]) {
// console.log("Enter pressed, focusing chat input.");
// If Enter is pressed and chat is not focused, focus chat
$('#chatInput').focus();
updateFocusIndicator();
} else {
// console.log("Keydown event, but game display not focused or not Enter key for chat focus. Active element:", document.activeElement);
}
});
});
// Function to update visual focus indicator
function updateFocusIndicator() {
var gameDisplay = $('#game-display');
var chatInput = $('#chatInput');
if (document.activeElement === gameDisplay[0]) {
gameDisplay.css('outline', '2px solid yellow'); // Or some other visual cue
chatInput.css('outline', 'none');
console.log("Focus: Game Display");
} else if (document.activeElement === chatInput[0]) {
chatInput.css('outline', '2px solid cyan'); // Or some other visual cue
gameDisplay.css('outline', 'none');
console.log("Focus: Chat Input");
} else {
gameDisplay.css('outline', 'none');
chatInput.css('outline', 'none');
console.log("Focus: None or Other");
}
}
</script>
<!-- Initialize the game when everything is ready -->
<script>
$(function() {
Game.init();
});
</script>
<!-- Block height update script -->
<script>
// Function to update block height display
function updateBlockHeight(height) {
$('#heightValue').text(height);
}
// Listen for block updates from server
socket.on('blockheight', function(height) {
console.log(`📈 BLOCK HEIGHT: ${height}`);
updateBlockHeight(height);
$('#statusValue').text('Connected');
$('#statusValue').css('color', '#0f0');
});
// Set a test value if no response
setTimeout(function() {
if ($('#heightValue').text() === 'Loading...') {
updateBlockHeight('---');
$('#statusValue').text('Waiting...');
$('#statusValue').css('color', '#ff0');
}
}, 5000);
</script>
<!-- Add browser detection info -->
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
<div>Browser: <span id="browser-name">checking...</span></div>
<div>Display: <span id="display-status">checking...</span></div>
<button onclick="document.getElementById('browser-info').style.display='none'" style="margin-top: 5px; background: #333; border: 1px solid #666; color: white; font-size: 10px; padding: 2px 4px;">
Close
<!-- Mode toggle button -->
<button id="toggle-mode" style="position: fixed; right: 10px; top: 10px; z-index: 1000; background: #333; color: white; padding: 5px; border: 1px solid #666; border-radius: 4px;">
Switch to ASCII Mode
</button>
</div>
<script>
// Check browser and display status
$(function() {
setTimeout(function() {
var browserInfo = document.getElementById('browser-info');
browserInfo.style.display = 'block';
// Detect browser
var browserName = "Unknown";
if (navigator.userAgent.indexOf("Firefox") !== -1) {
browserName = "Firefox";
} else if (navigator.userAgent.indexOf("Chrome") !== -1) {
browserName = "Chrome/Chromium";
} else if (navigator.userAgent.indexOf("Safari") !== -1) {
browserName = "Safari";
} else if (navigator.userAgent.indexOf("Edge") !== -1) {
browserName = "Edge";
}
document.getElementById('browser-name').textContent = browserName;
// Check display status
var rotdis = document.querySelector('.rotdis');
var canvas = rotdis ? rotdis.querySelector('canvas') : null;
if (canvas) {
document.getElementById('display-status').textContent =
"OK (" + canvas.width + "x" + canvas.height + ")";
document.getElementById('display-status').style.color = "#0f0";
} else {
document.getElementById('display-status').textContent = "NOT FOUND";
document.getElementById('display-status').style.color = "#f00";
}
// Hide after 10 seconds
setTimeout(function() {
browserInfo.style.display = 'none';
}, 10000);
}, 2000);
});
</script>
<!-- Add ASCII Mode button at the top right -->
<button id="toggle-mode" style="position: fixed; right: 10px; top: 10px; z-index: 1000; background: #333; color: white; padding: 5px; border: 1px solid #666; border-radius: 4px;">
Switch to ASCII Mode
</button>
<script>
document.getElementById('toggle-mode').addEventListener('click', function() {
try {
var button = document.getElementById('toggle-mode');
// Display mode switching has been removed - ASCII and tile modes are now equivalent
console.log("Display mode switching is no longer needed - modes are equivalent");
button.textContent = 'Mode switching disabled';
button.disabled = true;
} catch (e) {
console.error("Error switching display mode:", e);
}
});
</script>
<!-- Replace the queue_cancelled handler -->
<script>
// Handle queue cancellation
socket.on('queue_cancelled', function() {
console.log("Queue cancelled, returning to welcome screen");
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) {
Game._drawWelcomeScreen();
} else {
console.error("Game or _drawWelcomeScreen not available");
}
});
</script>
<!-- Add this before the closing </body> tag -->
<script>
// Game over handler
socket.on('game_over', function(data) {
console.log("🎮 GAME OVER received:", data);
// Update game state based on outcome
if (typeof Game !== 'undefined') {
if (data.status === 'won') {
// Show win message
$('#messages').append($('<li style="color:#0f0; font-weight:bold;">').text(
data.hasTreasure ?
"YOU ESCAPED WITH THE TREASURE! YOU WON!" :
"You escaped the dungeon alive!"
));
// Draw win screen
Game.drawWinScreen(data.hasTreasure);
} else {
// Show loss message
const reason = data.reason || "unknown";
let message = "You died in the dungeon!";
if (reason === 'monster') {
message = "You were killed by the monster!";
} else if (reason === 'timeout') {
message = "You didn't escape before the next block was found!";
<!-- Initialize game when ready -->
<script>
$(document).ready(function() {
console.log("Initializing Game...");
if (typeof Game !== 'undefined') {
Game.init();
} else {
console.error("Game object not found!");
}
$('#messages').append($('<li style="color:#f00; font-weight:bold;">').text(message));
// Draw lose screen
if (Game && Game.drawLoseScreen) {
Game.drawLoseScreen(reason);
}
}
// Set game as inactive to prevent further movement
if (Game) {
Game._gameActive = false;
}
}
scrollChat();
});
</script>
<!-- Initialize the Game object -->
<script>
$(document).ready(function() {
console.log("Initializing Game...");
if (typeof Game !== 'undefined') {
Game.init();
} else {
console.error("Game object not found!");
}
});
</script>
});
</script>
</body>
</html>
</html>
+93
View File
@@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowgue - A Wownero Roguelike</title>
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- Socket.io -->
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<!-- Initialize socket -->
<script>
window.socket = io();
</script>
<!-- Core libraries -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/rot.min.js"></script>
<script src="js/options.js"></script>
<script src="js/teletype.js"></script>
<!-- Game modules -->
<script src="js/game.js"></script>
<script src="js/ui.js"></script>
<script src="js/socketHandlers.js"></script>
<script src="js/inputHandler.js"></script>
</head>
<body>
<div class="container">
<!-- Header area -->
<header class="banner">
<div id="staticbanner">
<span id="banner"></span><span id="bannercursor">|</span>
</div>
</header>
<!-- Status area for block height -->
<div class="status">
<div id="blockHeight">Block Height: <span id="heightValue">Loading...</span></div>
<div id="connectionStatus">Status: <span id="statusValue">Connecting...</span></div>
</div>
<!-- Game display -->
<div id="game-display" class="rotdis"></div>
<!-- Chat messages area -->
<div class="chat-container">
<div id="messageBox">
<ul id="messages"></ul>
</div>
</div>
<!-- Chat input in its own row -->
<div class="input-container">
<form id="chatForm" action="">
<input id="chatInput" autocomplete="off" placeholder="Type a command..." />
<button type="submit">Send</button>
</form>
</div>
<!-- Footer -->
<div class="footer">
Wownero Roguelike v0.1.0 | Use WASD or arrow keys to move
</div>
</div>
<!-- Browser info panel -->
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
<div>Browser: <span id="browser-name">checking...</span></div>
<div>Display: <span id="display-status">checking...</span></div>
<button onclick="document.getElementById('browser-info').style.display='none'" style="margin-top: 5px; background: #333; border: 1px solid #666; color: white; font-size: 10px; padding: 2px 4px;">
Close
</button>
</div>
<!-- Mode toggle button -->
<button id="toggle-mode" style="position: fixed; right: 10px; top: 10px; z-index: 1000; background: #333; color: white; padding: 5px; border: 1px solid #666; border-radius: 4px;">
Switch to ASCII Mode
</button>
<!-- Initialize game when ready -->
<script>
$(document).ready(function() {
console.log("Initializing Game...");
if (typeof Game !== 'undefined') {
Game.init();
} else {
console.error("Game object not found!");
}
});
</script>
</body>
</html>
+470
View File
@@ -0,0 +1,470 @@
<!DOCTYPE html>
<html>
<head>
<title>Wowgue - A Wownero Roguelike</title>
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- First load socket.io -->
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<!-- THEN initialize the socket -->
<script>
// Create socket connection BEFORE loading game.js
window.socket = io();
</script>
<!-- Only AFTER socket is initialized, load other scripts -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/rot.min.js"></script>
<script src="js/options.js"></script>
<script src="js/teletype.js"></script> <!-- Load teletype.js -->
<!-- Use the original game.js with fixes -->
<script src="js/game.js"></script>
<!-- Modular game files - keeping as backup for now -->
<!-- <script src="js/displayManager.js"></script>
<script src="js/screenManager.js"></script>
<script src="js/gameState.js"></script>
<script src="js/inputHandler.js"></script>
<script src="js/game_refactored.js"></script> -->
</head>
<body>
<div class="container">
<!-- Header area -->
<header class="banner">
<div id="staticbanner">
<span id="banner"></span><span id="bannercursor">|</span>
</div>
</header>
<!-- Status area for block height -->
<div class="status">
<div id="blockHeight">Block Height: <span id="heightValue">Loading...</span></div>
<div id="connectionStatus">Status: <span id="statusValue">Connecting...</span></div>
</div>
<!-- Game display -->
<div id="game-display" class="rotdis"></div>
<!-- Chat messages area -->
<div class="chat-container">
<div id="messageBox">
<ul id="messages"></ul>
</div>
</div>
<!-- Chat input in its own row -->
<div class="input-container">
<form id="chatForm" action="">
<input id="chatInput" autocomplete="off" placeholder="Type a command..." />
<button type="submit">Send</button>
</form>
</div>
<!-- Footer -->
<div class="footer">
Wownero Roguelike v0.1.0 | Use WASD or arrow keys to move
</div>
</div>
<!-- Initialize teletype animation -->
<script>
$(function() {
$('#banner').teletype({
text: ["Welcome traveler...", "You have arrived at...", "The Wowngeon"],
typeDelay: 100,
backDelay: 50,
pause: 1500
});
// $(\'#bannercursor\').blink({ // <<< COMMENT OUT THIS BLOCK
// text: [\'|\', \' \'],
// delay: 500
// });
// After animations are complete, set static text
setTimeout(function() {
$('#banner, #bannercursor').fadeOut(500, function() {
$('#staticbanner').text("THE WOWNGEON");
});
}, 14000);
});
</script>
<!-- Global scroll helper function -->
<script>
function scrollChat() {
var messageBox = $('#messageBox');
messageBox.scrollTop(messageBox.prop("scrollHeight"));
}
</script>
<script>
$(function() {
// Socket event listeners
socket.on('connect', function() {
console.log("Connected with socket ID:", socket.id);
// Tell server our client ID
socket.emit('register_client', {
clientId: socket.id,
userAgent: navigator.userAgent
});
});
socket.on('welcome', function(msg) {
$('#messages').append($('<li>').text("Connected to server!"));
scrollChat();
});
// Handle waiting status updates
socket.on('waiting_status', function(data) {
console.log("🕒 WAITING STATUS received:", data);
if (data.status === 'waiting') {
$('#messages').append($('<li style="color:#ff0;">').text(data.message));
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
}
scrollChat();
});
socket.on('message', function(msg) {
$('#messages').append($('<li>').text(msg));
scrollChat();
});
// Add or update the game_start handler
socket.on('game_start', function(data) {
console.log("🎮 Game start received with data:", data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
if (typeof Game !== 'undefined' && Game.stopWaitingScreen) {
Game.stopWaitingScreen();
}
if (!data) {
$('#messages').append($('<li class="error">').text("Error: No game data received from server."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
return;
}
try {
// Use the original game.js structure
var success = Game.startGame(data.player, data.map, data.monster, data.items, data.visibleTiles);
console.log("Game start result:", success ? "SUCCESS" : "FAILED");
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed. Check console for details."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
} else {
// Draw the initial game screen - the game draws itself after startGame()
console.log("Game started successfully, initial screen should be drawn");
// Shift focus to the game display area after successful game start
$('#game-display').focus();
updateFocusIndicator();
}
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
}
});
// Game update handler
socket.on('game_update', function(data) {
// console.log("🎮 GAME UPDATE received:", data); // Reduced verbosity
if (typeof Game !== 'undefined' && Game._gameActive) { // Use Game._gameActive instead of gameState
Game.updateGameState(data); // Call updateGameState directly on Game object
} else {
// console.log("Game not active or Game undefined, skipping game_update draw.");
}
});
// Update the chat form submission handler that processes the "enter" command
$('#chatForm').submit(function(e) {
e.preventDefault();
var msg = $('#chatInput').val().trim();
// Don't send empty messages
if (!msg) return false;
console.log("📝 SENDING CHAT:", msg);
socket.emit('chat message', msg);
$('#chatInput').val('');
// If the message is "enter", show the waiting screen
// AND shift focus to game display if game is about to start
if (msg.toLowerCase() === 'enter') {
console.log("🔑 'ENTER' COMMAND DETECTED - expecting game_start response");
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
// Focus will be shifted in the 'game_start' handler after success
}
return false;
});
// Set initial focus to chat input
$('#chatInput').focus();
updateFocusIndicator(); // Initial focus indicator update
// Add focus/blur listeners for visual indicator
$('#chatInput').on('focus', updateFocusIndicator).on('blur', updateFocusIndicator);
$('#game-display').on('focus', updateFocusIndicator).on('blur', updateFocusIndicator);
// Keyboard listener for game actions (WASD, Arrow Keys)
// Ensure this is attached to an element that can receive focus, like the game-display div.
// The game-display div needs a tabindex attribute to be focusable.
$('#game-display').attr('tabindex', '-1'); // Make it focusable
$(document).on('keydown', function(e) {
// console.log(`Keydown event: ${e.key}`, "Active element:", document.activeElement, "Game active?:", Game && Game.gameState ? Game.gameState.gameActive : "Game undefined");
if (document.activeElement === $('#game-display')[0]) {
// console.log("Game display has focus.");
if (Game && Game._gameActive) { // Use Game._gameActive
// console.log("Game is active. Processing key:", e.key);
let dx = 0;
let dy = 0;
let moved = false;
switch(e.key) {
case 'w': case 'ArrowUp': dy = -1; moved = true; break;
case 's': case 'ArrowDown': dy = 1; moved = true; break;
case 'a': case 'ArrowLeft': dx = -1; moved = true; break;
case 'd': case 'ArrowRight': dx = 1; moved = true; break;
}
if (moved) {
console.log(`Attempting to move: dx=${dx}, dy=${dy}. Key: ${e.key}`);
e.preventDefault(); // Prevent page scrolling
// Emit movement to server instead of directly calling Game.movePlayer
socket.emit('player_move', { dx: dx, dy: dy });
console.log(`Emitted player_move: dx=${dx}, dy=${dy}`);
} else {
// console.log("Key pressed on game display, but not a movement key:", e.key);
}
} else {
console.log("Game display has focus, but Game is not active. Game object:", Game, "Game._gameActive:", Game && Game.gameState ? Game.gameState.gameActive : "N/A");
}
} else if (e.key === 'Enter' && document.activeElement !== $('#chatInput')[0]) {
// console.log("Enter pressed, focusing chat input.");
// If Enter is pressed and chat is not focused, focus chat
$('#chatInput').focus();
updateFocusIndicator();
} else {
// console.log("Keydown event, but game display not focused or not Enter key for chat focus. Active element:", document.activeElement);
}
});
});
// Function to update visual focus indicator
function updateFocusIndicator() {
var gameDisplay = $('#game-display');
var chatInput = $('#chatInput');
if (document.activeElement === gameDisplay[0]) {
gameDisplay.css('outline', '2px solid yellow'); // Or some other visual cue
chatInput.css('outline', 'none');
console.log("Focus: Game Display");
} else if (document.activeElement === chatInput[0]) {
chatInput.css('outline', '2px solid cyan'); // Or some other visual cue
gameDisplay.css('outline', 'none');
console.log("Focus: Chat Input");
} else {
gameDisplay.css('outline', 'none');
chatInput.css('outline', 'none');
console.log("Focus: None or Other");
}
}
</script>
<!-- Initialize the game when everything is ready -->
<script>
$(function() {
Game.init();
});
</script>
<!-- Block height update script -->
<script>
// Function to update block height display
function updateBlockHeight(height) {
$('#heightValue').text(height);
}
// Listen for block updates from server
socket.on('blockheight', function(height) {
console.log(`📈 BLOCK HEIGHT: ${height}`);
updateBlockHeight(height);
$('#statusValue').text('Connected');
$('#statusValue').css('color', '#0f0');
});
// Set a test value if no response
setTimeout(function() {
if ($('#heightValue').text() === 'Loading...') {
updateBlockHeight('---');
$('#statusValue').text('Waiting...');
$('#statusValue').css('color', '#ff0');
}
}, 5000);
</script>
<!-- Add browser detection info -->
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
<div>Browser: <span id="browser-name">checking...</span></div>
<div>Display: <span id="display-status">checking...</span></div>
<button onclick="document.getElementById('browser-info').style.display='none'" style="margin-top: 5px; background: #333; border: 1px solid #666; color: white; font-size: 10px; padding: 2px 4px;">
Close
</button>
</div>
<script>
// Check browser and display status
$(function() {
setTimeout(function() {
var browserInfo = document.getElementById('browser-info');
browserInfo.style.display = 'block';
// Detect browser
var browserName = "Unknown";
if (navigator.userAgent.indexOf("Firefox") !== -1) {
browserName = "Firefox";
} else if (navigator.userAgent.indexOf("Chrome") !== -1) {
browserName = "Chrome/Chromium";
} else if (navigator.userAgent.indexOf("Safari") !== -1) {
browserName = "Safari";
} else if (navigator.userAgent.indexOf("Edge") !== -1) {
browserName = "Edge";
}
document.getElementById('browser-name').textContent = browserName;
// Check display status
var rotdis = document.querySelector('.rotdis');
var canvas = rotdis ? rotdis.querySelector('canvas') : null;
if (canvas) {
document.getElementById('display-status').textContent =
"OK (" + canvas.width + "x" + canvas.height + ")";
document.getElementById('display-status').style.color = "#0f0";
} else {
document.getElementById('display-status').textContent = "NOT FOUND";
document.getElementById('display-status').style.color = "#f00";
}
// Hide after 10 seconds
setTimeout(function() {
browserInfo.style.display = 'none';
}, 10000);
}, 2000);
});
</script>
<!-- Add ASCII Mode button at the top right -->
<button id="toggle-mode" style="position: fixed; right: 10px; top: 10px; z-index: 1000; background: #333; color: white; padding: 5px; border: 1px solid #666; border-radius: 4px;">
Switch to ASCII Mode
</button>
<script>
document.getElementById('toggle-mode').addEventListener('click', function() {
try {
var button = document.getElementById('toggle-mode');
// Display mode switching has been removed - ASCII and tile modes are now equivalent
console.log("Display mode switching is no longer needed - modes are equivalent");
button.textContent = 'Mode switching disabled';
button.disabled = true;
} catch (e) {
console.error("Error switching display mode:", e);
}
});
</script>
<!-- Replace the queue_cancelled handler -->
<script>
// Handle queue cancellation
socket.on('queue_cancelled', function() {
console.log("Queue cancelled, returning to welcome screen");
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) {
Game._drawWelcomeScreen();
} else {
console.error("Game or _drawWelcomeScreen not available");
}
});
</script>
<!-- Add this before the closing </body> tag -->
<script>
// Game over handler
socket.on('game_over', function(data) {
console.log("🎮 GAME OVER received:", data);
// Update game state based on outcome
if (typeof Game !== 'undefined') {
if (data.status === 'won') {
// Show win message
$('#messages').append($('<li style="color:#0f0; font-weight:bold;">').text(
data.hasTreasure ?
"YOU ESCAPED WITH THE TREASURE! YOU WON!" :
"You escaped the dungeon alive!"
));
// Draw win screen
Game.drawWinScreen(data.hasTreasure);
} else {
// Show loss message
const reason = data.reason || "unknown";
let message = "You died in the dungeon!";
if (reason === 'monster') {
message = "You were killed by the monster!";
} else if (reason === 'timeout') {
message = "You didn't escape before the next block was found!";
}
$('#messages').append($('<li style="color:#f00; font-weight:bold;">').text(message));
// Draw lose screen
if (Game && Game.drawLoseScreen) {
Game.drawLoseScreen(reason);
}
}
// Set game as inactive to prevent further movement
if (Game) {
Game._gameActive = false;
}
}
scrollChat();
});
</script>
<!-- Initialize the Game object -->
<script>
$(document).ready(function() {
console.log("Initializing Game...");
if (typeof Game !== 'undefined') {
Game.init();
} else {
console.error("Game object not found!");
}
});
</script>
</body>
</html>
-126
View File
@@ -1,126 +0,0 @@
/**
* DisplayManager - Handles all ROT.js display operations
*/
var DisplayManager = {
_display: null,
_screenWidth: 0,
_screenHeight: 0,
_displayReady: false,
_isTileMode: true,
// Visual representation constants
symbols: {
player: "@",
wall: "#",
floor: "'",
monster: "~",
exit: ">",
entrance: "<",
treasure: "$",
border: "+"
},
init: function(screenWidth, screenHeight, containerId) {
console.log("DisplayManager.init called");
this._screenWidth = screenWidth;
this._screenHeight = screenHeight;
var displayOptions = {
width: this._screenWidth,
height: this._screenHeight,
forceSquareRatio: true,
layout: "tile",
bg: window.options.bg,
fg: window.options.fg || "#FFF",
tileWidth: window.options.tileWidth,
tileHeight: window.options.tileHeight,
tileSet: window.options.tileSet,
tileMap: window.options.tileMap,
tileColorize: false
};
try {
var gameDisplayContainer = document.getElementById(containerId);
if (!gameDisplayContainer) {
console.error("Game display container not found:", containerId);
return false;
}
this._display = new ROT.Display(displayOptions);
gameDisplayContainer.innerHTML = '';
gameDisplayContainer.appendChild(this._display.getContainer());
console.log("ROT.Display initialized successfully");
this._displayReady = true;
return true;
} catch (e) {
console.error("Error initializing ROT.Display:", e);
this._displayReady = false;
return false;
}
},
isReady: function() {
return this._display && this._displayReady;
},
clear: function() {
if (!this.isReady()) return;
const display = this._display;
display._data = {};
display._dirty = {};
var canvas = display.getContainer();
if (canvas && typeof canvas.getContext === 'function') {
var ctx = canvas.getContext("2d");
if (ctx) {
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}
},
draw: function(x, y, char, fg, bg) {
if (!this.isReady()) return;
this._display.draw(x, y, char, fg, bg);
},
drawText: function(x, y, text, fg) {
if (!this.isReady()) return;
if (fg) {
this._display.drawText(x, y, `%c{${fg}}${text}`);
} else {
this._display.drawText(x, y, text);
}
},
drawCenteredText: function(y, text, fg) {
if (!this.isReady()) return y;
const x = Math.floor((this._screenWidth - text.length) / 2);
this.drawText(x, y, text, fg);
return y + 1;
},
drawBorder: function(fg) {
if (!this.isReady()) return;
const borderChar = this.symbols.border;
const color = fg || "#FFF";
for (let x = 0; x < this._screenWidth; x++) {
for (let y = 0; y < this._screenHeight; y++) {
if (x === 0 || y === 0 || x === this._screenWidth-1 || y === this._screenHeight-1) {
this.draw(x, y, borderChar, color, null);
}
}
}
},
getScreenWidth: function() {
return this._screenWidth;
},
getScreenHeight: function() {
return this._screenHeight;
}
};
-147
View File
@@ -1,147 +0,0 @@
/**
* GameState - Manages game state and data
*/
var GameState = {
player: null,
monster: null,
entrance: null,
exit: null,
treasure: null,
visibleTiles: {},
exploredTiles: {},
gameActive: false,
map: {},
items: {},
init: function() {
this.reset();
},
reset: function() {
this.player = null;
this.monster = null;
this.entrance = null;
this.exit = null;
this.treasure = null;
this.visibleTiles = {};
this.exploredTiles = {};
this.gameActive = false;
this.map = {};
this.items = {};
},
startGame: function(data) {
console.log("Starting game with data:", data);
if (!data) {
console.error("No game data received!");
return false;
}
try {
this.gameActive = true;
// Set player position
this.player = data.player || { x: 12, y: 12 };
this.monster = data.monster;
this.entrance = data.entrance;
this.exit = data.exit;
this.treasure = data.treasure;
// Set visible tiles
if (data.visibleTiles && typeof data.visibleTiles === 'object') {
this.visibleTiles = data.visibleTiles;
console.log("Visible tiles:", Object.keys(this.visibleTiles).length, "rows");
} else {
// 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;
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();
return true;
} catch (err) {
console.error("Error starting game:", err);
return false;
}
},
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();
}
return true;
} catch (err) {
console.error("Error updating game state:", err);
return false;
}
},
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];
}
}
},
setGameActive: function(active) {
this.gameActive = active;
},
isGameActive: function() {
return this.gameActive;
},
getGameData: function() {
return {
player: this.player,
monster: this.monster,
entrance: this.entrance,
exit: this.exit,
treasure: this.treasure,
visibleTiles: this.visibleTiles,
exploredTiles: this.exploredTiles,
gameActive: this.gameActive,
map: this.map,
items: this.items
};
}
};
-139
View File
@@ -1,139 +0,0 @@
// 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);
}
};
}
/**
* Main Game object - coordinates all the managers
*/
var Game = {
// Managers
displayManager: DisplayManager,
screenManager: ScreenManager,
inputHandler: InputHandler,
gameState: GameState,
// Config
_screenWidth: options.width,
_screenHeight: options.height,
init: function() {
console.log("Game.init called");
// Initialize all managers
if (!this.displayManager.init(this._screenWidth, this._screenHeight, "game-display")) {
console.error("Failed to initialize display manager");
return false;
}
this.screenManager.init(this.displayManager);
this.gameState.init();
this.inputHandler.init();
// Set up input handler events
const self = this;
this.inputHandler.on('movement', function(direction) {
console.log("Movement input received:", direction);
});
// Draw welcome screen
this.screenManager.drawWelcomeScreen();
console.log("Game initialization complete");
return true;
},
startGame: function(data) {
console.log("🎮 Game start received with data:", data);
if (!this.gameState.startGame(data)) {
console.error("Failed to start game");
return false;
}
// Activate input handling
this.inputHandler.setGameActive(true);
this.gameState.setGameActive(true);
// Draw the game screen
this.screenManager.drawGameScreen(this.gameState.getGameData());
return true;
},
updateGameState: function(data) {
if (!this.gameState.updateGameState(data)) {
console.error("Failed to update game state");
return;
}
// Redraw the screen if game is active
if (this.gameState.isGameActive()) {
this.screenManager.drawGameScreen(this.gameState.getGameData());
}
},
showWinScreen: function(hasTreasure) {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawWinScreen(hasTreasure);
},
showLoseScreen: function(reason) {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawLoseScreen(reason);
},
showWaitingScreen: function() {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawWaitingScreen();
},
showWelcomeScreen: function() {
this.inputHandler.setGameActive(false);
this.gameState.setGameActive(false);
this.screenManager.drawWelcomeScreen();
},
clearDisplay: function() {
this.displayManager.clear();
}
};
// Socket event handlers
if (typeof window !== 'undefined' && window.socket) {
socket.on('game_start', function(data) {
console.log("🎮 Game start received with data:", data);
Game.startGame(data);
});
socket.on('game_update', function(data) {
Game.updateGameState(data);
});
socket.on('waiting_status', function(data) {
console.log("Waiting status received:", data);
Game.showWaitingScreen();
});
socket.on('queue_cancelled', function() {
console.log("Queue cancelled, returning to welcome screen");
Game.showWelcomeScreen();
});
socket.on('game_over', function(data) {
console.log("Game over received:", data);
if (data.status === 'won') {
Game.showWinScreen(data.hasTreasure);
} else {
Game.showLoseScreen(data.reason);
}
});
}
+88 -71
View File
@@ -1,85 +1,102 @@
/**
* InputHandler - Manages keyboard input and translates to game actions
* Input handlers for the Wowngeon game
*/
var InputHandler = {
gameActive: false,
eventCallbacks: {},
const InputHandler = {
init: function() {
console.log("Initializing keyboard handler");
const self = this;
window.addEventListener('keydown', function(e) {
self.handleKeyDown(e);
this.setupChatForm();
this.setupFocusHandlers();
this.setupKeyboardControls();
this.setupModeToggle();
},
setupChatForm: function() {
$('#chatForm').submit(function(e) {
e.preventDefault();
var msg = $('#chatInput').val().trim();
// Don't send empty messages
if (!msg) return false;
console.log("📝 SENDING CHAT:", msg);
socket.emit('chat message', msg);
$('#chatInput').val('');
// If the message is "enter", show the waiting screen
if (msg.toLowerCase() === 'enter') {
console.log("🔑 'ENTER' COMMAND DETECTED - expecting game_start response");
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
}
return false;
});
console.log("Keyboard handler initialized");
// Set initial focus to chat input
$('#chatInput').focus();
UI.updateFocusIndicator();
},
setGameActive: function(active) {
this.gameActive = active;
setupFocusHandlers: function() {
// Add focus/blur listeners for visual indicator
$('#chatInput').on('focus', UI.updateFocusIndicator).on('blur', UI.updateFocusIndicator);
$('#game-display').on('focus', UI.updateFocusIndicator).on('blur', UI.updateFocusIndicator);
// Make game display focusable
$('#game-display').attr('tabindex', '-1');
},
on: function(eventName, callback) {
if (!this.eventCallbacks[eventName]) {
this.eventCallbacks[eventName] = [];
}
this.eventCallbacks[eventName].push(callback);
setupKeyboardControls: function() {
$(document).on('keydown', function(e) {
if (document.activeElement === $('#game-display')[0]) {
if (Game && Game._gameActive) {
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
socket.emit('player_move', { dx: dx, dy: dy });
console.log(`Emitted player_move: dx=${dx}, dy=${dy}`);
}
} else {
console.log("Game display has focus, but Game is not active.");
}
} else if (e.key === 'Enter' && document.activeElement !== $('#chatInput')[0]) {
// If Enter is pressed and chat is not focused, focus chat
$('#chatInput').focus();
UI.updateFocusIndicator();
}
});
},
emit: function(eventName, data) {
if (this.eventCallbacks[eventName]) {
this.eventCallbacks[eventName].forEach(function(callback) {
callback(data);
setupModeToggle: function() {
var toggleButton = document.getElementById('toggle-mode');
if (toggleButton) {
toggleButton.addEventListener('click', function() {
try {
console.log("Display mode switching is no longer needed - modes are equivalent");
toggleButton.textContent = 'Mode switching disabled';
toggleButton.disabled = true;
} catch (e) {
console.error("Error switching display mode:", e);
}
});
}
},
handleKeyDown: function(e) {
if (!this.gameActive) {
return;
}
let direction = null;
switch (e.key) {
case 'ArrowUp':
case 'w':
case 'W':
direction = 'up';
break;
case 'ArrowDown':
case 's':
case 'S':
direction = 'down';
break;
case 'ArrowLeft':
case 'a':
case 'A':
direction = 'left';
break;
case 'ArrowRight':
case 'd':
case 'D':
direction = 'right';
break;
default:
return;
}
e.preventDefault();
console.log(`Key pressed: ${e.key}, sending direction: ${direction}`);
// Emit movement event
this.emit('movement', direction);
// Send to server via socket
if (window.socket && direction) {
window.socket.emit('move', direction);
}
}
};
// Initialize input handlers when DOM is ready
$(function() {
InputHandler.init();
});
-195
View File
@@ -1,195 +0,0 @@
/**
* ScreenManager - Handles drawing different game screens
*/
var ScreenManager = {
displayManager: null,
defaultFg: "#FFF",
init: function(displayManager) {
this.displayManager = displayManager;
},
drawWelcomeScreen: function() {
if (!this.displayManager.isReady()) return;
console.log("Drawing welcome screen");
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
// Draw content with centered text
let y = 3;
y = this.displayManager.drawCenteredText(y, "WOWGUE", this.defaultFg);
y += 4;
// Draw instructions
y = this.displayManager.drawCenteredText(y, "Type enter to start!", this.defaultFg);
y = this.displayManager.drawCenteredText(y, "Or type help...", this.defaultFg);
y += 2;
// Draw legend
const centerX = Math.floor(this.displayManager.getScreenWidth() / 2) - 11;
const symbols = this.displayManager.symbols;
this.displayManager.draw(centerX, y, symbols.player, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- This is you", this.defaultFg);
y++;
this.displayManager.draw(centerX, y, symbols.exit, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- Escape the dungeon", this.defaultFg);
y++;
this.displayManager.draw(centerX, y, symbols.monster, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- Avoid the monster", this.defaultFg);
y++;
this.displayManager.draw(centerX, y, symbols.treasure, this.defaultFg, null);
this.displayManager.drawText(centerX + 2, y, "- Secure the bag", this.defaultFg);
y++;
// Draw footer
y += 1;
this.displayManager.drawCenteredText(17, "Type enter in chat =", this.defaultFg);
},
drawWinScreen: function(hasTreasure) {
if (!this.displayManager.isReady()) return;
console.log("Drawing win screen, hasTreasure:", hasTreasure);
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
let y = 5;
y = this.displayManager.drawCenteredText(y, "YOU ESCAPED!", this.defaultFg);
y += 1;
const subtitle = hasTreasure ? "WITH THE TREASURE!" : "ALIVE!";
y = this.displayManager.drawCenteredText(y, subtitle, this.defaultFg);
y += 2;
if (hasTreasure) {
y = this.displayManager.drawCenteredText(y, "You won extra Wownero!", this.defaultFg);
y += 1;
}
y += 1;
this.displayManager.drawCenteredText(y, "Type ENTER to retry", this.defaultFg);
},
drawLoseScreen: function(reason) {
if (!this.displayManager.isReady()) return;
console.log("Drawing lose screen, reason:", reason);
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
let y = 5;
y = this.displayManager.drawCenteredText(y, "YOU DIED!", this.defaultFg);
y += 1;
let subtitle = "";
if (reason === 'monster') {
subtitle = "KILLED BY THE MONSTER";
} else if (reason === 'timeout') {
subtitle = "YOU RAN OUT OF TIME";
}
if (subtitle) {
y = this.displayManager.drawCenteredText(y, subtitle, this.defaultFg);
y += 1;
}
y += 2;
this.displayManager.drawCenteredText(y, "Type ENTER to retry", this.defaultFg);
},
drawWaitingScreen: function() {
if (!this.displayManager.isReady()) return;
console.log("Drawing waiting screen");
this.displayManager.clear();
this.displayManager.drawBorder(this.defaultFg);
const centerY = Math.floor(this.displayManager.getScreenHeight() / 2);
this.displayManager.drawCenteredText(centerY, "Waiting for next block...", this.defaultFg);
},
drawGameScreen: function(gameState) {
if (!this.displayManager.isReady()) return;
console.log("Drawing game screen...");
this.displayManager.clear();
if (!gameState.player) {
console.error("No player data in game state!");
return;
}
const centerX = Math.floor(this.displayManager.getScreenWidth() / 2);
const centerY = Math.floor(this.displayManager.getScreenHeight() / 2);
const symbols = this.displayManager.symbols;
const playerX = gameState.player.x;
const playerY = gameState.player.y;
// Draw visible tiles centered on player (absolute to relative coordinate conversion)
for (let worldY in gameState.visibleTiles) {
worldY = parseInt(worldY);
for (let worldX in gameState.visibleTiles[worldY]) {
worldX = parseInt(worldX);
// Convert world coordinates to screen coordinates relative to player
const screenX = centerX + (worldX - playerX);
const screenY = centerY + (worldY - playerY);
if (screenX >= 0 && screenY >= 0 &&
screenX < this.displayManager.getScreenWidth() &&
screenY < this.displayManager.getScreenHeight()) {
const tileType = gameState.visibleTiles[worldY][worldX];
if (tileType === 1) { // Wall
this.displayManager.draw(screenX, screenY, symbols.wall, this.defaultFg, null);
} else { // Floor
this.displayManager.draw(screenX, screenY, symbols.floor, this.defaultFg, null);
}
}
}
}
// Draw entities with stacking (now with correct coordinate conversion)
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.entrance, symbols.entrance, gameState.visibleTiles);
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.exit, symbols.exit, gameState.visibleTiles);
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.treasure, symbols.treasure, gameState.visibleTiles);
this._drawEntityStacked(centerX, centerY, playerX, playerY, gameState.monster, symbols.monster, gameState.visibleTiles);
// Always draw player at center
const baseTile = gameState.visibleTiles[playerY] && gameState.visibleTiles[playerY][playerX] === 1 ?
symbols.wall : symbols.floor;
this.displayManager.draw(centerX, centerY, [baseTile, symbols.player],
[this.defaultFg, this.defaultFg], [null, null]);
},
_drawEntityStacked: function(centerX, centerY, playerX, playerY, entity, symbol, visibleTiles) {
if (!entity) return;
const entityX = Array.isArray(entity) ? entity[0] : entity.x;
const entityY = Array.isArray(entity) ? entity[1] : entity.y;
// Convert world coordinates to screen coordinates relative to player
const screenX = centerX + (entityX - playerX);
const screenY = centerY + (entityY - playerY);
if (screenX >= 0 && screenY >= 0 &&
screenX < this.displayManager.getScreenWidth() &&
screenY < this.displayManager.getScreenHeight()) {
// Check if entity is visible
if (visibleTiles[entityY] && visibleTiles[entityY][entityX] !== undefined) {
const baseTile = visibleTiles[entityY][entityX] === 1 ?
this.displayManager.symbols.wall : this.displayManager.symbols.floor;
this.displayManager.draw(screenX, screenY, [baseTile, symbol],
[this.defaultFg, this.defaultFg], [null, null]);
}
}
}
};
+172
View File
@@ -0,0 +1,172 @@
/**
* Socket event handlers for the Wowngeon game
*/
const SocketHandlers = {
init: function() {
if (!window.socket) {
console.error("Socket not available!");
return;
}
this.registerEventHandlers();
},
registerEventHandlers: function() {
// Connection handlers
socket.on('connect', this.onConnect);
socket.on('welcome', this.onWelcome);
socket.on('message', this.onMessage);
// Game state handlers
socket.on('waiting_status', this.onWaitingStatus);
socket.on('game_start', this.onGameStart);
socket.on('game_update', this.onGameUpdate);
socket.on('game_over', this.onGameOver);
socket.on('queue_cancelled', this.onQueueCancelled);
// Block height handler
socket.on('blockheight', this.onBlockHeight);
},
onConnect: function() {
console.log("Connected with socket ID:", socket.id);
// Tell server our client ID
socket.emit('register_client', {
clientId: socket.id,
userAgent: navigator.userAgent
});
},
onWelcome: function(msg) {
$('#messages').append($('<li>').text("Connected to server!"));
UI.scrollChat();
},
onMessage: function(msg) {
$('#messages').append($('<li>').text(msg));
UI.scrollChat();
},
onWaitingStatus: function(data) {
console.log("🕒 WAITING STATUS received:", data);
if (data.status === 'waiting') {
$('#messages').append($('<li style="color:#ff0;">').text(data.message));
// Show waiting screen
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
}
UI.scrollChat();
},
onGameStart: function(data) {
console.log("🎮 Game start received with data:", data);
$('#messages').append($('<li class="game-start">').text("Starting game..."));
if (typeof Game !== 'undefined' && Game.stopWaitingScreen) {
Game.stopWaitingScreen();
}
if (!data) {
$('#messages').append($('<li class="error">').text("Error: No game data received from server."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
return;
}
try {
// Use the original game.js structure
var success = Game.startGame(data.player, data.map, data.monster, data.items, data.visibleTiles);
console.log("Game start result:", success ? "SUCCESS" : "FAILED");
if (!success) {
$('#messages').append($('<li class="error">').text("Game start failed. Check console for details."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) Game._drawWelcomeScreen();
} else {
// Draw the initial game screen - the game draws itself after startGame()
console.log("Game started successfully, initial screen should be drawn");
// Shift focus to the game display area after successful game start
$('#game-display').focus();
UI.updateFocusIndicator();
}
} catch (err) {
console.error("Error starting game:", err);
$('#messages').append($('<li class="error">').text("Error: " + err.message));
}
},
onGameUpdate: function(data) {
if (typeof Game !== 'undefined' && Game._gameActive) {
Game.updateGameState(data);
}
},
onGameOver: function(data) {
console.log("🎮 GAME OVER received:", data);
// Update game state based on outcome
if (typeof Game !== 'undefined') {
if (data.status === 'won') {
// Show win message
$('#messages').append($('<li style="color:#0f0; font-weight:bold;">').text(
data.hasTreasure ?
"YOU ESCAPED WITH THE TREASURE! YOU WON!" :
"You escaped the dungeon alive!"
));
// Draw win screen
Game.drawWinScreen(data.hasTreasure);
} else {
// Show loss message
const reason = data.reason || "unknown";
let message = "You died in the dungeon!";
if (reason === 'monster') {
message = "You were killed by the monster!";
} else if (reason === 'timeout') {
message = "You didn't escape before the next block was found!";
}
$('#messages').append($('<li style="color:#f00; font-weight:bold;">').text(message));
// Draw lose screen
if (Game && Game.drawLoseScreen) {
Game.drawLoseScreen(reason);
}
}
// Set game as inactive to prevent further movement
if (Game) {
Game._gameActive = false;
}
}
UI.scrollChat();
},
onQueueCancelled: function() {
console.log("Queue cancelled, returning to welcome screen");
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) {
Game._drawWelcomeScreen();
} else {
console.error("Game or _drawWelcomeScreen not available");
}
},
onBlockHeight: function(height) {
console.log(`📈 BLOCK HEIGHT: ${height}`);
UI.updateBlockHeight(height);
$('#statusValue').text('Connected');
$('#statusValue').css('color', '#0f0');
}
};
// Initialize when DOM is ready
$(function() {
SocketHandlers.init();
});
+112
View File
@@ -0,0 +1,112 @@
/**
* UI utilities and helpers for the Wowngeon game
*/
const UI = {
// Scroll chat to bottom
scrollChat: function() {
var messageBox = $('#messageBox');
messageBox.scrollTop(messageBox.prop("scrollHeight"));
},
// Update block height display
updateBlockHeight: function(height) {
$('#heightValue').text(height);
},
// Update visual focus indicator
updateFocusIndicator: function() {
var gameDisplay = $('#game-display');
var chatInput = $('#chatInput');
if (document.activeElement === gameDisplay[0]) {
gameDisplay.css('outline', '2px solid yellow');
chatInput.css('outline', 'none');
console.log("Focus: Game Display");
} else if (document.activeElement === chatInput[0]) {
chatInput.css('outline', '2px solid cyan');
gameDisplay.css('outline', 'none');
console.log("Focus: Chat Input");
} else {
gameDisplay.css('outline', 'none');
chatInput.css('outline', 'none');
console.log("Focus: None or Other");
}
},
// Initialize teletype animation
initTeletype: function() {
$('#banner').teletype({
text: ["Welcome traveler...", "You have arrived at...", "The Wowngeon"],
typeDelay: 100,
backDelay: 50,
pause: 1500
});
// After animations are complete, set static text
setTimeout(function() {
$('#banner, #bannercursor').fadeOut(500, function() {
$('#staticbanner').text("THE WOWNGEON");
});
}, 14000);
},
// Initialize browser detection
initBrowserDetection: function() {
setTimeout(function() {
var browserInfo = document.getElementById('browser-info');
if (!browserInfo) return;
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);
},
// Set test values if no server response
setTestValues: function() {
setTimeout(function() {
if ($('#heightValue').text() === 'Loading...') {
UI.updateBlockHeight('---');
$('#statusValue').text('Waiting...');
$('#statusValue').css('color', '#ff0');
}
}, 5000);
}
};
// Initialize UI components when DOM is ready
$(function() {
UI.initTeletype();
UI.initBrowserDetection();
UI.setTestValues();
});
+99
View File
@@ -0,0 +1,99 @@
const ROT = require('./rot.js');
/**
* Monster class - Manages monster state and AI
*/
class Monster {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
// Move monster to new position
moveTo(x, y) {
this.x = x;
this.y = y;
}
// Check if monster is at given coordinates
isAt(x, y) {
return this.x === x && this.y === y;
}
// Simple AI to move toward player
moveTowardPlayer(player, dungeon) {
if (!player || !dungeon || !dungeon.map) return;
const dx = Math.sign(player.x - this.x);
const dy = Math.sign(player.y - this.y);
// Try horizontal move first
if (dx !== 0) {
const newX = this.x + dx;
if (dungeon.map[this.y] && dungeon.map[this.y][newX] === 0) {
this.x = newX;
return;
}
}
// Try vertical move
if (dy !== 0) {
const newY = this.y + dy;
if (dungeon.map[newY] && dungeon.map[newY][this.x] === 0) {
this.y = newY;
return;
}
}
// Try random direction if direct path is blocked
const dirs = [[0,1],[1,0],[0,-1],[-1,0]];
const shuffledDirs = ROT.RNG.shuffle(dirs.slice());
for (const [dx, dy] of shuffledDirs) {
const nx = this.x + dx;
const ny = this.y + dy;
if (dungeon.map[ny] && dungeon.map[ny][nx] === 0) {
this.x = nx;
this.y = ny;
break;
}
}
}
// Check if monster has caught the player
hasCaughtPlayer(player) {
if (!player) return false;
// Check for direct overlap - monster on same tile as player
if (this.x === player.x && this.y === player.y) {
return true;
}
// Check for adjacent tiles (optional - if you want 1-tile proximity kills)
const dx = Math.abs(this.x - player.x);
const dy = Math.abs(this.y - player.y);
// If monster is adjacent (one tile away in any direction)
if ((dx <= 1 && dy === 0) || (dx === 0 && dy <= 1)) {
return true;
}
return false;
}
// Get monster state as object
getState() {
return {
x: this.x,
y: this.y
};
}
// Set monster state from object
setState(state) {
if (state.x !== undefined) this.x = state.x;
if (state.y !== undefined) this.y = state.y;
}
}
module.exports = Monster;
+48
View File
@@ -0,0 +1,48 @@
/**
* Player class - Manages player state and actions
*/
class Player {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
this.hasKey = false;
this.hasTreasure = false;
}
// Move player to new position
moveTo(x, y) {
this.x = x;
this.y = y;
}
// Check if player is at given coordinates
isAt(x, y) {
return this.x === x && this.y === y;
}
// Reset player state
reset() {
this.hasKey = false;
this.hasTreasure = false;
}
// Get player state as object
getState() {
return {
x: this.x,
y: this.y,
hasKey: this.hasKey,
hasTreasure: this.hasTreasure
};
}
// Set player state from object
setState(state) {
if (state.x !== undefined) this.x = state.x;
if (state.y !== undefined) this.y = state.y;
if (state.hasKey !== undefined) this.hasKey = state.hasKey;
if (state.hasTreasure !== undefined) this.hasTreasure = state.hasTreasure;
}
}
module.exports = Player;