Ajoute La Course du Char : mini-jeu de course 2D quotidien
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:
Valentin ROBIN
2026-07-27 18:54:04 +02:00
parent 9f54b459f3
commit 6af56d2ade
15 changed files with 985 additions and 5 deletions
+77
View File
@@ -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;
}