Ajoute La Course du Char : mini-jeu de course 2D quotidien
Build and deploy / deploy (push) Successful in 37s
Build and deploy / deploy (push) Successful in 37s
Premier mini-jeu compétitif du Tribunal (/course) : piste 2D générée de façon déterministe à partir de la date du jour (identique pour tout le monde, jamais stockée côté serveur), pilotage au joystick tactile virtuel, murs + obstacles qui ralentissent temporairement sans jamais stopper la course, fantômes du top 3 du jour. Chacun rejoue autant qu'il veut, seul le meilleur temps compte. Moteur de jeu maison (src/lib/chariot/) : canvas + requestAnimationFrame, sans dépendance externe, dans la continuité de l'existant (roulette). Navigation : nouvelle entrée "La Course du Char" (IconChariot), route protégée par le proxy comme le reste de l'app.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
export type Vec2 = { x: number; y: number };
|
||||
|
||||
export function add(a: Vec2, b: Vec2): Vec2 {
|
||||
return { x: a.x + b.x, y: a.y + b.y };
|
||||
}
|
||||
|
||||
export function sub(a: Vec2, b: Vec2): Vec2 {
|
||||
return { x: a.x - b.x, y: a.y - b.y };
|
||||
}
|
||||
|
||||
export function scale(a: Vec2, s: number): Vec2 {
|
||||
return { x: a.x * s, y: a.y * s };
|
||||
}
|
||||
|
||||
export function length(a: Vec2): number {
|
||||
return Math.hypot(a.x, a.y);
|
||||
}
|
||||
|
||||
export function normalize(a: Vec2): Vec2 {
|
||||
const len = length(a);
|
||||
return len > 0 ? scale(a, 1 / len) : { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
export function dot(a: Vec2, b: Vec2): number {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
export function rotate90(a: Vec2): Vec2 {
|
||||
return { x: -a.y, y: a.x };
|
||||
}
|
||||
|
||||
// Point le plus proche du point p sur le segment [a,b].
|
||||
export function closestPointOnSegment(p: Vec2, a: Vec2, b: Vec2): { point: Vec2; t: number } {
|
||||
const ab = sub(b, a);
|
||||
const abLenSq = dot(ab, ab);
|
||||
const t = abLenSq > 0 ? Math.max(0, Math.min(1, dot(sub(p, a), ab) / abLenSq)) : 0;
|
||||
return { point: add(a, scale(ab, t)), t };
|
||||
}
|
||||
|
||||
// Catmull-Rom uniforme : passe exactement par chaque point de contrôle,
|
||||
// samplesPerSegment points intermédiaires entre chaque paire de points.
|
||||
export function catmullRom(points: Vec2[], samplesPerSegment: number): Vec2[] {
|
||||
if (points.length < 2) return points.slice();
|
||||
|
||||
const result: Vec2[] = [];
|
||||
const at = (i: number) => points[Math.max(0, Math.min(points.length - 1, i))];
|
||||
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const p0 = at(i - 1);
|
||||
const p1 = at(i);
|
||||
const p2 = at(i + 1);
|
||||
const p3 = at(i + 2);
|
||||
|
||||
for (let s = 0; s < samplesPerSegment; s++) {
|
||||
const t = s / samplesPerSegment;
|
||||
const t2 = t * t;
|
||||
const t3 = t2 * t;
|
||||
result.push({
|
||||
x:
|
||||
0.5 *
|
||||
(2 * p1.x +
|
||||
(-p0.x + p2.x) * t +
|
||||
(2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
|
||||
(-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3),
|
||||
y:
|
||||
0.5 *
|
||||
(2 * p1.y +
|
||||
(-p0.y + p2.y) * t +
|
||||
(2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
|
||||
(-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.push(points[points.length - 1]);
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user