Spaces:
Paused
Paused
| 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<T extends Record<string, unknown>>( | |
| dirtyFields: Record<string, unknown> | boolean, | |
| allValues: T | |
| ): Partial<T> { | |
| // 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<T>); | |
| } |