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
| Jeu | Concept | Stack spécifique |
|---|---|---|
| Glitch | Platformer rétro dont chaque niveau introduit un bug comme mécanique | Canvas API · physique AABB |
| Pathfinder | Visualiseur d'algorithmes de pathfinding sur grille interactive | CSS Grid · BFS/DFS/A*/Dijkstra/Greedy |
| Pulse | Jeu de réflexes génératif avec musique procédurale | Web Audio API · PRNG mulberry32 |
| Échos | Puzzle à boucles temporelles — coopère avec tes versions passées | Canvas 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
- Créer le composant dans
src/components/<nom-jeu>/ - Ajouter un import
dynamicdansjeux/page.tsx - Ajouter l'entrée dans
GAMES[] - Si sidebar : ajouter
rightSidebar: true+ composant<NomSidebar> - Créer
docs/projects/jeux/<nom-jeu>.md
Points d'attention
ssr: falseobligatoire sur tous les composants jeux —canvas,requestAnimationFrame,AudioContextn'existent pas côté serveur.- Démontage : chaque jeu doit cleanup ses ressources (
cancelAnimationFrame,removeEventListener,AudioContext.close()) dans le return deuseEffect. 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 dansGAMES[].descriptioncar ce sont des données, pas des chaînes UI.