mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-19 07:08:33 +02:00
mostly working basics wow
This commit is contained in:
+117
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user