"use strict"; /** * YAML World Model Parser * Loads and validates world definitions from YAML files */ 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.YamlWorldParser = void 0; const fs = __importStar(require("fs/promises")); const yaml = __importStar(require("js-yaml")); class YamlWorldParser { /** * Load a world model from a YAML file */ static async loadFromFile(filePath) { try { const fileContents = await fs.readFile(filePath, 'utf8'); const worldData = yaml.load(fileContents); return this.validateAndTransform(worldData); } catch (error) { console.error(`Error loading world from ${filePath}:`, error); throw new Error(`Failed to load world from ${filePath}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Validate the loaded YAML data and transform it into a WorldModel */ static validateAndTransform(data) { if (!data || typeof data !== 'object') { throw new Error('Invalid world data: must be an object'); } const worldData = data; // Validate required top-level fields this.validateRequiredFields(worldData, ['title', 'author', 'version', 'introduction', 'rooms', 'initialState']); // Transform and validate the world model const worldModel = { title: this.validateString(worldData.title, 'title'), author: this.validateString(worldData.author, 'author'), version: this.validateString(worldData.version, 'version'), introduction: this.validateString(worldData.introduction, 'introduction'), rooms: this.validateRooms(worldData.rooms), objects: this.validateObjects(worldData.objects), characters: this.validateCharacters(worldData.characters), actions: this.validateActions(worldData.actions), initialState: this.validateInitialState(worldData.initialState) }; // Validate references between entities this.validateReferences(worldModel); return worldModel; } /** * Validate that an object has all required fields */ static validateRequiredFields(data, requiredFields) { for (const field of requiredFields) { if (!(field in data)) { throw new Error(`Missing required field: ${field}`); } } } /** * Validate that a value is a string */ static validateString(value, fieldName) { if (typeof value !== 'string') { throw new Error(`Field ${fieldName} must be a string`); } return value; } /** * Validate room definitions */ static validateRooms(rooms) { if (!rooms || typeof rooms !== 'object') { throw new Error('Rooms must be an object mapping room IDs to room definitions'); } const roomsData = rooms; const validatedRooms = {}; for (const [roomId, roomData] of Object.entries(roomsData)) { if (!roomData || typeof roomData !== 'object') { throw new Error(`Room ${roomId} must be an object`); } const room = roomData; this.validateRequiredFields(room, ['name', 'description', 'exits']); validatedRooms[roomId] = { id: roomId, name: this.validateString(room.name, `rooms.${roomId}.name`), description: this.validateString(room.description, `rooms.${roomId}.description`), exits: this.validateExits(room.exits, roomId), objects: this.validateStringArray(room.objects || [], `rooms.${roomId}.objects`), characters: this.validateStringArray(room.characters || [], `rooms.${roomId}.characters`) }; } return validatedRooms; } /** * Validate exit definitions */ static validateExits(exits, roomId) { if (!Array.isArray(exits)) { throw new Error(`Exits for room ${roomId} must be an array`); } return exits.map((exit, index) => { if (!exit || typeof exit !== 'object') { throw new Error(`Exit ${index} in room ${roomId} must be an object`); } const exitData = exit; this.validateRequiredFields(exitData, ['direction', 'targetRoomId']); return { direction: this.validateString(exitData.direction, `rooms.${roomId}.exits[${index}].direction`), targetRoomId: this.validateString(exitData.targetRoomId, `rooms.${roomId}.exits[${index}].targetRoomId`), description: exitData.description ? this.validateString(exitData.description, `rooms.${roomId}.exits[${index}].description`) : undefined, isLocked: typeof exitData.isLocked === 'boolean' ? exitData.isLocked : false, keyId: exitData.keyId ? this.validateString(exitData.keyId, `rooms.${roomId}.exits[${index}].keyId`) : undefined }; }); } /** * Validate object definitions */ static validateObjects(objects) { if (!objects) return {}; // Objects are optional if (typeof objects !== 'object') { throw new Error('Objects must be an object mapping object IDs to object definitions'); } const objectsData = objects; const validatedObjects = {}; for (const [objectId, objectData] of Object.entries(objectsData)) { if (!objectData || typeof objectData !== 'object') { throw new Error(`Object ${objectId} must be an object`); } const obj = objectData; this.validateRequiredFields(obj, ['name', 'description', 'traits', 'allowedActions']); validatedObjects[objectId] = { id: objectId, name: this.validateString(obj.name, `objects.${objectId}.name`), description: this.validateString(obj.description, `objects.${objectId}.description`), traits: this.validateStringArray(obj.traits, `objects.${objectId}.traits`), states: this.validateObjectStates(obj.states, objectId), allowedActions: this.validateStringArray(obj.allowedActions, `objects.${objectId}.allowedActions`), containedObjects: obj.containedObjects ? this.validateStringArray(obj.containedObjects, `objects.${objectId}.containedObjects`) : [] }; } return validatedObjects; } /** * Validate character definitions */ static validateCharacters(characters) { if (!characters) return {}; // Characters are optional if (typeof characters !== 'object') { throw new Error('Characters must be an object mapping character IDs to character definitions'); } const charactersData = characters; const validatedCharacters = {}; for (const [characterId, characterData] of Object.entries(charactersData)) { if (!characterData || typeof characterData !== 'object') { throw new Error(`Character ${characterId} must be an object`); } const character = characterData; this.validateRequiredFields(character, ['name', 'description', 'dialogue', 'defaultResponse']); validatedCharacters[characterId] = { id: characterId, name: this.validateString(character.name, `characters.${characterId}.name`), description: this.validateString(character.description, `characters.${characterId}.description`), dialogue: this.validateDialogue(character.dialogue, characterId), inventory: this.validateStringArray(character.inventory || [], `characters.${characterId}.inventory`), defaultResponse: this.validateString(character.defaultResponse, `characters.${characterId}.defaultResponse`), mood: character.mood ? this.validateString(character.mood, `characters.${characterId}.mood`) : undefined }; } return validatedCharacters; } /** * Validate action definitions */ static validateActions(actions) { if (!actions) return {}; // Actions are optional if (typeof actions !== 'object') { throw new Error('Actions must be an object mapping action names to action definitions'); } const actionsData = actions; const validatedActions = {}; for (const [actionName, actionData] of Object.entries(actionsData)) { if (!actionData || typeof actionData !== 'object') { throw new Error(`Action ${actionName} must be an object`); } const action = actionData; this.validateRequiredFields(action, ['patterns', 'handler']); validatedActions[actionName] = { name: actionName, patterns: this.validateStringArray(action.patterns, `actions.${actionName}.patterns`), requiresObject: typeof action.requiresObject === 'boolean' ? action.requiresObject : false, requiresTarget: typeof action.requiresTarget === 'boolean' ? action.requiresTarget : false, handler: this.validateString(action.handler, `actions.${actionName}.handler`) }; } return validatedActions; } /** * Validate initial game state */ static validateInitialState(initialState) { if (!initialState || typeof initialState !== 'object') { throw new Error('Initial state must be an object'); } const stateData = initialState; this.validateRequiredFields(stateData, ['currentRoomId']); return { currentRoomId: this.validateString(stateData.currentRoomId, 'initialState.currentRoomId'), inventory: this.validateStringArray(stateData.inventory || [], 'initialState.inventory'), visitedRooms: this.validateStringArray(stateData.visitedRooms || [], 'initialState.visitedRooms'), flags: this.validateFlags(stateData.flags), counters: this.validateCounters(stateData.counters) }; } /** * Validate object states (record of boolean values) */ static validateObjectStates(states, objectId) { if (!states) return {}; if (typeof states !== 'object') { throw new Error(`States for object ${objectId} must be an object`); } const statesData = states; const validatedStates = {}; for (const [stateName, stateValue] of Object.entries(statesData)) { if (typeof stateValue !== 'boolean') { throw new Error(`State ${stateName} for object ${objectId} must be a boolean value`); } validatedStates[stateName] = stateValue; } return validatedStates; } /** * Validate dialogue (record of string values) */ static validateDialogue(dialogue, characterId) { if (!dialogue || typeof dialogue !== 'object') { throw new Error(`Dialogue for character ${characterId} must be an object`); } const dialogueData = dialogue; const validatedDialogue = {}; for (const [topic, response] of Object.entries(dialogueData)) { validatedDialogue[topic] = this.validateString(response, `characters.${characterId}.dialogue.${topic}`); } return validatedDialogue; } /** * Validate flags (record of boolean values) */ static validateFlags(flags) { if (!flags) return {}; if (typeof flags !== 'object') { throw new Error('Flags must be an object'); } const flagsData = flags; const validatedFlags = {}; for (const [flagName, flagValue] of Object.entries(flagsData)) { if (typeof flagValue !== 'boolean') { throw new Error(`Flag ${flagName} must be a boolean value`); } validatedFlags[flagName] = flagValue; } return validatedFlags; } /** * Validate counters (record of number values) */ static validateCounters(counters) { if (!counters) return {}; if (typeof counters !== 'object') { throw new Error('Counters must be an object'); } const countersData = counters; const validatedCounters = {}; for (const [counterName, counterValue] of Object.entries(countersData)) { if (typeof counterValue !== 'number') { throw new Error(`Counter ${counterName} must be a numeric value`); } validatedCounters[counterName] = counterValue; } return validatedCounters; } /** * Validate that an array of strings is valid */ static validateStringArray(arr, fieldName) { if (!arr) return []; if (!Array.isArray(arr)) { throw new Error(`Field ${fieldName} must be an array`); } return arr.map((item, index) => { if (typeof item !== 'string') { throw new Error(`Item at index ${index} in ${fieldName} must be a string`); } return item; }); } /** * Validate references between entities */ static validateReferences(worldModel) { const { rooms, objects, characters, initialState } = worldModel; // Check that the initial room exists if (!rooms[initialState.currentRoomId]) { throw new Error(`Initial room ${initialState.currentRoomId} does not exist`); } // Check room exits for (const [roomId, room] of Object.entries(rooms)) { for (const exit of room.exits) { if (!rooms[exit.targetRoomId]) { throw new Error(`Room ${roomId} has an exit to non-existent room ${exit.targetRoomId}`); } if (exit.keyId && !objects[exit.keyId]) { throw new Error(`Room ${roomId} has an exit requiring non-existent key ${exit.keyId}`); } } // Check room objects for (const objectId of room.objects) { if (!objects[objectId]) { throw new Error(`Room ${roomId} contains non-existent object ${objectId}`); } } // Check room characters for (const characterId of room.characters) { if (!characters[characterId]) { throw new Error(`Room ${roomId} contains non-existent character ${characterId}`); } } } // Check object containment for (const [objectId, object] of Object.entries(objects)) { if (object.containedObjects) { for (const containedId of object.containedObjects) { if (!objects[containedId]) { throw new Error(`Object ${objectId} contains non-existent object ${containedId}`); } } } } // Check character inventory for (const [characterId, character] of Object.entries(characters)) { for (const objectId of character.inventory) { if (!objects[objectId]) { throw new Error(`Character ${characterId} has non-existent object ${objectId} in inventory`); } } } // Check player inventory for (const objectId of initialState.inventory) { if (!objects[objectId]) { throw new Error(`Initial inventory contains non-existent object ${objectId}`); } } } } exports.YamlWorldParser = YamlWorldParser; //# sourceMappingURL=yaml-parser.js.map