Jeux — Documentation

Collection de jeux et visualiseurs interactifs, accessible via /projects/jeux. Chaque jeu démontre une technique différente : Canvas API, Web Audio API, algorithmes de pathfinding, boucles temporelles.

Route : /projects/jeux Stack commune : TypeScript · Canvas HTML5 · React (useRef + useEffect + requestAnimationFrame)


Jeux disponibles

JeuConceptStack spécifique
GlitchPlatformer rétro dont chaque niveau introduit un bug comme mécaniqueCanvas API · physique AABB
PathfinderVisualiseur d'algorithmes de pathfinding sur grille interactiveCSS Grid · BFS/DFS/A*/Dijkstra/Greedy
PulseJeu de réflexes génératif avec musique procéduraleWeb Audio API · PRNG mulberry32
ÉchosPuzzle à boucles temporelles — coopère avec tes versions passéesCanvas tile-based · système de replay

Shell de navigation (src/app/projects/jeux/page.tsx)

Page parente qui orchestre la navigation entre jeux. Architecture en trois zones :

┌─────────────────────────────────────────────────────┐
│ Header : ← Portfolio · [GLITCH] · Docs              │
├──────────┬──────────────────────────┬───────────────┤
│ Sidebar  │                          │ Sidebar droite│
│ gauche   │     Zone de jeu          │ (optionelle)  │
│ (nav)    │                          │               │
└──────────┴──────────────────────────┴───────────────┘

État

const [active, setActive] = useState<GameId>("glitch");
// GameId = "glitch" | "pathfinder" | "pulse" | "echos"

Registry des jeux

Chaque jeu est décrit dans GAMES: GameMeta[] :

interface GameMeta {
  id: GameId;
  label: string;           // affiché en neon dans le header
  neonColor: string;       // couleur d'accent du jeu
  description: { fr: string; en: string };
  rightSidebar: boolean;   // affiche une sidebar droite (contrôles/info)
}

Chargement dynamique

Les composants sont chargés en dynamic avec ssr: false — Canvas et requestAnimationFrame requièrent le DOM :

const GlitchGame = dynamic(() => import("@/components/glitch/GlitchGame"), { ssr: false });

Chaque changement de jeu démonte/remonte le composant — pas d'état partagé entre jeux. Le cancelAnimationFrame dans le cleanup de chaque jeu est critique pour éviter les fuites.

Sidebars droites

Glitch, Pulse et Échos ont une rightSidebar affichant les contrôles du jeu en cours. Pathfinder gère ses propres contrôles dans son interface.

Les composants sidebar (GlitchSidebar, PulseSidebar, EchosSidebar) appellent useLang() directement — ils n'ont pas de prop lang. Toutes leurs chaînes sont dans i18n/fr.ts et i18n/en.ts sous games.glitch.*, games.pulse.*, games.echos.*.


Pattern commun aux jeux Canvas

Tous les jeux Canvas (Glitch, Pulse, Échos) partagent le même pattern :

useEffect(() => {
  const canvas = canvasRef.current!;
  const ctx = canvas.getContext('2d')!;
  const gs = initGS();  // état mutable (pas de useState — 60fps)
  let rafId: number;

  const loop = () => {
    tick(gs);           // mise à jour physique (pure)
    draw(ctx, gs);      // rendu Canvas
    rafId = requestAnimationFrame(loop);
  };

  rafId = requestAnimationFrame(loop);

  // Listeners input
  window.addEventListener('keydown', ...);
  window.addEventListener('keyup', ...);

  return () => {
    cancelAnimationFrame(rafId);   // CRITIQUE
    window.removeEventListener(...);
  };
}, []);

Pourquoi l'état mutable ? setState à 60fps forcerait 60 re-renders/s. L'état de jeu (GS) est un objet muté directement dans tick(), isolé du cycle de vie React.


Ajouter un nouveau jeu

  1. Créer le composant dans src/components/<nom-jeu>/
  2. Ajouter un import dynamic dans jeux/page.tsx
  3. Ajouter l'entrée dans GAMES[]
  4. Si sidebar : ajouter rightSidebar: true + composant <NomSidebar>
  5. Créer docs/projects/jeux/<nom-jeu>.md

Points d'attention

  • ssr: false obligatoire sur tous les composants jeux — canvas, requestAnimationFrame, AudioContext n'existent pas côté serveur.
  • Démontage : chaque jeu doit cleanup ses ressources (cancelAnimationFrame, removeEventListener, AudioContext.close()) dans le return de useEffect. Sans ça, plusieurs boucles tournent en parallèle quand l'utilisateur switche de jeu.
  • État mutable vs React state : les jeux utilisent des objets mutés en place pour la performance. Ne jamais mettre l'état de jeu dans useState.
  • i18n : toutes les chaînes UI de la page Jeux sont centralisées dans i18n/fr.ts / en.ts. Les descriptions courtes des jeux (sidebar gauche) restent dans GAMES[].description car ce sont des données, pas des chaînes UI.