diff --git a/src/main/java/sae/chuzzle/EtatJeu.java b/src/main/java/sae/chuzzle/EtatJeu.java index 4d5f670..a4c905f 100644 --- a/src/main/java/sae/chuzzle/EtatJeu.java +++ b/src/main/java/sae/chuzzle/EtatJeu.java @@ -21,6 +21,9 @@ public class EtatJeu { private boolean partieTerminee = false; private boolean hardMode = false; + // Ajout pour les objectifs : comptage des séries par couleur au dernier coup + private final int[] seriesParCouleurDernierCoup = new int[NB_TYPES]; + //- // CONSTRUCTEURS @@ -52,6 +55,10 @@ public class EtatJeu { return partieTerminee; } + public int[] getSeriesParCouleurDernierCoup() { + return seriesParCouleurDernierCoup; + } + // - // SAUVEGARDE ET RESTAURATION @@ -214,6 +221,10 @@ public class EtatJeu { } nbCoups++; + + // Reset des séries du dernier coup + for (int i = 0; i < NB_TYPES; i++) seriesParCouleurDernierCoup[i] = 0; + score += resoudreEtRemplir(); // Verrou après chaque coup ; 2 verrous en hard mode (plus difficile) @@ -297,7 +308,7 @@ public class EtatJeu { public int resoudreEtRemplir() { int baseTotal = 0; - int nbSeries = 0; + int nbSeriesTotal = 0; List series = trouverSeries(); @@ -305,7 +316,11 @@ public class EtatJeu { // Accumuler les points de base et le nombre de séries sur toutes les vagues baseTotal += calculerPointsBase(series); - nbSeries += compterNbSeries(series); + + // On compte les séries par couleur spécifiquement + compterSeriesParCouleur(series); + + nbSeriesTotal += compterNbSeries(series); boolean[][] aSupprimer = new boolean[NB_LIGNES][NB_COLONNES]; @@ -342,13 +357,45 @@ public class EtatJeu { series = trouverSeries(); } - if (nbSeries == 0) return 0; + if (nbSeriesTotal == 0) return 0; // Bonus : +50% par série supplémentaire après la première (spec SAÉ) - double bonus = 1.0 + (nbSeries - 1) * 0.5; + double bonus = 1.0 + (nbSeriesTotal - 1) * 0.5; return (int) (baseTotal * bonus); } + private void compterSeriesParCouleur(List series) { + boolean[][] masque = new boolean[NB_LIGNES][NB_COLONNES]; + for (int[] pos : series) masque[pos[0]][pos[1]] = true; + + // Horizontales + for (int l = 0; l < NB_LIGNES; l++) { + int c = 0; + while (c < NB_COLONNES) { + if (masque[l][c]) { + int type = grille[l][c]; + int fin = c + 1; + while (fin < NB_COLONNES && masque[l][fin] && grille[l][fin] == type) fin++; + if (fin - c >= 3) seriesParCouleurDernierCoup[type]++; + c = fin; + } else c++; + } + } + // Verticales + for (int col = 0; col < NB_COLONNES; col++) { + int l = 0; + while (l < NB_LIGNES) { + if (masque[l][col]) { + int type = grille[l][col]; + int fin = l + 1; + while (fin < NB_LIGNES && masque[fin][col] && grille[fin][col] == type) fin++; + if (fin - l >= 3) seriesParCouleurDernierCoup[type]++; + l = fin; + } else l++; + } + } + } + private int calculerPointsBase(List series) { boolean[][] masque = new boolean[NB_LIGNES][NB_COLONNES]; diff --git a/src/main/java/sae/chuzzle/VueGrille.java b/src/main/java/sae/chuzzle/VueGrille.java index 8768302..3d92922 100644 --- a/src/main/java/sae/chuzzle/VueGrille.java +++ b/src/main/java/sae/chuzzle/VueGrille.java @@ -1,8 +1,11 @@ package sae.chuzzle; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; @@ -10,9 +13,7 @@ import androidx.annotation.NonNull; public class VueGrille extends View { - // ========================================================= // Constantes - // ========================================================= private static final int NB_LIGNES = 6; private static final int NB_COLONNES = 6; @@ -21,48 +22,30 @@ public class VueGrille extends View { /** Symboles pour le mode daltonien, un par type. */ private static final String[] SYMBOLES = {"●", "■", "▲", "✚", "★", "♦", "✿"}; - // ========================================================= + // Données métier - // ========================================================= - /** Grille reçue depuis MainActivity (tableau brut). */ private int[][] grille = new int[NB_LIGNES][NB_COLONNES]; - - /** Verrous reçus depuis MainActivity. */ private boolean[][] verrous = new boolean[NB_LIGNES][NB_COLONNES]; - private boolean modeDaltonien = false; // ========================================================= - // État de l'animation de glissement + // État du glissement // ========================================================= - /** - * true = on anime une ligne, - * false = on anime une colonne, - * null = pas d'animation en cours. - */ private Boolean animEstLigne = null; - - /** Index de la ligne ou colonne en cours de glissement. */ private int animIndex = 0; - - /** - * Décalage courant en pixels (positif = droite/bas, - * négatif = gauche/haut). - */ private float animDecalagePx = 0f; - // ========================================================= + // 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; - // ========================================================= // Constructeurs - // ========================================================= public VueGrille(Context contexte) { super(contexte); @@ -81,17 +64,18 @@ public class VueGrille extends View { pinceauSymbole.setAntiAlias(true); pinceauSymbole.setColor(0xFF000000); pinceauSymbole.setTextAlign(Paint.Align.CENTER); + + pinceauSelection.setAntiAlias(true); + pinceauSelection.setStyle(Paint.Style.STROKE); + pinceauSelection.setStrokeWidth(12f); + pinceauSelection.setColor(0xFFFFFFFF); + + // Chargement de l'image de la chaine + imageChaine = BitmapFactory.decodeResource(getResources(), R.drawable.chaine); } - // ========================================================= // API publique - // ========================================================= - /** - * Reçoit une copie de la grille depuis MainActivity. - * VueGrille ne sait pas d'où viennent ces données, - * elle sait juste les dessiner. - */ public void definirGrille(int[][] nouvelleGrille) { for (int l = 0; l < NB_LIGNES; l++) { System.arraycopy(nouvelleGrille[l], 0, grille[l], 0, NB_COLONNES); @@ -111,17 +95,9 @@ public class VueGrille extends View { invalidate(); } - // ========================================================= - // API animation de glissement (appelée par GestionnaireTactile) - // ========================================================= - /** - * Déclenche l'animation de glissement en temps réel. - * - * @param estLigne true = on déplace une ligne, false = une colonne - * @param index index de la ligne ou colonne (0-based) - * @param decalagePx décalage courant en pixels (peut être négatif) - */ + // API animation de glissement (appelée par GestionnaireTactile) + public void definirGlissement(boolean estLigne, int index, float decalagePx) { this.animEstLigne = estLigne; this.animIndex = index; @@ -129,22 +105,43 @@ public class VueGrille extends View { invalidate(); } - /** - * Annule l'animation de glissement et revient à l'affichage normal. - */ public void annulerGlissement() { this.animEstLigne = null; invalidate(); } - // ========================================================= + /** + * Vérifie si la sélection actuelle (ligne ou colonne) contient au moins un verrou. + */ + private boolean estSelectionBloquee() { + if (animEstLigne == null) return false; + if (animEstLigne) { + for (int c = 0; c < NB_COLONNES; c++) { + if (verrous[animIndex][c]) return true; + } + } else { + for (int l = 0; l < NB_LIGNES; l++) { + if (verrous[l][animIndex]) return true; + } + } + return false; + } + // Dessin - // ========================================================= @Override protected void onDraw(@NonNull Canvas canvas) { super.onDraw(canvas); + // Mise à jour de la couleur du contour de sélection si bloqué + if (animEstLigne != null) { + if (estSelectionBloquee()) { + pinceauSelection.setColor(0xFF000000); // Noir + } else { + pinceauSelection.setColor(0xFFFFFFFF); // Blanc + } + } + int largeur = getWidth(); int hauteur = getHeight(); @@ -156,50 +153,41 @@ public class VueGrille extends View { float margeGauche = (largeur - tailleCase * NB_COLONNES) / 2f; float margeHaut = (hauteur - tailleCase * NB_LIGNES) / 2f; + // - On limite le dessin à la zone de la grille + canvas.save(); + canvas.clipRect(margeGauche, margeHaut, + margeGauche + NB_COLONNES * tailleCase, + margeHaut + NB_LIGNES * tailleCase); + pinceauSymbole.setTextSize(tailleCase * 0.4f); for (int ligne = 0; ligne < NB_LIGNES; ligne++) { for (int colonne = 0; colonne < NB_COLONNES; colonne++) { - // -------------------------------------------------- - // Calcul de l'offset d'animation pour cette case - // -------------------------------------------------- float offsetX = 0f; float offsetY = 0f; if (animEstLigne != null) { + int nbCases = Math.round(animDecalagePx / tailleCase); if (animEstLigne && ligne == animIndex) { - // Décalage horizontal de toute la ligne - offsetX = animDecalagePx; + offsetX = nbCases * tailleCase; } else if (!animEstLigne && colonne == animIndex) { - // Décalage vertical de toute la colonne - offsetY = animDecalagePx; + offsetY = nbCases * tailleCase; } } - // -------------------------------------------------- - // Position de base de la case - // -------------------------------------------------- float x1 = margeGauche + colonne * tailleCase + 6 + offsetX; float y1 = margeHaut + ligne * tailleCase + 6 + offsetY; float x2 = margeGauche + (colonne + 1) * tailleCase - 6 + offsetX; float y2 = margeHaut + (ligne + 1) * tailleCase - 6 + offsetY; - // -------------------------------------------------- - // Dessin avec wrap-around (la case qui sort d'un - // côté réapparaît de l'autre côté) - // -------------------------------------------------- dessinerCase(canvas, ligne, colonne, x1, y1, x2, y2, tailleCase, margeGauche, margeHaut, offsetX, offsetY); } } + canvas.restore(); } - /** - * Dessine une case à la position donnée. - * Si la case sort des bords (wrap-around), elle est aussi dessinée - * du côté opposé de la grille. - */ private void dessinerCase(Canvas canvas, int ligne, int colonne, float x1, float y1, float x2, float y2, @@ -208,66 +196,50 @@ public class VueGrille extends View { float offsetX, float offsetY) { int type = grille[ligne][colonne]; - - // Bornage de la grille (en pixels) - float borneGaucheGrille = margeGauche; - float bordDroiteGrille = margeGauche + NB_COLONNES * tailleCase; - float borneHautGrille = margeHaut; - float bordBasGrille = margeHaut + NB_LIGNES * tailleCase; - - // Largeur totale de la ligne / hauteur totale de la colonne en pixels float largeurGrille = NB_COLONNES * tailleCase; - float hauteurGrille = NB_LIGNES * tailleCase; + float hauteurGrille = NB_LIGNES * tailleCase; - // Dessine la case principale + // Dessin case principale dessinerRectCase(canvas, type, ligne, colonne, x1, y1, x2, y2); - // Wrap-around horizontal (pour les lignes) + // --- Wrap-around (réapparition de l'autre côté) --- if (offsetX != 0f) { - float wrapX1 = x1, wrapX2 = x2; - - if (x2 > bordDroiteGrille) { - // La case déborde à droite → réapparaît à gauche - wrapX1 = x1 - largeurGrille; - wrapX2 = x2 - largeurGrille; - dessinerRectCase(canvas, type, ligne, colonne, wrapX1, y1, wrapX2, y2); - } else if (x1 < borneGaucheGrille) { - // La case déborde à gauche → réapparaît à droite - wrapX1 = x1 + largeurGrille; - wrapX2 = x2 + largeurGrille; - dessinerRectCase(canvas, type, ligne, colonne, wrapX1, y1, wrapX2, y2); + float bordD = margeGauche + largeurGrille; + float bordG = margeGauche; + if (x2 > bordD) { + dessinerRectCase(canvas, type, ligne, colonne, x1 - largeurGrille, y1, x2 - largeurGrille, y2); + } else if (x1 < bordG) { + dessinerRectCase(canvas, type, ligne, colonne, x1 + largeurGrille, y1, x2 + largeurGrille, y2); } } - - // Wrap-around vertical (pour les colonnes) if (offsetY != 0f) { - float wrapY1 = y1, wrapY2 = y2; - - if (y2 > bordBasGrille) { - // La case déborde en bas → réapparaît en haut - wrapY1 = y1 - hauteurGrille; - wrapY2 = y2 - hauteurGrille; - dessinerRectCase(canvas, type, ligne, colonne, x1, wrapY1, x2, wrapY2); - } else if (y1 < borneHautGrille) { - // La case déborde en haut → réapparaît en bas - wrapY1 = y1 + hauteurGrille; - wrapY2 = y2 + hauteurGrille; - dessinerRectCase(canvas, type, ligne, colonne, x1, wrapY1, x2, wrapY2); + float bordB = margeHaut + hauteurGrille; + float bordH = margeHaut; + if (y2 > bordB) { + dessinerRectCase(canvas, type, ligne, colonne, x1, y1 - hauteurGrille, x2, y2 - hauteurGrille); + } else if (y1 < bordH) { + dessinerRectCase(canvas, type, ligne, colonne, x1, y1 + hauteurGrille, x2, y2 + hauteurGrille); } } } /** * Dessine le rectangle coloré d'une case + verrou + symbole daltonien. + * Ajoute un contour gras si la case est sélectionnée. */ private void dessinerRectCase(Canvas canvas, int type, int ligne, int colonne, float x1, float y1, float x2, float y2) { - // Couleur de fond definirCouleur(type); canvas.drawRoundRect(x1, y1, x2, y2, 20, 20, pinceauCase); + if (animEstLigne != null) { + if ((animEstLigne && ligne == animIndex) || (!animEstLigne && colonne == animIndex)) { + canvas.drawRoundRect(x1, y1, x2, y2, 20, 20, pinceauSelection); + } + } + // Assombrir si verrouillée if (verrous[ligne][colonne]) { pinceauCase.setARGB(120, 0, 0, 0); @@ -275,24 +247,28 @@ public class VueGrille extends View { } float cx = (x1 + x2) / 2f; - float cy = (y1 + y2) / 2f - - (pinceauSymbole.descent() + pinceauSymbole.ascent()) / 2f; + float cy = (y1 + y2) / 2f - (pinceauSymbole.descent() + pinceauSymbole.ascent()) / 2f; - // Symbole daltonien if (modeDaltonien) { canvas.drawText(SYMBOLES[type % NB_TYPES], cx, cy, pinceauSymbole); } - // Cadenas par-dessus si verrouillée - if (verrous[ligne][colonne]) { + // Dessin de l'image de la chaine si verrouillée + if (verrous[ligne][colonne] && imageChaine != null) { + float size = (x2 - x1); + float chainSize = size * 0.55f; // Taille de chaque morceau de chaine + + // Dessin d'une chaine en haut à gauche + canvas.drawBitmap(imageChaine, null, new RectF(x1, y1, x1 + chainSize, y1 + chainSize), null); + + // Dessin d'une chaine en bas à droite + canvas.drawBitmap(imageChaine, null, new RectF(x2 - chainSize, y2 - chainSize, x2, y2), null); + } else if (verrous[ligne][colonne]) { + // Fallback si l'image n'est pas trouvée canvas.drawText("🔒", cx, cy, pinceauSymbole); } } - // ========================================================= - // Utilitaire privé - // ========================================================= - private void definirCouleur(int type) { switch (type % NB_TYPES) { case 0: pinceauCase.setARGB(255, 200, 200, 200); break; @@ -304,4 +280,4 @@ public class VueGrille extends View { case 6: pinceauCase.setARGB(255, 255, 90, 90); break; } } -} \ No newline at end of file +} diff --git a/src/main/res/drawable/chaine.png b/src/main/res/drawable/chaine.png new file mode 100644 index 0000000..da042e8 Binary files /dev/null and b/src/main/res/drawable/chaine.png differ