diff --git a/Images/Colonne.png b/Images/Colonne.png new file mode 100644 index 0000000..848061f Binary files /dev/null and b/Images/Colonne.png differ diff --git a/Images/Fantome.png b/Images/Fantome.png new file mode 100644 index 0000000..e7d192f Binary files /dev/null and b/Images/Fantome.png differ diff --git a/Images/Mur.png b/Images/Mur.png new file mode 100644 index 0000000..0cc89bf Binary files /dev/null and b/Images/Mur.png differ diff --git a/Images/Pacman.png b/Images/Pacman.png new file mode 100644 index 0000000..d60a327 Binary files /dev/null and b/Images/Pacman.png differ diff --git a/Images/Vide.png b/Images/Vide.png new file mode 100644 index 0000000..60c9d68 Binary files /dev/null and b/Images/Vide.png differ diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..165507e --- /dev/null +++ b/src/Main.java @@ -0,0 +1,21 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +import VueControleur.VueControleurGyromite; +import modele.plateau.Jeu; + +public class Main { + public static void main(String[] args) { + Jeu jeu = new Jeu(); + + VueControleurGyromite vc = new VueControleurGyromite(jeu); + + jeu.getOrdonnanceur().addObserver(vc); + + vc.setVisible(true); + jeu.start(300); + } +} diff --git a/src/VueControleur/VueControleurGyromite.java b/src/VueControleur/VueControleurGyromite.java new file mode 100644 index 0000000..980d1d5 --- /dev/null +++ b/src/VueControleur/VueControleurGyromite.java @@ -0,0 +1,140 @@ +package VueControleur; + +import java.awt.GridLayout; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Observable; +import java.util.Observer; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import javax.swing.*; + +import modele.deplacements.Controle4Directions; +import modele.deplacements.Direction; +import modele.plateau.*; + + +/** Cette classe a deux fonctions : + * (1) Vue : proposer une représentation graphique de l'application (cases graphiques, etc.) + * (2) Controleur : écouter les évènements clavier et déclencher le traitement adapté sur le modèle (flèches direction Pacman, etc.)) + * + */ +public class VueControleurGyromite extends JFrame implements Observer { + private Jeu jeu; // référence sur une classe de modèle : permet d'accéder aux données du modèle pour le rafraichissement, permet de communiquer les actions clavier (ou souris) + + private int sizeX; // taille de la grille affichée + private int sizeY; + + // icones affichées dans la grille + private ImageIcon icoHero; + private ImageIcon icoVide; + private ImageIcon icoMur; + private ImageIcon icoColonne; + + private JLabel[][] tabJLabel; // cases graphique (au moment du rafraichissement, chaque case va être associée à une icône, suivant ce qui est présent dans le modèle) + + + public VueControleurGyromite(Jeu _jeu) { + sizeX = jeu.SIZE_X; + sizeY = _jeu.SIZE_Y; + jeu = _jeu; + + chargerLesIcones(); + placerLesComposantsGraphiques(); + ajouterEcouteurClavier(); + } + + private void ajouterEcouteurClavier() { + addKeyListener(new KeyAdapter() { // new KeyAdapter() { ... } est une instance de classe anonyme, il s'agit d'un objet qui correspond au controleur dans MVC + @Override + public void keyPressed(KeyEvent e) { + switch(e.getKeyCode()) { // on regarde quelle touche a été pressée + case KeyEvent.VK_LEFT : Controle4Directions.getInstance().setDirectionCourante(Direction.gauche); break; + case KeyEvent.VK_RIGHT : Controle4Directions.getInstance().setDirectionCourante(Direction.droite); break; + case KeyEvent.VK_DOWN : Controle4Directions.getInstance().setDirectionCourante(Direction.bas); break; + case KeyEvent.VK_UP : Controle4Directions.getInstance().setDirectionCourante(Direction.haut); break; + } + } + }); + } + + + private void chargerLesIcones() { + icoHero = chargerIcone("Images/Pacman.png"); + icoVide = chargerIcone("Images/Vide.png"); + icoColonne = chargerIcone("Images/Colonne.png"); + icoMur = chargerIcone("Images/Mur.png"); + } + + private ImageIcon chargerIcone(String urlIcone) { + BufferedImage image = null; + + try { + image = ImageIO.read(new File(urlIcone)); + } catch (IOException ex) { + Logger.getLogger(VueControleurGyromite.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + + return new ImageIcon(image); + } + + private void placerLesComposantsGraphiques() { + setTitle("Gyromite"); + setSize(400, 250); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // permet de terminer l'application à la fermeture de la fenêtre + + JComponent grilleJLabels = new JPanel(new GridLayout(sizeY, sizeX)); // grilleJLabels va contenir les cases graphiques et les positionner sous la forme d'une grille + + tabJLabel = new JLabel[sizeX][sizeY]; + + for (int y = 0; y < sizeY; y++) { + for (int x = 0; x < sizeX; x++) { + JLabel jlab = new JLabel(); + tabJLabel[x][y] = jlab; // on conserve les cases graphiques dans tabJLabel pour avoir un accès pratique à celles-ci (voir mettreAJourAffichage() ) + grilleJLabels.add(jlab); + } + } + add(grilleJLabels); + } + + + /** + * Il y a une grille du côté du modèle ( jeu.getGrille() ) et une grille du côté de la vue (tabJLabel) + */ + private void mettreAJourAffichage() { + + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + if (jeu.getGrille()[x][y] instanceof Heros) { // si la grille du modèle contient un Pacman, on associe l'icône Pacman du côté de la vue + // System.out.println("Héros !"); + tabJLabel[x][y].setIcon(icoHero); + } else if (jeu.getGrille()[x][y] instanceof Mur) { + tabJLabel[x][y].setIcon(icoMur); + } else if (jeu.getGrille()[x][y] instanceof Colonne) { + tabJLabel[x][y].setIcon(icoColonne); + } else { + tabJLabel[x][y].setIcon(icoVide); + } + } + } + } + + @Override + public void update(Observable o, Object arg) { + mettreAJourAffichage(); + /* + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + mettreAJourAffichage(); + } + }); + */ + + } +} diff --git a/src/modele/deplacements/Colonne.java b/src/modele/deplacements/Colonne.java new file mode 100644 index 0000000..fd437e4 --- /dev/null +++ b/src/modele/deplacements/Colonne.java @@ -0,0 +1,9 @@ +package modele.deplacements; + +/** + * A la reception d'une commande, toutes les cases (EntitesDynamiques) des colonnes se déplacent dans la direction définie + * (vérifier "collisions" avec le héros) + */ +public class Colonne extends RealisateurDeDeplacement { + protected boolean realiserDeplacement() { return false; } // TODO +} diff --git a/src/modele/deplacements/Controle4Directions.java b/src/modele/deplacements/Controle4Directions.java new file mode 100644 index 0000000..71cedda --- /dev/null +++ b/src/modele/deplacements/Controle4Directions.java @@ -0,0 +1,55 @@ +package modele.deplacements; + +import modele.plateau.Entite; +import modele.plateau.EntiteDynamique; + +/** + * Controle4Directions permet d'appliquer une direction (connexion avec le clavier) à un ensemble d'entités dynamiques + */ +public class Controle4Directions extends RealisateurDeDeplacement { + private Direction directionCourante; + // Design pattern singleton + private static Controle4Directions c3d; + + public static Controle4Directions getInstance() { + if (c3d == null) { + c3d = new Controle4Directions(); + } + return c3d; + } + + public void setDirectionCourante(Direction _directionCourante) { + directionCourante = _directionCourante; + } + + public boolean realiserDeplacement() { + boolean ret = false; + for (EntiteDynamique e : lstEntitesDynamiques) { + if (directionCourante != null) + switch (directionCourante) { + case gauche: + case droite: + if (e.avancerDirectionChoisie(directionCourante)) + ret = true; + break; + + case haut: + // on ne peut pas sauter sans prendre appui + // (attention, test d'appui réalisé à partir de la position courante, si la gravité à été appliquée, il ne s'agit pas de la position affichée, amélioration possible) + Entite eBas = e.regarderDansLaDirection(Direction.bas); + if (eBas != null && eBas.peutServirDeSupport()) { + if (e.avancerDirectionChoisie(Direction.haut)) + ret = true; + } + break; + } + } + + return ret; + + } + + public void resetDirection() { + directionCourante = null; + } +} diff --git a/src/modele/deplacements/Direction.java b/src/modele/deplacements/Direction.java new file mode 100644 index 0000000..c04ec96 --- /dev/null +++ b/src/modele/deplacements/Direction.java @@ -0,0 +1,14 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package modele.deplacements; + +/** Type énuméré des directions : les directions correspondent à un ensemble borné de valeurs, connu à l'avance + * + * + */ +public enum Direction { + haut, bas, gauche, droite +} diff --git a/src/modele/deplacements/Gravite.java b/src/modele/deplacements/Gravite.java new file mode 100644 index 0000000..d23b661 --- /dev/null +++ b/src/modele/deplacements/Gravite.java @@ -0,0 +1,21 @@ +package modele.deplacements; + +import modele.plateau.Entite; +import modele.plateau.EntiteDynamique; + +public class Gravite extends RealisateurDeDeplacement { + @Override + public boolean realiserDeplacement() { + boolean ret = false; + + for (EntiteDynamique e : lstEntitesDynamiques) { + Entite eBas = e.regarderDansLaDirection(Direction.bas); + if (eBas == null || (eBas != null && !eBas.peutServirDeSupport())) { + if (e.avancerDirectionChoisie(Direction.bas)) + ret = true; + } + } + + return ret; + } +} diff --git a/src/modele/deplacements/IA.java b/src/modele/deplacements/IA.java new file mode 100644 index 0000000..0f8d1d5 --- /dev/null +++ b/src/modele/deplacements/IA.java @@ -0,0 +1,5 @@ +package modele.deplacements; + +public class IA extends RealisateurDeDeplacement { + protected boolean realiserDeplacement() { return false; } // TODO +} diff --git a/src/modele/deplacements/Ordonnanceur.java b/src/modele/deplacements/Ordonnanceur.java new file mode 100644 index 0000000..a380bc5 --- /dev/null +++ b/src/modele/deplacements/Ordonnanceur.java @@ -0,0 +1,53 @@ +package modele.deplacements; + +import modele.plateau.Jeu; + +import java.util.ArrayList; +import java.util.Observable; + +import static java.lang.Thread.*; + +public class Ordonnanceur extends Observable implements Runnable { + private Jeu jeu; + private ArrayList lstDeplacements = new ArrayList(); + private long pause; + public void add(RealisateurDeDeplacement deplacement) { + lstDeplacements.add(deplacement); + } + + public Ordonnanceur(Jeu _jeu) { + jeu = _jeu; + } + + public void start(long _pause) { + pause = _pause; + new Thread(this).start(); + } + + @Override + public void run() { + boolean update = false; + + while(true) { + jeu.resetCmptDepl(); + for (RealisateurDeDeplacement d : lstDeplacements) { + if (d.realiserDeplacement()) + update = true; + } + + Controle4Directions.getInstance().resetDirection(); + + if (update) { + setChanged(); + notifyObservers(); + } + + try { + sleep(pause); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + } +} diff --git a/src/modele/deplacements/RealisateurDeDeplacement.java b/src/modele/deplacements/RealisateurDeDeplacement.java new file mode 100644 index 0000000..242c3bf --- /dev/null +++ b/src/modele/deplacements/RealisateurDeDeplacement.java @@ -0,0 +1,15 @@ +package modele.deplacements; + +import modele.plateau.EntiteDynamique; + +import java.util.ArrayList; + +/** +Tous les déplacement sont déclenchés par cette classe (gravité, controle clavier, IA, etc.) + */ +public abstract class RealisateurDeDeplacement { + protected ArrayList lstEntitesDynamiques = new ArrayList(); + protected abstract boolean realiserDeplacement(); + + public void addEntiteDynamique(EntiteDynamique ed) {lstEntitesDynamiques.add(ed);}; +} diff --git a/src/modele/plateau/Bot.java b/src/modele/plateau/Bot.java new file mode 100644 index 0000000..b13b738 --- /dev/null +++ b/src/modele/plateau/Bot.java @@ -0,0 +1,23 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package modele.plateau; + +import java.util.Random; + +/** + * Ennemis (Smicks) + */ +public class Bot extends EntiteDynamique { + private Random r = new Random(); + + public Bot(Jeu _jeu) { + super(_jeu); + } + + public boolean peutEtreEcrase() { return true; } + public boolean peutServirDeSupport() { return true; } + public boolean peutPermettreDeMonterDescendre() { return false; }; +} diff --git a/src/modele/plateau/Colonne.java b/src/modele/plateau/Colonne.java new file mode 100644 index 0000000..a9415a5 --- /dev/null +++ b/src/modele/plateau/Colonne.java @@ -0,0 +1,9 @@ +package modele.plateau; + +public class Colonne extends EntiteDynamique { + public Colonne(Jeu _jeu) { super(_jeu); } + + public boolean peutEtreEcrase() { return false; } + public boolean peutServirDeSupport() { return true; } + public boolean peutPermettreDeMonterDescendre() { return false; }; +} diff --git a/src/modele/plateau/Entite.java b/src/modele/plateau/Entite.java new file mode 100644 index 0000000..a56b695 --- /dev/null +++ b/src/modele/plateau/Entite.java @@ -0,0 +1,20 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package modele.plateau; + +import modele.deplacements.Direction; + +public abstract class Entite { + protected Jeu jeu; + + public Entite(Jeu _jeu) { + jeu = _jeu; + } + + public abstract boolean peutEtreEcrase(); // l'entité peut être écrasée (par exemple par une colonne ...) + public abstract boolean peutServirDeSupport(); // permet de stopper la gravité, prendre appui pour sauter + public abstract boolean peutPermettreDeMonterDescendre(); // si utilisation de corde (attention, l'environnement ne peut pour l'instant sotker qu'une entité par case (si corde : 2 nécessaires), améliorations à prévoir) +} diff --git a/src/modele/plateau/EntiteDynamique.java b/src/modele/plateau/EntiteDynamique.java new file mode 100644 index 0000000..dfe7910 --- /dev/null +++ b/src/modele/plateau/EntiteDynamique.java @@ -0,0 +1,15 @@ +package modele.plateau; + +import modele.deplacements.Direction; + +/** + * Entités amenées à bouger (colonnes, ennemis) + */ +public abstract class EntiteDynamique extends Entite { + public EntiteDynamique(Jeu _jeu) { super(_jeu); } + + public boolean avancerDirectionChoisie(Direction d) { + return jeu.deplacerEntite(this, d); + } + public Entite regarderDansLaDirection(Direction d) {return jeu.regarderDansLaDirection(this, d);} +} diff --git a/src/modele/plateau/EntiteStatique.java b/src/modele/plateau/EntiteStatique.java new file mode 100644 index 0000000..0909109 --- /dev/null +++ b/src/modele/plateau/EntiteStatique.java @@ -0,0 +1,14 @@ +package modele.plateau; + +/** + * Ne bouge pas (murs...) + */ +public abstract class EntiteStatique extends Entite { + public EntiteStatique(Jeu _jeu) { + super(_jeu); + } + + public boolean peutEtreEcrase() { return false; } + public boolean peutServirDeSupport() { return true; } + public boolean peutPermettreDeMonterDescendre() { return false; }; +} \ No newline at end of file diff --git a/src/modele/plateau/Heros.java b/src/modele/plateau/Heros.java new file mode 100644 index 0000000..992d257 --- /dev/null +++ b/src/modele/plateau/Heros.java @@ -0,0 +1,19 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package modele.plateau; + +/** + * Héros du jeu + */ +public class Heros extends EntiteDynamique { + public Heros(Jeu _jeu) { + super(_jeu); + } + + public boolean peutEtreEcrase() { return true; } + public boolean peutServirDeSupport() { return true; } + public boolean peutPermettreDeMonterDescendre() { return false; }; +} diff --git a/src/modele/plateau/Jeu.java b/src/modele/plateau/Jeu.java new file mode 100644 index 0000000..6c7e371 --- /dev/null +++ b/src/modele/plateau/Jeu.java @@ -0,0 +1,175 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package modele.plateau; + +import modele.deplacements.Controle4Directions; +import modele.deplacements.Direction; +import modele.deplacements.Gravite; +import modele.deplacements.Ordonnanceur; + +import java.awt.Point; +import java.util.HashMap; + +/** Actuellement, cette classe gère les postions + * (ajouter conditions de victoire, chargement du plateau, etc.) + */ +public class Jeu { + + public static final int SIZE_X = 20; + public static final int SIZE_Y = 10; + + // compteur de déplacements horizontal et vertical (1 max par défaut, à chaque pas de temps) + private HashMap cmptDeplH = new HashMap(); + private HashMap cmptDeplV = new HashMap(); + + private Heros hector; + + private HashMap map = new HashMap(); // permet de récupérer la position d'une entité à partir de sa référence + private Entite[][] grilleEntites = new Entite[SIZE_X][SIZE_Y]; // permet de récupérer une entité à partir de ses coordonnées + + private Ordonnanceur ordonnanceur = new Ordonnanceur(this); + + public Jeu() { + initialisationDesEntites(); + } + + public void resetCmptDepl() { + cmptDeplH.clear(); + cmptDeplV.clear(); + } + + public void start(long _pause) { + ordonnanceur.start(_pause); + } + + public Entite[][] getGrille() { + return grilleEntites; + } + + public Heros getHector() { + return hector; + } + + private void initialisationDesEntites() { + hector = new Heros(this); + addEntite(hector, 2, 1); + + Gravite g = new Gravite(); + g.addEntiteDynamique(hector); + ordonnanceur.add(g); + + Controle4Directions.getInstance().addEntiteDynamique(hector); + ordonnanceur.add(Controle4Directions.getInstance()); + + // murs extérieurs horizontaux + for (int x = 0; x < 20; x++) { + addEntite(new Mur(this), x, 0); + addEntite(new Mur(this), x, 9); + } + + // murs extérieurs verticaux + for (int y = 1; y < 9; y++) { + addEntite(new Mur(this), 0, y); + addEntite(new Mur(this), 19, y); + } + + addEntite(new Mur(this), 2, 6); + addEntite(new Mur(this), 3, 6); + } + + private void addEntite(Entite e, int x, int y) { + grilleEntites[x][y] = e; + map.put(e, new Point(x, y)); + } + + /** Permet par exemple a une entité de percevoir sont environnement proche et de définir sa stratégie de déplacement + * + */ + public Entite regarderDansLaDirection(Entite e, Direction d) { + Point positionEntite = map.get(e); + return objetALaPosition(calculerPointCible(positionEntite, d)); + } + + /** Si le déplacement de l'entité est autorisé (pas de mur ou autre entité), il est réalisé + * Sinon, rien n'est fait. + */ + public boolean deplacerEntite(Entite e, Direction d) { + boolean retour = false; + + Point pCourant = map.get(e); + + Point pCible = calculerPointCible(pCourant, d); + + if (contenuDansGrille(pCible) && objetALaPosition(pCible) == null) { // a adapter (collisions murs, etc.) + // compter le déplacement : 1 deplacement horizontal et vertical max par pas de temps par entité + switch (d) { + case bas: + case haut: + if (cmptDeplV.get(e) == null) { + cmptDeplV.put(e, 1); + + retour = true; + } + break; + case gauche: + case droite: + if (cmptDeplH.get(e) == null) { + cmptDeplH.put(e, 1); + retour = true; + + } + break; + } + } + + if (retour) { + deplacerEntite(pCourant, pCible, e); + } + + return retour; + } + + + private Point calculerPointCible(Point pCourant, Direction d) { + Point pCible = null; + + switch(d) { + case haut: pCible = new Point(pCourant.x, pCourant.y - 1); break; + case bas : pCible = new Point(pCourant.x, pCourant.y + 1); break; + case gauche : pCible = new Point(pCourant.x - 1, pCourant.y); break; + case droite : pCible = new Point(pCourant.x + 1, pCourant.y); break; + + } + + return pCible; + } + + private void deplacerEntite(Point pCourant, Point pCible, Entite e) { + grilleEntites[pCourant.x][pCourant.y] = null; + grilleEntites[pCible.x][pCible.y] = e; + map.put(e, pCible); + } + + /** Indique si p est contenu dans la grille + */ + private boolean contenuDansGrille(Point p) { + return p.x >= 0 && p.x < SIZE_X && p.y >= 0 && p.y < SIZE_Y; + } + + private Entite objetALaPosition(Point p) { + Entite retour = null; + + if (contenuDansGrille(p)) { + retour = grilleEntites[p.x][p.y]; + } + + return retour; + } + + public Ordonnanceur getOrdonnanceur() { + return ordonnanceur; + } +} diff --git a/src/modele/plateau/Mur.java b/src/modele/plateau/Mur.java new file mode 100644 index 0000000..53055c0 --- /dev/null +++ b/src/modele/plateau/Mur.java @@ -0,0 +1,5 @@ +package modele.plateau; + +public class Mur extends EntiteStatique { + public Mur(Jeu _jeu) { super(_jeu); } +}