Projet Snake

Création du jeu Snake en utilisant le language Java et la bibliothèque Swing

Slides & Videos

Members

NameContribution
VINTEL Toine- organisation
- UML
- structure code
Francesco PrinceGestion du déroulement du jeu, initialisation
ALLAOUI YacineEcriture et réflexion sur la partie Plateau et Pomme
élaboration de la présentation
Réflexion général sur le projet (partie code et structuration)
TORRADO LéoRéflexion sur le code (partie game, collision)
diagramme UML
rédaction des documents

State of the Art

Business Aspect

1) Analyse du problème  

  1) Analyse du problème  

  • Règles du jeu

  L’application snake est un jeu vidéo dans lequel le joueur dirige un serpent qui grandit à mesure qu’il mange des pommes, le jeu se finissant si le serpent rentre en collision avec les bords de l’écran ou de lui-même car son corps constitue aussi obstacle. Le but du jeu étant d’obtenir le score le plus élevé.  

  • Origine

  Le jeu est d’abord apparu dans les années 70 dans les salles d’arcade et sur les consoles Atari. Il a ensuite disparu pour réapparaître sur les premiers téléphones au début des années 2000. Sa facilité à développer et sa licence gratuite en fait un jeu très populaire mais reste moins joué que d’autres vieux jeux comme Tetris.   

  • Tour de jeu

  Le serpent avance dans une direction constante et se fait réorienter par le joueur lorsqu’il appuie sur une touche. Le serpent ne peut se faire réorienter qu’une seule fois avant qu’il n’avance.  

  • Stratégie

  Plus le jeu avance et plus il devient difficile de contrôler les mouvements du serpent (- d’espace). Il faut donc positionner le corps du serpent de manière à ce que la tête puisse facilement prendre la pomme suivante.  Dans ces cas-là une stratégie simple consiste à empiler le serpent sur lui-même de manière à ce qu’il remplisse un côté du plateau. La pomme apparaît de l’autre côté et il faudra transférer tout le corps vers ce côté-là avant de la manger.  

  • Variantes

  Il existe à ce jour plusieurs variantes au jeu de snake :

  • variation de la vitesse du serpent.
  • augmentation du nombre de pommes qui apparaissent dans le parcours.
  • apparition d’obstacles aléatoires après que le serpent a mangé un certain nombre de pommes.
Technical Aspect

2) Modélisation et Conception

2) Modélisation  

  • Objets

  Les acteurs du jeu sont le joueur, le serpent et la pomme.  

  • Classes

  Les classe utilisées pour les représenter sont :

  • Coordonnée : Gère les coordonnées des différents objets
  • Game : Gère le déroulement d’une partie
  • Plateau : Affiche les différents objets
  • Pomme : Contient les informations relatives à la pomme (position, …)
  • Serpent : Contient les informations relatives au serpent (position, taille, …)

                Le serpent est composé d’un tableau de Coordonnée.  

  • Modèle Quasi MVC ?

  Le programme est donc divisé en 3 parties fonctionnelles.    D’abord le Modèle composé de Game, Pomme, Serpent et Coordonnée qui contient les informations du jeu sous formes de variables.   Ensuite la Vue gérée par Plateau qui se met à jour à chaque modification du modèle.   Enfin la partie Contrôleur est implémentée en ajoutant un Listener sur la fenêtre du jeu.  

  • Solutions techniques

  Pour l’affichage du jeu, on a utilisé la bibliothèque Swing. Elle permet l ‘affichage de widget de manière organisé (grille, wrap, …) et est facile à mettre en place.    Les bibliothèques Color et Border permettent de modifier la forme et le fond des widgets.   Pour le déroulement du jeu, une variable s’occupe de vérifier si le joueur a déjà appuyé sur un bouton et le serpent ne se déplace qu’après un temps prédéfini.   Durant chaque déplacement du serpent ou création d’une nouvelle pomme, on vérifie s’ il y a collision en comparant les coordonnées des objets.   3) Conception  

  1. détailler méthode 
  2. modélisation serpent

  Le serpent est composé d’un ensemble de nœud relié à la tête. Le serpent avance dans une direction. Lorsque le serpent mange une pomme, son corps grossit, on ajoute alors aux tableaux des nœuds la dernière position de la queue.  

  1. Vérification des collisions

Il y a 3 types de collisions à tester lors d’une partie :

  1. Collision avec les rebords de la fenêtre : A l’aide d’un ‘if’, le jeu teste si la tête du serpent a atteint les coordonnées du bord de la fenêtre de jeu, si c’est le cas, un booléen ‘true’ est renvoyé et la partie se termine.
  2. Collision avec la pomme : A l’aide d’un ‘if’ dans un ‘for’, le jeu teste si la tête du serpent a atteint les coordonnées de la pomme, si c’est le cas, un booléen ‘true’ est renvoyé, le score augmente de 1 et et une autre pomme apparait sur une cellule inhabitée par le serpent.
  3. Collision avec le corps du serpent : À l’aide d’un ‘if’ dans un ‘for’, le jeu teste si la tête du serpent a atteint les coordonnées d’une partie du serpent, si c’est le cas, un booléen ‘true’ est renvoyée et la partie se termine.

Project Description

Problem Definition
Construire avec le Langage java un jeux interactif, ou le but et d'avoir le meilleur score au jeux du snake.
Ce projet est l'occasion pour nous de mettre en pratique nos connaissance en java et de nous améliorer encore dans le développement de ce langage, surtout que c'est pour nous le premier qu'on fait et donc une bonne première expérience à acquérir.
Challenges & Motivation
les défis qu'on avait à relever était d'établir comment on aller construire les différentes partie de notre jeu et avec quel outils on aller arriver à notre résultat souhaiter, aussi la principale motivation derrière ce projet était de mettre en application les connaissances acquissent en cours de JAVA et UML pour établir de A à Z une application qui mettent en valeur l'étendu de notre maitrises mais aussi pour nous améliorer dans le développement du langage JAVA

Comment ce problème est posé
Gérer les déplacement du serpent
Gérer le début et la fin de partie (Qd ça commence ? Qd ca finit)
Affichage du serpent en fonction du temps (le serpent avance dans une direction fixe à une vitesse donnée)
Gérer la taille et la vitesse du serpent (plus il est gros et plus il va vite)
Bille apparaît aléatoirement

Comment le problème est traité
Bibliothèque EventListener et KeyAdapter
Vérification continue de la variable Etat Partie
Mise à jour de l’affichage en fonction des données stockés dans le modèle
Event mangerBille => maj modele
Bibliothèque Rand => (x,y)
Real and Complete Usecases
Technical Description

Hardware

Materials
ImageNamePart NumberPriceCountLink
🛒
Schematic

Software

Arduino Code

package snake;

import java.util.Random;

public class Coordonnee {
	
	// Définitions des attributs
	private int x;
	private int y;
	
	// Définitions des constructeurs
	public Coordonnee () {
		this.x = 0;
		this.y = 0;		
	}
	
	public Coordonnee (int a, int b) {
		this.x = a;
		this.y = b;		
	}
	
	public Coordonnee (Coordonnee c) {
		this.x = c.get_x();
		this.y = c.get_y();		
	}

	// Définitions des méthodes
	public int get_x() {
		return this.x;		
	}
	
	public void set_x(int a) {
		this.x = a;		
	}
	
	public int get_y() {
		return this.y;
	}
	
	public void set_y(int b) {
		this.y = b;		
	}
}

package snake;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

public class Game extends JFrame implements KeyListener {	
	
	private JPanel mainPanel;	
	public Plateau board;
	public JLabel score;
	public Serpent snake;
	public Pomme   apple;
		
	private int longeur;
	private int largeur;
	
	public boolean actionJoueur;
	
	static final int HAUT 	= 1;
	static final int DROITE = 2;
	static final int BAS 	= 3;
	static final int GAUCHE = 4;
	
	public Game() {
		this.longeur = 10;
		this.largeur = 10;
		
		this.initComponent();
		this.initWindow();		
		addListener();
		launch_game();			
		while(check_collision() == false) {	
			this.refresh();
			this.attendreAction(500);
			if(this.actionJoueur == false) {
				this.snake.avancerSerpent(0);
			}
		}
		//close_game();				
	}
	
	public void initWindow() {
		new JFrame();
		setSize(new Dimension(1000, 1000));	
		setTitle("Projet Snake");
		
		mainPanel = new JPanel();
		mainPanel.setLayout(new GridLayout(2,1));		
		mainPanel.add(this.board);
		mainPanel.add(this.score);
		add(mainPanel);
	}
	
	public void initComponent() {		
		this.board  = new Plateau(this.longeur,this.largeur);
		this.score = new JLabel("Score", SwingConstants.CENTER);
		this.score.setFont(new Font("Serif", Font.PLAIN, 30));
		this.snake  = new Serpent();
		this.addNewPomme();
	}
	
	public void attendreAction(int t) {
		try 
		{
			Thread.sleep(t);
		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}	
	}
	
	public void addNewPomme() {
		this.apple = new Pomme(randCoordonnee());
		while(check_collision_pomme() == true) {
			this.apple = new Pomme(randCoordonnee());
		}		
	}
	
	public void refresh() {
		this.actionJoueur = false;
		this.score.setText("Score : "+String.valueOf(this.snake.getLongueur()-2));
		this.board.clear();
		this.board.show_serpent(this.snake);
		this.board.show_pomme(this.apple);
	}	
	
	public void launch_game() {
		setVisible(true);	
	}
	
	public void close_game() {
		setVisible(false);	
	}
	
	// Gestion coordonnée aléatoire	
	public Coordonnee randCoordonnee() {
		Coordonnee coord = new Coordonnee();
		coord.set_x(rand(0, this.longeur));
		coord.set_y(rand(0, this.largeur));
		return coord;
	}
	
	public int rand(int a, int b) {
	   Random random = new Random();
	   int nb;
	   nb = a + random.nextInt(b-a);
	   return nb;
	}
	
	// Gestion collision
	public boolean check_collision() {
		boolean rep = false;
		
		if(check_collision_pomme() == true) {
			System.out.println("Manger Pomme");
			this.snake.grossir();
			this.addNewPomme();
		}
		
		if(check_collision_border() == true) {
			rep = true;
		}
		
		if(check_collision_serpent() == true) {
			rep = true;
		}			
		
		return rep;
	}
	
	public boolean check_collision_border() {
		boolean rep = false;
		
		int x = this.snake.getNoeud(0).get_x();
		if(x < 0 || x >= this.longeur) {
			System.out.println("COLLISION !");
			rep = true;
		}

		int y = this.snake.getNoeud(0).get_y();		
		if(y < 0 || y >= this.largeur) {
			System.out.println("COLLISION !");
			rep = true;
		}		
		
		return rep;
	}
	
	public boolean check_collision_serpent() {		
		Coordonnee coord_tete = this.snake.getNoeud(0);
		int longueur = this.snake.getLongueur();	
		for (int i=1; i < longueur; i++) {
			if(this.snake.getNoeud(i).get_x() == coord_tete.get_x() && this.snake.getNoeud(i).get_y() == coord_tete.get_y()) {
				System.out.println("COLLISION !");
				return true;
			}
		}				
		return false;
	}
	
	public boolean check_collision_pomme() {	
		int longueur = this.snake.getLongueur();	
		for (int i=0; i < longueur; i++) {			
			if(this.snake.getNoeud(i).get_x() == this.apple.get_x() && this.snake.getNoeud(i).get_y() == this.apple.get_y()) {
				return true;
			}
		}	
		return false;
	}	
	
	// gestion des événements
	public void addListener() {
		addKeyListener(this);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			public void windowOpened(WindowEvent e) { 
			requestFocus();	
			}
		});
	}
	
	public void keyPressed(KeyEvent evt) {
		System.out.println("Le joueur appuie sur une touche");
		if(this.actionJoueur == false) {
			switch(evt.getKeyCode()) {
				case KeyEvent.VK_UP:
					System.out.println("touche UP appuyé");
					this.snake.avancerSerpent(HAUT);
					break;
				case KeyEvent.VK_RIGHT:
					System.out.println("touche RIGHT appuyé");
					this.snake.avancerSerpent(DROITE);
					break;
				case KeyEvent.VK_DOWN:
					System.out.println("touche DOWN appuyé");
					this.snake.avancerSerpent(BAS);
					break;
				case KeyEvent.VK_LEFT:
					System.out.println("touche LEFT appuyé");
					this.snake.avancerSerpent(GAUCHE);
					break;
				default:
					System.out.println("mauvaise touche appuyé");
					break;
			}		
			this.actionJoueur = true;
		}
		else {
			System.out.println("Action déja utilisée");
		}
	}
	
	public void keyReleased(KeyEvent evt) {} 
	
	public void keyTyped(KeyEvent evt) {}	
}
package snake;

import javax.swing.JFrame;

public class Main {
	// Test déroulement partie
	
	public static void main(String[] args) {
		System.out.println("Début du jeu");
		
		Game partie = new Game();

		System.out.println("Fin du jeu");
	}	
}
package snake;

import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class Plateau extends JPanel {
	
	// D�finition des attributs
	private JPanel mainframe;
	private JPanel cases_panel;
	private JButton[][] tab_cases;
	
	private int longueurPlateau;
	private int largeurPlateau;
	
	// D�finition des constructeurs
	public Plateau(int x, int y) {
		this.longueurPlateau = x;
		this.largeurPlateau = y;

		this.initPlateau();
		this.createCasePanel();
		this.createTabCase();		
	}
	
	// Cr�ation des widgets	
	public void initPlateau() {
		new JPanel();
		setSize(new Dimension(300, 300));
		setBackground(Color.BLACK);
		setLayout(new GridLayout(1,1));
	}
	
	public void createCasePanel() {
		this.cases_panel = new JPanel();
		this.cases_panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
		this.cases_panel.setBackground(Color.GREEN);
		this.cases_panel.setOpaque(false);
		this.cases_panel.setMinimumSize(new Dimension(300, 300));
		this.cases_panel.setLayout(new GridLayout(largeurPlateau, longueurPlateau));	
		add(this.cases_panel);
	}
	
	public void createMainframe() {
		this.mainframe = new JPanel();
		this.mainframe.setBackground(Color.BLACK);	
		this.mainframe.setOpaque(false);
		//this.mainframe.setBorder(new LineBorder(Color.BLACK, 5));
		this.mainframe.setSize(300,300);		
	}
	
	public void createTabCase() {
		this.tab_cases = new JButton[this.longueurPlateau][this.largeurPlateau];
		for(int j=0; j < this.largeurPlateau; j++) {
			for(int i=0; i < this.longueurPlateau; i++) {
				this.tab_cases[i][j] = createCase();	
				this.cases_panel.add(tab_cases[i][j]);			
			}
		}		
	}
	
	public JButton createCase() {
		JButton new_case = new JButton();
		
		// Gestion couleur
		new_case.setBackground(Color.BLACK);
		new_case.setOpaque(true);
		
		// Gestion bordure
		Border border = new LineBorder(Color.BLACK, 1);
		new_case.setBorder(border);
		
		new_case.setEnabled(false); // bouton non clickable
		
		return new_case;
	}
	
	// D�finition des getters et setters	

	
	// D�finition des m�thodes
	public void clear() {
		for(int i=0; i < this.longueurPlateau; i++) {
			for(int j=0; j < this.largeurPlateau; j++) {
				this.tab_cases[i][j].setBackground(Color.GREEN);	
			}
		}		
	}
	
	public void show_serpent(Serpent s) {
		int longueur = s.getLongueur();
		
		Coordonnee coor_tete_serpent = s.getNoeud(0);
		this.tab_cases[coor_tete_serpent.get_x()][coor_tete_serpent.get_y()].setBackground(Color.ORANGE);	
		for(int i=1; i < longueur; i++) {
			coor_tete_serpent = s.getNoeud(i);
			this.tab_cases[coor_tete_serpent.get_x()][coor_tete_serpent.get_y()].setBackground(Color.YELLOW);			
		}
	
	}
	
	public void show_pomme(Pomme p) {
		Coordonnee coor_pomme = p.get_coodonnee();
		this.tab_cases[coor_pomme.get_x()][coor_pomme.get_y()].setBackground(Color.RED);
	}	
}
package snake;

public class Pomme {
	// Définition des attributs
	private Coordonnee coor;
	
	// D�finition des constructeurs
	public Pomme() {
		this.coor = new Coordonnee();	
	}
	
	public Pomme(int x, int y) {
		this.coor = new Coordonnee(x,y);
	}
	
	public Pomme(Coordonnee c) {
		this.coor = c;
	}
	
	// D�finition des getters et setters
	public Coordonnee get_coodonnee() {
		return this.coor;
	}
	
	public int get_x() {
		return this.coor.get_x();
	}
	
	public int get_y() {
		return this.coor.get_y();
	}
}
package snake;

import java.util.ArrayList;

public class Serpent {
	
	// Définition des attributs
	private int longueur;
	private int direction = DROITE;
	private Coordonnee old_queue; 
	private ArrayList body; 
	
	static final int HAUT 	= 1;
	static final int DROITE = 2;
	static final int BAS 	= 3;
	static final int GAUCHE = 4;
	
	// D�finition des constructeurs
	public Serpent() {
		Coordonnee c1 = new Coordonnee(0,0);
		Coordonnee c2 = new Coordonnee(0,1);
		this.body = new ArrayList<>();
		this.addNoeud(c1);
		this.addNoeud(c2);
	}
	 
	public Serpent(int a, int b) {
		Coordonnee c1 = new Coordonnee(0,0);
		Coordonnee c2 = new Coordonnee(0,1);
		this.body = new ArrayList<>();
		this.addNoeud(c1);
		this.addNoeud(c2);
	}
	
	public Serpent(Coordonnee c) {
		Coordonnee c1 = new Coordonnee(0,0);
		Coordonnee c2 = new Coordonnee(0,1);
		this.body = new ArrayList<>();
		this.addNoeud(c1);
		this.addNoeud(c2);
	}

	// D�finition des getters et setters
	public int getLongueur() {
		return this.longueur;
	}
	
	public int getDirection() {
		return this.direction;
	}	
	
	public void setDirection(int d) {
		this.direction = d;
	}
	
	public Coordonnee getOldQueue() {
		return this.old_queue;
	}	
	
	public void setOldQueue(Coordonnee c) {
		this.old_queue = c;
	}	
	
	public Coordonnee getNoeud(int i) {
		return this.body.get(i);
	}
	
	public void setNoeud(int i, Coordonnee c) {
		this.body.set(i, c);
	}	
	
	public void addNoeud(Coordonnee c) {
		this.body.add(c);
		this.longueur = this.body.size();
	}	

	// D�finition des m�thodes
	public void avancerSerpent(int direction) {
		System.out.println("Le serpent avance");	

		if (direction == 0) {
			direction = this.getDirection(); 
		}
		int longueur = this.getLongueur();	
		
		Coordonnee coor_old_next_noeud = new Coordonnee();
		Coordonnee coor_old_queue = new Coordonnee();
		Coordonnee coor_old_tete = new Coordonnee();
		Coordonnee coord_new_tete = new Coordonnee();
		
		int new_x = 0;
		int new_y = 0;
		
		if (direction == HAUT || direction == DROITE || direction == BAS || direction == GAUCHE ) {
			coor_old_queue = this.getNoeud(longueur-1);				
			coor_old_tete = this.getNoeud(0);

			switch(direction) {
			case HAUT: 
				System.out.println("Le serpent avance en haut");
				this.setDirection(HAUT);
				new_x = coor_old_tete.get_x() + 0;
				new_y = coor_old_tete.get_y() - 1;
				coord_new_tete = new Coordonnee(new_x, new_y);				
				break;			
			case DROITE: 
				System.out.println("Le serpent tourne � droite");
				this.setDirection(DROITE);
				new_x = coor_old_tete.get_x() + 1;
				new_y = coor_old_tete.get_y() + 0;
				coord_new_tete = new Coordonnee(new_x, new_y);				
				break;			
			case BAS: 
				System.out.println("Le serpent avanca en bas");
				this.setDirection(BAS);
				new_x = coor_old_tete.get_x() + 0;
				new_y = coor_old_tete.get_y() + 1;
				coord_new_tete = new Coordonnee(new_x, new_y);				
				break;				
			case GAUCHE: 
				System.out.println("Le serpent tourne � gauche");
				this.setDirection(GAUCHE);
				new_x = coor_old_tete.get_x() - 1;
				new_y = coor_old_tete.get_y() + 0;
				coord_new_tete = new Coordonnee(new_x, new_y);				
				break;		
			default:
				System.out.println("Erreur: le serpent n'avance pas");
				break;
			}		
			
			this.setOldQueue(coor_old_queue);
			for(int i=longueur-1; i > 0; i--) {
				coor_old_next_noeud = this.getNoeud(i-1);
				this.setNoeud(i, coor_old_next_noeud);					
			}	
			this.setNoeud(0, coord_new_tete);	
		}	
	}
	
	public void grossir() {
		int longueur = this.getLongueur();			
		Coordonnee coor_old_queue = this.getOldQueue();
		this.addNoeud(coor_old_queue);	
	}
}

External Services