From 1e7f70ab6b9a7bc833f6f522d87d35efbd06e275 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 28 Mar 2026 14:18:17 +0100 Subject: [PATCH] updateBoule/immortel --- Jeu.db | Bin 20480 -> 0 bytes linea/BouleBonus.java | 89 +++++++++++-- linea/Cercle.java | 37 +++--- linea/DatabaseConnection.java | 94 +++++++++++--- linea/Jeu.db | Bin 20480 -> 24576 bytes linea/Jeu.java | 227 +++++++++++++++++++++++++++++++--- linea/Ligne.java | 67 ++++++---- linea/LineaAppli.java | 150 +++++++++++++++++++--- linea/ZoneDessin.java | 162 +++++++++++++++++------- 9 files changed, 679 insertions(+), 147 deletions(-) delete mode 100644 Jeu.db diff --git a/Jeu.db b/Jeu.db deleted file mode 100644 index ae846f779513e38b0622899316e73307294ec904..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI#&uij96u|MM+7=eN-OCaREzD&>vFv64fpukCWNqxm6zWN$oh}xWSQFi|g&z7} zcarf}++FCg$af$y$;+F}`;3>@`PCYxO5DejFi6EW>yu^M)^{N+%PQ$(Umwen-Itd) z`q$pL{@-QE`Z{|#($VtKsdfD4_{+y79P8e0v5a+=4n@P6 z3jUFG(e`BX(#@7yStk{WHCibj zv7bKY(B>_3E*GfHv!b<`X<6l4u%kE>zP#Pw*TC)mgIWm-x9tn}rqwd*yWTr+YBjr? zuP%8U>5jRVsK=>_25RfJR9Np;uo4BKI@g)i`GK8RJpY}~X;961uP}poR;+kX*w?pI zJWkZ~F3yYIDlO~Hb<@v4009ILKmY**5I_I{1Q0*~fgKj0|KH)wr8EQ(KmY**5I_I{ z1Q0*~0R$ET^#A-75I_I{1Q0*~0R#|0009IL*nI)||J~nXDntMQ1Q0*~0R#|0009IL GK;RV<&e=2o diff --git a/linea/BouleBonus.java b/linea/BouleBonus.java index 7b95a0a..c5014ab 100644 --- a/linea/BouleBonus.java +++ b/linea/BouleBonus.java @@ -11,6 +11,14 @@ public class BouleBonus extends ObjetGraphique { private double vitesse = 5.0; private boolean estVerte; // true = bonus (niveau +1), false = malus (niveau -1) private int frameCounter = 0; + private double vitesseVerticale = 0.0; + private double amplitudeOndulation = 0.2; + private double vitesseHorizontaleLisse = 0.0; + private double phaseOndulation = Math.random() * Math.PI * 2; // phase unique par boule + + private static double clamp(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } public BouleBonus(double x, double y, boolean estVerte) { this.x = x; @@ -19,8 +27,14 @@ public class BouleBonus extends ObjetGraphique { if (estVerte) { this.couleur = new Color(0.0f, 0.8f, 0.0f); // Vert + this.rayon = 15; + this.amplitudeOndulation = 0.45; // onde visible plus ample + this.vitesseHorizontaleLisse = 4.8; } else { this.couleur = new Color(0.8f, 0.0f, 0.0f); // Rouge + this.rayon = 14; + this.amplitudeOndulation = 0.14; + this.vitesseHorizontaleLisse = 4.3; } } @@ -37,20 +51,18 @@ public class BouleBonus extends ObjetGraphique { Graphics2D g2D = (Graphics2D) g; // Effet de pulsation (le rayon augmente/diminue) - double rayonAffiche = rayon + Math.sin(frameCounter * 0.1) * 3; + double rayonAffiche = rayon + Math.sin(frameCounter * 0.12) * 1.5; - // Dessiner le cercle rempli + // Dessiner le cercle rempli (centré sur x,y) g2D.setColor(couleur); - g2D.fillOval((int)(x - rayonAffiche/2), (int)(y - rayonAffiche), - (int)rayonAffiche, (int)rayonAffiche); + g2D.fillOval((int)(x - rayonAffiche), (int)(y - rayonAffiche), + (int)(2 * rayonAffiche), (int)(2 * rayonAffiche)); // Contour g2D.setStroke(new BasicStroke(2.0f)); g2D.setColor(estVerte ? new Color(0.0f, 1.0f, 0.0f) : new Color(1.0f, 0.0f, 0.0f)); - g2D.drawOval((int)(x - rayonAffiche/2), (int)(y - rayonAffiche), - (int)rayonAffiche, (int)rayonAffiche); - - frameCounter++; + g2D.drawOval((int)(x - rayonAffiche), (int)(y - rayonAffiche), + (int)(2 * rayonAffiche), (int)(2 * rayonAffiche)); } @Override @@ -58,6 +70,64 @@ public class BouleBonus extends ObjetGraphique { // Déplacement vers la gauche (même vitesse que la ligne) x -= vitesse; } + + public void animerAvecCible(double vitesseLigne, double cibleY) { + animerAvecCible(vitesseLigne, cibleY, 0.0); + } + + public void animerAvecCible(double vitesseLigne, double cibleY, double cibleVitesseY) { + // Vertes: trajectoire fluide à intercepter. Rouges: poursuite plus nette avec fenêtre d'esquive. + double facteurHorizontal = estVerte ? 0.88 : 0.64; + double vitesseMax = estVerte ? 9.2 : 5.8; + double vitessePoursuite = estVerte ? 0.075 : 0.050; // meilleure réactivité + double limiteVerticale = estVerte ? 1.85 : 1.05; // accélération verticale + double amortissement = estVerte ? 0.87 : 0.81; // moins de friction + double zoneMorte = estVerte ? 18.0 : 16.0; // zone mort réduite + double vitesseVerticaleMax = estVerte ? 4.2 : 2.6; // vitesses verticales plus hautes + + double vitesseLigneSecurisee = Math.max(0.0, vitesseLigne); + double vitesseCible = Math.min(vitesseMax, vitesseLigneSecurisee * facteurHorizontal); + if (!estVerte) { + // Les rouges gardent une vitesse plus stable d'une boule à l'autre. + vitesseCible = vitesseCible * 0.68 + 4.5 * 0.32; + } + double deltaVitesse = clamp(vitesseCible - vitesseHorizontaleLisse, -0.25, 0.25); + double vitesseMaxLocale = vitesseMax; + if (!estVerte && x < 380) { + // En fin d'écran, on plafonne un peu la vitesse pour laisser une fenêtre d'esquive stable. + double facteurApproche = clamp((380.0 - x) / 180.0, 0.0, 1.0); + vitesseMaxLocale = vitesseMax - (0.55 * facteurApproche); + } + vitesseHorizontaleLisse = clamp(vitesseHorizontaleLisse + deltaVitesse, 4.0, vitesseMaxLocale); + vitesse = vitesseHorizontaleLisse; + x -= vitesse; + + double vitesseJoueurLimitee = clamp(cibleVitesseY, -6.0, 6.0); + // Verte: anticipation légère + oscillation. Rouge: AUCUNE anticipation → trajectoire lisible, esquivable. + double anticipation = estVerte ? vitesseJoueurLimitee * 3.2 : 0.0; + double cibleLisse = cibleY + anticipation + (estVerte ? Math.sin(frameCounter * 0.055 + phaseOndulation) * 45.0 : 0.0); + + double deltaY = cibleLisse - y; + if (Math.abs(deltaY) < zoneMorte) { + deltaY = 0; + } + + vitesseVerticale += clamp(deltaY * vitessePoursuite, -limiteVerticale, limiteVerticale); + vitesseVerticale *= amortissement; + vitesseVerticale = clamp(vitesseVerticale, -vitesseVerticaleMax, vitesseVerticaleMax); + + double ondulation = Math.sin(frameCounter * 0.085 + (estVerte ? 0.0 : 1.1)) * amplitudeOndulation; + y += vitesseVerticale + ondulation; + frameCounter++; + + if (y < 20) { + y = 20; + vitesseVerticale = 0; + } else if (y > 580) { + y = 580; + vitesseVerticale = 0; + } + } // Vérifier collision avec le cercle public boolean collisionAvec(Cercle c) { @@ -66,7 +136,8 @@ public class BouleBonus extends ObjetGraphique { double cRayon = c.getRayon(); double dist = Math.hypot(cx - x, cy - y); - return dist <= (rayon + cRayon); + double seuil = estVerte ? (rayon + cRayon - 6.0) : (rayon + cRayon - 9.0); + return dist <= seuil; } public void setVitesse(double vitesse) { diff --git a/linea/Cercle.java b/linea/Cercle.java index c311c30..96564cb 100644 --- a/linea/Cercle.java +++ b/linea/Cercle.java @@ -71,6 +71,10 @@ public class Cercle extends ObjetGraphique{ // il s'agit plutôt d'arcs de cercl //------------------------------------------------------------------------- public void Monter(){ montee = true; + // Évite l'effet "chute incontrôlable" quand on reprend la montée tard. + if (vitesse > 2.0) { + vitesse = 2.0; + } } @@ -108,31 +112,36 @@ public class Cercle extends ObjetGraphique{ // il s'agit plutôt d'arcs de cercl //------------------------------------------------------------------------- @Override void Animer() { - // pas est à prendre comme un "delta t" - - // chute libre - vitesse = vitesse + 9.81 * pas; + double gravite = 0.95; + double poussee = 1.45; + double amortissement = 0.92; - // impulsion if (montee==true) { - vitesse = vitesse - impulsion *pas; + vitesse -= poussee; + } else { + vitesse += gravite; } - - depY = 1/2 * 9.81 + vitesse * pas; - - if (depY<-10) { - depY=-10; + + // Lissage global pour un ressenti plus régulier. + vitesse *= amortissement; + + if (vitesse < -6.5) { + vitesse = -6.5; } - if (depY>10){ - depY =10; + if (vitesse > 6.5) { + vitesse = 6.5; } - y+=depY; + + depY = vitesse; + y += depY; //position if(y<= 0 + rayon){ y = 0 + rayon; + vitesse = 0; }else if(y>=600 - rayon){ y = 600 - rayon; + vitesse = 0; } } diff --git a/linea/DatabaseConnection.java b/linea/DatabaseConnection.java index 9f3d922..c240af5 100644 --- a/linea/DatabaseConnection.java +++ b/linea/DatabaseConnection.java @@ -65,14 +65,6 @@ public class DatabaseConnection { ); """; - String createNiveau = """ - CREATE TABLE IF NOT EXISTS Niveau ( - id_niveau INTEGER PRIMARY KEY AUTOINCREMENT, - nom TEXT, - nb_Objet INTEGER NOT NULL - ); - """; - String createScore = """ CREATE TABLE IF NOT EXISTS Score ( id_score INTEGER PRIMARY KEY AUTOINCREMENT, @@ -80,16 +72,23 @@ public class DatabaseConnection { nb_mort INTEGER, temps_jeu INTEGER, id_compte INTEGER, - id_niveau INTEGER, - FOREIGN KEY(id_compte) REFERENCES Compte(id_compte), - FOREIGN KEY(id_niveau) REFERENCES Niveau(id_niveau) + FOREIGN KEY(id_compte) REFERENCES Compte(id_compte) + ); + """; + + String createProgressionCampagne = """ + CREATE TABLE IF NOT EXISTS ProgressionCampagne ( + id_compte INTEGER PRIMARY KEY, + niveau_debloque_max INTEGER NOT NULL DEFAULT 1, + FOREIGN KEY(id_compte) REFERENCES Compte(id_compte) ); """; try (Statement stmt = conn.createStatement()) { stmt.executeUpdate(createCompte); - stmt.executeUpdate(createNiveau); stmt.executeUpdate(createScore); + stmt.executeUpdate("DROP TABLE IF EXISTS Niveau"); + stmt.executeUpdate(createProgressionCampagne); System.out.println("Tables créées / existantes OK"); } catch (SQLException e) { System.err.println("Erreur création tables : " + e.getMessage()); @@ -131,7 +130,11 @@ public class DatabaseConnection { ps.setString(1, pseudo.trim()); ps.executeUpdate(); try (ResultSet rs = ps.getGeneratedKeys()) { - if (rs.next()) return rs.getInt(1); + if (rs.next()) { + int idCompte = rs.getInt(1); + initialiserProgressionCampagne(idCompte); + return idCompte; + } } } catch (SQLException e) { System.err.println("Erreur création compte : " + e.getMessage()); @@ -155,8 +158,11 @@ public class DatabaseConnection { public void supprimerCompte(int idCompte) { if (conn == null || idCompte <= 0) return; - try (PreparedStatement ps1 = conn.prepareStatement("DELETE FROM Score WHERE id_compte = ?"); + try (PreparedStatement ps0 = conn.prepareStatement("DELETE FROM ProgressionCampagne WHERE id_compte = ?"); + PreparedStatement ps1 = conn.prepareStatement("DELETE FROM Score WHERE id_compte = ?"); PreparedStatement ps2 = conn.prepareStatement("DELETE FROM Compte WHERE id_compte = ?")) { + ps0.setInt(1, idCompte); + ps0.executeUpdate(); ps1.setInt(1, idCompte); ps1.executeUpdate(); ps2.setInt(1, idCompte); @@ -181,6 +187,53 @@ public class DatabaseConnection { return pseudos; } + private void initialiserProgressionCampagne(int idCompte) { + if (conn == null || idCompte <= 0) return; + String sql = "INSERT OR IGNORE INTO ProgressionCampagne (id_compte, niveau_debloque_max) VALUES (?, 1)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, idCompte); + ps.executeUpdate(); + } catch (SQLException e) { + System.err.println("Erreur init progression campagne : " + e.getMessage()); + } + } + + public int getNiveauDebloqueCampagne(int idCompte) { + if (conn == null || idCompte <= 0) return 1; + + initialiserProgressionCampagne(idCompte); + String sql = "SELECT niveau_debloque_max FROM ProgressionCampagne WHERE id_compte = ?"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, idCompte); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + int niveau = rs.getInt("niveau_debloque_max"); + if (niveau < 1) return 1; + return Math.min(100, niveau); + } + } + } catch (SQLException e) { + System.err.println("Erreur lecture progression campagne : " + e.getMessage()); + } + return 1; + } + + public void debloquerNiveauCampagne(int idCompte, int niveauTermine) { + if (conn == null || idCompte <= 0) return; + + int niveauCible = Math.min(100, Math.max(1, niveauTermine + 1)); + initialiserProgressionCampagne(idCompte); + + String sql = "UPDATE ProgressionCampagne SET niveau_debloque_max = MAX(niveau_debloque_max, ?) WHERE id_compte = ?"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, niveauCible); + ps.setInt(2, idCompte); + ps.executeUpdate(); + } catch (SQLException e) { + System.err.println("Erreur mise à jour progression campagne : " + e.getMessage()); + } + } + public String getStatsParCompte(int idCompte) { if (conn == null || idCompte <= 0) { return "Aucune statistique disponible."; @@ -207,5 +260,18 @@ public class DatabaseConnection { } return "Aucune statistique disponible."; } + + public String getStatsCampagneParCompte(int idCompte) { + if (conn == null || idCompte <= 0) { + return "Campagne : non disponible."; + } + + int niveauMax = getNiveauDebloqueCampagne(idCompte); + int niveauxTermines = Math.max(0, niveauMax - 1); + + return "Campagne\n" + + "Niveau max débloqué : " + niveauMax + + "\nNiveaux terminés : " + niveauxTermines; + } } diff --git a/linea/Jeu.db b/linea/Jeu.db index 17e7c0ec66d3fc769dc37aeed8bdf68e2253657d..329c5969b9eeb774c5b9551dfcde3801acfdcbcb 100644 GIT binary patch delta 362 zcmZozz}Rqrae}mY>*VEYWnke^X5cU9SLK!AQRY$J z*tnKEqOpvXUEJ81v01nzF)1fCpeR4RD7CmaGe6HcF}ENwJrA1@n{$w>V~DFlh@+E_ zs{$513L29y@JhKT1O$2dItE25c)LdGa4A4RUS?TpVrhIzYEn*qVQFf7ZeoRkr(cMx zyK9hwpMQvgU#O3df{Ux0W2jGvg5hLVK3g`hUd_!iyqSz3pDozj$(N!iEY295oL`j6 z!pX=i$?2S*TTqg^S_lg-LwvZ-L$iVlQ;_5 zoE)4xJ3Bi&I2p^CgM*L%I1X<3W`>XEZaK|}$glgq?0r!#WVC)hit5OVa>51H;+rV1 z(&Ra;^61ET*DE%tM)Bclrz2balhZ*@j9oOl2mQ02``e?WG8;b_Z+eA8N%+PWKJkGG z-tmeTJmCRj+~N`g9Ah7Q*hUv+eZt~OTtG&QP$Dv~bfL^CTUJtK7)YWlmKZgOgnW)w s0Y^<1HkwsAZckM|fJK!t@94PAfy(EWirH^6PnEEpiYVq86|+(Kzo}U#Q2+n{ diff --git a/linea/Jeu.java b/linea/Jeu.java index e286a81..dce9290 100644 --- a/linea/Jeu.java +++ b/linea/Jeu.java @@ -29,9 +29,11 @@ public class Jeu implements KeyListener, ActionListener { private int _niv; protected Timer horloge; + protected JFrame fenetre; protected double score = 0; protected JLabel labScore; protected JLabel labMeilleurScore; + protected JLabel labNiveau; private DatabaseConnection db; private int idCompte; private int meilleurSansCompte = 0; @@ -39,6 +41,7 @@ public class Jeu implements KeyListener, ActionListener { private int tempsSansCompteSec = 0; private long debutPartieMs = 0; private boolean immortel = false; + private final boolean modeCampagne; private int meilleurActuel() { return idCompte > 0 ? db.getMeilleurScoreParCompte(idCompte) : meilleurSansCompte; @@ -46,11 +49,11 @@ public class Jeu implements KeyListener, ActionListener { private String statsActuelles() { if (idCompte > 0) { - return db.getStatsParCompte(idCompte); + return db.getStatsParCompte(idCompte) + "\n\n" + db.getStatsCampagneParCompte(idCompte); } - return "Nombre de morts : " + mortsSansCompte - + "\nTemps de jeu total : " + tempsSansCompteSec + " s" - + "\nMeilleur score : " + meilleurSansCompte; + return "💀 Nombre de morts: " + mortsSansCompte + + "\n⏱️ Temps total: " + tempsSansCompteSec + "s" + + "\n🏆 Meilleur score: " + meilleurSansCompte; } private void enregistrerPartie(int scoreActuel, int tempsPartieSec) { @@ -67,26 +70,173 @@ public class Jeu implements KeyListener, ActionListener { // CONSTRUCTEUR //------------------------------------------------------------------------- public Jeu(DatabaseConnection db, int idCompte, int niveau) { + this(db, idCompte, niveau, false); + } + + public Jeu(DatabaseConnection db, int idCompte, int niveau, boolean modeCampagne) { this.db = db; this.idCompte = idCompte; this._niv = niveau; + this.modeCampagne = modeCampagne; labScore = new JLabel(); labScore.setText("

score : 0

"); labMeilleurScore = new JLabel(); + labNiveau = new JLabel(); JPanel panneauScores = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 0)); panneauScores.setOpaque(false); panneauScores.add(labScore); panneauScores.add(labMeilleurScore); + panneauScores.add(labNiveau); ecran.add(panneauScores, BorderLayout.NORTH); rafraichirMeilleurScore(); + rafraichirNiveau(); } private void rafraichirMeilleurScore() { labMeilleurScore.setText("

meilleur : " + meilleurActuel() + "

"); } + private void rafraichirNiveau() { + labNiveau.setText("

niveau : " + _niv + "

"); + } + + private void rafraichirTitreFenetre() { + if (fenetre == null) { + return; + } + String titre = modeCampagne ? "Linea Game [CAMPAGNE]" : "Linea Game"; + fenetre.setTitle(titre + (immortel ? " [MODE IMMORTEL 🛡️]" : "")); + } + + private String categorieNiveau(int niveau) { + if (niveau <= 2) return "Facile"; + if (niveau <= 4) return "Moyen"; + if (niveau <= 6) return "Difficile"; + if (niveau <= 8) return "Tres Difficile"; + if (niveau <= 10) return "Expert"; + if (niveau <= 12) return "Cauchemar"; + if (niveau <= 14) return "Chaos"; + if (niveau <= 16) return "Infernal"; + if (niveau <= 18) return "Apocalypse"; + if (niveau <= 20) return "Extreme"; + if (niveau <= 30) return "Infini"; + if (niveau <= 50) return "Au-dela"; + return "Cosmos"; + } + + private void appliquerThemeNiveau() { + String categorie = categorieNiveau(_niv); + Color fond; + Color couleurLigne; + Color couleurAvant; + Color couleurArriere; + Color couleurTexte; + + switch (categorie) { + case "Facile": + fond = new Color(225, 245, 225); + couleurLigne = new Color(45, 120, 45); + couleurAvant = new Color(55, 185, 80); + couleurArriere = new Color(175, 70, 70); + couleurTexte = new Color(20, 50, 20); + break; + case "Moyen": + fond = new Color(215, 235, 250); + couleurLigne = new Color(40, 95, 150); + couleurAvant = new Color(50, 165, 120); + couleurArriere = new Color(185, 80, 85); + couleurTexte = new Color(20, 45, 80); + break; + case "Difficile": + fond = new Color(250, 235, 200); + couleurLigne = new Color(155, 95, 40); + couleurAvant = new Color(195, 145, 55); + couleurArriere = new Color(175, 70, 40); + couleurTexte = new Color(85, 55, 20); + break; + case "Tres Difficile": + fond = new Color(245, 215, 195); + couleurLigne = new Color(145, 70, 40); + couleurAvant = new Color(185, 110, 65); + couleurArriere = new Color(175, 55, 45); + couleurTexte = new Color(80, 35, 25); + break; + case "Expert": + fond = new Color(210, 190, 235); + couleurLigne = new Color(105, 55, 160); + couleurAvant = new Color(130, 85, 200); + couleurArriere = new Color(170, 60, 95); + couleurTexte = new Color(45, 20, 70); + break; + case "Cauchemar": + fond = new Color(175, 160, 210); + couleurLigne = new Color(85, 65, 150); + couleurAvant = new Color(120, 90, 190); + couleurArriere = new Color(155, 55, 110); + couleurTexte = new Color(245, 245, 245); + break; + case "Chaos": + fond = new Color(205, 120, 120); + couleurLigne = new Color(170, 40, 45); + couleurAvant = new Color(225, 115, 40); + couleurArriere = new Color(200, 30, 60); + couleurTexte = new Color(245, 245, 245); + break; + case "Infernal": + fond = new Color(165, 70, 55); + couleurLigne = new Color(190, 35, 25); + couleurAvant = new Color(235, 95, 25); + couleurArriere = new Color(210, 25, 20); + couleurTexte = new Color(245, 245, 245); + break; + case "Apocalypse": + fond = new Color(120, 40, 45); + couleurLigne = new Color(210, 30, 40); + couleurAvant = new Color(245, 95, 35); + couleurArriere = new Color(225, 20, 35); + couleurTexte = new Color(245, 245, 245); + break; + case "Extreme": + fond = new Color(70, 70, 70); + couleurLigne = new Color(225, 225, 225); + couleurAvant = new Color(255, 235, 70); + couleurArriere = new Color(240, 80, 80); + couleurTexte = new Color(245, 245, 245); + break; + case "Infini": + fond = new Color(35, 55, 85); + couleurLigne = new Color(95, 170, 255); + couleurAvant = new Color(70, 220, 255); + couleurArriere = new Color(180, 80, 255); + couleurTexte = new Color(245, 245, 245); + break; + case "Au-dela": + fond = new Color(20, 30, 70); + couleurLigne = new Color(120, 155, 255); + couleurAvant = new Color(80, 205, 255); + couleurArriere = new Color(205, 95, 255); + couleurTexte = new Color(245, 245, 245); + break; + default: + fond = new Color(10, 15, 45); + couleurLigne = new Color(140, 120, 255); + couleurAvant = new Color(120, 225, 255); + couleurArriere = new Color(235, 95, 255); + couleurTexte = new Color(245, 245, 245); + break; + } + + ecran.setCouleurFond(fond); + ligne.setCouleurLigne(couleurLigne); + demiCercleAvant.setCouleur(couleurAvant); + demiCercleArriere.setCouleur(couleurArriere); + labScore.setForeground(couleurTexte); + labMeilleurScore.setForeground(couleurTexte); + labNiveau.setForeground(couleurTexte); + } + private boolean choisirNouveauCompte() { List pseudos = new ArrayList<>(db.getPseudos()); pseudos.add(0, "Sans compte"); @@ -173,7 +323,7 @@ public class Jeu implements KeyListener, ActionListener { return "🌌 Niveau " + niveau + " - Cosmos"; } } - + private void choisirModeImmortel() { int result = JOptionPane.showConfirmDialog( null, @@ -202,15 +352,24 @@ public class Jeu implements KeyListener, ActionListener { demiCercleAvant = new Cercle(90, -180); demiCercleArriere = new Cercle(90, 180); ligne = new Ligne(_niv); + ecran.setNiveau(_niv); - // 3. Configuration visuelle - demiCercleArriere.setCouleur(new Color(0.8f, 0.0f, 0.0f)); - demiCercleAvant.setCouleur(new Color(0.0f, 0.8f, 0.0f)); + // 3. En mode immortel, positionner les cercles loin (la force les attirera vers la ligne) + // En mode normal, ils restent au centre (y = 200 par défaut du constructeur) + if (immortel) { + demiCercleAvant.y = -300; + demiCercleArriere.y = 300; + } - // 4. Ajout à l'écran (l'ordre définit la superposition) + // 4. Configuration visuelle + appliquerThemeNiveau(); + + // 5. Ajout à l'écran (l'ordre définit la superposition) ecran.ajouterObjet(demiCercleArriere); ecran.ajouterObjet(ligne); ecran.ajouterObjet(demiCercleAvant); + + rafraichirNiveau(); } public void demarrer() { @@ -220,7 +379,8 @@ public class Jeu implements KeyListener, ActionListener { // Passer le flag immortel à l'écran ecran.setImmortel(immortel); - JFrame fenetre = new JFrame("Linea Game" + (immortel ? " [MODE IMMORTEL 🛡️]" : "")); + fenetre = new JFrame(); + rafraichirTitreFenetre(); // Initialise les objets une première fois initialiserPartie(); @@ -243,12 +403,10 @@ public class Jeu implements KeyListener, ActionListener { // Réinitialisation des variables de jeu score = 0; labScore.setText("

score : 0

"); - - // Demander le mode immortel à chaque reset - choisirModeImmortel(); // Passer le flag immortel à l'écran ecran.setImmortel(immortel); + rafraichirTitreFenetre(); // Ré-initialisation des objets graphiques initialiserPartie(); @@ -271,6 +429,7 @@ public class Jeu implements KeyListener, ActionListener { int bonus = ecran.getBonusRecupere(); if (bonus != 0) { ecran.reinitialiserBonus(); + int niveauActuel = _niv; int nouveauNiveau = _niv + bonus; // Limiter le niveau entre 1 et 100 @@ -279,6 +438,10 @@ public class Jeu implements KeyListener, ActionListener { } else if (nouveauNiveau > 100) { nouveauNiveau = 100; } + + if (modeCampagne && idCompte > 0 && bonus > 0) { + db.debloquerNiveauCampagne(idCompte, niveauActuel); + } _niv = nouveauNiveau; resetLevel(); @@ -295,18 +458,46 @@ public class Jeu implements KeyListener, ActionListener { rafraichirMeilleurScore(); while (true) { - Object[] options = {"Relancer", "Changer de compte", "Changer de niveau", "Voir stats", "Quitter"}; + Object[] options = modeCampagne + ? new Object[]{"▶️ Relancer", "📊 Statistiques", "❌ Quitter"} + : new Object[]{"▶️ Relancer", "👤 Compte", "📈 Niveau", "📊 Statistiques", "❌ Quitter"}; + String message = "🏁 GAME OVER\n\n" + + "Score: " + scoreActuel + "\n" + + "Meilleur: " + meilleurActuel() + "\n" + + "Durée: " + tempsPartieSec + "s"; + int choix = JOptionPane.showOptionDialog(null, - "Perdu\nScore : " + scoreActuel + "\nMeilleur : " + meilleurActuel(), - "Game Over", + message, + "🏁 Fin de Partie", JOptionPane.DEFAULT_OPTION, - JOptionPane.INFORMATION_MESSAGE, + JOptionPane.PLAIN_MESSAGE, null, options, options[0]); if (choix == 0) { + int modeRelance = JOptionPane.showConfirmDialog( + null, + "Relancer en mode immortel ?", + "Relancer", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE + ); + + if (modeRelance == JOptionPane.CANCEL_OPTION || modeRelance == JOptionPane.CLOSED_OPTION) { + continue; + } + + immortel = (modeRelance == JOptionPane.YES_OPTION); + ecran.setImmortel(immortel); resetLevel(); break; } + if (modeCampagne) { + if (choix == 1) { + JOptionPane.showMessageDialog(null, statsActuelles(), "📊 Statistiques", JOptionPane.INFORMATION_MESSAGE); + continue; + } + System.exit(0); + } if (choix == 1) { if (choisirNouveauCompte()) { resetLevel(); @@ -324,7 +515,7 @@ public class Jeu implements KeyListener, ActionListener { continue; } if (choix == 3) { - JOptionPane.showMessageDialog(null, statsActuelles(), "Statistiques", JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(null, statsActuelles(), "📊 Statistiques", JOptionPane.INFORMATION_MESSAGE); continue; } System.exit(0); diff --git a/linea/Ligne.java b/linea/Ligne.java index e81d3c3..04fb297 100644 --- a/linea/Ligne.java +++ b/linea/Ligne.java @@ -12,11 +12,11 @@ public class Ligne extends ObjetGraphique{ private double xCercle = 400; private Segment SegCourant; // vitesse de déplacement (augmente légèrement chaque frame) - private double vitesse = 5.0; + private double vitesse = 4.8; // croissance initiale (fractionnelle) appliquée chaque frame - private double croissance = 0.001; // ~0.1% initial + private double croissance = 0.0008; // montée plus progressive // facteur qui amplifie la croissance elle-même (pour accélérer la montée) - private double facteurCroissance = 1.00003; // croissance augmente légèrement + private double facteurCroissance = 1.00002; // accélération plus douce private int niveau = 1; // niveau de difficulté, à augmenter pour rendre le jeu plus difficile @@ -95,50 +95,63 @@ public class Ligne extends ObjetGraphique{ } // appliquer la croissance (vitesse *= 1 + croissance) - vitesse *= (1.0 + croissance + (niveau * 0.00001)); // augmenter la croissance avec le niveau + vitesse *= (1.0 + croissance + (niveau * 0.000006)); // hausse niveau plus progressive // augmenter légèrement la croissance pour que l'accélération s'amplifie croissance *= facteurCroissance; } - // Indique si l'axe horizontal du cercle (cx) se trouve au niveau - // d'un des segments de la ligne -> le cercle est "sur la ligne" - public boolean estSurLaLigne(double cx) { + // Trouve le segment couvrant l'abscisse cx (une seule itération) + private Segment trouverSegmentAuX(double cx) { for (Segment seg : segments) { double x1 = seg.x; double x2 = seg.x + seg.xLong; - if ((cx >= Math.min(x1, x2)) && (cx <= Math.max(x1, x2))) { - return true; + if (cx >= Math.min(x1, x2) && cx <= Math.max(x1, x2)) { + return seg; } } - return false; + return null; + } + + // Résultat de contact : null = pas sur la ligne, sinon double[]{ligneY, distance} + public double[] contactInfo(double cx, double cy, double rayon) { + Segment seg = trouverSegmentAuX(cx); + if (seg == null) return null; + double ligneY = (seg.xLong != 0) ? seg.y + ((cx - seg.x) / seg.xLong) * seg.yLong : seg.y; + double dist = pointSegmentDistance(cx, cy, seg.x, seg.y, seg.x + seg.xLong, seg.y + seg.yLong); + return new double[]{ligneY, dist}; + } + + // Indique si l'axe horizontal du cercle (cx) se trouve au niveau + // d'un des segments de la ligne -> le cercle est "sur la ligne" + public boolean estSurLaLigne(double cx) { + return trouverSegmentAuX(cx) != null; } // Vérifie la collision entre la ligne (segments) et un cercle public boolean collisionAvec(Cercle c) { - double cx = c.getX(); - double cy = c.getY(); - double rayon = c.getRayon(); - - for (Segment seg : segments) { - double x1 = seg.x; - double y1 = seg.y; - double x2 = seg.x + seg.xLong; - double y2 = seg.y + seg.yLong; - - double dist = pointSegmentDistance(cx, cy, x1, y1, x2, y2); - if (dist <= rayon) { - return true; - } - } - return false; + double[] info = contactInfo(c.getX(), c.getY(), c.getRayon()); + return info != null && info[1] <= c.getRayon(); } + // Retourne la coordonnée Y de la ligne à l'abscisse cx + public double getYAuX(double cx) { + Segment seg = trouverSegmentAuX(cx); + if (seg == null || seg.xLong == 0) return 300; + return seg.y + ((cx - seg.x) / seg.xLong) * seg.yLong; + } + // Obtenir la vitesse actuelle de la ligne (pour les boules bonus) public double getVitesse() { return vitesse; } - + + public void setCouleurLigne(Color couleur) { + for (Segment seg : segments) { + seg.setCouleur(couleur); + } + } + // distance minimale entre un point (px,py) et un segment (x1,y1)-(x2,y2) private double pointSegmentDistance(double px, double py, double x1, double y1, double x2, double y2) { double vx = x2 - x1; diff --git a/linea/LineaAppli.java b/linea/LineaAppli.java index d6356f5..33dac9c 100644 --- a/linea/LineaAppli.java +++ b/linea/LineaAppli.java @@ -1,13 +1,29 @@ package linea; +import java.awt.GridLayout; import java.util.List; +import javax.swing.ButtonGroup; +import javax.swing.JComboBox; +import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.UIManager; public class LineaAppli { + private static class SelectionJeu { + final int idCompte; + final boolean modeCampagne; + + SelectionJeu(int idCompte, boolean modeCampagne) { + this.idCompte = idCompte; + this.modeCampagne = modeCampagne; + } + } + private static String choisirPseudo(DatabaseConnection db, String message, int messageType) { List pseudos = db.getPseudos(); if (pseudos.isEmpty()) { @@ -50,7 +66,38 @@ public class LineaAppli { if (result == JOptionPane.OK_OPTION) { return list.getSelectedIndex() + 1; } - return 1; + return -1; + } + + private static int choisirNiveauCampagne(DatabaseConnection db, int idCompte) { + int niveauMaxDebloque = db.getNiveauDebloqueCampagne(idCompte); + if (niveauMaxDebloque < 1) { + niveauMaxDebloque = 1; + } + + String[] niveaux = new String[niveauMaxDebloque]; + for (int i = 1; i <= niveauMaxDebloque; i++) { + niveaux[i - 1] = genererLabelNiveau(i); + } + + JList list = new JList<>(niveaux); + list.setSelectedIndex(niveauMaxDebloque - 1); + list.setVisibleRowCount(15); + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setPreferredSize(new java.awt.Dimension(350, 350)); + + int result = JOptionPane.showConfirmDialog( + null, + scrollPane, + "🏆 Campagne - Niveaux débloqués (1 à " + niveauMaxDebloque + ")", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE + ); + + if (result == JOptionPane.OK_OPTION) { + return list.getSelectedIndex() + 1; + } + return -1; } private static String genererLabelNiveau(int niveau) { @@ -129,6 +176,63 @@ public class LineaAppli { } } + private static SelectionJeu choisirCompteEtModeRapide(DatabaseConnection db) { + while (true) { + List pseudos = db.getPseudos(); + + JPanel panel = new JPanel(new GridLayout(0, 1, 8, 8)); + panel.add(new JLabel("Compte :")); + + String[] optionsCompte = new String[pseudos.size() + 2]; + optionsCompte[0] = "Sans compte"; + for (int i = 0; i < pseudos.size(); i++) { + optionsCompte[i + 1] = pseudos.get(i); + } + optionsCompte[optionsCompte.length - 1] = "Gérer les comptes..."; + + JComboBox comboCompte = new JComboBox<>(optionsCompte); + panel.add(comboCompte); + + panel.add(new JLabel("Mode :")); + JRadioButton modeClassique = new JRadioButton("Classique", true); + JRadioButton modeCampagne = new JRadioButton("Campagne"); + ButtonGroup group = new ButtonGroup(); + group.add(modeClassique); + group.add(modeCampagne); + + JPanel panelMode = new JPanel(new GridLayout(1, 2, 8, 0)); + panelMode.add(modeClassique); + panelMode.add(modeCampagne); + panel.add(panelMode); + + int result = JOptionPane.showConfirmDialog( + null, + panel, + "Jouer rapidement", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE + ); + + if (result != JOptionPane.OK_OPTION) { + return null; + } + + String choixCompte = (String) comboCompte.getSelectedItem(); + if (choixCompte == null) { + return null; + } + + if ("Gérer les comptes...".equals(choixCompte)) { + menuComptes(db); + continue; + } + + int idCompte = "Sans compte".equals(choixCompte) ? -1 : db.getIdParPseudo(choixCompte); + boolean campagne = modeCampagne.isSelected(); + return new SelectionJeu(idCompte, campagne); + } + } + //------------------------------------------------------------------------- // Classe de base de l'application, rien à modifier ici //------------------------------------------------------------------------- @@ -140,31 +244,37 @@ public class LineaAppli { db.createTables(); while (true) { - Object[] options = {"Comptes", "Sans compte", "Quitter"}; - int choix = JOptionPane.showOptionDialog(null, - "Choisissez une action :", - "Menu", JOptionPane.DEFAULT_OPTION, - JOptionPane.QUESTION_MESSAGE, null, options, options[0]); - - if (choix == JOptionPane.CLOSED_OPTION || choix == 2) { + SelectionJeu selection = choisirCompteEtModeRapide(db); + if (selection == null) { return; } - switch (choix) { - case 1 -> { - int niveau = choisirNiveau(); - new Jeu(db, -1, niveau).demarrer(); + if (!selection.modeCampagne) { + int niveau = choisirNiveau(); + if (niveau > 0) { + new Jeu(db, selection.idCompte, niveau).demarrer(); return; } - case 0 -> { - Integer idCompte = menuComptes(db); - if (idCompte != null) { - int niveau = choisirNiveau(); - new Jeu(db, idCompte, niveau).demarrer(); - return; - } - } + continue; } + + if (selection.idCompte > 0) { + int niveau = choisirNiveauCampagne(db, selection.idCompte); + if (niveau > 0) { + new Jeu(db, selection.idCompte, niveau, true).demarrer(); + return; + } + continue; + } + + JOptionPane.showMessageDialog( + null, + "Campagne sans compte : progression non sauvegardée.", + "Campagne", + JOptionPane.INFORMATION_MESSAGE + ); + new Jeu(db, -1, 1, true).demarrer(); + return; } } } diff --git a/linea/ZoneDessin.java b/linea/ZoneDessin.java index 9730a0b..571e990 100644 --- a/linea/ZoneDessin.java +++ b/linea/ZoneDessin.java @@ -29,7 +29,10 @@ public class ZoneDessin extends JPanel { private ArrayList boolesBonus = new ArrayList(); // compteur pour générer les boules à intervalle régulier private int compteurBoule = 0; - private int intervalleBoule = 80; // générer une boule tous les 80 frames + private int intervalleBouleBase = 165; // moins de boules sur la durée + private int delaiInitialBoule = 97; // apparition initiale avancée de 3% + private int niveauActuel = 1; + private Double dernierJoueurY = null; // type de bonus récupéré (-1 = rouge, 1 = vert, 0 = aucun) private int bonusRecupere = 0; @@ -38,10 +41,24 @@ public class ZoneDessin extends JPanel { setPreferredSize(new Dimension(800, 600)); setBackground(new Color(220,170,0)); } + + public void setCouleurFond(Color couleurFond) { + setBackground(couleurFond); + } public void setImmortel(boolean immortel) { this.immortel = immortel; } + + public void setNiveau(int niveau) { + if (niveau < 1) { + niveauActuel = 1; + } else if (niveau > 100) { + niveauActuel = 100; + } else { + niveauActuel = niveau; + } + } public int getBonusRecupere() { return bonusRecupere; @@ -68,6 +85,20 @@ public class ZoneDessin extends JPanel { if (estArrete==true) { return; } + + Cercle cercleReference = null; + for (ObjetGraphique obj : listeObjets) { + if (obj instanceof Cercle) { + cercleReference = (Cercle) obj; + break; + } + } + double joueurY = (cercleReference != null) ? cercleReference.getY() : 300; + double vitesseJoueurY = 0.0; + if (dernierJoueurY != null) { + vitesseJoueurY = joueurY - dernierJoueurY; + } + dernierJoueurY = joueurY; // --- 0. Récupérer la ligne pour synchroniser les boules --- Ligne ligneObjet = null; @@ -79,67 +110,107 @@ public class ZoneDessin extends JPanel { } // --- 1. Générer une boule au bord droit sur la ligne --- + double progression = Math.min(1.0, (niveauActuel - 1) / 25.0); + int intervalleBoule = (int) Math.round(intervalleBouleBase - progression * 20.0); // ~165 -> 145 + double probaVerteBase = 0.35 - progression * 0.06; // ~35% -> 29% + double variationAleatoire = (Math.random() - 0.5) * 0.10; // +/- 5% + double probaVerte = Math.max(0.22, Math.min(0.45, probaVerteBase + variationAleatoire)); + compteurBoule++; if (compteurBoule >= intervalleBoule && ligneObjet != null) { compteurBoule = 0; - boolean estVerte = Math.random() < 0.7; - - // On récupère le Y du dernier segment de la ligne (bord droit de l'écran) - // Note: Si votre classe Ligne n'a pas de méthode pour avoir le Y à X=800, - // utilisez une valeur proche de la fin de votre liste de segments. - double spawnX = 800; - double spawnY = 300; // Valeur par défaut + boolean estVerte = Math.random() < probaVerte; + + double spawnX = 800; + double spawnY; + if (estVerte) { + // Vertes: proches de la trajectoire du joueur pour être récupérables. + double margeVerte = 95.0 + progression * 45.0; + spawnY = joueurY + (Math.random() * (2.0 * margeVerte)) - margeVerte; + } else { + // Rouges: visent davantage le joueur, mais démarrent avec une marge d'esquive. + double offset = 220 + progression * 20.0 + Math.random() * 150.0; + spawnY = joueurY + (Math.random() < 0.5 ? -offset : offset); + } + + if (spawnY < 20) spawnY = 20; + if (spawnY > 580) spawnY = 580; + + if (!estVerte) { + // Marge suffisante pour esquiver, sans rendre les rouges inoffensives. + double distanceMin = 125.0; + if (Math.abs(spawnY - joueurY) < distanceMin) { + if (spawnY >= joueurY) { + spawnY = Math.min(580, joueurY + distanceMin); + } else { + spawnY = Math.max(20, joueurY - distanceMin); + } + } + } // On crée la boule au bord droit (800) BouleBonus boule = new BouleBonus(spawnX, spawnY, estVerte); - - // On lui donne la vitesse actuelle de la ligne pour qu'elle suive le mouvement - boule.setVitesse(ligneObjet.getVitesse()); boolesBonus.add(boule); } // --- 2. Animation des objets --- for (ObjetGraphique obj : listeObjets) obj.Animer(); - // Mettre à jour la vitesse des boules (car la ligne accélère avec le temps) + // Mettre à jour et animer les boules (une seule fois par frame) if (ligneObjet != null) { for (BouleBonus boule : boolesBonus) { - boule.setVitesse(ligneObjet.getVitesse()); - boule.Animer(); + boule.animerAvecCible(ligneObjet.getVitesse(), joueurY, vitesseJoueurY); } } - // Les boules bougent via leur propre Animer() qui ajuste leur vitesse - - for (BouleBonus boule : boolesBonus) { - boule.Animer(); - } - - // 2. vérifier collisions entre une Ligne et un Cercle - for (ObjetGraphique obj : listeObjets) { - if (obj instanceof Ligne) { - Ligne l = (Ligne) obj; - for (ObjetGraphique other : listeObjets) { - if (other instanceof Cercle) { - Cercle c = (Cercle) other; - // On commence à surveiller une fois que le centre du - // cercle est au-dessus d'un segment (le cercle est "sur la ligne"). - if (l.estSurLaLigne(c.getX())) { - hadBeenOnLine = true; - // Si le cercle n'est plus en contact (distance > rayon) - // alors le joueur perd (il doit maintenir le contact). - if (!l.collisionAvec(c)) { - // Ignorer la collision si on est en mode immortel - if (!immortel) { - collisionOccur = true; - estArrete = true; - break; - } - } - } + // 2. vérifier collision entre la Ligne et le Cercle (une seule passe de segments) + if (ligneObjet != null && cercleReference != null) { + double[] info = ligneObjet.contactInfo( + cercleReference.getX(), cercleReference.getY(), cercleReference.getRayon()); + + boolean enContact = (info != null) && (info[1] <= cercleReference.getRayon()); + double ligneY = (info != null) ? info[0] : ligneObjet.getYAuX(cercleReference.getX()); + + // Phase initiale en mode immortel : force d'attraction depuis le début + if (!hadBeenOnLine && immortel) { + double delta = ligneY - cercleReference.y; + cercleReference.vitesse += delta * 0.12; + + // Marquer le premier contact + if (enContact) { + hadBeenOnLine = true; + } + } else if (hadBeenOnLine && immortel) { + // Phase maintenance : après le premier contact, maintenir le cercle sur la ligne + double rayon = cercleReference.getRayon(); + double limite = ligneY - rayon; + double limiteBas = ligneY + rayon; + + // Clamping : empêcher le cercle de sortir au-dessus + if (cercleReference.y < limite) { + cercleReference.y = limite; + if (cercleReference.vitesse < 0) cercleReference.vitesse *= -0.3; + } + // Clamping : empêcher le cercle de sortir en-dessous + else if (cercleReference.y > limiteBas) { + cercleReference.y = limiteBas; + if (cercleReference.vitesse > 0) cercleReference.vitesse *= -0.3; + } + } else if (info != null && !enContact && !immortel) { + // Mode normal : mort si hors contact + collisionOccur = true; + estArrete = true; + } + + // Synchroniser tous les autres Cercle en mode immortel + if (immortel) { + for (ObjetGraphique obj : listeObjets) { + if (obj instanceof Cercle && obj != cercleReference) { + Cercle autre = (Cercle) obj; + autre.y = cercleReference.y; + autre.vitesse = cercleReference.vitesse; } } - if (estArrete) break; } } @@ -164,7 +235,7 @@ public class ZoneDessin extends JPanel { //4. Détruire la boule si elle dépasse le joueur sans être touchée --- // Le cercle est à X = 400. Si la boule est à X < 350, elle est "passée". - if (!collision && boule.getX() < 350) { + if (!collision && boule.getX() < 320) { boolesBonus.remove(i); // On ne change pas bonusRecupere ici (donc rien ne se passe) } @@ -205,7 +276,8 @@ public class ZoneDessin extends JPanel { collisionOccur = false; hadBeenOnLine = false; bonusRecupere = 0; - compteurBoule = 0; + compteurBoule = -delaiInitialBoule; + dernierJoueurY = null; repaint(); } } \ No newline at end of file