diff --git a/app/src/main/java/sae/chuzzle/Controleur.java b/app/src/main/java/sae/chuzzle/Controleur.java index 6c96353..7e333b3 100644 --- a/app/src/main/java/sae/chuzzle/Controleur.java +++ b/app/src/main/java/sae/chuzzle/Controleur.java @@ -7,13 +7,12 @@ import android.widget.Button; import android.widget.TextView; import android.widget.Toast; -public class Controleur { +public class Controleur implements View.OnClickListener { private final Activity activite; private final EtatJeu etatJeu; private final VueGrille vueGrille; private final long graine; - private final TextView tvScore; private final TextView tvCoups; @@ -25,14 +24,11 @@ public class Controleur { private TextView tvNbObjectifs; private TextView tvCles; private Button btnReinitialisationObjectif; - private View layoutCles; + private View layoutCles; public Controleur(Activity activite, EtatJeu etatJeu, VueGrille vueGrille, long graine, TextView tvScore, TextView tvCoups, boolean hardMode) { - - - this.activite = activite; this.etatJeu = etatJeu; this.vueGrille = vueGrille; @@ -49,17 +45,21 @@ public class Controleur { this.layoutCles = activite.findViewById(R.id.layoutCles); this.btnReinitialisationObjectif = activite.findViewById(R.id.btnReinitialisationObjectif); - if (tvObjectif != null) tvObjectif.setVisibility(View.VISIBLE); - if (tvNbObjectifs != null) tvNbObjectifs.setVisibility(View.VISIBLE); - if (layoutCles != null) layoutCles.setVisibility(View.VISIBLE); - + if (tvObjectif != null) { + tvObjectif.setVisibility(View.VISIBLE); + } + if (tvNbObjectifs != null) { + tvNbObjectifs.setVisibility(View.VISIBLE); + } + if (layoutCles != null) { + layoutCles.setVisibility(View.VISIBLE); + } if (btnReinitialisationObjectif != null) { - btnReinitialisationObjectif.setOnClickListener(v -> reinitialiserObjectif()); + btnReinitialisationObjectif.setOnClickListener(this); } } - rafraichirAffichage(); } @@ -86,8 +86,8 @@ public class Controleur { */ public void gererFinDeCoup() { - // 1 clé tous les 5 coups - if (hardMode && etatJeu.obtenirNbCoups() % 5 == 0) { + // 1 clé tous les 4 coups + if (hardMode && etatJeu.obtenirNbCoups() % 4 == 0) { gestionnaireObjectifs.ajouterCle(); } // --- Logique Hard Mode @@ -120,7 +120,7 @@ public class Controleur { } /** - * Tente de réinitialiser l'objectif en cours en consommant 3 clés. + * Réinitialiser l'objectif en cours si la clé est consommé. * Affiche un Toast si le joueur n'a pas assez de clés. */ public void reinitialiserObjectif() { @@ -167,6 +167,14 @@ public class Controleur { } } + @Override + public void onClick(View v) { + if (v.getId() == R.id.btnReinitialisationObjectif) { + reinitialiserObjectif(); + } + } + + public void rafraichirAffichage() { tvScore.setText("Score : " + etatJeu.obtenirScore()); tvCoups.setText("Coups : " + etatJeu.obtenirNbCoups()); diff --git a/app/src/main/java/sae/chuzzle/EtatJeu.java b/app/src/main/java/sae/chuzzle/EtatJeu.java index e598950..29856a2 100644 --- a/app/src/main/java/sae/chuzzle/EtatJeu.java +++ b/app/src/main/java/sae/chuzzle/EtatJeu.java @@ -6,15 +6,13 @@ import java.util.Random; /** * Gère la session de jeu (score, coups, état de la partie) et délègue la - * manipulation - * physique de la grille à la classe Plateau. + * manipulation des pièces de la grille à la classe Plateau. */ public class EtatJeu { private final Plateau plateau; private final Random aleatoire; private final boolean hardMode; - private int score = 0; private int nbCoups = 0; private boolean partieTerminee = false; @@ -49,6 +47,11 @@ public class EtatJeu { return plateau.copierGrille(); } + /** + * Retourne l'état des verrous (cases bloquées) + * On crée une copie pour que personne ne puisse modifier les verrous + * directement sans passer par les règles de cette classe + */ public boolean[][] obtenirVerrous() { // Copie manuelle pour respecter l'encapsulation boolean[][] v = plateau.getVerrous(); @@ -61,6 +64,10 @@ public class EtatJeu { // --- Actions + /** + * Tente de déplacer une ligne ou une colonne. + * Si le mouvement ne crée aucune série de 3 couleurs, il est annulé. + */ public boolean appliquerCoup(boolean estLigne, int index, int sens) { if (partieTerminee || plateau.estBloque(estLigne, index)) { return false; @@ -93,6 +100,10 @@ public class EtatJeu { return true; } + /** + * Gère les "cascades" : quand des pièces disparaissent, d'autres tombent + * et peuvent créer de nouvelles séries automatiquement. + */ private int resoudreCascades() { int baseTotal = 0; int nbSeriesTotal = 0; @@ -176,6 +187,9 @@ public class EtatJeu { return new int[] { pts, count }; } + /** + * Définit combien de points rapporte une série selon sa taille (3, 4, 5...). + */ private int pointsPourLongueur(int n) { if (n == 3) return 8; @@ -186,6 +200,10 @@ public class EtatJeu { return 64; } + /** + * Transforme une liste de positions en une "carte" (masque) de la grille + * pour savoir rapidement quelles cases doivent être supprimées. + */ private boolean[][] creerMasque(List positions) { boolean[][] m = new boolean[Plateau.NB_LIGNES][Plateau.NB_COLONNES]; for (int[] p : positions) { @@ -200,6 +218,10 @@ public class EtatJeu { } } + /** + * Parcourt l'intégralité des possibilités pour vérifier si le joueur + * peut encore effectuer au moins un mouvement créant une série. + */ public boolean aUnCoupValide() { for (int i = 0; i < Plateau.NB_LIGNES; i++) { if (plateau.estBloque(true, i)) { @@ -224,6 +246,10 @@ public class EtatJeu { return false; } + /** + * Simule un déplacement pour voir s'il créerait une série, + * sans modifier la "vraie" grille de jeu du joueur. + */ private boolean simulerCoup(boolean estLigne, int idx, int s) { int[][] save = plateau.copierGrille(); if (estLigne) { @@ -236,10 +262,14 @@ public class EtatJeu { return ok; } + public void forcerFinDePartie() { partieTerminee = true; } + /** + * Prépare les données du jeu pour les sauvegarder (en cas de fermeture de l'app). + */ public void sauvegarderEtat(Bundle b) { int[] g = new int[36]; boolean[] v = new boolean[36]; @@ -258,6 +288,9 @@ public class EtatJeu { b.putBoolean("partieTerminee", partieTerminee); } + /** + * Recharge les données du jeu sauvegardées précédemment. + */ public void restaurerEtat(Bundle b) { if (!b.containsKey("grille")) return; diff --git a/app/src/main/java/sae/chuzzle/GestionnaireObjectifs.java b/app/src/main/java/sae/chuzzle/GestionnaireObjectifs.java index f304a84..df4d952 100644 --- a/app/src/main/java/sae/chuzzle/GestionnaireObjectifs.java +++ b/app/src/main/java/sae/chuzzle/GestionnaireObjectifs.java @@ -29,7 +29,7 @@ public class GestionnaireObjectifs { // N (nbSeriesCible) tel que M - N >= 1 et N <= 3. for (int m = 2; m <= 5; m++) { for (int n = 1; n <= m - 1 && n <= 3; n++) { - // Probabilité inversement proportionnelle à la facilité (M-N). + // Plus l'objectif offre une marge de coups importante (M-N), plus il a de chances de tomber. // Plus (M-N) est petit, plus c'est dur. int diff = m - n; int poids = diff * diff; // On utilise le carré pour accentuer la différence de probabilité @@ -99,4 +99,3 @@ public class GestionnaireObjectifs { } } } - diff --git a/app/src/main/java/sae/chuzzle/GestionnaireTactile.java b/app/src/main/java/sae/chuzzle/GestionnaireTactile.java index 3e386cf..f747229 100644 --- a/app/src/main/java/sae/chuzzle/GestionnaireTactile.java +++ b/app/src/main/java/sae/chuzzle/GestionnaireTactile.java @@ -3,21 +3,28 @@ package sae.chuzzle; import android.view.MotionEvent; import android.view.View; +/** + * Gère les interactions tactiles sur la grille de jeu. + * Traduit les mouvements de doigt en déplacements de lignes ou colonnes. + */ public class GestionnaireTactile implements View.OnTouchListener { private static final int NB_LIGNES = Plateau.NB_LIGNES; private static final int NB_COLONNES = Plateau.NB_COLONNES; + + // Distance minimale (en pixels) avant de décider si le joueur déplace une ligne ou une colonne. + // Cela évite les déclenchements accidentels lors d'un simple clic. private static final float SEUIL_DIRECTION_PX = 10f; private final VueGrille vueGrille; private final EtatJeu etatJeu; private final Controleur controleur; - private float touchDebutX, touchDebutY; - private int ligneTouchee, colonneTouchee; - private Boolean estLigne; - private int indexGlissement; - private float decalagePx; + private float touchDebutX, touchDebutY; // Position initiale du doigt + private int ligneTouchee, colonneTouchee; // Indices de la case située sous le doigt au départ + private Boolean estLigne; // null = direction inconnue, true = horizontal, false = vertical + private int indexGlissement; // Numéro de la ligne ou colonne que l'on fait glisser + private float decalagePx; // Distance parcourue par le doigt depuis le début private float tailleCase; public GestionnaireTactile(VueGrille vueGrille, @@ -61,12 +68,15 @@ public class GestionnaireTactile implements View.OnTouchListener { estLigne = null; decalagePx = 0f; + // Calcule le décalage pour que le doigt vise bien les cases centrées float margeGauche = (vueGrille.getWidth() - tailleCase * NB_COLONNES) / 2f; float margeHaut = (vueGrille.getHeight() - tailleCase * NB_LIGNES) / 2f; + // Conversion des coordonnées pixels en indices de tableau (0 à 5) ligneTouchee = (int) ((touchDebutY - margeHaut) / tailleCase); colonneTouchee = (int) ((touchDebutX - margeGauche) / tailleCase); + // Sécurité pour éviter de sortir des limites du tableau si on touche les bords ligneTouchee = Math.max(0, Math.min(NB_LIGNES - 1, ligneTouchee)); colonneTouchee = Math.max(0, Math.min(NB_COLONNES - 1, colonneTouchee)); } @@ -75,8 +85,10 @@ public class GestionnaireTactile implements View.OnTouchListener { float dx = event.getX() - touchDebutX; float dy = event.getY() - touchDebutY; + // on détermine si le joueur glisse vers le côté ou vers le haut/bas if (estLigne == null) { if (Math.abs(dx) > SEUIL_DIRECTION_PX || Math.abs(dy) > SEUIL_DIRECTION_PX) { + // On verrouille l'axe : celui qui a la plus grande distance parcourue gagne estLigne = Math.abs(dx) >= Math.abs(dy); if (estLigne) { diff --git a/app/src/main/java/sae/chuzzle/MainActivity.java b/app/src/main/java/sae/chuzzle/MainActivity.java index 1b44579..11f1e86 100644 --- a/app/src/main/java/sae/chuzzle/MainActivity.java +++ b/app/src/main/java/sae/chuzzle/MainActivity.java @@ -13,8 +13,6 @@ public class MainActivity extends Activity { private Button btnMenu; private VueGrille vueGrille; - - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -23,7 +21,6 @@ public class MainActivity extends Activity { // --- Modèle long graine = getIntent().getLongExtra("graine", System.currentTimeMillis()); hardMode = getSharedPreferences("chuzzle_prefs", MODE_PRIVATE).getBoolean("hard_mode", false); - etatJeu = new EtatJeu(graine, hardMode); diff --git a/app/src/main/java/sae/chuzzle/Plateau.java b/app/src/main/java/sae/chuzzle/Plateau.java index 66fe9f2..713f6b9 100644 --- a/app/src/main/java/sae/chuzzle/Plateau.java +++ b/app/src/main/java/sae/chuzzle/Plateau.java @@ -21,6 +21,10 @@ public class Plateau { initialiserGrilleSansTriples(); } + /** + * Remplit la grille aléatoirement au début de la partie. + * On s'assure qu'aucune série de 3 n'est créée automatiquement au démarrage. + */ private void initialiserGrilleSansTriples() { for (int l = 0; l < NB_LIGNES; l++) { for (int c = 0; c < NB_COLONNES; c++) { @@ -33,6 +37,9 @@ public class Plateau { } } + /** + * Vérifie si placer une couleur à une position précise créerait un alignement de 3. + */ private boolean creeTriple(int l, int c, int val) { if (c >= 2 && grille[l][c - 1] == val && grille[l][c - 2] == val) { return true; @@ -43,6 +50,10 @@ public class Plateau { return false; } + /** + * Fait défiler une colonne horizontalement. C'est un mouvement circulaire : + * les cases qui sortent par la gauche reviennent par la droite (comme un rouleau) + */ public void decalerLigne(int ligne, int sens) { int s = ((sens % NB_COLONNES) + NB_COLONNES) % NB_COLONNES; for (int etape = 0; etape < s; etape++) { @@ -54,6 +65,10 @@ public class Plateau { } } + /** + * Fait défiler une colonne verticalement. C'est un mouvement circulaire : + * les cases qui sortent par le bas reviennent par le haut. + */ public void decalerColonne(int col, int sens) { int s = ((sens % NB_LIGNES) + NB_LIGNES) % NB_LIGNES; for (int etape = 0; etape < s; etape++) { @@ -65,6 +80,10 @@ public class Plateau { } } + /** + * Parcourt toute la grille pour identifier les alignements de 3 couleurs ou plus. + * Retourne nne liste de coordonnées [ligne, colonne] des cases à supprimer. + */ public List trouverSeries() { boolean[][] aSupprimer = new boolean[NB_LIGNES][NB_COLONNES]; // Analyse horizontale @@ -112,6 +131,10 @@ public class Plateau { return positions; } + /** + * Vérifie si une ligne ou une colonne contient au moins une case verrouillée. + * Si c'est le cas, elle ne peut pas être déplacée par le joueur. + */ public boolean estBloque(boolean estLigne, int index) { if (estLigne) { for (int c = 0; c < NB_COLONNES; c++) { @@ -129,6 +152,10 @@ public class Plateau { return false; } + /** + * Ajoute un nouveau verrou sur une case libre au hasard. + * La fréquence d'apparition augmente au fur et à mesure que la partie avance. + */ public void ajouterVerrou(int nbCoups) { int intervalle = Math.max(1, 5 - nbCoups / 10); if (nbCoups % intervalle != 0) { @@ -149,6 +176,9 @@ public class Plateau { verrous[choix[0]][choix[1]] = true; } + /** + * Libère les verrous sur les cases qui viennent d'être complétées dans une série. + */ public void libererVerrous(boolean[][] masque) { for (int l = 0; l < NB_LIGNES; l++) { for (int c = 0; c < NB_COLONNES; c++) { @@ -159,6 +189,9 @@ public class Plateau { } } + /** + * Création d'un double de la grille actuelle (utile pour tester des mouvements). + */ public int[][] copierGrille() { int[][] copie = new int[NB_LIGNES][NB_COLONNES]; for (int l = 0; l < NB_LIGNES; l++) { @@ -180,7 +213,11 @@ public class Plateau { public boolean[][] getVerrous() { return verrous; } - + + /** + * Logique de gravité : les cases vides (supprimées) sont comblées par les cases du dessus. + * De nouvelles couleurs sont générées au sommet du plateau. + */ public void faireTomber(boolean[][] aSupprimer) { for (int col = 0; col < NB_COLONNES; col++) { List survivants = new ArrayList<>(); diff --git a/app/src/main/java/sae/chuzzle/SeedActivity.java b/app/src/main/java/sae/chuzzle/SeedActivity.java index 3146351..2b4cbc8 100644 --- a/app/src/main/java/sae/chuzzle/SeedActivity.java +++ b/app/src/main/java/sae/chuzzle/SeedActivity.java @@ -8,11 +8,10 @@ import android.widget.Button; import android.widget.EditText; import android.widget.Toast; -public class SeedActivity extends Activity implements View.OnClickListener { +public class SeedActivity extends Activity { private EditText etGraine; private Button btnJouer; - private Button btnRetour; @Override @@ -23,43 +22,18 @@ public class SeedActivity extends Activity implements View.OnClickListener { etGraine = findViewById(R.id.etGraine); btnJouer = findViewById(R.id.btnJouer); btnRetour = findViewById(R.id.btnBack); - - btnRetour.setOnClickListener(this); - - btnJouer.setOnClickListener(this); + SeedControleur controleur = new SeedControleur(this, etGraine); + btnRetour.setOnClickListener(controleur); + btnJouer.setOnClickListener(controleur); } - @Override - public void onClick(View v) { - if (v == btnJouer) { - lancerPartieAvecGraine(); - } - if (v == btnRetour) { - Intent intent = new Intent(this, MenuActivity.class); - startActivity(intent); - } - } - - private void lancerPartieAvecGraine() { - String texte = etGraine.getText().toString().trim(); - - if (texte.isEmpty()) { - Toast.makeText(this, "Veuillez entrer une graine.", - Toast.LENGTH_SHORT - ).show(); - return; - } - - long graine; - - try { - graine = Long.parseLong(texte); - } catch (NumberFormatException e) { - graine = texte.hashCode(); - } - + public void lancerPartie(long graine) { Intent intent = new Intent(this, MainActivity.class); intent.putExtra("graine", graine); startActivity(intent); } + public void retourMenu() { + Intent intent = new Intent(this, MenuActivity.class); + startActivity(intent); + } } \ No newline at end of file diff --git a/app/src/main/java/sae/chuzzle/SeedControleur.java b/app/src/main/java/sae/chuzzle/SeedControleur.java new file mode 100644 index 0000000..cff4efb --- /dev/null +++ b/app/src/main/java/sae/chuzzle/SeedControleur.java @@ -0,0 +1,53 @@ +package sae.chuzzle; + +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; + +/** + * Contrôleur pour la saisie de la graine. + * Gère la validation de la saisie et la navigation. + */ +public class SeedControleur implements View.OnClickListener { + + private final SeedActivity activite; + private final EditText etGraine; + + public SeedControleur(SeedActivity activite, EditText etGraine) { + this.activite = activite; + this.etGraine = etGraine; + } + + @Override + public void onClick(View v) { + int id = v.getId(); + + if (id == R.id.btnJouer) { + traiterGraine(); + } else if (id == R.id.btnBack) { + activite.retourMenu(); + } + } + + /** + * Valide le texte saisi et demande à l'activité de lancer la partie. + */ + private void traiterGraine() { + String texte = etGraine.getText().toString().trim(); + + if (texte.isEmpty()) { + Toast.makeText(activite, "Veuillez entrer une graine.", Toast.LENGTH_SHORT).show(); + return; + } + + long graine; + try { + graine = Long.parseLong(texte); + } catch (NumberFormatException e) { + + graine = texte.hashCode(); + } + + activite.lancerPartie(graine); + } +} diff --git a/app/src/main/java/sae/chuzzle/VueGrille.java b/app/src/main/java/sae/chuzzle/VueGrille.java index cf2146a..53b3099 100644 --- a/app/src/main/java/sae/chuzzle/VueGrille.java +++ b/app/src/main/java/sae/chuzzle/VueGrille.java @@ -19,7 +19,7 @@ public class VueGrille extends View { private static final int NB_COLONNES = 6; private static final int NB_TYPES = 7; - /** Symboles pour le mode daltonien, un par type. */ + /** Symboles pour les daltoniens, un par type. */ private static final String[] SYMBOLES = {"●", "■", "▲", "✚", "★", "♦", "✿"}; // Données nécessaires au dessin @@ -33,8 +33,8 @@ public class VueGrille extends View { // Outils de dessin - private final Paint pinceauCase = new Paint(); - private final Paint pinceauSymbole = new Paint(); + private final Paint pinceauCase = new Paint(); + private final Paint pinceauSymbole = new Paint(); private final Paint pinceauSelection = new Paint(); private Bitmap imageChaine; @@ -130,14 +130,18 @@ public class VueGrille extends View { pinceauSymbole.setTextSize(tailleCase * 0.4f); + // On parcourt chaque case de la grille pour la dessiner for (int ligne = 0; ligne < NB_LIGNES; ligne++) { for (int colonne = 0; colonne < NB_COLONNES; colonne++) { float offsetX = 0f; float offsetY = 0f; + // Si un glissement est en cours, on calcule le décalage visuel if (animEstLigne != null) { + // On convertit les pixels du doigt en nombre de cases de décalage int nbCases = Math.round(animDecalagePx / tailleCase); + // On applique le décalage uniquement à la ligne ou colonne que le joueur bouge if (animEstLigne && ligne == animIndex) { offsetX = nbCases * tailleCase; } else if (!animEstLigne && colonne == animIndex) { @@ -150,6 +154,7 @@ public class VueGrille extends View { float x2 = margeGauche + (colonne + 1) * tailleCase - 6 + offsetX; float y2 = margeHaut + (ligne + 1) * tailleCase - 6 + offsetY; + // On dessine la case (et ses éventuelles copies pour l'effet de boucle) dessinerCase(canvas, ligne, colonne, x1, y1, x2, y2, tailleCase, margeGauche, margeHaut, offsetX, offsetY); } @@ -170,7 +175,7 @@ public class VueGrille extends View { // Dessin case principale dessinerRectCase(canvas, ligne, colonne, x1, y1, x2, y2); - // Réapparition de l'autre côté (wrap) + // Réapparition de l'autre côté (effet boucle infini) if (offsetX != 0f) { float bordD = margeGauche + largeurGrille; float bordG = margeGauche;