export_level
Export the current working draft as PuzzleData JSON for building, validating, and publishing Ice Puzzle levels with integrated solver feedback and quality checks.
Instructions
Export the current working draft as PuzzleData JSON
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/tools/level-management.ts:114-123 (handler)The complete export_level tool registration including name, description, input schema, and async handler function. The handler calls draftStore.exportPuzzleData() and returns the PuzzleData as formatted JSON.{ name: 'export_level', description: 'Export the current working draft as PuzzleData JSON.', inputSchema: { type: 'object', properties: {} }, handler: async () => { const puzzleData = draftStore.exportPuzzleData(); if (!puzzleData) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] }; return { content: [{ type: 'text', text: JSON.stringify(puzzleData, null, 2) }] }; }, },
- src/store/draft-store.ts:195-269 (helper)The exportPuzzleData() helper method that converts the current DraftState into a PuzzleData object. Handles warp pairs conversion, barrier normalization, obstacle filtering, and optional fields (warps, thinIceTiles, pushableRocks, pressurePlate, barrier).exportPuzzleData(): PuzzleData | null { if (!this.currentDraft) { return null; } const draft = this.currentDraft; // Convert warp pairs to WarpZones const warps = draft.warpPairs.flatMap(pair => pair.positions.map(pos => ({ x: pos.x, y: pos.y, id: pair.id, })) ); // Barrier: prefer draft.barrier (Position), fall back to first barrier obstacle const barrierObstacles = draft.obstacles.filter(obs => obs.type === 'barrier'); const exportedBarrier = draft.barrier || (barrierObstacles.length > 0 ? { x: barrierObstacles[0].x, y: barrierObstacles[0].y } : null); // Regular obstacles: exclude barrier (separate field) const regularObstacles = draft.obstacles.filter( obs => obs.type !== 'barrier' && obs.type !== 'thin_ice' && obs.type !== 'pushable_rock' ); // Normalize legacy encodings where thin ice / pushable rocks were stored as obstacles. const normalizedThinIce = [...draft.thinIceTiles]; for (const obs of draft.obstacles) { if (obs.type === 'thin_ice' && !normalizedThinIce.some((tile) => tile.x === obs.x && tile.y === obs.y)) { normalizedThinIce.push({ x: obs.x, y: obs.y }); } } const normalizedPushableRocks = [...draft.pushableRocks]; for (const obs of draft.obstacles) { if (obs.type === 'pushable_rock' && !normalizedPushableRocks.some((rock) => rock.x === obs.x && rock.y === obs.y)) { normalizedPushableRocks.push({ x: obs.x, y: obs.y }); } } const puzzleData: PuzzleData = { id: draft.id || this.generateId(), name: draft.name, theme: 'ice', width: draft.gridWidth, height: draft.gridHeight, par: draft.par, start: draft.startPosition, goal: draft.goalPosition, obstacles: regularObstacles, }; if (warps.length > 0) { puzzleData.warps = warps; } if (normalizedThinIce.length > 0) { puzzleData.thinIceTiles = normalizedThinIce; } if (normalizedPushableRocks.length > 0) { puzzleData.pushableRocks = normalizedPushableRocks; } if (draft.pressurePlate) { puzzleData.pressurePlate = draft.pressurePlate; } if (exportedBarrier) { puzzleData.barrier = exportedBarrier; } return puzzleData; }
- src/core/types.ts:28-43 (schema)The PuzzleData interface definition that defines the output schema structure for exported levels, including required fields (id, name, theme, width, height, par, start, goal, obstacles) and optional fields (warps, thinIceTiles, pushableRocks, pressurePlate, barrier).export interface PuzzleData { id: string; name: string; theme: string; width: number; height: number; par: number; start: Position; goal: Position; obstacles: Obstacle[]; warps?: WarpZone[]; thinIceTiles?: Position[]; pushableRocks?: Position[]; pressurePlate?: Position; barrier?: Position; }
- src/tools/level-management.ts:34-125 (registration)The getLevelManagementTools() function that returns an array of tool definitions including export_level. This is the registration point where export_level is exported and made available to the MCP system.export function getLevelManagementTools(): ToolDefinition[] { return [ { name: 'create_level', description: 'Create a new empty ice puzzle level. This becomes the current working draft.', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Level name' }, width: { type: 'number', description: 'Grid width (5-25, default 10)', minimum: 5, maximum: 25 }, height: { type: 'number', description: 'Grid height (5-25, default 10)', minimum: 5, maximum: 25 }, }, required: ['name'], }, handler: async (args) => { const draft = draftStore.createDraft(args.name, args.width, args.height); const viz = renderLevel(draft, { showCoords: true }); return { content: [{ type: 'text', text: `Level "${args.name}" created!\n\n${formatDraftSummary(draft)}\n\n${viz}` }] }; }, }, { name: 'get_level', description: 'Get the current working draft level state with full details and visualization.', inputSchema: { type: 'object', properties: {} }, handler: async () => { const draft = draftStore.getCurrentDraft(); if (!draft) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] }; // Auto-solve if dirty if (draft.isDirty || !draft.lastSolverResult) { const puzzleData = draftStore.exportPuzzleData(); if (puzzleData) { const result = solve(puzzleData); draftStore.updateDraft({ lastSolverResult: result, isDirty: false }); } } const current = draftStore.getCurrentDraft()!; const viz = renderLevel(current, { showCoords: true, showSolution: true, solution: current.lastSolverResult?.solution }); return { content: [{ type: 'text', text: `${formatDraftSummary(current)}\n\n${viz}` }] }; }, }, { name: 'list_drafts', description: 'List all saved draft levels.', inputSchema: { type: 'object', properties: {} }, handler: async () => { const drafts = draftStore.listDrafts(); if (drafts.length === 0) return { content: [{ type: 'text', text: 'No saved drafts.' }] }; const list = drafts.map((d, i) => `${i + 1}. ${d.name} (${d.id}) - ${d.description || 'No description'}`).join('\n'); return { content: [{ type: 'text', text: `Saved drafts:\n${list}` }] }; }, }, { name: 'delete_draft', description: 'Delete a saved draft level.', inputSchema: { type: 'object', properties: { draftId: { type: 'string', description: 'Draft ID to delete' } }, required: ['draftId'], }, handler: async (args) => { const success = draftStore.deleteDraft(args.draftId); return { content: [{ type: 'text', text: success ? `Draft ${args.draftId} deleted.` : `Draft ${args.draftId} not found.` }] }; }, }, { name: 'import_level', description: 'Import a level from PuzzleData JSON. Sets it as the current working draft.', inputSchema: { type: 'object', properties: { puzzleData: { type: 'object', description: 'PuzzleData JSON object' } }, required: ['puzzleData'], }, handler: async (args) => { const validation = validatePuzzleData(args.puzzleData); if (!validation.valid) return { content: [{ type: 'text', text: `Invalid puzzle data: ${validation.error}` }] }; const draft = draftStore.importPuzzleData(validation.data!); const viz = renderLevel(draft, { showCoords: true }); return { content: [{ type: 'text', text: `Level imported!\n\n${formatDraftSummary(draft)}\n\n${viz}` }] }; }, }, { name: 'export_level', description: 'Export the current working draft as PuzzleData JSON.', inputSchema: { type: 'object', properties: {} }, handler: async () => { const puzzleData = draftStore.exportPuzzleData(); if (!puzzleData) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] }; return { content: [{ type: 'text', text: JSON.stringify(puzzleData, null, 2) }] }; }, }, ]; }