Files
ai.interactive.fiction/public/js/game-loop-module.js
2026-05-14 23:18:30 +02:00

318 lines
10 KiB
JavaScript

/**
* Game Loop Module for AI Interactive Fiction
* Manages the main game logic and connects various modules
*/
import { BaseModule } from './base-module.js';
class GameLoopModule extends BaseModule {
constructor() {
super('game-loop', 'Game Loop');
// Dependencies
this.dependencies = ['ui-controller', 'socket-client', 'text-buffer', 'sentence-queue', 'playback-coordinator', 'animation-queue', 'audio-manager', 'tts-factory', 'ui-input-handler'];
// Game state
this.gameState = {
started: false,
canLoad: false,
currentRoom: null,
inventory: [],
commandHistory: []
};
this.isRunning = false;
// Bind methods using parent's bindMethods utility
this.bindMethods([
'start',
'setupUiEventListeners',
'setupSocketEventListeners',
'updateGameState',
'updateUIState',
'refreshGameApiState',
'requestStartGame',
'requestSaveGame',
'requestLoadGame',
'resetClientPlaybackAndDisplay',
'addText'
]);
}
async initialize() {
this.reportProgress(100, "Game loop initialized");
return true;
}
start() {
console.log("GameLoop: Starting game sequence...");
try {
// The dependencies are now automatically available via getModule
this.updateUIState();
this.setupUiEventListeners();
console.log("GameLoop: Setting up socket listeners and connecting...");
// Set up socket event listeners and connect
this.setupSocketEventListeners();
// Set the game loop as running
this.isRunning = true;
} catch (error) {
console.error("Error starting game loop:", error);
}
}
setupUiEventListeners() {
if (this.uiEventsBound) return;
this.uiEventsBound = true;
document.addEventListener('ui:game:restart', () => this.requestStartGame());
document.addEventListener('ui:game:save', () => this.requestSaveGame());
document.addEventListener('ui:game:load', () => this.requestLoadGame());
}
setupSocketEventListeners() {
// Get the socket client module using parent's getModule method
const socketClient = this.getModule('socket-client');
if (!socketClient) {
console.error("Socket client module not found");
return;
}
// Connect UI controller to socket client for command handling
const uiController = this.getModule('ui-controller');
if (uiController) {
uiController.socketClient = socketClient;
} else {
console.warn("GameLoop: UI Controller not ready for Socket Client assignment.");
}
// Listen for socket connection event
socketClient.on('connect', () => {
console.log("GameLoop: Socket connected event received.");
this.refreshGameApiState();
});
// Listen for game state updates
socketClient.on('gameStateUpdate', (data) => {
console.log("GameLoop: Game state update received", data);
this.updateGameState(data);
});
// Listen for narrative responses
socketClient.on('narrativeResponse', (data) => {
console.log("GameLoop: Narrative response received", data);
// Text processing is handled by socket-client -> text-buffer -> ui-controller pipeline
});
// Listen for game introduction
socketClient.on('gameIntroduction', (data) => {
console.log("GameLoop: Received gameIntroduction");
this.gameState.started = true;
this.gameState.canSave = true;
this.updateUIState();
// Text processing is handled by socket-client -> text-buffer -> ui-controller pipeline
});
socketClient.on('gameSaved', () => {
this.gameState.canLoad = true;
this.updateUIState();
});
socketClient.on('gameLoaded', () => {
this.gameState.started = true;
this.gameState.canSave = true;
this.gameState.canLoad = true;
this.updateUIState();
});
// Connect to the socket server
socketClient.connect().then(success => {
if (success) {
console.log("GameLoop: Socket connection established successfully.");
} else {
console.error("GameLoop: Failed to connect to socket server");
}
});
}
async refreshGameApiState() {
const socketClient = this.getModule('socket-client');
if (!socketClient || !socketClient.getConnectionStatus()) return;
const [running, hasSave] = await Promise.all([
socketClient.isGameRunning(),
socketClient.hasSaveGame(1)
]);
this.gameState.started = Boolean(running?.result);
this.gameState.canSave = this.gameState.started;
this.gameState.canLoad = Boolean(hasSave?.result);
this.updateUIState();
}
/**
* Update the game state
* @param {Object} data - New game state data
*/
updateGameState(data) {
if (!data) return;
// Update game state
if (data.currentRoom) {
this.gameState.currentRoom = data.currentRoom;
}
if (data.inventory) {
this.gameState.inventory = data.inventory;
}
// Update UI with new game state
this.updateUIState();
}
/**
* Update UI with current game state
*/
updateUIState() {
const uiController = this.getModule('ui-controller');
if (!uiController) return;
// Update UI components based on game state
const state = {
canRestart: true,
canSave: Boolean(this.gameState.started),
canLoad: Boolean(this.gameState.canLoad),
gameStarted: Boolean(this.gameState.started)
};
document.body.dataset.gameRunning = state.gameStarted ? 'true' : 'false';
uiController.updateButtonStates(state);
}
/**
* Request to start a new game
*/
async requestStartGame() {
const socketClient = this.getModule('socket-client');
if (!socketClient) return;
await this.resetClientPlaybackAndDisplay();
const response = await socketClient.newGame();
if (!response?.success) {
console.error('GameLoop: newGame failed', response);
return;
}
this.gameState.started = true;
this.gameState.canSave = true;
this.gameState.canLoad = Boolean(response.canLoad);
this.updateUIState();
}
/**
* Request to save the current game
*/
async requestSaveGame() {
const socketClient = this.getModule('socket-client');
if (!socketClient || !this.gameState.started) return;
const response = await socketClient.saveGame(1);
if (response?.success) {
this.gameState.canLoad = true;
this.updateUIState();
}
}
/**
* Request to load a saved game
*/
async requestLoadGame() {
const socketClient = this.getModule('socket-client');
if (!socketClient) return;
const hasSave = await socketClient.hasSaveGame(1);
if (!hasSave?.result) {
this.gameState.canLoad = false;
this.updateUIState();
return;
}
await this.resetClientPlaybackAndDisplay();
const response = await socketClient.loadGame(1);
if (response?.success) {
this.gameState.started = true;
this.gameState.canSave = true;
this.gameState.canLoad = true;
this.updateUIState();
}
}
async resetClientPlaybackAndDisplay() {
const playbackCoordinator = this.getModule('playback-coordinator');
if (playbackCoordinator && typeof playbackCoordinator.stop === 'function') {
await playbackCoordinator.stop();
}
const animationQueue = this.getModule('animation-queue');
if (animationQueue && typeof animationQueue.clearAll === 'function') {
animationQueue.clearAll();
}
const ttsFactory = this.getModule('tts-factory');
if (ttsFactory && typeof ttsFactory.stop === 'function') {
ttsFactory.stop();
}
const audioManager = this.getModule('audio-manager');
if (audioManager && typeof audioManager.stopAllSounds === 'function') {
audioManager.stopAllSounds();
}
const sentenceQueue = this.getModule('sentence-queue');
if (sentenceQueue && typeof sentenceQueue.clear === 'function') {
sentenceQueue.clear();
}
const textBuffer = this.getModule('text-buffer');
if (textBuffer && typeof textBuffer.clear === 'function') {
textBuffer.clear();
}
const uiController = this.getModule('ui-controller');
if (uiController) {
uiController.clearDisplay();
}
const inputHandler = this.getModule('ui-input-handler');
if (inputHandler && typeof inputHandler.clearHistory === 'function') {
inputHandler.clearHistory();
}
}
/**
* Manually add text to the buffer
* Useful for testing or adding local messages
* @param {string} text - Text to add
*/
addText(text) {
// Use parent's getModule method
const textBuffer = this.getModule('text-buffer');
if (!textBuffer) {
console.warn("Text buffer not available");
return;
}
textBuffer.addText(text);
}
}
// Create the singleton instance
const GameLoop = new GameLoopModule();
// Export the module
export { GameLoop };