File size: 9,322 Bytes
3a7a84c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
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>);
}