Snake

Un jeu simple, ancien, et instructif à programmer

Tout d'abord: le jeu. Histoire de s'amuser un peu, et de tester les différents éléments.
Le contrôle des mouvements se fait avec les flèches, simples ou du pavé numérique.

fsde

Le code javascript complet, détaillé

Le serpent du jeu est représenté par une liste de points, c'est-à-dire par une liste de couples de coordonnées.
C'est le tableau sn, initialisé par:
var sn=new Array;
sn=[[4,4],[5,4],[6,4],[7,4]];
Ainsi, tout au long de l'exécution on aura la longueur du serpent donnée par Ls=sn.length;, et sn[i] les coordonnées du i-ème point du serpent:
  • sn[i][0]: la 1ère coordonnée (en abscisse, ou horizontalement)
  • sn[i][1]: la 2ème coordonnée (en ordonnée, ou verticalement)
  • sn[Ls-1]: contient, et de même que ci-dessus, la tête du serpent, pour la quelle on doit vérifier qu'elle ne rencontre pas un bord du canvas, ni un autre point du serpent (c'est-à-dire de devient pas égal à un sn[i] pour un i compris entre 0 et Ls)

Les différentes fonctions:
  • KeyDrive : l'écoute des événements clavier, et leur pilotage.
    Ici, la touche entrer (keyCode==13) nous intéresse pour lancer et mettre en pause le jeu. On (re)lance le jeu avec setInterval; on l'arrête (ou le pause) en stopant justement ceci avec clearInterval.
    Lorsqu'on n'est pas en pause (GameState=="playing"), on écoute aussi les flèches du pavé numérique pour diriger le serpent, keyCode= 98, 100, 102 et 104, et en stockant la direction correspondante dans la variable Dir qui est alors utilisée directement (plus précisément toutes 100ms=0,1s du setInterval) dans la fonction MoveSnake:
    A noter: Au tout début du script, on commence par désactiver le comportement par défaut des flèches du clavier, dans la fonction NoDefaultArrow appelée à travers tout le document par le document.addEventListener("keydown",….
    Le paramètre false en fin de addEventListener
  • MoveSnake : LA fonction principale.
    La direction de déplacement est Dir.
    On vérifie si l'éventuelle nouvelle tête du serpent, dont les coordonnées seraient sn[Ls-1] en se déplaçant dans la direction Dir:
    • si Dir=="right": déplacement vers le haut, la nouvelle position de la tête est sn[Ls][0]=sn[Ls-1][0]+1 (+1 en abscisse) et sn[Ls][0]=sn[Ls-1][0] (inchangé en ordonnée)
    • si Dir=="up": déplacement vers le haut, idem mais l'abscisse est cette fois inchangée, et "-1" en ordonnée ()
    Pour déplacer le serpent, en situation normale (sans rencontrer de mur, de fruit, …), on supprime en fait la queue du serpent sn[0]: chaque sn[i] prend les valeurs de sn[i+1], tandis que la tête sn[Ls-1] est calculée comme précédemment:
    • si Dir=="right": déplacement vers le haut, la nouvelle position de la tête est sn[Ls-1][0]=sn[Ls-2][0]+1 (+1 en abscisse) et sn[Ls-1][0]=sn[Ls-2][0] (inchangé en ordonnée)
    La longueur du serpent Ls=sn.length est ici inchangée.
  • Draw : fonction qui, comme son nom l'indique, dessine ce qu'il y a à dessiner, le serpent et le fruit.
    Pour chaque point du serpent, donc nombre entier i entre 0 et Ls-1 on trace le pixel de coordonnées sn[i][0] et sn[i][1], qui, en utilisant le facteur d'échelle zoom, est un carré de côté zoom justement: fillRect( , ,zoom,zoom).
  • GameOver : fonction au nom assez explicite… Cette fonction change ici simplement la couleur d'affichage en virant au rouge, et surtout arrête le déroulement du setInterval, c'est-à-dire stop le déroulement automatique du jeu, donc animation…
    On pourrait bien sûr faire ici une fonction plus complète, qui affiche que le jeu est fini, compte d'une certaine façon le nombre, ou encore affiche le temps que le joueur à résisté au jeu…


Code: Select all
<canvas id="snake" width="400" height="300" style="border:1px solid black"></canvas>

<script>
// On désactive le comportement par défaut des flèches du clavier (défilement dans la page...)
function NoDefaultArrow(event) {
 if ((event.keyCode==37)||(event.keyCode==38)||(event.keyCode==39)||(event.keyCode==40)) {
  event.stopPropagation();
  event.preventDefault();
}
document.addEventListener("keydown", NoDefaultArrow, false);

// et puis on s'occupe du canvas et du jeu:
canvas = document.getElementById("snake");
ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
Width=document.getElementById("snake").width;
Height=document.getElementById("snake").height;
zoom=10; // pixels size inside canvas
// Initialisation
var sn=new Array;
sn=[[4,4],[5,4],[6,4],[7,4]];
Ls=sn.length; // Taille initiale du serpent
GameState="paused";Dir="right";
Fruit=[Math.floor(Width/zoom*Math.random()),Math.floor(Height/zoom*Math.random())];
ctx.font="15pt Calibri,Geneva,Arial";ctx.fillText("Press Enter to start...",50,100);
Draw();
// et on lance l'écoute des touches du clavier
document.onkeydown = KeyDrive;


function Draw() {//Trace le serpent et le fruit
 ctx.clearRect(0,0,Width,Height);
 for (i=0;i<Ls;i++) {ctx.fillRect(zoom*sn[i][0],zoom*sn[i][1],zoom,zoom);}
  ctx.fillStyle="green";
  ctx.fillRect(zoom*Fruit[0],zoom*Fruit[1],zoom,zoom);
  ctx.fillStyle="black";
}

function GameOver() {//Comme son non l'indique...
 ctx.fillStyle="red";clearInterval(sI);
}

function MoveSnake() {/*LA fonction principal qui calcule le nouveau tableau sn des positions du serpent,
si il ne se mord pas, ne rencontre pas un mur, et qui grandit quand il prend un fruit*/

if (// Si la tête du serpent rencontre le fruit
  (Dir=="up" && sn[Ls-1][0]==Fruit[0] && sn[Ls-1][1]==Fruit[1]+1)
  || (Dir=="down" && sn[Ls-1][0]==Fruit[0] && sn[Ls-1][1]==Fruit[1]-1)
  || (Dir=="right" && sn[Ls-1][0]==Fruit[0]-1 && sn[Ls-1][1]==Fruit[1])
  || (Dir=="left" && sn[Ls-1][0]==Fruit[0]+1 && sn[Ls-1][1]==Fruit[1])) {
    if (Dir=="up") {sn[Ls]=[sn[Ls-1][0],sn[Ls-1][1]-1];}
    if (Dir=="down") {sn[Ls]=[sn[Ls-1][0],sn[Ls-1][1]+1];}
    if (Dir=="right") {sn[Ls]=[sn[Ls-1][0]+1,sn[Ls-1][1]];}
    if (Dir=="left") {sn[Ls]=[sn[Ls-1][0]-1,sn[Ls-1][1]];}
    Ls++;// Incrémente la longueur du serpent et on génère une nouvelle position aléatoire de fruit
    Fruit=[Math.floor(Width/zoom*Math.random()),Math.floor(Height/zoom*Math.random())];
  }
else {// Sinon un déplacement simple dans la direction "Dir"
  for (i=0;i<Ls-1;i++) {sn[i]=sn[i+1];}
  if (Dir=="up") {sn[Ls-1]=[sn[Ls-2][0],sn[Ls-2][1]-1];}
  if (Dir=="down") {sn[Ls-1]=[sn[Ls-2][0],sn[Ls-2][1]+1];}
  if (Dir=="right") {sn[Ls-1]=[sn[Ls-2][0]+1,sn[Ls-2][1]];}
  if (Dir=="left") {sn[Ls-1]=[sn[Ls-2][0]-1,sn[Ls-2][1]];}
}
// On vérifie que le serpent ne déborde pas du canvas
if (zoom*sn[Ls-1][0]==Width || sn[Ls-1][0]==-1 || sn[Ls-1][1]==-1 || zoom*sn[Ls-1][1]==Height) {GameOver();}
// On vérifie que le serpent ne se mord pas:
for (i=0;i<Ls-1;i++) {if (sn[Ls-1][0]==sn[i][0] && sn[Ls-1][1]==sn[i][1]) {GameOver();}}
// et dans tous les cas, on retrace
Draw();
}

function KeyDrive(e) {// Pilotage au clavier
if(e.keyCode==13) {
  if (GameState=="paused") {GameState="playing";sI=setInterval('MoveSnake()',100);}
  else if (GameState=="playing") {GameState="paused";clearInterval(sI);
     ctx.font="15pt Calibri,Geneva,Arial";ctx.fillText("Pause... Press space to resume...",50,100);}
}
if (GameState=="playing") {//Codes des touches du pavé numérique
 if(e.keyCode==104) {Dir="up";}
 else if(e.keyCode==98) {Dir="down";}
 else if(e.keyCode==102) {Dir="right";}
 else if(e.keyCode==100) {Dir="left";}
 MoveSnake();
}
}
</script>

Voir aussi: