Snake
Le Snake classique sur un canvas, avec une vitesse qui monte à chaque fruit. Le bon exercice pour écrire sa première vraie boucle de jeu.
- Faire tourner une boucle avec requestAnimationFrame
- Gérer les collisions sur une grille
- Mettre les directions en file d'attente
Pourquoi un Snake ? C'est le « Hello world » des jeux vidéo. Une grille, un serpent qui est juste une liste de cases, un fruit au hasard. Mais il oblige à résoudre les vrais problèmes d'un jeu : la boucle, le temps, les collisions et les commandes.
Le serpent avance d'une case à chaque pas : on ajoute une tête, on retire la queue. S'il mange, on garde la queue.
Pourquoi en JavaScript simple ? Un canvas, un tableau et une boucle. Aucun moteur de jeu n'est nécessaire pour comprendre comment ils fonctionnent tous.
L'ÉTAT
const N = 18; // cases par côté let serpent = [{ x: 8, y: 9 }, { x: 7, y: 9 }]; let dir = { x: 1, y: 0 }; let file = []; // les virages demandés let fruit = { x: 12, y: 5 }; let pas = 150; // millisecondes par caseAVANCER D'UNE CASE
function avancer() { if (file.length) dir = file.shift(); const tete = { x: serpent[0].x + dir.x, y: serpent[0].y + dir.y }; const mur = tete.x < 0 || tete.y < 0 || tete.x >= N || tete.y >= N; const corps = serpent.some(s => s.x === tete.x && s.y === tete.y); if (mur || corps) return perdu(); serpent.unshift(tete); if (tete.x === fruit.x && tete.y === fruit.y) { pas = Math.max(60, pas - 6); fruit = caseLibre(); } else serpent.pop(); }LA BOUCLE
On accumule le temps écoulé et on avance autant de cases qu'il faut : la vitesse ne dépend pas de l'écran.
let reserve = 0, avant = 0; function boucle(t) { reserve += t - (avant || t); avant = t; while (reserve >= pas) { reserve -= pas; avancer(); } dessiner(); requestAnimationFrame(boucle); }TOURNER
Interdit de faire demi-tour, et deux virages rapides ne doivent pas se perdre.
function tourner(x, y) { const ref = file.at(-1) || dir; if (x === -ref.x && y === -ref.y) return; if (file.length < 2) file.push({ x, y }); }DESSINER
function dessiner() { ctx.clearRect(0, 0, cv.width, cv.height); const t = cv.width / N; ctx.fillRect(fruit.x * t + 5, fruit.y * t + 5, t - 10, t - 10); serpent.forEach(s => ctx.fillRect(s.x * t + 1, s.y * t + 1, t - 2, t - 2)); }
PIÈGE 1 : setInterval pour la boucle
Mauvais : setInterval(avancer, 150). Il continue quand l'onglet est caché, et ne se cale pas sur l'affichage.
Correct : requestAnimationFrame pour dessiner, et un compteur de temps pour décider quand avancer.
PIÈGE 2 : Le demi-tour suicidaire
Mauvais : appliquer chaque touche tout de suite. Haut puis Gauche très vite, et le serpent se mord avant d'avoir bougé.
Correct : une petite file de virages, chacun vérifié par rapport au précédent.
PIÈGE 3 : Un fruit dans le serpent
Mauvais : tirer une case au hasard sans vérifier. Le fruit apparaît sous le corps, invisible.
Correct : retirer au sort tant que la case est occupée.
- Des murs qui téléportent de l'autre côté : remplacez la collision par un modulo sur N.
- Un fruit doré, rare, qui vaut cinq points et disparaît au bout de quelques secondes.
- Les meilleurs scores enregistrés : c'est la variante « Snake avec classement ».
- Mettez le jeu en pause quand l'onglet est caché, avec visibilitychange.
- Sur un grand écran, dessinez le canvas à la taille réelle multipliée par devicePixelRatio pour des bords nets.
Le jeu tourne en dehors de React : l'état vit dans un useRef, pas dans un useState, pour éviter soixante rendus par seconde.
Seul le score passe dans un useState pour l'affichage.
Le useEffect lance la boucle et l'annule avec cancelAnimationFrame au démontage.
Chargement des commentaires…