/** * Shalloteer Game Validator * Provides real-time validation and feedback for generated game code */ interface ValidationResult { valid: boolean; errors: string[]; warnings: string[]; suggestions: string[]; } const VALID_ENTITY_TAGS = [ 'world', 'entity', 'static-part', 'dynamic-part', 'kinematic-part', 'player', 'tween', 'animation', 'collider', 'renderer', 'transform' ]; const VALID_ATTRIBUTES = { 'world': ['canvas', 'sky', 'fog', 'gravity'], 'static-part': ['pos', 'euler', 'size', 'shape', 'color', 'mass', 'restitution', 'friction'], 'dynamic-part': ['pos', 'euler', 'size', 'shape', 'color', 'mass', 'restitution', 'friction', 'linear-damping', 'angular-damping'], 'kinematic-part': ['pos', 'euler', 'size', 'shape', 'color'], 'player': ['pos', 'speed', 'jump-height'], 'tween': ['target', 'from', 'to', 'duration', 'ease', 'loop', 'delay'], 'entity': ['pos', 'euler', 'scale', 'transform', 'body', 'collider', 'renderer'], }; const VALID_SHAPES = ['box', 'sphere', 'capsule', 'cylinder', 'cone']; const VALID_EASE_TYPES = ['linear', 'sine-in', 'sine-out', 'sine-in-out', 'quad-in', 'quad-out', 'quad-in-out']; const VALID_LOOP_TYPES = ['none', 'repeat', 'ping-pong']; export function validateShalloteerGame(html: string): ValidationResult { const result: ValidationResult = { valid: true, errors: [], warnings: [], suggestions: [] }; try { // Extract world tag content const worldMatch = html.match(/]*>([\s\S]*?)<\/world>/); if (!worldMatch) { result.valid = false; result.errors.push("Missing tag. All game content must be inside a tag."); result.suggestions.push("Wrap your game entities in: ..."); return result; } // Check for canvas attribute const canvasMatch = worldMatch[0].match(/canvas="([^"]*)"/); if (!canvasMatch) { result.warnings.push("Missing canvas attribute on tag."); result.suggestions.push("Add canvas=\"#game-canvas\" to your tag."); } // Validate entity tags const entityRegex = /<([\w-]+)([^>]*)>/g; let match; const usedTags = new Set(); while ((match = entityRegex.exec(worldMatch[1])) !== null) { const [, tagName, attributes] = match; usedTags.add(tagName); if (!VALID_ENTITY_TAGS.includes(tagName)) { result.errors.push(`Invalid tag: <${tagName}>. Valid tags are: ${VALID_ENTITY_TAGS.join(', ')}`); result.valid = false; } // Validate attributes for known tags if (VALID_ATTRIBUTES[tagName as keyof typeof VALID_ATTRIBUTES]) { const validAttrs = VALID_ATTRIBUTES[tagName as keyof typeof VALID_ATTRIBUTES]; const attrRegex = /(\w+(?:-\w+)*)="([^"]*)"/g; let attrMatch; while ((attrMatch = attrRegex.exec(attributes)) !== null) { const [, attrName, attrValue] = attrMatch; if (!validAttrs.includes(attrName) && !['id', 'class'].includes(attrName)) { result.warnings.push(`Unknown attribute '${attrName}' on <${tagName}>. Valid attributes: ${validAttrs.join(', ')}`); } // Validate specific attribute values if (attrName === 'shape' && !VALID_SHAPES.includes(attrValue)) { result.errors.push(`Invalid shape '${attrValue}'. Valid shapes: ${VALID_SHAPES.join(', ')}`); result.valid = false; } if (attrName === 'ease' && !VALID_EASE_TYPES.includes(attrValue)) { result.warnings.push(`Unknown ease type '${attrValue}'. Valid types: ${VALID_EASE_TYPES.join(', ')}`); } if (attrName === 'loop' && !VALID_LOOP_TYPES.includes(attrValue)) { result.warnings.push(`Unknown loop type '${attrValue}'. Valid types: ${VALID_LOOP_TYPES.join(', ')}`); } } } } // Provide suggestions based on what's missing if (!usedTags.has('static-part') && !usedTags.has('dynamic-part')) { result.suggestions.push("Add some objects to your game! Use for platforms and for physics objects."); } if (!html.includes('GAME.run()') && !html.includes('GAME.withSystem')) { result.errors.push("Missing GAME initialization. Add GAME.run() in a script tag."); result.valid = false; } // Check for common mistakes if (html.includes('/>') && (html.includes('static-part />') || html.includes('dynamic-part />'))) { result.warnings.push("Use explicit closing tags (e.g., ) instead of self-closing for better compatibility."); } if (!html.includes('#game-canvas')) { result.errors.push("Missing canvas element. Add before the tag."); result.valid = false; } } catch (error) { result.valid = false; result.errors.push(`Parsing error: ${error}`); } return result; } export function generateCorrectionPrompt(validation: ValidationResult, originalPrompt: string): string { let prompt = "The generated game code has some issues that need to be fixed:\n\n"; if (validation.errors.length > 0) { prompt += "**ERRORS (must fix):**\n"; validation.errors.forEach(error => { prompt += `- ${error}\n`; }); prompt += "\n"; } if (validation.warnings.length > 0) { prompt += "**WARNINGS (should fix):**\n"; validation.warnings.forEach(warning => { prompt += `- ${warning}\n`; }); prompt += "\n"; } if (validation.suggestions.length > 0) { prompt += "**SUGGESTIONS:**\n"; validation.suggestions.forEach(suggestion => { prompt += `- ${suggestion}\n`; }); prompt += "\n"; } prompt += `Please regenerate the game code fixing these issues. Original request: "${originalPrompt}"`; return prompt; } export function createGameExamples(): string { return ` ## Working Shalloteer Game Examples: ### Example 1: Basic Platformer \`\`\`html \`\`\` ### Example 2: Moving Platform \`\`\`html \`\`\` ### Example 3: Physics Objects \`\`\`html \`\`\` Remember: - ALL game entities must be inside tags - Use explicit closing tags () not self-closing () - Include canvas element and GAME.run() script - Player controller is auto-created if not specified `; }