262 lines
10 KiB
JavaScript
262 lines
10 KiB
JavaScript
"use strict";
|
|
/**
|
|
* Command-line interface for running the interactive fiction game
|
|
*/
|
|
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;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.GameRunner = void 0;
|
|
const readline = __importStar(require("readline"));
|
|
const path = __importStar(require("path"));
|
|
const dotenv = __importStar(require("dotenv"));
|
|
const game_engine_1 = require("../engine/game-engine");
|
|
const openrouter_provider_1 = require("../llm/openrouter-provider");
|
|
// Load environment variables
|
|
dotenv.config();
|
|
class GameRunner {
|
|
constructor() {
|
|
this.rl = null;
|
|
this.gameContext = '';
|
|
this.gameHistory = [];
|
|
this.suggestedCommands = [];
|
|
this.engine = new game_engine_1.TextAdventureEngine();
|
|
this.llmProvider = new openrouter_provider_1.OpenRouterProvider();
|
|
}
|
|
/**
|
|
* Initialize the game
|
|
*/
|
|
async initialize(worldPath) {
|
|
console.log('Initializing game...');
|
|
// Initialize LLM provider
|
|
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
const model = process.env.OPENROUTER_MODEL;
|
|
if (!apiKey || !model) {
|
|
throw new Error('Missing required environment variables: OPENROUTER_API_KEY and/or OPENROUTER_MODEL');
|
|
}
|
|
await this.llmProvider.initialize({
|
|
apiKey,
|
|
model,
|
|
temperature: 0.7,
|
|
maxTokens: 800
|
|
});
|
|
// Load the world
|
|
const resolvedPath = path.resolve(worldPath);
|
|
console.log(`Loading world from ${resolvedPath}...`);
|
|
await this.engine.loadWorld(resolvedPath);
|
|
console.log('Game initialized successfully!');
|
|
}
|
|
/**
|
|
* Start the game in CLI mode
|
|
*/
|
|
async start() {
|
|
// Create readline interface for CLI mode
|
|
this.rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout
|
|
});
|
|
try {
|
|
// Display introduction
|
|
const introText = await this.engine.start();
|
|
console.log('\n' + introText + '\n');
|
|
// Look at initial room
|
|
const initialLook = this.engine.processAction({ action: 'look', confidence: 1 });
|
|
// Generate narrative description
|
|
const narrativeRequest = {
|
|
action: 'look',
|
|
result: initialLook.message,
|
|
roomDescription: this.engine.getCurrentRoomDescription(),
|
|
visibleObjects: this.engine.getVisibleObjects(),
|
|
visibleCharacters: this.engine.getVisibleCharacters(),
|
|
tone: 'descriptive'
|
|
};
|
|
const narrative = await this.llmProvider.generateNarrative(narrativeRequest);
|
|
console.log('\n' + narrative.text + '\n');
|
|
// Store suggestions if available
|
|
if (narrative.suggestions && narrative.suggestions.length > 0) {
|
|
this.suggestedCommands = narrative.suggestions;
|
|
}
|
|
// Update game context
|
|
this.updateGameContext(narrative.text);
|
|
// Start the game loop
|
|
this.gameLoop();
|
|
}
|
|
catch (error) {
|
|
console.error('Error starting game:', error);
|
|
this.end();
|
|
}
|
|
}
|
|
/**
|
|
* The main game loop for CLI mode
|
|
*/
|
|
gameLoop() {
|
|
if (!this.rl)
|
|
return;
|
|
this.rl.question('> ', async (input) => {
|
|
if (input.toLowerCase() === 'quit' || input.toLowerCase() === 'exit') {
|
|
this.end();
|
|
return;
|
|
}
|
|
const response = await this.processCommand(input);
|
|
console.log('\n' + response + '\n');
|
|
// Continue the game loop
|
|
this.gameLoop();
|
|
});
|
|
}
|
|
/**
|
|
* Process a player command and return the narrative response
|
|
* Used by both CLI and web interfaces
|
|
*/
|
|
async processCommand(input) {
|
|
try {
|
|
// Process player input
|
|
const actionRequest = {
|
|
playerInput: input,
|
|
currentRoom: this.engine.getWorldModel().rooms[this.engine.getCurrentState().currentRoomId].name,
|
|
visibleObjects: this.engine.getVisibleObjects().map(id => this.engine.getWorldModel().objects[id].name),
|
|
visibleCharacters: this.engine.getVisibleCharacters().map(id => this.engine.getWorldModel().characters[id].name),
|
|
possibleActions: this.engine.getAvailableActions(),
|
|
inventory: this.engine.getCurrentState().inventory.map(id => this.engine.getWorldModel().objects[id].name),
|
|
gameContext: this.gameContext
|
|
};
|
|
if (this.rl) {
|
|
console.log('Thinking...');
|
|
}
|
|
// Translate player input to action
|
|
const action = await this.llmProvider.translateAction(actionRequest);
|
|
// Process the action in the game engine
|
|
const actionResult = this.engine.processAction(action);
|
|
// If state changed, update it
|
|
if (actionResult.stateChanged && actionResult.newState) {
|
|
this.engine.getCurrentState().currentRoomId = actionResult.newState.currentRoomId;
|
|
this.engine.getCurrentState().inventory = actionResult.newState.inventory;
|
|
this.engine.getCurrentState().visitedRooms = actionResult.newState.visitedRooms;
|
|
this.engine.getCurrentState().flags = actionResult.newState.flags;
|
|
this.engine.getCurrentState().counters = actionResult.newState.counters;
|
|
}
|
|
// Generate narrative description
|
|
const narrativeRequest = {
|
|
action: `${action.action}${action.object ? ' ' + action.object : ''}${action.target ? ' on ' + action.target : ''}`,
|
|
result: actionResult.message,
|
|
roomDescription: this.engine.getCurrentRoomDescription(),
|
|
visibleObjects: this.engine.getVisibleObjects().map(id => this.engine.getWorldModel().objects[id].name),
|
|
visibleCharacters: this.engine.getVisibleCharacters().map(id => this.engine.getWorldModel().characters[id].name),
|
|
previousContext: this.gameHistory.slice(-3).join('\n'),
|
|
tone: 'descriptive'
|
|
};
|
|
const narrative = await this.llmProvider.generateNarrative(narrativeRequest);
|
|
// Store suggestions if available
|
|
if (narrative.suggestions && narrative.suggestions.length > 0) {
|
|
this.suggestedCommands = narrative.suggestions;
|
|
}
|
|
// Update game context with the new narrative
|
|
this.updateGameContext(narrative.text);
|
|
// Return the narrative text
|
|
return narrative.text;
|
|
}
|
|
catch (error) {
|
|
console.error('Error processing input:', error);
|
|
return 'Something went wrong. Please try again.';
|
|
}
|
|
}
|
|
/**
|
|
* End the game
|
|
*/
|
|
end() {
|
|
console.log('\nThanks for playing!');
|
|
if (this.rl) {
|
|
this.rl.close();
|
|
this.rl = null;
|
|
}
|
|
this.engine.end();
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
process.exit(0);
|
|
}
|
|
}
|
|
/**
|
|
* Update the game context with new narrative
|
|
*/
|
|
updateGameContext(narrative) {
|
|
// Add to history
|
|
this.gameHistory.push(narrative);
|
|
// Keep history limited to last 10 entries
|
|
if (this.gameHistory.length > 10) {
|
|
this.gameHistory.shift();
|
|
}
|
|
// Update current context (last 5 entries)
|
|
this.gameContext = this.gameHistory.slice(-5).join('\n');
|
|
}
|
|
/**
|
|
* Get the current game state
|
|
* Used by web interface
|
|
*/
|
|
getGameState() {
|
|
return {
|
|
world: this.engine.getWorldModel(),
|
|
currentRoomId: this.engine.getCurrentState().currentRoomId,
|
|
inventory: this.engine.getCurrentState().inventory,
|
|
visitedRooms: this.engine.getCurrentState().visitedRooms,
|
|
flags: this.engine.getCurrentState().flags,
|
|
counters: this.engine.getCurrentState().counters
|
|
};
|
|
}
|
|
/**
|
|
* Get the current room description
|
|
* Used by web interface
|
|
*/
|
|
getCurrentRoomDescription() {
|
|
const roomId = this.engine.getCurrentState().currentRoomId;
|
|
return this.engine.getWorldModel().rooms[roomId].description;
|
|
}
|
|
/**
|
|
* Get suggested actions for the current game state
|
|
* Used by web interface
|
|
*/
|
|
getSuggestions() {
|
|
return this.suggestedCommands;
|
|
}
|
|
/**
|
|
* Load a saved game state
|
|
* Used by web interface
|
|
*/
|
|
loadGameState(savedState) {
|
|
// Set the current state to match the saved state
|
|
this.engine.getCurrentState().currentRoomId = savedState.currentRoomId;
|
|
this.engine.getCurrentState().inventory = savedState.inventory;
|
|
this.engine.getCurrentState().visitedRooms = savedState.visitedRooms;
|
|
this.engine.getCurrentState().flags = savedState.flags;
|
|
this.engine.getCurrentState().counters = savedState.counters;
|
|
}
|
|
}
|
|
exports.GameRunner = GameRunner;
|
|
//# sourceMappingURL=game-runner.js.map
|