"use strict"; /** * AI Interactive Fiction - Web Server * Serves the web UI and handles WebSocket communication */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.io = exports.server = exports.app = void 0; exports.startServer = startServer; const path_1 = __importDefault(require("path")); const express_1 = __importDefault(require("express")); const http_1 = __importDefault(require("http")); const socket_io_1 = require("socket.io"); const dotenv = __importStar(require("dotenv")); const game_runner_1 = require("./cli/game-runner"); const fs_1 = require("fs"); // Load environment variables dotenv.config(); // Create Express application const app = (0, express_1.default)(); exports.app = app; const server = http_1.default.createServer(app); exports.server = server; const io = new socket_io_1.Server(server); exports.io = io; // Get port from environment variables or use default const DEFAULT_PORT = 3000; const PORT = process.env.PORT ? parseInt(process.env.PORT) : DEFAULT_PORT; const PORT_RANGE = 10; // Try up to 10 ports starting from the default // Serve static files from the public directory app.use(express_1.default.static(path_1.default.join(__dirname, '../public'))); // Set up game sessions const gameSessions = new Map(); // Handle socket connections io.on('connection', (socket) => { console.log(`New client connected: ${socket.id}`); // Start a new game socket.on('startGame', async () => { try { // Initialize game runner const gameRunner = new game_runner_1.GameRunner(); const worldFile = process.env.DEFAULT_WORLD_FILE || './data/worlds/example_world.yml'; // Initialize the game await gameRunner.initialize(worldFile); // Store game session gameSessions.set(socket.id, gameRunner); // Send introduction to client const gameState = gameRunner.getGameState(); socket.emit('gameIntroduction', { introduction: gameState.world.introduction, initialRoomDescription: gameRunner.getCurrentRoomDescription(), currentRoomId: gameState.currentRoomId }); } catch (error) { console.error('Error starting game:', error); socket.emit('error', { message: 'Failed to start game. Please try again.' }); } }); // Process player command socket.on('playerCommand', async (data) => { try { const gameRunner = gameSessions.get(socket.id); if (!gameRunner) { socket.emit('error', { message: 'Game session not found. Please start a new game.' }); return; } // Process command and get response const response = await gameRunner.processCommand(data.command); // Send narrative response to client socket.emit('narrativeResponse', { text: response, gameState: { currentRoomId: gameRunner.getGameState().currentRoomId }, suggestions: gameRunner.getSuggestions() }); } catch (error) { console.error('Error processing command:', error); socket.emit('error', { message: 'Failed to process command. Please try again.' }); } }); // Save game state socket.on('saveGame', () => { try { const gameRunner = gameSessions.get(socket.id); if (!gameRunner) { socket.emit('error', { message: 'Game session not found. Please start a new game.' }); return; } // Store save data in session socket.data.savedGame = gameRunner.getGameState(); socket.emit('gameSaved'); } catch (error) { console.error('Error saving game:', error); socket.emit('error', { message: 'Failed to save game. Please try again.' }); } }); // Load game state socket.on('loadGame', () => { try { const gameRunner = gameSessions.get(socket.id); if (!gameRunner) { socket.emit('error', { message: 'Game session not found. Please start a new game.' }); return; } // Check if there's a saved game if (!socket.data.savedGame) { socket.emit('error', { message: 'No saved game found.' }); return; } // Load saved game gameRunner.loadGameState(socket.data.savedGame); // Send current state to client socket.emit('gameLoaded', { currentRoomDescription: gameRunner.getCurrentRoomDescription(), currentRoomId: gameRunner.getGameState().currentRoomId }); } catch (error) { console.error('Error loading game:', error); socket.emit('error', { message: 'Failed to load game. Please try again.' }); } }); // Handle disconnection socket.on('disconnect', () => { console.log(`Client disconnected: ${socket.id}`); // Clean up game session if (gameSessions.has(socket.id)) { gameSessions.delete(socket.id); } }); }); // Ensure required asset folders exist function ensureDirectories() { const dirs = [ path_1.default.join(__dirname, '../public'), path_1.default.join(__dirname, '../public/js'), path_1.default.join(__dirname, '../public/css'), path_1.default.join(__dirname, '../public/images'), path_1.default.join(__dirname, '../public/fonts') ]; for (const dir of dirs) { if (!(0, fs_1.existsSync)(dir)) { (0, fs_1.mkdirSync)(dir, { recursive: true }); } } } // Copy kokoro-js library from node_modules if not already present function ensureKokoroJs() { const source = path_1.default.join(__dirname, '../node_modules/kokoro-js/dist/index.js'); const destination = path_1.default.join(__dirname, '../public/js/kokoro-js.js'); if ((0, fs_1.existsSync)(source) && !(0, fs_1.existsSync)(destination)) { (0, fs_1.copyFileSync)(source, destination); console.log(`Copied kokoro-js from ${source} to ${destination}`); } } // Start the server with port fallback async function startServer(initialPort, range) { let currentPort = initialPort; const maxPort = initialPort + range; // Try ports in the specified range while (currentPort < maxPort) { try { // Ensure directories exist ensureDirectories(); // Ensure kokoro-js is copied try { ensureKokoroJs(); } catch (error) { console.error('Error copying kokoro-js:', error); } // Try to start the server on the current port await new Promise((resolve, reject) => { server.listen(currentPort, () => { console.log(`AI Interactive Fiction web server running on http://localhost:${currentPort}`); resolve(); }); server.on('error', (error) => { // If port is in use, try next port if (error.code === 'EADDRINUSE') { console.log(`Port ${currentPort} is in use, trying next port...`); server.close(); currentPort++; reject(); } else { // For other errors, log and reject console.error('Server error:', error); reject(error); } }); }); // If we reach here, server started successfully return; } catch (error) { // If we reach the max port and still fail, throw an error if (currentPort >= maxPort - 1) { throw new Error(`Failed to start server on ports ${initialPort} to ${maxPort - 1}`); } // Otherwise try the next port // The loop continues as the rejection above increments currentPort } } } // Start the server when this module is run directly if (require.main === module) { startServer(PORT, PORT_RANGE).catch(error => { console.error('Failed to start server:', error); process.exit(1); }); } //# sourceMappingURL=server.js.map