import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.sql.Connection;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class Jeu implements KeyListener, ActionListener{
//-------------------------------------------------------------------------
// PROPRIETES
//-------------------------------------------------------------------------
// Ecran : instance de ZoneDessin, qui contiendra tous les objets graphiques
// On ne va pas créer directement l'écran car ça va créer un flash
// Dès le lancement du jeu avec l'image de fond par défaut et l'imga du niveau
// et les animera
protected ZoneDessin ecran;
// Le cercle : en fait 2 demis-cercles, un qui passera sous la ligne
// un qui passera dessus
protected Cercle demiCercleAvant = new Cercle(90,-180); // celui qui est sur la ligne
protected Cercle demiCercleArriere = new Cercle(90,180); // celui qui est derrière la ligne
// A FAIRE : ajouter les objets graphiques manquants, s'il y en a
protected Ligne laLigne = new Ligne(); // Ligne infinie du jeu
// Timer : un objet qui émet des événements à un intervalle choisi,
// il sert à donner le pas de l'animation
protected Timer horloge;
// Une variable qui contiendra le score
protected double score=0;
// un label qui servira à afficher le score
protected JLabel labScore;
protected JLabel labTriche;
protected JLabel labPause;
protected boolean jeuCommence = false;
protected boolean pause = false;
protected boolean modeTriche = false;
protected BonusMalus itemCourant = new BonusMalus(1500, 300, true); // On crée un seul item (carré ou triangle) qui va tourner en boucle pendant la partie
protected int chronoBonus = 0;// Compteur qui va gérer la durée du bonus x5 (125 frames = 5 secondes)
// Propriété de ma connexion à ma base de données
Connection conn = null;
private int utilisateurId;
// Variables actuelles du début de jeu
protected int idNiveauActuel = 1;
protected Niveau niveauEnCours;
protected int segmentsTermines = 0; // Variable compteur de segments
protected Segment dernierSegmentValide = null;
// On sauvegarde la fenêtre principale de la partie en cours dans une variable.
// Cela nous permettra de la fermer proprement (avec la méthode .dispose())
// quand le joueur voudra lancer une nouvelle partie.
protected JFrame fenetrePrincipale;
protected javax.swing.JButton btnRejouer;
//-------------------------------------------------------------------------
// METHODES
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Constructeur de la classe
//-------------------------------------------------------------------------
public Jeu(int utilisateurId){
// Créer les tables de la base de données si elles n'existent pas
GestionBDD.creerTableUtilisateurSiAbsente();
GestionBDD.creerTableScoreSiAbsente();
JFrame fenetre = new JFrame();
this.utilisateurId = utilisateurId;
Background premierFond = null;
// On crée le jeu avec sa base de données
try {
conn = NiveauxDataConnect.getInstance().getConnection();
System.out.println("Connexion réussie !!!");
} catch (Exception ex) {
ex.printStackTrace();
javax.swing.JOptionPane.showMessageDialog(fenetre,"Erreur de connexion à la base de données. Le jeu ne peut pas démarrer.",
"Erreur Fatale",
javax.swing.JOptionPane.ERROR_MESSAGE
);
}
// On récupère le niveau 1
Niveau n1 = NiveauxDataConnect.recupererNiveau(conn, 1);
if( n1 != null ){
try {
// On charge l'mage du niveau 1
java.net.URL url = Jeu.class.getResource("/" + n1.getImage());
if(url != null){
java.awt.Image img = javax.imageio.ImageIO.read(url);
premierFond = new Background(img);
}
} catch (Exception e) {
e.printStackTrace();
}
}
this.ecran = new ZoneDessin(premierFond);
// Gestion du score : a réactiver en fin de TP, inutile au début
ecran.setLayout(null);
labScore = new JLabel();
labScore.setText("
score : 0
");
labScore.setBounds(20, 0, 200, 50);
ecran.add(labScore);
labTriche = new JLabel();
labTriche.setText("Mode de triche activé
");
labTriche.setBounds(600, 0, 200, 50); // Placé en haut à droite (la fenêtre fait 800 de large)
labTriche.setVisible(false); // Caché par défaut au lancement
ecran.add(labTriche);
labPause = new JLabel();
labPause.setText("Pause
");
labPause.setBounds(365, 300, 200, 50);//Placé au millieu de l'écran
labPause.setVisible(false);// Caché par défaut au lancement
ecran.add(labPause);
}
//-------------------------------------------------------------------------
// Méthodes qu'il faut implémenter pour être
// conforme à un KeyListener
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Appui sur une touche
// -> l'événement est émis lorsqu'on appuie, puis selon le rythme de
// répétition du clavier
//-------------------------------------------------------------------------
@Override
public void keyPressed(KeyEvent e){
// keyCode 38 : up
// keyCode 40 : down
int keyCode = e.getKeyCode();
if (keyCode==38){ // touche "flèche vers le haut"
// On demande aux deux demi-cercle de "monter"
demiCercleAvant.Monter();
demiCercleArriere.Monter();
}
if(keyCode==32){//touche espace
modeTriche =!modeTriche;
labTriche.setVisible(modeTriche); //afficher ou cacher le label
demiCercleAvant.setModeTriche(modeTriche);
demiCercleArriere.setModeTriche(modeTriche);
}
if(keyCode==27){//touche echap
pause = !pause;//active ou desactive le mode pause à chaque fois que la touche espace est appuyé
labPause.setVisible(pause);
if(pause==false && this.ecran.partiePerdue==false){
this.horloge.start();
}else{
this.horloge.stop();// Le jeux se stoppe si le joueur appuie sur pause
}
}
}
//-------------------------------------------------------------------------
// Relâchement de la touche
//-------------------------------------------------------------------------
@Override
public void keyReleased(KeyEvent e){
// keyCode 38 : up
// keyCode 40 : down
int keyCode = e.getKeyCode();
if (keyCode==38){
// On demande aux deux demi-cercle "d'arrêter de monter"
demiCercleAvant.ArreterMonter();
demiCercleArriere.ArreterMonter();
}
}
//-------------------------------------------------------------------------
// Une méthode que nous n'utilisons pas
//-------------------------------------------------------------------------
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
//-------------------------------------------------------------------------
// Démarrage du jeu :
// création de diverses instances et
//-------------------------------------------------------------------------
public void demarrer(){
// Création d'une fenêtre
JFrame fenetre = new JFrame();
this.fenetrePrincipale = fenetre; // On sauvegarde la fenêtre principale de la partie en cours dans une variable. Cela nous permettra de la fermer proprement (avec la méthode .dispose()) quand le joueur voudra lancer une nouvelle partie.
// A FAIRE :
// placer dans l'instance de l'écran tous les objets graphiques nécessaires
// par exemple : ecran.ajouterObjet(demiCerleArriere);
this.ecran.ajouterObjet(demiCercleArriere);
this.ecran.ajouterObjet(laLigne);
this.ecran.ajouterObjet(demiCercleAvant);
// on indique que c'est le jeu qui traitera les appuis sur une touche
ecran.addKeyListener(this);
ecran.setFocusable(true);
fenetre.setContentPane(ecran);
fenetre.pack();
fenetre.setLocation(100, 100);
fenetre.setVisible(true);
// s'assurer que l'écran reçoit le focus clavier
ecran.requestFocusInWindow();
fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
javax.swing.JButton boutonscores = BoutonsManager.creerBoutonScores(fenetre, utilisateurId);
this.ecran.add(boutonscores);
// Création du timer
horloge = new Timer(40, this);
//propriétés dont on aura besoin pour utiliser NiveauxDataConnect
//---------------------------------------------------------------------------------
// C'est ici qu'on appelle NiveauxDataConnect pour initialiser la connexion à la base de données des niveaux
// On fera une synchronisation pour éviter que le jeu se fige
// pendant l'initialisation de la base de données, qui peut prendre un peu de temps
//--------------------------------------------------------------------------------
// démarrer le timer sur l'EDT même si l'initialisation BDD a échoué
if (horloge != null && !horloge.isRunning()) {
chargerNiveau(idNiveauActuel);
horloge.start();
}
// A FAIRE :
// donner la couleur des 2 demi-cercles, par exemple : demiCerleArriere.setCouleur(new Color(0.8f,0.0f,0.0f));
//demiCercleArriere.setCouleur(new Color(26, 95, 161));
//demiCercleAvant.setCouleur(new Color(26, 95, 161));
ecran.ajouterObjet(itemCourant);
javax.swing.JButton bscores = BoutonsManager.creerBoutonScores(fenetre, utilisateurId);
this.ecran.add(bscores);
// On initialise le bouton avec son texte
btnRejouer = new javax.swing.JButton("Rejouer");
// Positionnement absolu sur l'écran : (x, y, largeur, hauteur)
// On le place vers le centre (x=300), un peu vers le bas (y=400)
// pour qu'il apparaisse proprement sous le grand texte "Game Over"
btnRejouer.setBounds(300, 400, 200, 50);
btnRejouer.setBackground(new java.awt.Color(46, 204, 113));
btnRejouer.setForeground(java.awt.Color.WHITE);
btnRejouer.setFont(new java.awt.Font("Arial", java.awt.Font.BOLD, 20));
// TRÈS IMPORTANT : Le bouton est invisible tant que la partie est en cours !
// Il ne s'affichera qu'au moment précis où on perd (ou gagne).
btnRejouer.setVisible(false);
btnRejouer.addActionListener(e -> {
// ÉTAPE 1 : On détruit la fenêtre de la partie actuelle.
// Cela libère la mémoire et ferme l'écran du jeu fini.
fenetrePrincipale.dispose();
// ÉTAPE 2 : On crée une toute nouvelle instance de Jeu,
// On lui repasse l'ID de l'utilisateur pour qu'il reste connecté à son compte.
Jeu nouveauJeu = new Jeu(this.utilisateurId);
// ÉTAPE 3 : On lance cette nouvelle partie.
// Cela va automatiquement créer une nouvelle fenêtre, remettre le score à 0,
// et recharger le niveau 1. C'est magique !
nouveauJeu.demarrer();
});
// Enfin, on ajoute physiquement le bouton à notre ZoneDessin (l'écran)
this.ecran.add(btnRejouer);
}
//------------------------------------------------------------------------------------------------
// Méthode permettant de charger le niveau et de l'appliquer
//------------------------------------------------------------------------------------------------
public void chargerNiveau(int id){
System.out.println(conn);
this.niveauEnCours = NiveauxDataConnect.recupererNiveau(conn, id);
if(this.niveauEnCours != null){
// Remise à zéro de l'état propre au niveau
this.segmentsTermines = 0;
this.dernierSegmentValide = null;
this.ecran.partiePerdue = false;
// Formule : Gravité de base - (Niveau * incrément)
// Niveau 1 : 10.0 + (1 * 1.50) = 8.5 (poids normal)
// Niveau 4 : 10.0 + (4 * 1.50) = 4.0 (Très léger et plane)
double graviteNiveau = 10.0 - (this.niveauEnCours.getId() * 1.5);
// Sécurité: La gravité du niveau ne doit pas descendre en dessous de 2
// Sinon le cercle ne retomberait plus jamais
if(graviteNiveau < 2.0){
graviteNiveau = 2.0;
}
this.demiCercleAvant.setGravite(graviteNiveau);
this.demiCercleArriere.setGravite(graviteNiveau);
// On corse le jeu avec son ID
//On applique les paramètres aux options de jeu
this.laLigne.generer(niveauEnCours.getNbSegments(), niveauEnCours.getId());
this.laLigne.setVitesseLigne(niveauEnCours.getVitesseLigne()); // On modifie la vitesse de déplacement
this.demiCercleAvant.setCouleur(niveauEnCours.getCouleurCercle());
this.demiCercleArriere.setCouleur(niveauEnCours.getCouleurCercle());
this.ecran.changerImageFond(niveauEnCours.getImage());
this.itemCourant.vitesse = niveauEnCours.getVitesseLigne();
}else{
JFrame fenetre = new JFrame();
JOptionPane.showMessageDialog(fenetre, "CONGRATULATIONS !!! BRAVO VOUS AVEZ TERMINE LE JEU");
System.out.println("Fin du jeu !!! BRAVO VOUS AVEZ TERMINE LE JEU");
horloge.stop();
if (btnRejouer != null) {
btnRejouer.setVisible(true);
}
}
}
//------------------------------------------------------------------------------------------------
// Méthode permettant de passer au niveau suivant à travers l'id
//------------------------------------------------------------------------------------------------
private void passerAuNiveauSuivant(){
// Nettoyage de l'état de validation avant de charger le niveau suivant
this.segmentsTermines = 0;
this.dernierSegmentValide = null;
// On incrémente l'ID du niveau actuel
idNiveauActuel++;
JFrame fenetre = new JFrame();
// Petite pause avec un message
horloge.stop();
JOptionPane.showMessageDialog(fenetre, "Niveau terminé ! Préparez-vous pour le niveau suivant ;)");
chargerNiveau(idNiveauActuel);
horloge.start();
}
//------------------------------------------------------------------------------------------------
// Méthode appelée lorsqu'un événement timer se produit
// -> C'est ici que la loqique et l'animation du jeu sont traitées
// -> C'est ici que la vérification de la collision entre la ligne et le cercle doit être faite
//------------------------------------------------------------------------------------------------
@Override
public void actionPerformed(ActionEvent e) {
// 1. Animer et recalculer le segment courant
this.ecran.traiterBoucleAnimation();
this.laLigne.actualiserSegCourant();
if (itemCourant.actif){
itemCourant.y = this.laLigne.getYSurLigne(itemCourant.x) - 40;
}
// Mode triche : Le cercle suit la ligne
// On utilisera le calcul pour déterminer si la ligne traverse le cercle
if (modeTriche && this.laLigne.getSegCourant() != null) {
// double yPoint = this.getSegCourant().y + (this.SegCourant.yLong / this.SegCourant.xLong) * (this.xCercle - this.SegCourant.x);
double yPointTricheArrire = this.laLigne.getSegCourant().getY() + (this.laLigne.getSegCourant().getYLong() / this.laLigne.getSegCourant().getXLong()) * (this.demiCercleArriere.getX() - this.laLigne.getSegCourant().getX());
double yPointTricheAvant = this.laLigne.getSegCourant().getY() + (this.laLigne.getSegCourant().getYLong() / this.laLigne.getSegCourant().getXLong()) * (this.demiCercleAvant.getX() - this.laLigne.getSegCourant().getX());
this.demiCercleAvant.y = yPointTricheArrire ;
this.demiCercleArriere.y = yPointTricheAvant;
}
if (itemCourant.estTouche(demiCercleAvant)) {
if (itemCourant.estBonus) {
chronoBonus = 125;
} else {
score -= 2000;
if (score < 0){
score = 0;
}
}
itemCourant.x = -1000; // On le place à droite de l'écran pour qu'il disparaisse
itemCourant.actif = false; // On le désactive pour qu'on ne puisse pas le ramasser en boucle
}
// Si le bonus est inactif (ramassé) ou s'il est sorti de l'écran par la gauche
if (!itemCourant.actif || itemCourant.x < -50) {
if (Math.random() < 0.01) { //taux d apparition d'un bonus/malus : 1% à chaque frame
// On cherche un segment qui est en train d'arriver par la droite (genre vers X = 800)
for (Segment s : this.laLigne.listeSegments) {
if (s.x > 750 && s.x < 850) {
itemCourant.x = s.x; // On l'accroche au début du segment
itemCourant.estBonus = Math.random() > 0.5;// 50% de chance d'être un bonus
itemCourant.actif = true; // On le réactive !
break;
}
}
}
}
// 2. On vérifie si la ligne traverse bien le cercle
boolean verification = this.laLigne.estDansCercle(this.demiCercleAvant);
// NB: Le nombre de segment s'incrémente qu'il y est collision ou pas
if(verification){
// On compte seulement lorsqu'on change de segment
Segment segmentCourant = this.laLigne.getSegCourant();
if(segmentCourant != null && segmentCourant != this.dernierSegmentValide){
this.dernierSegmentValide = segmentCourant;
this.segmentsTermines++;
// Ajouter les points continuellement tant que le segment est vert (cyan)
if(chronoBonus>0){
this.score+=(this.niveauEnCours.getMultiplicateurScore()*5);//si le compteur est actif, on ajoute 5 fois le score du niveau
}else{
this.score+=this.niveauEnCours.getMultiplicateurScore();
}
this.jeuCommence = true;
}
// Vérification si on a atteint l'objectif du niveau : Nb de Segments du niveau atteint
if(this.segmentsTermines >= this.niveauEnCours.getNbSegments()){
passerAuNiveauSuivant();
}
}else{
// On compte seulement lorsqu'on change de segment
Segment segmentCourant = this.laLigne.getSegCourant();
if(segmentCourant != null && segmentCourant != this.dernierSegmentValide){
this.dernierSegmentValide = segmentCourant;
this.segmentsTermines++;
}
//-------------------------------------------------------------------------------------------------------------------
// A FAIRE: Le jeu peut s'arrêter si la ligne n'est pas dans le cercle
// NB: Le jeu commence quand la ligne est dans le cercle
// Au début le ligne n'est pas dans le cercle car la ligne est à droite du cercle, et elle se déplace vers la gauche
//-------------------------------------------------------------------------------------------------------------------
if (this.laLigne.getSegCourant()!=null && !this.modeTriche) { // Le jeu s'arrête si le joueur n'a pas traversé la première ligne
this.horloge.stop(); // 1. Arrêter le temps
this.ecran.partiePerdue = true; // 2. Signaler à l'écran
this.btnRejouer.setVisible(true); // 3. Afficher le bouton "Rejouer"
this.ecran.repaint(); // 4. Forcer l'affichage du texte
GestionBDD.ajouterScore((int)this.score, this.utilisateurId);// enregistrement du score dans la base de donne
}
}
if (chronoBonus>0) {
chronoBonus--; //On enlève 1 frame au compteur
}
labScore.setText("score : " + this.score + "
");
}
}