Projet Snake
Création du jeu Snake en utilisant le language Java et la bibliothèque Swing
Slides & Videos
Members
Name | Contribution |
---|---|
VINTEL Toine | - organisation - UML - structure code |
Francesco Prince | Gestion du déroulement du jeu, initialisation |
ALLAOUI Yacine | Ecriture 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éo | Réflexion sur le code (partie game, collision) diagramme UML rédaction des documents |
State of the Art
Business Aspect
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
- 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
- détailler méthode
- 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.
- Vérification des collisions
Il y a 3 types de collisions à tester lors d’une partie :
- 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.
- 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.
- 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
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
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
Image | Name | Part Number | Price | Count | Link |
---|---|---|---|---|---|
🛒 |
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 ArrayListbody; 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); } }