import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" import { parse } from "@babel/parser"; import generate from "@babel/generator"; import type { VariableDeclaration } from "@babel/types"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } // Function to extract sound name from powerLogic string export function extractSoundNameFromPowerLogic(powerLogic: string) { // Regular expression to match playSound('soundName') or playSound("soundName") const playSoundRegex = /playSound\(['"]([^'"]+)['"]\)/; const match = powerLogic.match(playSoundRegex); return match ? match[1] : null; } export const adjustColor = (color: string, amount: number): string => { return ( "#" + color .replace(/^#/, "") .replace(/../g, (color) => ( "0" + Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16) ).substr(-2) ) ); }; export const lerp = (start: number, end: number, factor: number): number => { return start + (end - start) * factor; }; export const easeOutQuart = (t: number): number => { return 1 - Math.pow(1 - t, 4); }; export const generateIndianPrimeTimeSchedule = ( numberOfVideos: number ): string[] => { const schedule: string[] = []; const timeZone = "Asia/Kolkata"; const nowInIndia = new Date(new Date().toLocaleString("en-US", { timeZone })); for (let i = 0; i < numberOfVideos; i++) { const targetDate = new Date(nowInIndia); targetDate.setDate(nowInIndia.getDate() + i); const minHour = 7; const maxHour = 22; const randomHour = Math.floor(Math.random() * (maxHour - minHour + 1)) + minHour; const randomMinute = Math.floor(Math.random() * 60); targetDate.setHours(randomHour, randomMinute, 0, 0); const year = targetDate.getFullYear(); const month = String(targetDate.getMonth() + 1).padStart(2, "0"); const day = String(targetDate.getDate()).padStart(2, "0"); const hour = String(randomHour).padStart(2, "0"); const minute = String(randomMinute).padStart(2, "0"); const isoStringInIndia = `${year}-${month}-${day}T${hour}:${minute}:00.000`; const finalUtcDate = new Date(isoStringInIndia + "+05:30"); schedule.push(finalUtcDate.toISOString()); } return schedule; }; // In lib/utils.ts // The checkCollision function is fine and does not need to be changed. export const checkCollision = ( rect1: { x: number; y: number; width: number; height: number }, rect2: { x: number; y: number; width: number; height: number } ): boolean => rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y; /** * A more robust collision resolution function for two dynamic objects (heroes/squares). * It prevents objects from getting stuck by first correcting their positions * and then applying realistic elastic collision physics. * @param f1 The first fighter object. * @param f2 The second fighter object. */ export const resolveCollision = ( f1: { x: number; y: number; width: number; height: number; dx: number; dy: number; }, f2: { x: number; y: number; width: number; height: number; dx: number; dy: number; } ) => { // --- 1. Calculate Overlap and Centers --- const f1CenterX = f1.x + f1.width / 2; const f1CenterY = f1.y + f1.height / 2; const f2CenterX = f2.x + f2.width / 2; const f2CenterY = f2.y + f2.height / 2; const dx = f1CenterX - f2CenterX; const dy = f1CenterY - f2CenterY; const combinedHalfWidths = f1.width / 2 + f2.width / 2; const combinedHalfHeights = f1.height / 2 + f2.height / 2; const overlapX = combinedHalfWidths - Math.abs(dx); const overlapY = combinedHalfHeights - Math.abs(dy); // This check is important for when objects are perfectly aligned if (overlapX <= 0 || overlapY <= 0) return; // --- 2. POSITION CORRECTION (The "Anti-Stuck" Logic) --- // This is the most critical part. We determine which axis has the *least* overlap // and push the objects apart only on that axis. This prevents "juddering". if (overlapX < overlapY) { // Push apart on the X-axis const pushAmount = overlapX / 2; if (dx > 0) { // f1 is to the right of f2 f1.x += pushAmount; f2.x -= pushAmount; } else { // f1 is to the left of f2 f1.x -= pushAmount; f2.x += pushAmount; } } else { // Push apart on the Y-axis const pushAmount = overlapY / 2; if (dy > 0) { // f1 is below f2 f1.y += pushAmount; f2.y -= pushAmount; } else { // f1 is above f2 f1.y -= pushAmount; f2.y += pushAmount; } } // --- 3. ELASTIC COLLISION (Realistic Momentum Transfer) --- // We model the collision as if two balls are hitting each other. // This feels much more natural than a simple velocity swap. const distance = Math.sqrt(dx * dx + dy * dy); // Normal vector (the direction of the collision) const nx = dx / distance; const ny = dy / distance; // Tangent vector const tx = -ny; const ty = nx; // Dot products of velocities with the normal and tangent vectors const dpTan1 = f1.dx * tx + f1.dy * ty; const dpTan2 = f2.dx * tx + f2.dy * ty; const dpNorm1 = f1.dx * nx + f1.dy * ny; const dpNorm2 = f2.dx * nx + f2.dy * ny; // Conservation of momentum along the normal axis // For simplicity, we assume both objects have equal mass. const m1 = (dpNorm1 * 0 + 2 * 1 * dpNorm2) / 1; // Assuming mass is 1 const m2 = (dpNorm2 * 0 + 2 * 1 * dpNorm1) / 1; // Update velocities. The tangent velocity remains unchanged, // while the normal velocity is updated based on the collision. f1.dx = tx * dpTan1 + nx * m2; f1.dy = ty * dpTan1 + ny * m2; f2.dx = tx * dpTan2 + nx * m1; f2.dy = ty * dpTan2 + ny * m1; }; export function extractAndCleanCode(rawResponse: string): string { if (!rawResponse) return ""; // 1. Trim whitespace. This is important for the regex anchors to work. let code = rawResponse.trim(); // 2. First, try to extract from a multi-line markdown block. const markdownRegex = /^```(?:javascript|js)?\s*([\s\S]*?)\s*```$/; let match = code.match(markdownRegex); if (match && match[1]) { // Case 1: Found ```...```. Use the content. return match[1].trim(); } // 3. If that fails, try to extract from a single-line backtick wrap. // THE FIX: Use ^ and $ to ensure it ONLY matches if the whole string is wrapped. const singleBacktickRegex = /^`([\s\S]*)`$/; match = code.match(singleBacktickRegex); if (match && match[1]) { // Case 2: Found `...`. Use the content. return match[1].trim(); } // 4. If no wrappers are found, assume the entire string is the code. // Also, perform a final cleanup for a trailing semicolon, which can happen in any case. if (code.endsWith(";")) { code = code.slice(0, -1); } // Case 3: Return the raw (but cleaned) code. return code; } // Separate function for sanitization and validation export function sanitizePowerLogicCode(extractedCode: string): string { let finalSanitizedCode: string; try { const wrappedCode = `const ability = ${extractedCode};`; // Parse to AST for validation const ast = parse(wrappedCode, { sourceType: "module", plugins: ["typescript"], }); // Generate clean code from the AST. This is the sanitization step. const variableDeclaration = ast.program.body[0] as VariableDeclaration; const functionExpressionNode = variableDeclaration.declarations[0].init; if (!functionExpressionNode) { throw new Error( "AI generated code could not be parsed as a function expression." ); } const { code } = generate(functionExpressionNode, { comments: false, concise: true, }); finalSanitizedCode = code; } catch (e: unknown) { console.error("Failed to parse or generate code from AST:", e); if (e instanceof Error) { throw new Error( `AI generated invalid JavaScript code. Reason: ${e.message}` ); } else { throw new Error("AI generated invalid JavaScript code. Unknown error."); } } return finalSanitizedCode; } // A utility to get only the values that have been changed by the user export function getDirtyValues>( dirtyFields: Record | boolean, allValues: T ): Partial { // If dirtyFields is not an object, there's nothing to process. if (typeof dirtyFields !== "object" || dirtyFields === null) { return {}; } return Object.keys(dirtyFields).reduce((acc, key) => { // If the key is not in allValues, ignore it (should not happen) if (!(key in allValues)) { return acc; } // If the field is dirty (marked as true), we take the entire value. // This is the key change: if any sub-property of `basicAttack` is dirty, // `dirtyFields.basicAttack` will be an object. If a simple field like // `heroName` is dirty, `dirtyFields.heroName` will be `true`. // We treat both cases as "this key is dirty" and grab its full value from `allValues`. acc[key as keyof T] = allValues[key] as T[keyof T]; return acc; }, {} as Partial); }