mmy / lib /utils.ts
Mohammad Shahid
first commit
3a7a84c
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>);
}