Snake

Game engine experiences
Open source code

Présentation

Snake, de l’anglais signifiant « serpent », est un jeu vidéo populaire créé au milieu des années 1970, disponible de par sa simplicité sur l’ensemble des plate-formes de jeu existantes sous des noms de clone. Il s’est de nouveau fait connaître dans les années 1990 avec l’émergence du nouveau support de jeu qu’est le téléphone portable. Aujourd’hui, il est toujours aussi populaire et est devenu un classique dans les jeux vidéo.

Le joueur contrôle une longue et fine créature semblable à un serpent, qui doit slalomer entre les bords de l’écran et les obstacles qui parsèment le niveau. Pour gagner chacun des niveaux, le joueur doit faire manger à son serpent un certain nombre de pastilles ou de fruits (de la nourriture en général), allongeant à chaque fois la taille de la bestiole. Alors que le serpent avance inexorablement, le joueur ne peut que lui indiquer une direction à suivre (en haut, en bas, à gauche, à droite) afin d’éviter que la tête du serpent ne touche les murs ou son propre corps, dans ce cas il risque de mourir.

Le niveau de difficulté est contrôlé par l’aspect du niveau (simple ou labyrinthique), le nombre de pastilles à manger, l’allongement du serpent et sa vitesse.

Le code Javascript


// variables
var canvas, ctx, longueur, angle, compteur, ajouteAnneaux, victoire, vitesse, droite, gauche, snake, pomme, posx, posy, stockAnneaux, timer;
var W = 480;
var H = 480;

// charger les images du jeu
var snakeImg = new Image();
var head = new Image();
var pommeImg = new Image();
var fond = new Image();
fond.src = "assets/fond.jpg";
pommeImg.src = "assets/pomme.png";
head.src = "assets/head.png";
snakeImg.src = "assets/anneau.png";

// préparation du jeu
window.onload = function() {
	canvas = document.getElementById('canvas');
	ctx = canvas.getContext('2d');
	canvas.width = W;
	canvas.height = H;
	init();
}

// initialisation du jeu
function init() {

	snake = {};
	pomme = {};
	posx = [];
	posy = [];
	stockAnneaux = [];
	longueur = 5;
	angle = 0;
	compteur = 0;
	ajouteAnneaux = 4;
	victoire = 140;
	vitesse = 10;
	droite = 0;
	gauche = 0;

	for (var i=0; i<longueur; i++) {
		stockAnneaux.push({x:0,y:0,width:snakeImg.width,height:snakeImg.height})
	}

	snake.x = W*.5;
	snake.y = H*.5;
	snake.width = head.width;
	snake.height = head.height;
				
	pomme.x = 30+Math.random()*(W-60);
	pomme.y = 30+Math.random()*(H-60);
	pomme.width = pommeImg.width;
	pomme.height = pommeImg.height;
		
	canvas.setAttribute('tabindex','1');
	canvas.focus();
	timer = setInterval(main,60);
	canvas.addEventListener("keydown", appuie, false);
	canvas.addEventListener("keyup", relache, false);
}
     
// gestion clavier
function appuie(e){	
	if (e.keyCode == 39) droite = 1;
	if (e.keyCode == 37) gauche = 1;
} 
	
// gestion clavier
function relache(e){	
	if (e.keyCode == 39) droite = 0;
	if (e.keyCode == 37) gauche = 0;
} 
	 
// boucle principale
function main(){
	var i;
	var j;

	// Direction
	angle += (parseInt(droite)-parseInt(gauche))*20;
	
	// Enregistre la position de la tête
	posx[0] = snake.x;
	posy[0] = snake.y
		
	// Deplace la tête
	snake.x += Math.cos(angle*Math.PI/180)*vitesse;
	snake.y += Math.sin(angle*Math.PI/180)*vitesse;

	// Envoye le dernier anneau en premier 
	var dernier = longueur-1-compteur++;
	if (dernier == 0) compteur = 0;
	stockAnneaux[dernier].x = posx[0];
	stockAnneaux[dernier].y = posy[0];
		
	// Déplace les anneaux
	for (i=longueur-1; i>0; i--) {
		posx[i] = posx[i-1];
		posy[i] = posy[i-1];
	}

	// si la tête touche de la nourriture
	if (collisions(snake,pomme)) {
			
		// ajoute des anneaux
		for (i=0; i<ajouteAnneaux; i++) {
			posx[longueur] = posx[longueur-1];
			posy[longueur] = posy[longueur-1];
			longueur++;
			stockAnneaux.push({x:0,y:0,width:snakeImg.width,height:snakeImg.height});
		}
			
		// place la pomme ailleurs
		pomme.x = 30+Math.random()*(W-60);
		pomme.y = 30+Math.random()*(H-60);
			
		// Repositionne le Snake complet (assure la cohésion)
		for (i=0; i<longueur; i++) {
			stockAnneaux[i].x = posx[i];
			stockAnneaux[i].y = posy[i];
		}
		compteur = 0;
	}
		
	// Vérifie si la tête touche un bord
	if (snake.x<0 || snake.x>W || snake.y<0 || snake.y>H) finPartie();
	
	// Vérifie si la tête touche un anneau
	var point = {x:snake.x+Math.cos(angle*Math.PI/180)*4, y:snake.y+Math.sin(angle*Math.PI/180)*4, width:1, height:1};
	for (i =0; i<stockAnneaux.length; i++){
		if(collisions(point,stockAnneaux[i])) finPartie();
	}

	// vérifie si le joueur gagne
	if (longueur>=victoire) gagnePartie();

	// dessin final
	render();
} 
	
// collisions
function collisions(A,B) {
	if (A.y+A.height < B.y || A.y > B.y+B.height || A.x > B.x+B.width || A.x+A.width < B.x) return false;
	return true;
}
	
function finPartie(){
	alert("Perdu, cliquez pour rejouer.");
	clearInterval(timer);
	init();
}
	
function gagnePartie(){
	alert("Bravo, cliquez pour rejouer.");
	clearInterval(timer);
	init();
}
	 
// Dessine le jeu
function render() {	
	ctx.drawImage(fond,0,0);
	ctx.save();
		
	ctx.translate(snake.x + snake.width / 4, snake.y + snake.height / 2);
	ctx.rotate(angle*Math.PI/180);
	ctx.drawImage(head,-snake.width/4,-snake.height/2);
	ctx.translate(-(snake.x + snake.width / 4), -(snake.y + snake.height / 2));
	ctx.restore();
		
	for(var i =0; i<stockAnneaux.length;i++){
		ctx.drawImage(snakeImg,posx[i],posy[i]);
	}
	ctx.drawImage(pommeImg,pomme.x,pomme.y);
}

A retenir

Ce qui est notable dans ce programme c’est la manière dont on va gérer les anneaux du serpent. Si l’on essaye d’ajouter des anneaux à la suite du dernier, on risque fort de se compliquer la tâche, il faut non seulement les coordonnées du dernier anneau, mais également savoir dans quelle direction le placer. Sachant qu’on utilise la trigonométrie, nous avons 360 possibilités pour placer ces nouveaux anneaux. C’est pourquoi il faut essayer de penser le système autrement, la solution est de placer tout nouvel anneau à l’ancienne place de la tête du serpent, ce dernier avançant en permanence. Ainsi on simplifie son positionnement, mais on s’assure aussi que le serpent n’est pas dans une configuration où il risque d’entrer en collision avec ses propres anneaux quand il mange une pomme, les nouveaux anneaux s’ajoutant juste derrière sa tête.

L’autre petite chose utile ici c’est la trigonométrie. Pour ceux qui n’en auraient aucune notion, on joue avec des angles et on utilise le sinus et le cosinus de l’angle pour déterminer une direction sur les deux axes (x et y). Il suffit ensuite de multiplier cette direction sur chaque axe par une vitesse donnée et l’objet avance. Attention, dans la plupart des langages de programmation les angles sont exprimés en radians, il nous faut donc opérer une conversion entre les degrés et les radians pour faire nos calculs.

Enfin, dernière petite astuce concernant les graphismes, chaque objet possède un point d’origine ou de pivot, c’est lui qu’on utilise pour placer l’objet, or ce point est la plupart du temps le point situé aux coordonnées 0,0 de l’objet soit son point haut gauche. Afin d’afficher correctement nos objets à l’écran il va nous falloir appliquer une petite correction pour replacer virtuellement ce point au centre de l’objet avant de l’afficher.

2
Commentaires

avatar
1 Fils de commentaires
1 Réponses de fil
0 Abonnés
 
Commentaire avec le plus de réactions
Le plus populaire des commentaires
2 Auteurs du commentaire
cmarzin@noos.frspi Auteurs de commentaires récents
  S’abonner  
S'abonner
spi
Invité

test de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu lontest de blabla un peu… Lire la suite »

Facebook Google Linked Skype Twitter
© 2019 Cmarzin - Tous droits réservés | SIRET: 483 511 101 00030 | Mentions légales