import { draftStore } from '../store/draft-store.js';
import { solve } from '../core/solver.js';
import { renderLevel } from '../core/ascii-renderer.js';
import { validatePosition } from '../core/validator.js';
import { Position } from '../core/types.js';
interface ToolDefinition {
name: string;
description: string;
inputSchema: object;
handler: (args: any) => Promise<{ content: Array<{ type: 'text'; text: string }> }>;
}
function formatSolverResult(result: any): string {
if (!result) return '';
if (!result.solvable) {
return `\n❌ UNSOLVABLE (checked ${result.iterations} states)`;
}
return `\n✓ Solvable in ${result.moves} moves: ${result.solution?.join(' → ') || ''}`;
}
function autoSolveAndVisualize(): 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}`;
}
function checkActiveDraft(): string | null {
const draft = draftStore.getCurrentDraft();
if (!draft) {
return 'No active draft. Use create_level first.';
}
return null;
}
function checkPositionValid(x: number, y: number, width: number, height: number): string | null {
const result = validatePosition(x, y, width, height);
if (!result.valid) {
return result.error || `Position (${x}, ${y}) is out of bounds for ${width}x${height} grid`;
}
return null;
}
export function getSpecialElementTools(): ToolDefinition[] {
return [
{
name: 'add_warp_pair',
description: 'Create a warp pair that teleports the player between two positions',
inputSchema: {
type: 'object',
properties: {
x1: { type: 'number', description: 'X coordinate of first warp' },
y1: { type: 'number', description: 'Y coordinate of first warp' },
x2: { type: 'number', description: 'X coordinate of second warp' },
y2: { type: 'number', description: 'Y coordinate of second warp' }
},
required: ['x1', 'y1', 'x2', 'y2']
},
handler: async (args: { x1: number; y1: number; x2: number; y2: number }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
const draft = draftStore.getCurrentDraft()!;
// Validate both positions
const error1 = checkPositionValid(args.x1, args.y1, draft.gridWidth, draft.gridHeight);
if (error1) {
return { content: [{ type: 'text', text: error1 }] };
}
const error2 = checkPositionValid(args.x2, args.y2, draft.gridWidth, draft.gridHeight);
if (error2) {
return { content: [{ type: 'text', text: error2 }] };
}
// Check not on start/goal
if ((args.x1 === draft.startPosition.x && args.y1 === draft.startPosition.y) ||
(args.x2 === draft.startPosition.x && args.y2 === draft.startPosition.y)) {
return { content: [{ type: 'text', text: 'Cannot place warp on start position' }] };
}
if ((args.x1 === draft.goalPosition.x && args.y1 === draft.goalPosition.y) ||
(args.x2 === draft.goalPosition.x && args.y2 === draft.goalPosition.y)) {
return { content: [{ type: 'text', text: 'Cannot place warp on goal position' }] };
}
// Check not same position
if (args.x1 === args.x2 && args.y1 === args.y2) {
return { content: [{ type: 'text', text: 'Warp positions must be different' }] };
}
draftStore.addWarpPair(args.x1, args.y1, args.x2, args.y2);
const warpId = `warp_${Date.now()}`;
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Added warp pair ${warpId}: (${args.x1},${args.y1}) ↔ (${args.x2},${args.y2})\n\n${result}`
}]
};
}
},
{
name: 'remove_warp',
description: 'Remove a warp pair by its ID',
inputSchema: {
type: 'object',
properties: {
warpId: { type: 'string', description: 'ID of the warp pair to remove' }
},
required: ['warpId']
},
handler: async (args: { warpId: string }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
draftStore.removeWarp(args.warpId);
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Removed warp pair ${args.warpId}\n\n${result}`
}]
};
}
},
{
name: 'add_thin_ice',
description: 'Add thin ice tiles that break after the player crosses them',
inputSchema: {
type: 'object',
properties: {
positions: {
type: 'array',
items: {
type: 'object',
properties: {
x: { type: 'number' },
y: { type: 'number' }
},
required: ['x', 'y']
},
description: 'Array of positions to place thin ice'
}
},
required: ['positions']
},
handler: async (args: { positions: Position[] }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
const draft = draftStore.getCurrentDraft()!;
for (const pos of args.positions) {
const validationError = checkPositionValid(pos.x, pos.y, draft.gridWidth, draft.gridHeight);
if (validationError) {
return { content: [{ type: 'text', text: validationError }] };
}
}
draftStore.addThinIce(args.positions);
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Added thin ice at ${args.positions.length} position(s)\n\n${result}`
}]
};
}
},
{
name: 'remove_thin_ice',
description: 'Remove thin ice tiles from specified positions',
inputSchema: {
type: 'object',
properties: {
positions: {
type: 'array',
items: {
type: 'object',
properties: {
x: { type: 'number' },
y: { type: 'number' }
},
required: ['x', 'y']
},
description: 'Array of positions to remove thin ice from'
}
},
required: ['positions']
},
handler: async (args: { positions: Position[] }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
for (const pos of args.positions) {
draftStore.removeThinIce([pos]);
}
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Removed thin ice from ${args.positions.length} position(s)\n\n${result}`
}]
};
}
},
{
name: 'add_pushable_rock',
description: 'Add pushable rocks that can be pushed one square by the player',
inputSchema: {
type: 'object',
properties: {
positions: {
type: 'array',
items: {
type: 'object',
properties: {
x: { type: 'number' },
y: { type: 'number' }
},
required: ['x', 'y']
},
description: 'Array of positions to place pushable rocks'
}
},
required: ['positions']
},
handler: async (args: { positions: Position[] }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
const draft = draftStore.getCurrentDraft()!;
for (const pos of args.positions) {
const validationError = checkPositionValid(pos.x, pos.y, draft.gridWidth, draft.gridHeight);
if (validationError) {
return { content: [{ type: 'text', text: validationError }] };
}
}
draftStore.addPushableRock(args.positions);
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Added pushable rock(s) at ${args.positions.length} position(s)\n\n${result}`
}]
};
}
},
{
name: 'remove_pushable_rock',
description: 'Remove pushable rocks from specified positions',
inputSchema: {
type: 'object',
properties: {
positions: {
type: 'array',
items: {
type: 'object',
properties: {
x: { type: 'number' },
y: { type: 'number' }
},
required: ['x', 'y']
},
description: 'Array of positions to remove pushable rocks from'
}
},
required: ['positions']
},
handler: async (args: { positions: Position[] }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
for (const pos of args.positions) {
draftStore.removePushableRock([pos]);
}
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Removed pushable rock(s) from ${args.positions.length} position(s)\n\n${result}`
}]
};
}
},
{
name: 'set_pressure_plate',
description: 'Set the pressure plate position (only one allowed per level)',
inputSchema: {
type: 'object',
properties: {
x: { type: 'number', description: 'X coordinate' },
y: { type: 'number', description: 'Y coordinate' }
},
required: ['x', 'y']
},
handler: async (args: { x: number; y: number }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
const draft = draftStore.getCurrentDraft()!;
const validationError = checkPositionValid(args.x, args.y, draft.gridWidth, draft.gridHeight);
if (validationError) {
return { content: [{ type: 'text', text: validationError }] };
}
draftStore.setPressurePlate(args.x, args.y);
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Set pressure plate at (${args.x},${args.y})\n\n${result}`
}]
};
}
},
{
name: 'remove_pressure_plate',
description: 'Remove the pressure plate from the level',
inputSchema: {
type: 'object',
properties: {}
},
handler: async () => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
draftStore.removePressurePlate();
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Removed pressure plate\n\n${result}`
}]
};
}
},
{
name: 'set_barrier',
description: 'Set the barrier position (only one allowed per level, deactivated by pressure plate)',
inputSchema: {
type: 'object',
properties: {
x: { type: 'number', description: 'X coordinate' },
y: { type: 'number', description: 'Y coordinate' }
},
required: ['x', 'y']
},
handler: async (args: { x: number; y: number }) => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
const draft = draftStore.getCurrentDraft()!;
const validationError = checkPositionValid(args.x, args.y, draft.gridWidth, draft.gridHeight);
if (validationError) {
return { content: [{ type: 'text', text: validationError }] };
}
draftStore.setBarrier(args.x, args.y);
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Set barrier at (${args.x},${args.y})\n\n${result}`
}]
};
}
},
{
name: 'remove_barrier',
description: 'Remove the barrier from the level',
inputSchema: {
type: 'object',
properties: {}
},
handler: async () => {
const error = checkActiveDraft();
if (error) {
return { content: [{ type: 'text', text: error }] };
}
draftStore.removeBarrier();
const result = autoSolveAndVisualize();
return {
content: [{
type: 'text',
text: `Removed barrier\n\n${result}`
}]
};
}
}
];
}