import { draftStore } from '../store/draft-store.js';
import { solve } from '../core/solver.js';
import { renderLevel, formatSolverResult } from '../core/ascii-renderer.js';
import { validatePosition, validatePlaceableTileType, validateNotOnStartOrGoal } from '../core/validator.js';
import { PlaceableTileType, DraftState } from '../core/types.js';
interface ToolDefinition {
name: string;
description: string;
inputSchema: object;
handler: (args: any) => Promise<{ content: Array<{ type: 'text'; text: string }> }>;
}
function autoSolveAndVisualize(draft: DraftState): string {
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 });
const solverInfo = current.lastSolverResult ? formatSolverResult(current.lastSolverResult) : '';
return `${viz}\n${solverInfo}`;
}
export function getTileOperationTools(): ToolDefinition[] {
return [
{
name: 'place_tile',
description: 'Place a tile (rock, lava, or hot_coals) at the specified position. Auto-solves after placement.',
inputSchema: {
type: 'object',
properties: {
x: { type: 'number', description: 'X coordinate' },
y: { type: 'number', description: 'Y coordinate' },
type: { type: 'string', enum: ['rock', 'lava', 'hot_coals', 'spike'], description: 'Tile type' },
},
required: ['x', 'y', 'type'],
},
handler: async (args) => {
const draft = draftStore.getCurrentDraft();
if (!draft) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] };
const posCheck = validatePosition(args.x, args.y, draft.gridWidth, draft.gridHeight);
if (!posCheck.valid) return { content: [{ type: 'text', text: posCheck.error! }] };
if (!validatePlaceableTileType(args.type)) return { content: [{ type: 'text', text: `Invalid tile type: ${args.type}. Use rock, lava, hot_coals, or spike.` }] };
const startGoalCheck = validateNotOnStartOrGoal(args.x, args.y, draft);
if (!startGoalCheck.valid) return { content: [{ type: 'text', text: startGoalCheck.error! }] };
const updated = draftStore.placeElement(args.x, args.y, args.type);
return { content: [{ type: 'text', text: `Placed ${args.type} at (${args.x}, ${args.y})\n\n${autoSolveAndVisualize(updated)}` }] };
},
},
{
name: 'remove_tile',
description: 'Remove any tile/obstacle at the specified position. Auto-solves after removal.',
inputSchema: {
type: 'object',
properties: {
x: { type: 'number', description: 'X coordinate' },
y: { type: 'number', description: 'Y coordinate' },
},
required: ['x', 'y'],
},
handler: async (args) => {
const draft = draftStore.getCurrentDraft();
if (!draft) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] };
const updated = draftStore.removeElement(args.x, args.y);
return { content: [{ type: 'text', text: `Removed element at (${args.x}, ${args.y})\n\n${autoSolveAndVisualize(updated)}` }] };
},
},
{
name: 'place_tiles_batch',
description: 'Place multiple tiles at once. More efficient than placing one at a time. Auto-solves after all placements.',
inputSchema: {
type: 'object',
properties: {
tiles: {
type: 'array',
items: {
type: 'object',
properties: {
x: { type: 'number' },
y: { type: 'number' },
type: { type: 'string', enum: ['rock', 'lava', 'hot_coals', 'spike'] },
},
required: ['x', 'y', 'type'],
},
description: 'Array of tiles to place',
},
},
required: ['tiles'],
},
handler: async (args) => {
const draft = draftStore.getCurrentDraft();
if (!draft) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] };
const errors: string[] = [];
let placed = 0;
for (const tile of args.tiles) {
const posCheck = validatePosition(tile.x, tile.y, draft.gridWidth, draft.gridHeight);
if (!posCheck.valid) { errors.push(`(${tile.x},${tile.y}): ${posCheck.error}`); continue; }
if (!validatePlaceableTileType(tile.type)) { errors.push(`(${tile.x},${tile.y}): invalid type ${tile.type}`); continue; }
const sgCheck = validateNotOnStartOrGoal(tile.x, tile.y, draft);
if (!sgCheck.valid) { errors.push(`(${tile.x},${tile.y}): ${sgCheck.error}`); continue; }
draftStore.placeElement(tile.x, tile.y, tile.type);
placed++;
}
const current = draftStore.getCurrentDraft()!;
const result = `Placed ${placed}/${args.tiles.length} tiles.${errors.length ? '\nErrors:\n' + errors.join('\n') : ''}`;
return { content: [{ type: 'text', text: `${result}\n\n${autoSolveAndVisualize(current)}` }] };
},
},
{
name: 'fill_region',
description: 'Fill a rectangular region with a tile type. Auto-solves after fill.',
inputSchema: {
type: 'object',
properties: {
x1: { type: 'number', description: 'Top-left X' },
y1: { type: 'number', description: 'Top-left Y' },
x2: { type: 'number', description: 'Bottom-right X' },
y2: { type: 'number', description: 'Bottom-right Y' },
type: { type: 'string', enum: ['rock', 'lava', 'hot_coals', 'spike'], description: 'Tile type' },
},
required: ['x1', 'y1', 'x2', 'y2', 'type'],
},
handler: async (args) => {
const draft = draftStore.getCurrentDraft();
if (!draft) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] };
if (!validatePlaceableTileType(args.type)) return { content: [{ type: 'text', text: `Invalid tile type: ${args.type}` }] };
let placed = 0;
const minX = Math.max(1, Math.min(args.x1, args.x2));
const maxX = Math.min(draft.gridWidth - 2, Math.max(args.x1, args.x2));
const minY = Math.max(1, Math.min(args.y1, args.y2));
const maxY = Math.min(draft.gridHeight - 2, Math.max(args.y1, args.y2));
for (let y = minY; y <= maxY; y++) {
for (let x = minX; x <= maxX; x++) {
const sgCheck = validateNotOnStartOrGoal(x, y, draft);
if (!sgCheck.valid) continue;
draftStore.placeElement(x, y, args.type);
placed++;
}
}
const current = draftStore.getCurrentDraft()!;
return { content: [{ type: 'text', text: `Filled ${placed} tiles with ${args.type}\n\n${autoSolveAndVisualize(current)}` }] };
},
},
{
name: 'clear_region',
description: 'Clear all tiles in a rectangular region. Auto-solves after clear.',
inputSchema: {
type: 'object',
properties: {
x1: { type: 'number', description: 'Top-left X' },
y1: { type: 'number', description: 'Top-left Y' },
x2: { type: 'number', description: 'Bottom-right X' },
y2: { type: 'number', description: 'Bottom-right Y' },
},
required: ['x1', 'y1', 'x2', 'y2'],
},
handler: async (args) => {
const draft = draftStore.getCurrentDraft();
if (!draft) return { content: [{ type: 'text', text: 'No active draft. Use create_level first.' }] };
let cleared = 0;
const minX = Math.max(1, Math.min(args.x1, args.x2));
const maxX = Math.min(draft.gridWidth - 2, Math.max(args.x1, args.x2));
const minY = Math.max(1, Math.min(args.y1, args.y2));
const maxY = Math.min(draft.gridHeight - 2, Math.max(args.y1, args.y2));
for (let y = minY; y <= maxY; y++) {
for (let x = minX; x <= maxX; x++) {
draftStore.removeElement(x, y);
cleared++;
}
}
const current = draftStore.getCurrentDraft()!;
return { content: [{ type: 'text', text: `Cleared ${cleared} positions\n\n${autoSolveAndVisualize(current)}` }] };
},
},
];
}