Aller au contenu
← 1001 Widgets
N° 021 · ●●○

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.

JSCanvas
Ce qu'on apprend
  • Faire tourner une boucle avec requestAnimationFrame
  • Gérer les collisions sur une grille
  • Mettre les directions en file d'attente
Pourquoi ce langage

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.

Comment ça marche
  1. 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 case
  2. AVANCER 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();
    }
  3. 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);
    }
  4. 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 });
    }
  5. 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èges

PIÈGE 1 : setInterval pour la boucle

Piège

Mauvais : setInterval(avancer, 150). Il continue quand l'onglet est caché, et ne se cale pas sur l'affichage.

Fix

Correct : requestAnimationFrame pour dessiner, et un compteur de temps pour décider quand avancer.

PIÈGE 2 : Le demi-tour suicidaire

Piège

Mauvais : appliquer chaque touche tout de suite. Haut puis Gauche très vite, et le serpent se mord avant d'avoir bougé.

Fix

Correct : une petite file de virages, chacun vérifié par rapport au précédent.

PIÈGE 3 : Un fruit dans le serpent

Piège

Mauvais : tirer une case au hasard sans vérifier. Le fruit apparaît sous le corps, invisible.

Fix

Correct : retirer au sort tant que la case est occupée.

PERSONNALISER
  • 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 ».
OPTIMISATION
  • 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.
VERS REACT

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.

Variantes
Commentaires
0/1000

Chargement des commentaires…