Refactor: Nettoyage du code et amélioration de la documentation
- Suppression des logs de débogage et commentaires superflus. - Ajout/Amélioration des commentaires JavaDoc pour les classes et méthodes principales (Game, GameStats, MainActivity, OnSwipeTouchListener). - Mise à jour des en-têtes de fichiers pour refléter le rôle actuel des classes. - Revue générale pour la clarté et la cohérence du code.
This commit is contained in:
parent
b32d1e0986
commit
21ff127536
@ -1,114 +1,121 @@
|
|||||||
// Fichier Game.java
|
// Fichier Game.java
|
||||||
// Contient la logique principale du jeu 2048 : gestion du plateau, mouvements, fusions, score.
|
/**
|
||||||
// Cette classe est maintenant indépendante du contexte Android et de la persistance des données.
|
* Représente la logique métier du jeu 2048. Gère l'état du plateau de jeu,
|
||||||
/*
|
* les déplacements, les fusions, le score de la partie en cours, et les conditions
|
||||||
Fonctions principales :
|
* de victoire ou de défaite. Cette classe est conçue pour être indépendante du
|
||||||
- board : Matrice 2D (int[][]) représentant la grille du jeu.
|
* framework Android (pas de dépendance au Contexte ou aux SharedPreferences).
|
||||||
- currentScore : Suivi du score de la partie en cours.
|
|
||||||
- highestScore : Stocke le meilleur score global (reçu de l'extérieur via setHighestScore).
|
|
||||||
- addNewTile() : Ajoute une nouvelle tuile aléatoire sur une case vide.
|
|
||||||
- pushUp(), pushDown(), pushLeft(), pushRight() : Gèrent la logique de déplacement et de fusion, retournent un booléen indiquant si le plateau a changé.
|
|
||||||
- getHighestTileValue() : Retourne la valeur de la plus haute tuile sur le plateau.
|
|
||||||
- États gameWon, gameOver : Indiquent si la partie est gagnée ou terminée.
|
|
||||||
- Méthodes pour vérifier les conditions de victoire et de fin de partie.
|
|
||||||
- toString(), deserialize() : Sérialisent/désérialisent l'état essentiel du jeu (plateau, score courant) pour la sauvegarde externe.
|
|
||||||
|
|
||||||
Relations :
|
|
||||||
- MainActivity : Crée une instance de Game, appelle ses méthodes (pushX, addNewTile, getters), lui fournit le meilleur score global via setHighestScore, et utilise son état pour l'affichage et la sauvegarde.
|
|
||||||
*/
|
*/
|
||||||
package legion.muyue.best2048;
|
package legion.muyue.best2048;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting; // Pour les méthodes de test éventuelles
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class Game {
|
public class Game {
|
||||||
|
|
||||||
|
/** Le plateau de jeu, une matrice 2D d'entiers. 0 représente une case vide. */
|
||||||
private int[][] board;
|
private int[][] board;
|
||||||
|
/** Générateur de nombres aléatoires pour l'ajout de nouvelles tuiles. */
|
||||||
private final Random randomNumberGenerator;
|
private final Random randomNumberGenerator;
|
||||||
|
/** Score de la partie actuellement en cours. */
|
||||||
private int currentScore = 0;
|
private int currentScore = 0;
|
||||||
private int highestScore = 0; // Stocke le HS fourni par MainActivity
|
/** Meilleur score global (reçu et stocké, mais non géré logiquement ici). */
|
||||||
|
private int highestScore = 0;
|
||||||
|
/** Taille du plateau de jeu (nombre de lignes/colonnes). */
|
||||||
private static final int BOARD_SIZE = 4;
|
private static final int BOARD_SIZE = 4;
|
||||||
|
/** Indicateur si la condition de victoire (>= 2048) a été atteinte. */
|
||||||
private boolean gameWon = false;
|
private boolean gameWon = false;
|
||||||
|
/** Indicateur si la partie est terminée (plus de mouvements possibles). */
|
||||||
private boolean gameOver = false;
|
private boolean gameOver = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur pour une nouvelle partie.
|
* Constructeur pour démarrer une nouvelle partie.
|
||||||
* Initialise un plateau vide, définit le score à 0 et ajoute deux tuiles initiales.
|
* Initialise un plateau vide, le score à 0, et ajoute deux tuiles initiales.
|
||||||
*/
|
*/
|
||||||
public Game() {
|
public Game() {
|
||||||
this.randomNumberGenerator = new Random();
|
this.randomNumberGenerator = new Random();
|
||||||
// Le highScore sera défini par MainActivity après l'instanciation.
|
|
||||||
initializeNewBoard();
|
initializeNewBoard();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur utilisé lors de la restauration d'une partie sauvegardée.
|
* Constructeur pour restaurer une partie à partir d'un état sauvegardé.
|
||||||
|
* Le meilleur score (`highestScore`) doit être défini séparément via {@link #setHighestScore(int)}.
|
||||||
|
* Recalcule les états `gameWon` et `gameOver` en fonction du plateau fourni.
|
||||||
|
*
|
||||||
* @param board Le plateau de jeu restauré.
|
* @param board Le plateau de jeu restauré.
|
||||||
* @param score Le score courant restauré.
|
* @param score Le score courant restauré.
|
||||||
*/
|
*/
|
||||||
public Game(int[][] board, int score) {
|
public Game(int[][] board, int score) {
|
||||||
|
// Valider les dimensions du plateau fourni ? Pourrait être ajouté.
|
||||||
this.board = board;
|
this.board = board;
|
||||||
this.currentScore = score;
|
this.currentScore = score;
|
||||||
this.randomNumberGenerator = new Random();
|
this.randomNumberGenerator = new Random();
|
||||||
// Le highScore sera défini par MainActivity après l'instanciation.
|
checkWinCondition();
|
||||||
checkWinCondition(); // Recalcule l'état basé sur le plateau chargé
|
|
||||||
checkGameOverCondition();
|
checkGameOverCondition();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Getters / Setters ---
|
// --- Getters / Setters ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retourne la valeur de la cellule aux coordonnées spécifiées.
|
* Retourne la valeur de la tuile aux coordonnées spécifiées.
|
||||||
* @param row Ligne de la cellule (0-based).
|
* @param row Ligne (0 à BOARD_SIZE-1).
|
||||||
* @param column Colonne de la cellule (0-based).
|
* @param column Colonne (0 à BOARD_SIZE-1).
|
||||||
* @return Valeur de la cellule, ou 0 si indices invalides (sécurité).
|
* @return Valeur de la tuile, ou 0 si les coordonnées sont invalides.
|
||||||
*/
|
*/
|
||||||
public int getCellValue(int row, int column) {
|
public int getCellValue(int row, int column) {
|
||||||
if (row < 0 || row >= BOARD_SIZE || column < 0 || column >= BOARD_SIZE) { return 0; }
|
if (isIndexValid(row, column)) {
|
||||||
return this.board[row][column];
|
return this.board[row][column];
|
||||||
}
|
}
|
||||||
|
return 0; // Retourne 0 pour indice invalide
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Définit la valeur de la cellule aux coordonnées spécifiées.
|
* Définit la valeur d'une tuile aux coordonnées spécifiées.
|
||||||
* @param row Ligne de la cellule (0-based).
|
* Ne fait rien si les coordonnées sont invalides.
|
||||||
* @param col Colonne de la cellule (0-based).
|
* @param row Ligne (0 à BOARD_SIZE-1).
|
||||||
* @param value Nouvelle valeur.
|
* @param col Colonne (0 à BOARD_SIZE-1).
|
||||||
|
* @param value Nouvelle valeur de la tuile.
|
||||||
*/
|
*/
|
||||||
public void setCellValue(int row, int col, int value) {
|
public void setCellValue(int row, int col, int value) {
|
||||||
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) { return; }
|
if (isIndexValid(row, col)) {
|
||||||
this.board[row][col] = value;
|
this.board[row][col] = value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @return Le score actuel de la partie. */
|
/** @return Le score actuel de la partie. */
|
||||||
public int getCurrentScore() { return currentScore; }
|
public int getCurrentScore() { return currentScore; }
|
||||||
|
|
||||||
// public void setCurrentScore(int currentScore) { this.currentScore = currentScore; } // Setter interne si nécessaire
|
/** @return Le meilleur score connu par cet objet (défini via setHighestScore). */
|
||||||
|
|
||||||
/** @return Le meilleur score connu par cet objet Game (synchronisé par MainActivity). */
|
|
||||||
public int getHighestScore() { return highestScore; }
|
public int getHighestScore() { return highestScore; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Définit le meilleur score global connu. Appelé par MainActivity après chargement
|
* Met à jour la valeur du meilleur score stockée dans cet objet Game.
|
||||||
* des préférences ou après une mise à jour du score.
|
* Typiquement appelé par la classe gérant la persistance (MainActivity).
|
||||||
* @param highScore Le meilleur score connu.
|
* @param highScore Le meilleur score global à stocker.
|
||||||
*/
|
*/
|
||||||
public void setHighestScore(int highScore) { this.highestScore = highScore; }
|
public void setHighestScore(int highScore) { this.highestScore = highScore; }
|
||||||
|
|
||||||
/** @return L'état de victoire de la partie (true si >= 2048 atteint). */
|
/** @return true si une tuile 2048 (ou plus) a été atteinte, false sinon. */
|
||||||
public boolean isGameWon() { return gameWon; }
|
public boolean isGameWon() { return gameWon; }
|
||||||
|
|
||||||
public void setGameWon(boolean gameWon) { this.gameWon = gameWon; }
|
/** @return true si aucune case n'est vide ET aucun mouvement/fusion n'est possible, false sinon. */
|
||||||
|
|
||||||
/** @return L'état de fin de partie (true si aucun mouvement possible). */
|
|
||||||
public boolean isGameOver() { return gameOver; }
|
public boolean isGameOver() { return gameOver; }
|
||||||
|
|
||||||
public void setGameOver(boolean gameOver) { this.gameOver = gameOver; }
|
/** Met à jour la valeur de gameWon si partie gagné **/
|
||||||
|
private void setGameWon(boolean won) {this.gameWon = won;}
|
||||||
|
|
||||||
/** @return Une copie du plateau de jeu actuel (pour la sauvegarde externe). */
|
/** Met à jour la valeur de gameWon si partie gagné **/
|
||||||
|
private void setGameOver(boolean over) {this.gameOver = over;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne une copie profonde du plateau de jeu actuel.
|
||||||
|
* Utile pour la sérialisation ou pour éviter des modifications externes non désirées.
|
||||||
|
* @return Une nouvelle matrice 2D représentant l'état actuel du plateau.
|
||||||
|
*/
|
||||||
public int[][] getBoard() {
|
public int[][] getBoard() {
|
||||||
// Retourne une copie pour éviter modifications externes accidentelles
|
|
||||||
int[][] copy = new int[BOARD_SIZE][BOARD_SIZE];
|
int[][] copy = new int[BOARD_SIZE][BOARD_SIZE];
|
||||||
for(int i=0; i<BOARD_SIZE; i++) {
|
for(int i=0; i<BOARD_SIZE; i++) {
|
||||||
System.arraycopy(this.board[i], 0, copy[i], 0, BOARD_SIZE);
|
System.arraycopy(this.board[i], 0, copy[i], 0, BOARD_SIZE);
|
||||||
@ -117,32 +124,27 @@ public class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise ou réinitialise le plateau pour une nouvelle partie.
|
* (Ré)Initialise le plateau de jeu pour une nouvelle partie.
|
||||||
* Met le score à 0 et ajoute deux tuiles aléatoires.
|
* Remplit le plateau de zéros, réinitialise le score et les états `gameWon`/`gameOver`,
|
||||||
|
* puis ajoute deux tuiles initiales.
|
||||||
*/
|
*/
|
||||||
private void initializeNewBoard() {
|
private void initializeNewBoard() {
|
||||||
this.board = new int[BOARD_SIZE][BOARD_SIZE];
|
this.board = new int[BOARD_SIZE][BOARD_SIZE]; // Crée une nouvelle matrice vide
|
||||||
this.currentScore = 0;
|
this.currentScore = 0;
|
||||||
this.gameWon = false;
|
this.gameWon = false;
|
||||||
this.gameOver = false;
|
this.gameOver = false;
|
||||||
addNewTile();
|
addNewTile(); // Ajoute la première tuile
|
||||||
addNewTile();
|
addNewTile(); // Ajoute la seconde tuile
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Logique du Jeu ---
|
// --- Logique du Jeu ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajoute une nouvelle tuile (selon les probabilités définies)
|
* Ajoute une nouvelle tuile (2, 4, 8, etc., selon probabilités) sur une case vide aléatoire.
|
||||||
* sur une case vide aléatoire du plateau. Ne fait rien si le plateau est plein.
|
* Si le plateau est plein, cette méthode ne fait rien.
|
||||||
*/
|
*/
|
||||||
public void addNewTile() {
|
public void addNewTile() {
|
||||||
if (!hasEmptyCell()) { return; }
|
List<int[]> emptyCells = findEmptyCells();
|
||||||
List<int[]> emptyCells = new ArrayList<>();
|
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
|
||||||
if (board[row][col] == 0) { emptyCells.add(new int[]{row, col}); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!emptyCells.isEmpty()) {
|
if (!emptyCells.isEmpty()) {
|
||||||
int[] randomCell = emptyCells.get(randomNumberGenerator.nextInt(emptyCells.size()));
|
int[] randomCell = emptyCells.get(randomNumberGenerator.nextInt(emptyCells.size()));
|
||||||
int value = generateRandomTileValue();
|
int value = generateRandomTileValue();
|
||||||
@ -151,113 +153,150 @@ public class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Génère aléatoirement la valeur d'une nouvelle tuile (2, 4, 8, etc.)
|
* Trouve toutes les cellules vides sur le plateau.
|
||||||
* selon des probabilités prédéfinies.
|
* @return Une liste de tableaux d'entiers `[row, col]` pour chaque cellule vide.
|
||||||
* @return La valeur de la nouvelle tuile.
|
*/
|
||||||
|
private List<int[]> findEmptyCells() {
|
||||||
|
List<int[]> emptyCells = new ArrayList<>();
|
||||||
|
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||||
|
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||||
|
if (board[row][col] == 0) {
|
||||||
|
emptyCells.add(new int[]{row, col});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère la valeur pour une nouvelle tuile en utilisant des probabilités prédéfinies.
|
||||||
|
* @return La valeur (2, 4, 8, ...).
|
||||||
*/
|
*/
|
||||||
private int generateRandomTileValue() {
|
private int generateRandomTileValue() {
|
||||||
int randomValue = randomNumberGenerator.nextInt(10000);
|
int randomValue = randomNumberGenerator.nextInt(10000); // Base 10000 pour pourcentages fins
|
||||||
if (randomValue < 8540) return 2; // ~85%
|
if (randomValue < 8540) return 2; // 85.40%
|
||||||
if (randomValue < 9740) return 4; // ~12%
|
if (randomValue < 9740) return 4; // 12.00%
|
||||||
if (randomValue < 9940) return 8; // ~2%
|
if (randomValue < 9940) return 8; // 2.00%
|
||||||
if (randomValue < 9990) return 16; // ~0.5%
|
if (randomValue < 9990) return 16; // 0.50%
|
||||||
// ... (autres probabilités)
|
if (randomValue < 9995) return 32; // 0.05%
|
||||||
if (randomValue < 9995) return 32;
|
if (randomValue < 9998) return 64; // 0.03%
|
||||||
if (randomValue < 9998) return 64;
|
if (randomValue < 9999) return 128;// 0.01%
|
||||||
if (randomValue < 9999) return 128;
|
return 256; // 0.01%
|
||||||
return 256;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tente de déplacer et fusionner les tuiles vers le HAUT.
|
* Tente de déplacer et fusionner les tuiles vers le HAUT.
|
||||||
* Met à jour le score interne en cas de fusion.
|
* Met à jour le score interne et vérifie les états win/gameOver.
|
||||||
* Vérifie les conditions de victoire/défaite après le mouvement.
|
* @return true si le plateau a été modifié, false sinon.
|
||||||
* @return true si au moins une tuile a bougé ou fusionné, false sinon.
|
|
||||||
*/
|
*/
|
||||||
public boolean pushUp() {
|
public boolean pushUp() { return processMove(MoveDirection.UP); }
|
||||||
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
|
||||||
hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
for (int row = 1; row < BOARD_SIZE; row++) {
|
|
||||||
if (getCellValue(row, col) != 0) {
|
|
||||||
int currentValue = getCellValue(row, col); int currentRow = row;
|
|
||||||
while (currentRow > 0 && getCellValue(currentRow - 1, col) == 0) { setCellValue(currentRow - 1, col, currentValue); setCellValue(currentRow, col, 0); currentRow--; boardChanged = true; }
|
|
||||||
if (currentRow > 0 && getCellValue(currentRow - 1, col) == currentValue && !hasMerged[currentRow - 1]) {
|
|
||||||
int newValue = getCellValue(currentRow - 1, col) * 2; setCellValue(currentRow - 1, col, newValue); setCellValue(currentRow, col, 0);
|
|
||||||
currentScore += newValue; hasMerged[currentRow - 1] = true; boardChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tente de déplacer et fusionner les tuiles vers le BAS.
|
* Tente de déplacer et fusionner les tuiles vers le BAS.
|
||||||
* @return true si changement, false sinon.
|
* @return true si le plateau a été modifié, false sinon.
|
||||||
*/
|
*/
|
||||||
public boolean pushDown() {
|
public boolean pushDown() { return processMove(MoveDirection.DOWN); }
|
||||||
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
|
||||||
hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
for (int row = BOARD_SIZE - 2; row >= 0; row--) {
|
|
||||||
if (getCellValue(row, col) != 0) {
|
|
||||||
int currentValue = getCellValue(row, col); int currentRow = row;
|
|
||||||
while (currentRow < BOARD_SIZE - 1 && getCellValue(currentRow + 1, col) == 0) { setCellValue(currentRow + 1, col, currentValue); setCellValue(currentRow, col, 0); currentRow++; boardChanged = true; }
|
|
||||||
if (currentRow < BOARD_SIZE - 1 && getCellValue(currentRow + 1, col) == currentValue && !hasMerged[currentRow + 1]) {
|
|
||||||
int newValue = getCellValue(currentRow + 1, col) * 2; setCellValue(currentRow + 1, col, newValue); setCellValue(currentRow, col, 0);
|
|
||||||
currentScore += newValue; hasMerged[currentRow + 1] = true; boardChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tente de déplacer et fusionner les tuiles vers la GAUCHE.
|
* Tente de déplacer et fusionner les tuiles vers la GAUCHE.
|
||||||
* @return true si changement, false sinon.
|
* @return true si le plateau a été modifié, false sinon.
|
||||||
*/
|
*/
|
||||||
public boolean pushLeft() {
|
public boolean pushLeft() { return processMove(MoveDirection.LEFT); }
|
||||||
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
|
||||||
hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
for (int col = 1; col < BOARD_SIZE; col++) {
|
|
||||||
if (getCellValue(row, col) != 0) {
|
|
||||||
int currentValue = getCellValue(row, col); int currentCol = col;
|
|
||||||
while (currentCol > 0 && getCellValue(row, currentCol - 1) == 0) { setCellValue(row, currentCol - 1, currentValue); setCellValue(row, currentCol, 0); currentCol--; boardChanged = true; }
|
|
||||||
if (currentCol > 0 && getCellValue(row, currentCol - 1) == currentValue && !hasMerged[currentCol - 1]) {
|
|
||||||
int newValue = getCellValue(row, currentCol - 1) * 2; setCellValue(row, currentCol - 1, newValue); setCellValue(row, currentCol, 0);
|
|
||||||
currentScore += newValue; hasMerged[currentCol - 1] = true; boardChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tente de déplacer et fusionner les tuiles vers la DROITE.
|
* Tente de déplacer et fusionner les tuiles vers la DROITE.
|
||||||
* @return true si changement, false sinon.
|
* @return true si le plateau a été modifié, false sinon.
|
||||||
*/
|
*/
|
||||||
public boolean pushRight() {
|
public boolean pushRight() { return processMove(MoveDirection.RIGHT); }
|
||||||
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
/** Énumération interne pour clarifier le traitement des mouvements. */
|
||||||
hasMerged = new boolean[BOARD_SIZE];
|
private enum MoveDirection { UP, DOWN, LEFT, RIGHT }
|
||||||
for (int col = BOARD_SIZE - 2; col >= 0; col--) {
|
|
||||||
|
/**
|
||||||
|
* Méthode générique pour traiter un mouvement (déplacement et fusion) dans une direction donnée.
|
||||||
|
* Contient la logique de base partagée par les méthodes pushX.
|
||||||
|
* @param direction La direction du mouvement.
|
||||||
|
* @return true si le plateau a été modifié, false sinon.
|
||||||
|
*/
|
||||||
|
private boolean processMove(MoveDirection direction) {
|
||||||
|
boolean boardChanged = false;
|
||||||
|
// Itère sur l'axe perpendiculaire au mouvement
|
||||||
|
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||||
|
boolean[] hasMerged = new boolean[BOARD_SIZE]; // Pour éviter double fusion sur l'axe de mouvement
|
||||||
|
// Itère sur l'axe du mouvement, dans le bon sens
|
||||||
|
int start = (direction == MoveDirection.DOWN || direction == MoveDirection.RIGHT) ? BOARD_SIZE - 2 : 1;
|
||||||
|
int end = (direction == MoveDirection.DOWN || direction == MoveDirection.RIGHT) ? -1 : BOARD_SIZE;
|
||||||
|
int step = (direction == MoveDirection.DOWN || direction == MoveDirection.RIGHT) ? -1 : 1;
|
||||||
|
|
||||||
|
for (int j = start; j != end; j += step) {
|
||||||
|
int row = (direction == MoveDirection.UP || direction == MoveDirection.DOWN) ? j : i;
|
||||||
|
int col = (direction == MoveDirection.LEFT || direction == MoveDirection.RIGHT) ? j : i;
|
||||||
|
|
||||||
if (getCellValue(row, col) != 0) {
|
if (getCellValue(row, col) != 0) {
|
||||||
int currentValue = getCellValue(row, col); int currentCol = col;
|
int currentValue = getCellValue(row, col);
|
||||||
while (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == 0) { setCellValue(row, currentCol + 1, currentValue); setCellValue(row, currentCol, 0); currentCol++; boardChanged = true; }
|
int currentRow = row;
|
||||||
if (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == currentValue && !hasMerged[currentCol + 1]) {
|
int currentCol = col;
|
||||||
int newValue = getCellValue(row, currentCol + 1) * 2; setCellValue(row, currentCol + 1, newValue); setCellValue(row, currentCol, 0);
|
|
||||||
currentScore += newValue; hasMerged[currentCol + 1] = true; boardChanged = true;
|
// Calcule la position cible après déplacement dans les cases vides
|
||||||
|
int targetRow = currentRow;
|
||||||
|
int targetCol = currentCol;
|
||||||
|
int nextRow = targetRow + ((direction == MoveDirection.UP) ? -1 : (direction == MoveDirection.DOWN) ? 1 : 0);
|
||||||
|
int nextCol = targetCol + ((direction == MoveDirection.LEFT) ? -1 : (direction == MoveDirection.RIGHT) ? 1 : 0);
|
||||||
|
|
||||||
|
while (isIndexValid(nextRow, nextCol) && getCellValue(nextRow, nextCol) == 0) {
|
||||||
|
targetRow = nextRow;
|
||||||
|
targetCol = nextCol;
|
||||||
|
nextRow = targetRow + ((direction == MoveDirection.UP) ? -1 : (direction == MoveDirection.DOWN) ? 1 : 0);
|
||||||
|
nextCol = targetCol + ((direction == MoveDirection.LEFT) ? -1 : (direction == MoveDirection.RIGHT) ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Déplace la tuile si sa position cible est différente
|
||||||
|
if (targetRow != currentRow || targetCol != currentCol) {
|
||||||
|
setCellValue(targetRow, targetCol, currentValue);
|
||||||
|
setCellValue(currentRow, currentCol, 0);
|
||||||
|
boardChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifie la fusion potentielle avec la case suivante dans la direction du mouvement
|
||||||
|
int mergeTargetRow = targetRow + ((direction == MoveDirection.UP) ? -1 : (direction == MoveDirection.DOWN) ? 1 : 0);
|
||||||
|
int mergeTargetCol = targetCol + ((direction == MoveDirection.LEFT) ? -1 : (direction == MoveDirection.RIGHT) ? 1 : 0);
|
||||||
|
int mergeIndex = (direction == MoveDirection.UP || direction == MoveDirection.DOWN) ? mergeTargetRow : mergeTargetCol;
|
||||||
|
|
||||||
|
if (isIndexValid(mergeTargetRow, mergeTargetCol) &&
|
||||||
|
getCellValue(mergeTargetRow, mergeTargetCol) == currentValue &&
|
||||||
|
!hasMerged[mergeIndex])
|
||||||
|
{
|
||||||
|
int newValue = currentValue * 2;
|
||||||
|
setCellValue(mergeTargetRow, mergeTargetCol, newValue);
|
||||||
|
setCellValue(targetRow, targetCol, 0); // La tuile qui fusionne disparaît
|
||||||
|
currentScore += newValue;
|
||||||
|
hasMerged[mergeIndex] = true;
|
||||||
|
boardChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
}
|
||||||
|
// Vérifie les conditions de fin après chaque type de mouvement complet
|
||||||
|
checkWinCondition();
|
||||||
|
checkGameOverCondition();
|
||||||
|
return boardChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sérialise l'état actuel du jeu (plateau et score courant) en une chaîne.
|
* Vérifie si les indices de ligne et colonne sont valides pour le plateau.
|
||||||
|
* @param row Ligne.
|
||||||
|
* @param col Colonne.
|
||||||
|
* @return true si les indices sont dans les limites [0, BOARD_SIZE-1].
|
||||||
|
*/
|
||||||
|
private boolean isIndexValid(int row, int col) {
|
||||||
|
return row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sérialise l'état essentiel du jeu (plateau et score courant) pour sauvegarde.
|
||||||
* Format: "val,val,...,val,score"
|
* Format: "val,val,...,val,score"
|
||||||
* @return Chaîne sérialisée.
|
* @return Chaîne représentant l'état du jeu.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
@ -271,53 +310,57 @@ public class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crée un nouvel objet Game à partir d'une chaîne sérialisée (plateau + score).
|
* Crée un objet Game à partir de sa représentation sérialisée (plateau + score).
|
||||||
* @param serializedState Chaîne générée par toString().
|
* Le meilleur score doit être défini séparément après la création.
|
||||||
* @return Nouvel objet Game, ou null si la désérialisation échoue.
|
* @param serializedState La chaîne issue de {@link #toString()}.
|
||||||
|
* @return Une nouvelle instance de Game, ou null en cas d'erreur de format.
|
||||||
*/
|
*/
|
||||||
public static Game deserialize(String serializedState) {
|
public static Game deserialize(String serializedState) {
|
||||||
if (serializedState == null || serializedState.isEmpty()) return null;
|
if (serializedState == null || serializedState.isEmpty()) return null;
|
||||||
String[] values = serializedState.split(",");
|
String[] values = serializedState.split(",");
|
||||||
if (values.length != (BOARD_SIZE * BOARD_SIZE + 1)) return null;
|
if (values.length != (BOARD_SIZE * BOARD_SIZE + 1)) return null; // +1 pour le score
|
||||||
int[][] newBoard = new int[BOARD_SIZE][BOARD_SIZE]; int index = 0;
|
int[][] newBoard = new int[BOARD_SIZE][BOARD_SIZE]; int index = 0;
|
||||||
try {
|
try {
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) { newBoard[row][col] = Integer.parseInt(values[index++]); }
|
for (int col = 0; col < BOARD_SIZE; col++) { newBoard[row][col] = Integer.parseInt(values[index++]); }
|
||||||
}
|
}
|
||||||
int score = Integer.parseInt(values[index]);
|
int score = Integer.parseInt(values[index]); // Le dernier élément est le score
|
||||||
return new Game(newBoard, score);
|
return new Game(newBoard, score);
|
||||||
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { return null; }
|
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifie si une tuile >= 2048 existe sur le plateau et met à jour l'état `gameWon`.
|
* Vérifie si la condition de victoire (une tuile >= 2048) est atteinte.
|
||||||
|
* Met à jour l'état interne `gameWon`.
|
||||||
*/
|
*/
|
||||||
private void checkWinCondition() {
|
private void checkWinCondition() {
|
||||||
if (!gameWon) { // Optimisation: inutile de revérifier si déjà gagné
|
if (!gameWon) {
|
||||||
for(int r=0; r<BOARD_SIZE; r++) for(int c=0; c<BOARD_SIZE; c++) if(getCellValue(r,c)>=2048) { setGameWon(true); return; }
|
for (int r=0; r<BOARD_SIZE; r++) for (int c=0; c<BOARD_SIZE; c++) if (getCellValue(r,c) >= 2048) {
|
||||||
|
setGameWon(true); return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifie s'il reste des mouvements possibles (case vide ou fusion adjacente).
|
* Vérifie si la condition de fin de partie est atteinte (plateau plein ET aucun mouvement possible).
|
||||||
* Met à jour l'état `gameOver`.
|
* Met à jour l'état interne `gameOver`.
|
||||||
*/
|
*/
|
||||||
private void checkGameOverCondition() {
|
private void checkGameOverCondition() {
|
||||||
if (hasEmptyCell()) { setGameOver(false); return; } // Si case vide, pas game over
|
if (hasEmptyCell()) { setGameOver(false); return; } // Pas game over si case vide
|
||||||
// Vérifie fusions adjacentes possibles
|
// Vérifie s'il existe au moins une fusion possible
|
||||||
for (int r=0; r<BOARD_SIZE; r++) for (int c=0; c<BOARD_SIZE; c++) {
|
for (int r=0; r<BOARD_SIZE; r++) for (int c=0; c<BOARD_SIZE; c++) {
|
||||||
int current = getCellValue(r,c);
|
int current = getCellValue(r,c);
|
||||||
// Vérifie voisins (haut, bas, gauche, droite)
|
|
||||||
if ((r>0 && getCellValue(r-1,c)==current) || (r<BOARD_SIZE-1 && getCellValue(r+1,c)==current) ||
|
if ((r>0 && getCellValue(r-1,c)==current) || (r<BOARD_SIZE-1 && getCellValue(r+1,c)==current) ||
|
||||||
(c>0 && getCellValue(r,c-1)==current) || (c<BOARD_SIZE-1 && getCellValue(r,c+1)==current)) {
|
(c>0 && getCellValue(r,c-1)==current) || (c<BOARD_SIZE-1 && getCellValue(r,c+1)==current)) {
|
||||||
setGameOver(false); return; // Fusion possible, pas game over
|
setGameOver(false); return; // Fusion possible -> pas game over
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setGameOver(true); // Aucune case vide et aucune fusion -> game over
|
setGameOver(true); // Aucune case vide et aucune fusion -> game over
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true s'il y a au moins une case vide sur le plateau, false sinon.
|
* Vérifie si le plateau contient au moins une case vide (valeur 0).
|
||||||
|
* @return true si une case vide existe, false sinon.
|
||||||
*/
|
*/
|
||||||
private boolean hasEmptyCell() {
|
private boolean hasEmptyCell() {
|
||||||
for (int r=0; r<BOARD_SIZE; r++) for (int c=0; c<BOARD_SIZE; c++) if (getCellValue(r,c)==0) return true;
|
for (int r=0; r<BOARD_SIZE; r++) for (int c=0; c<BOARD_SIZE; c++) if (getCellValue(r,c)==0) return true;
|
||||||
@ -325,8 +368,8 @@ public class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trouve et retourne la valeur de la tuile la plus élevée actuellement sur le plateau.
|
* Retourne la valeur de la plus haute tuile présente sur le plateau.
|
||||||
* @return La valeur maximale trouvée.
|
* @return La valeur maximale trouvée, ou 0 si le plateau est vide.
|
||||||
*/
|
*/
|
||||||
public int getHighestTileValue() {
|
public int getHighestTileValue() {
|
||||||
int maxTile = 0;
|
int maxTile = 0;
|
||||||
|
@ -1,30 +1,22 @@
|
|||||||
// Fichier GameStats.java
|
// Fichier GameStats.java
|
||||||
// Gère le stockage, le chargement et la mise à jour des statistiques du jeu 2048.
|
/**
|
||||||
/*
|
* Gère la collecte, la persistance (via SharedPreferences) et l'accès
|
||||||
Fonctions principales :
|
* aux statistiques du jeu 2048, pour les modes solo et multijoueur (si applicable).
|
||||||
- Contient tous les champs relatifs aux statistiques (solo et multijoueur).
|
|
||||||
- loadStats(), saveStats() : Charge et sauvegarde les statistiques via SharedPreferences.
|
|
||||||
- Méthodes pour mettre à jour les statistiques : startGame(), recordMove(), recordMerge(), recordWin(), recordLoss(), endGame().
|
|
||||||
- Getters pour accéder aux valeurs des statistiques.
|
|
||||||
- formatTime() : Méthode utilitaire pour formater le temps.
|
|
||||||
|
|
||||||
Relations :
|
|
||||||
- MainActivity : Crée une instance de GameStats, l'utilise pour charger/sauvegarder les stats et met à jour les stats via ses méthodes. Récupère les valeurs via les getters pour l'affichage.
|
|
||||||
- SharedPreferences : Utilisé pour la persistance des statistiques.
|
|
||||||
*/
|
*/
|
||||||
package legion.muyue.best2048;
|
package legion.muyue.best2048;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class GameStats {
|
public class GameStats {
|
||||||
|
|
||||||
// Clés SharedPreferences (inchangées)
|
// --- Constantes pour SharedPreferences ---
|
||||||
private static final String PREFS_NAME = "Best2048_Prefs";
|
private static final String PREFS_NAME = "Best2048_Prefs";
|
||||||
private static final String HIGH_SCORE_KEY = "high_score";
|
private static final String HIGH_SCORE_KEY = "high_score"; // Clé partagée avec Game/MainActivity
|
||||||
|
// Clés spécifiques aux statistiques
|
||||||
private static final String STATS_TOTAL_GAMES_PLAYED = "totalGamesPlayed";
|
private static final String STATS_TOTAL_GAMES_PLAYED = "totalGamesPlayed";
|
||||||
// ... (autres clés inchangées) ...
|
|
||||||
private static final String STATS_TOTAL_GAMES_STARTED = "totalGamesStarted";
|
private static final String STATS_TOTAL_GAMES_STARTED = "totalGamesStarted";
|
||||||
private static final String STATS_TOTAL_MOVES = "totalMoves";
|
private static final String STATS_TOTAL_MOVES = "totalMoves";
|
||||||
private static final String STATS_TOTAL_PLAY_TIME_MS = "totalPlayTimeMs";
|
private static final String STATS_TOTAL_PLAY_TIME_MS = "totalPlayTimeMs";
|
||||||
@ -34,6 +26,7 @@ public class GameStats {
|
|||||||
private static final String STATS_PERFECT_GAMES = "perfectGames";
|
private static final String STATS_PERFECT_GAMES = "perfectGames";
|
||||||
private static final String STATS_BEST_WINNING_TIME_MS = "bestWinningTimeMs";
|
private static final String STATS_BEST_WINNING_TIME_MS = "bestWinningTimeMs";
|
||||||
private static final String STATS_WORST_WINNING_TIME_MS = "worstWinningTimeMs";
|
private static final String STATS_WORST_WINNING_TIME_MS = "worstWinningTimeMs";
|
||||||
|
// ... (autres clés stats) ...
|
||||||
private static final String STATS_MP_GAMES_WON = "multiplayerGamesWon";
|
private static final String STATS_MP_GAMES_WON = "multiplayerGamesWon";
|
||||||
private static final String STATS_MP_GAMES_PLAYED = "multiplayerGamesPlayed";
|
private static final String STATS_MP_GAMES_PLAYED = "multiplayerGamesPlayed";
|
||||||
private static final String STATS_MP_BEST_WINNING_STREAK = "multiplayerBestWinningStreak";
|
private static final String STATS_MP_BEST_WINNING_STREAK = "multiplayerBestWinningStreak";
|
||||||
@ -42,20 +35,27 @@ public class GameStats {
|
|||||||
private static final String STATS_MP_LOSSES = "totalMultiplayerLosses";
|
private static final String STATS_MP_LOSSES = "totalMultiplayerLosses";
|
||||||
private static final String STATS_MP_HIGH_SCORE = "multiplayerHighScore";
|
private static final String STATS_MP_HIGH_SCORE = "multiplayerHighScore";
|
||||||
|
|
||||||
// Champs statistiques (inchangés)
|
|
||||||
|
// --- Champs de Statistiques ---
|
||||||
|
// Générales & Solo
|
||||||
private int totalGamesPlayed;
|
private int totalGamesPlayed;
|
||||||
private int totalGamesStarted;
|
private int totalGamesStarted;
|
||||||
private int totalMoves;
|
private int totalMoves;
|
||||||
private int currentMoves;
|
|
||||||
private long totalPlayTimeMs;
|
private long totalPlayTimeMs;
|
||||||
private long currentGameStartTimeMs;
|
|
||||||
private int mergesThisGame;
|
|
||||||
private int totalMerges;
|
private int totalMerges;
|
||||||
private int highestTile;
|
private int highestTile;
|
||||||
private int numberOfTimesObjectiveReached;
|
private int numberOfTimesObjectiveReached; // Nombre de victoires (>= 2048)
|
||||||
private int perfectGames;
|
private int perfectGames; // Concept non défini ici
|
||||||
private long bestWinningTimeMs;
|
private long bestWinningTimeMs;
|
||||||
private long worstWinningTimeMs;
|
private long worstWinningTimeMs;
|
||||||
|
private int overallHighScore; // Meilleur score global
|
||||||
|
|
||||||
|
// Partie en cours (non persistées telles quelles)
|
||||||
|
private int currentMoves;
|
||||||
|
private long currentGameStartTimeMs;
|
||||||
|
private int mergesThisGame;
|
||||||
|
|
||||||
|
// Multijoueur
|
||||||
private int multiplayerGamesWon;
|
private int multiplayerGamesWon;
|
||||||
private int multiplayerGamesPlayed;
|
private int multiplayerGamesPlayed;
|
||||||
private int multiplayerBestWinningStreak;
|
private int multiplayerBestWinningStreak;
|
||||||
@ -63,36 +63,39 @@ public class GameStats {
|
|||||||
private long multiplayerTotalTimeMs;
|
private long multiplayerTotalTimeMs;
|
||||||
private int totalMultiplayerLosses;
|
private int totalMultiplayerLosses;
|
||||||
private int multiplayerHighestScore;
|
private int multiplayerHighestScore;
|
||||||
private int overallHighScore;
|
|
||||||
|
|
||||||
|
/** Contexte nécessaire pour accéder aux SharedPreferences. */
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur de GameStats.
|
* Constructeur. Initialise l'objet et charge immédiatement les statistiques
|
||||||
* Charge immédiatement les statistiques sauvegardées via SharedPreferences.
|
* depuis les SharedPreferences.
|
||||||
* @param context Le contexte de l'application (nécessaire pour SharedPreferences).
|
* @param context Contexte de l'application.
|
||||||
*/
|
*/
|
||||||
public GameStats(Context context) {
|
public GameStats(Context context) {
|
||||||
this.context = context;
|
this.context = context.getApplicationContext(); // Utilise le contexte applicatif
|
||||||
loadStats();
|
loadStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Persistance (SharedPreferences) ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Charge toutes les statistiques (générales et multijoueur) et le high score global
|
* Charge toutes les statistiques persistantes depuis les SharedPreferences.
|
||||||
* depuis les SharedPreferences.
|
* Appelé par le constructeur.
|
||||||
*/
|
*/
|
||||||
public void loadStats() {
|
public void loadStats() {
|
||||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
overallHighScore = prefs.getInt(HIGH_SCORE_KEY, 0);
|
overallHighScore = prefs.getInt(HIGH_SCORE_KEY, 0);
|
||||||
totalGamesPlayed = prefs.getInt(STATS_TOTAL_GAMES_PLAYED, 0);
|
totalGamesPlayed = prefs.getInt(STATS_TOTAL_GAMES_PLAYED, 0);
|
||||||
totalGamesStarted = prefs.getInt(STATS_TOTAL_GAMES_STARTED, 0);
|
totalGamesStarted = prefs.getInt(STATS_TOTAL_GAMES_STARTED, 0);
|
||||||
|
// ... (chargement de toutes les autres clés persistantes) ...
|
||||||
totalMoves = prefs.getInt(STATS_TOTAL_MOVES, 0);
|
totalMoves = prefs.getInt(STATS_TOTAL_MOVES, 0);
|
||||||
totalPlayTimeMs = prefs.getLong(STATS_TOTAL_PLAY_TIME_MS, 0);
|
totalPlayTimeMs = prefs.getLong(STATS_TOTAL_PLAY_TIME_MS, 0);
|
||||||
totalMerges = prefs.getInt(STATS_TOTAL_MERGES, 0);
|
totalMerges = prefs.getInt(STATS_TOTAL_MERGES, 0);
|
||||||
highestTile = prefs.getInt(STATS_HIGHEST_TILE, 0);
|
highestTile = prefs.getInt(STATS_HIGHEST_TILE, 0);
|
||||||
numberOfTimesObjectiveReached = prefs.getInt(STATS_OBJECTIVE_REACHED_COUNT, 0);
|
numberOfTimesObjectiveReached = prefs.getInt(STATS_OBJECTIVE_REACHED_COUNT, 0);
|
||||||
perfectGames = prefs.getInt(STATS_PERFECT_GAMES, 0);
|
perfectGames = prefs.getInt(STATS_PERFECT_GAMES, 0);
|
||||||
bestWinningTimeMs = prefs.getLong(STATS_BEST_WINNING_TIME_MS, Long.MAX_VALUE);
|
bestWinningTimeMs = prefs.getLong(STATS_BEST_WINNING_TIME_MS, Long.MAX_VALUE); // MAX_VALUE comme défaut pour 'best'
|
||||||
worstWinningTimeMs = prefs.getLong(STATS_WORST_WINNING_TIME_MS, 0);
|
worstWinningTimeMs = prefs.getLong(STATS_WORST_WINNING_TIME_MS, 0);
|
||||||
multiplayerGamesWon = prefs.getInt(STATS_MP_GAMES_WON, 0);
|
multiplayerGamesWon = prefs.getInt(STATS_MP_GAMES_WON, 0);
|
||||||
multiplayerGamesPlayed = prefs.getInt(STATS_MP_GAMES_PLAYED, 0);
|
multiplayerGamesPlayed = prefs.getInt(STATS_MP_GAMES_PLAYED, 0);
|
||||||
@ -104,15 +107,16 @@ public class GameStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sauvegarde toutes les statistiques (générales et multijoueur) et le high score global
|
* Sauvegarde toutes les statistiques persistantes dans les SharedPreferences.
|
||||||
* dans les SharedPreferences.
|
* Appelé typiquement dans `onPause` de l'activité.
|
||||||
*/
|
*/
|
||||||
public void saveStats() {
|
public void saveStats() {
|
||||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
editor.putInt(HIGH_SCORE_KEY, overallHighScore);
|
editor.putInt(HIGH_SCORE_KEY, overallHighScore); // Sauvegarde le HS global
|
||||||
editor.putInt(STATS_TOTAL_GAMES_PLAYED, totalGamesPlayed);
|
editor.putInt(STATS_TOTAL_GAMES_PLAYED, totalGamesPlayed);
|
||||||
editor.putInt(STATS_TOTAL_GAMES_STARTED, totalGamesStarted);
|
editor.putInt(STATS_TOTAL_GAMES_STARTED, totalGamesStarted);
|
||||||
|
// ... (sauvegarde de toutes les autres clés persistantes) ...
|
||||||
editor.putInt(STATS_TOTAL_MOVES, totalMoves);
|
editor.putInt(STATS_TOTAL_MOVES, totalMoves);
|
||||||
editor.putLong(STATS_TOTAL_PLAY_TIME_MS, totalPlayTimeMs);
|
editor.putLong(STATS_TOTAL_PLAY_TIME_MS, totalPlayTimeMs);
|
||||||
editor.putInt(STATS_TOTAL_MERGES, totalMerges);
|
editor.putInt(STATS_TOTAL_MERGES, totalMerges);
|
||||||
@ -128,12 +132,15 @@ public class GameStats {
|
|||||||
editor.putLong(STATS_MP_TOTAL_TIME_MS, multiplayerTotalTimeMs);
|
editor.putLong(STATS_MP_TOTAL_TIME_MS, multiplayerTotalTimeMs);
|
||||||
editor.putInt(STATS_MP_LOSSES, totalMultiplayerLosses);
|
editor.putInt(STATS_MP_LOSSES, totalMultiplayerLosses);
|
||||||
editor.putInt(STATS_MP_HIGH_SCORE, multiplayerHighestScore);
|
editor.putInt(STATS_MP_HIGH_SCORE, multiplayerHighestScore);
|
||||||
editor.apply();
|
|
||||||
|
editor.apply(); // Applique les changements de manière asynchrone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Méthodes de Mise à Jour des Statistiques ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Met à jour les statistiques lors du démarrage d'une nouvelle partie.
|
* Doit être appelée au début de chaque nouvelle partie.
|
||||||
* Incrémente le nombre de parties démarrées et réinitialise les compteurs de la partie en cours.
|
* Incrémente le compteur de parties démarrées et réinitialise les stats de la partie en cours.
|
||||||
*/
|
*/
|
||||||
public void startGame() {
|
public void startGame() {
|
||||||
totalGamesStarted++;
|
totalGamesStarted++;
|
||||||
@ -143,8 +150,7 @@ public class GameStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enregistre un mouvement effectué pendant la partie en cours.
|
* Enregistre un mouvement réussi (qui a modifié le plateau).
|
||||||
* Incrémente le compteur de mouvements de la partie et le compteur total.
|
|
||||||
*/
|
*/
|
||||||
public void recordMove() {
|
public void recordMove() {
|
||||||
currentMoves++;
|
currentMoves++;
|
||||||
@ -152,8 +158,8 @@ public class GameStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enregistre une ou plusieurs fusions survenues pendant la partie en cours.
|
* Enregistre une ou plusieurs fusions survenues lors d'un mouvement.
|
||||||
* @param numberOfMerges Le nombre de fusions à ajouter (idéalement précis).
|
* @param numberOfMerges Le nombre de fusions (si connu, sinon 1 par défaut).
|
||||||
*/
|
*/
|
||||||
public void recordMerge(int numberOfMerges) {
|
public void recordMerge(int numberOfMerges) {
|
||||||
if (numberOfMerges > 0) {
|
if (numberOfMerges > 0) {
|
||||||
@ -163,8 +169,8 @@ public class GameStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Met à jour la statistique de la plus haute tuile atteinte si la valeur fournie est supérieure.
|
* Met à jour la statistique de la plus haute tuile atteinte globalement.
|
||||||
* @param tileValue La valeur de la tuile candidate.
|
* @param tileValue La valeur de la tuile la plus haute de la partie en cours.
|
||||||
*/
|
*/
|
||||||
public void updateHighestTile(int tileValue) {
|
public void updateHighestTile(int tileValue) {
|
||||||
if (tileValue > this.highestTile) {
|
if (tileValue > this.highestTile) {
|
||||||
@ -173,27 +179,27 @@ public class GameStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enregistre une victoire et met à jour les statistiques associées (temps, nombre de victoires).
|
* Enregistre une victoire et met à jour les temps associés.
|
||||||
* @param timeTakenMs Le temps mis pour gagner cette partie en millisecondes.
|
* @param timeTakenMs Temps écoulé pour cette partie gagnante.
|
||||||
*/
|
*/
|
||||||
public void recordWin(long timeTakenMs) {
|
public void recordWin(long timeTakenMs) {
|
||||||
numberOfTimesObjectiveReached++;
|
numberOfTimesObjectiveReached++;
|
||||||
endGame(timeTakenMs); // Finalise les stats de la partie
|
|
||||||
|
|
||||||
if (timeTakenMs < bestWinningTimeMs) { bestWinningTimeMs = timeTakenMs; }
|
if (timeTakenMs < bestWinningTimeMs) { bestWinningTimeMs = timeTakenMs; }
|
||||||
if (timeTakenMs > worstWinningTimeMs) { worstWinningTimeMs = timeTakenMs; }
|
if (timeTakenMs > worstWinningTimeMs) { worstWinningTimeMs = timeTakenMs; }
|
||||||
|
endGame(timeTakenMs); // Finalise aussi le temps total et parties jouées
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enregistre une défaite et finalise les statistiques de la partie.
|
* Enregistre une défaite.
|
||||||
*/
|
*/
|
||||||
public void recordLoss() {
|
public void recordLoss() {
|
||||||
|
// Calcule le temps écoulé avant de finaliser
|
||||||
endGame(System.currentTimeMillis() - currentGameStartTimeMs);
|
endGame(System.currentTimeMillis() - currentGameStartTimeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finalise les statistiques à la fin d'une partie (incrémente parties jouées, ajoute temps de jeu).
|
* Finalise les statistiques générales à la fin d'une partie (victoire ou défaite).
|
||||||
* @param timeTakenMs Le temps total de la partie qui vient de se terminer.
|
* @param timeTakenMs Temps total de la partie terminée.
|
||||||
*/
|
*/
|
||||||
public void endGame(long timeTakenMs) {
|
public void endGame(long timeTakenMs) {
|
||||||
totalGamesPlayed++;
|
totalGamesPlayed++;
|
||||||
@ -201,8 +207,9 @@ public class GameStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajoute une durée au temps de jeu total cumulé.
|
* Ajoute une durée (en ms) au temps de jeu total enregistré.
|
||||||
* @param durationMs Durée à ajouter en millisecondes.
|
* Typiquement appelé dans `onPause`.
|
||||||
|
* @param durationMs Durée à ajouter.
|
||||||
*/
|
*/
|
||||||
public void addPlayTime(long durationMs) {
|
public void addPlayTime(long durationMs) {
|
||||||
if (durationMs > 0) {
|
if (durationMs > 0) {
|
||||||
@ -210,13 +217,13 @@ public class GameStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Getters ---
|
// --- Getters pour l'affichage ---
|
||||||
public int getTotalGamesPlayed() { return totalGamesPlayed; }
|
public int getTotalGamesPlayed() { return totalGamesPlayed; }
|
||||||
public int getTotalGamesStarted() { return totalGamesStarted; }
|
public int getTotalGamesStarted() { return totalGamesStarted; }
|
||||||
public int getTotalMoves() { return totalMoves; }
|
public int getTotalMoves() { return totalMoves; }
|
||||||
public int getCurrentMoves() { return currentMoves; }
|
public int getCurrentMoves() { return currentMoves; }
|
||||||
public long getTotalPlayTimeMs() { return totalPlayTimeMs; }
|
public long getTotalPlayTimeMs() { return totalPlayTimeMs; }
|
||||||
public long getCurrentGameStartTimeMs() { return currentGameStartTimeMs; }
|
public long getCurrentGameStartTimeMs() { return currentGameStartTimeMs; } // Utile pour calcul durée en cours
|
||||||
public int getMergesThisGame() { return mergesThisGame; }
|
public int getMergesThisGame() { return mergesThisGame; }
|
||||||
public int getTotalMerges() { return totalMerges; }
|
public int getTotalMerges() { return totalMerges; }
|
||||||
public int getHighestTile() { return highestTile; }
|
public int getHighestTile() { return highestTile; }
|
||||||
@ -224,6 +231,8 @@ public class GameStats {
|
|||||||
public int getPerfectGames() { return perfectGames; }
|
public int getPerfectGames() { return perfectGames; }
|
||||||
public long getBestWinningTimeMs() { return bestWinningTimeMs; }
|
public long getBestWinningTimeMs() { return bestWinningTimeMs; }
|
||||||
public long getWorstWinningTimeMs() { return worstWinningTimeMs; }
|
public long getWorstWinningTimeMs() { return worstWinningTimeMs; }
|
||||||
|
public int getOverallHighScore() { return overallHighScore; } // Getter pour HS global
|
||||||
|
// Getters Multiplayer
|
||||||
public int getMultiplayerGamesWon() { return multiplayerGamesWon; }
|
public int getMultiplayerGamesWon() { return multiplayerGamesWon; }
|
||||||
public int getMultiplayerGamesPlayed() { return multiplayerGamesPlayed; }
|
public int getMultiplayerGamesPlayed() { return multiplayerGamesPlayed; }
|
||||||
public int getMultiplayerBestWinningStreak() { return multiplayerBestWinningStreak; }
|
public int getMultiplayerBestWinningStreak() { return multiplayerBestWinningStreak; }
|
||||||
@ -231,32 +240,25 @@ public class GameStats {
|
|||||||
public long getMultiplayerTotalTimeMs() { return multiplayerTotalTimeMs; }
|
public long getMultiplayerTotalTimeMs() { return multiplayerTotalTimeMs; }
|
||||||
public int getTotalMultiplayerLosses() { return totalMultiplayerLosses; }
|
public int getTotalMultiplayerLosses() { return totalMultiplayerLosses; }
|
||||||
public int getMultiplayerHighestScore() { return multiplayerHighestScore; }
|
public int getMultiplayerHighestScore() { return multiplayerHighestScore; }
|
||||||
public int getOverallHighScore() { return overallHighScore; } // Getter pour le HS global
|
|
||||||
|
|
||||||
// --- Setters ---
|
// --- Setters ---
|
||||||
/**
|
/** Met à jour la valeur interne du high score global. */
|
||||||
* Met à jour la valeur interne du meilleur score global.
|
|
||||||
* Appelé par MainActivity pour synchroniser le high score lu depuis les préférences.
|
|
||||||
* @param highScore Le meilleur score lu.
|
|
||||||
*/
|
|
||||||
public void setHighestScore(int highScore) {
|
public void setHighestScore(int highScore) {
|
||||||
// On met à jour seulement si la valeur externe est supérieure,
|
// Met à jour si la nouvelle valeur est meilleure
|
||||||
// car GameStats gère aussi la mise à jour via les scores des parties.
|
if (highScore > this.overallHighScore) {
|
||||||
// Ou plus simplement, on fait confiance à la valeur lue par MainActivity.
|
|
||||||
this.overallHighScore = highScore;
|
this.overallHighScore = highScore;
|
||||||
|
// La sauvegarde se fait via saveStats() globalement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Définit le timestamp de démarrage pour la partie en cours.
|
|
||||||
* @param timeMs Timestamp en millisecondes.
|
|
||||||
*/
|
|
||||||
public void setCurrentGameStartTimeMs(long timeMs) {
|
|
||||||
this.currentGameStartTimeMs = timeMs;
|
|
||||||
}
|
}
|
||||||
|
/** Définit le timestamp de début de la partie en cours. */
|
||||||
|
public void setCurrentGameStartTimeMs(long timeMs) { this.currentGameStartTimeMs = timeMs; }
|
||||||
|
|
||||||
// --- Méthodes calculées ---
|
// --- Méthodes Calculées ---
|
||||||
|
/** @return Temps moyen par partie terminée en millisecondes. */
|
||||||
public long getAverageGameTimeMs() { return (totalGamesPlayed > 0) ? totalPlayTimeMs / totalGamesPlayed : 0; }
|
public long getAverageGameTimeMs() { return (totalGamesPlayed > 0) ? totalPlayTimeMs / totalGamesPlayed : 0; }
|
||||||
|
/** @return Score moyen par partie multijoueur terminée. */
|
||||||
public int getMultiplayerAverageScore() { return (multiplayerGamesPlayed > 0) ? (int)(multiplayerTotalScore / multiplayerGamesPlayed) : 0; }
|
public int getMultiplayerAverageScore() { return (multiplayerGamesPlayed > 0) ? (int)(multiplayerTotalScore / multiplayerGamesPlayed) : 0; }
|
||||||
|
/** @return Temps moyen par partie multijoueur terminée en millisecondes. */
|
||||||
public long getMultiplayerAverageTimeMs() { return (multiplayerGamesPlayed > 0) ? multiplayerTotalTimeMs / multiplayerGamesPlayed : 0; }
|
public long getMultiplayerAverageTimeMs() { return (multiplayerGamesPlayed > 0) ? multiplayerTotalTimeMs / multiplayerGamesPlayed : 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -264,6 +266,7 @@ public class GameStats {
|
|||||||
* @param milliseconds Durée en millisecondes.
|
* @param milliseconds Durée en millisecondes.
|
||||||
* @return Chaîne de temps formatée.
|
* @return Chaîne de temps formatée.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
public static String formatTime(long milliseconds) {
|
public static String formatTime(long milliseconds) {
|
||||||
long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
|
long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
|
||||||
long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
|
long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
|
||||||
|
@ -1,32 +1,14 @@
|
|||||||
// Fichier MainActivity.java
|
// Fichier MainActivity.java
|
||||||
// Activité principale de l'application 2048, gère l'interface utilisateur et la coordination du jeu.
|
/**
|
||||||
/*
|
* Activité principale de l'application 2048.
|
||||||
Fonctions principales :
|
* Gère l'interface utilisateur (plateau, scores, boutons), coordonne les interactions
|
||||||
- findViews() : Récupère les références des éléments UI du layout XML.
|
* avec la logique du jeu (classe Game) et la gestion des statistiques (classe GameStats).
|
||||||
- initializeGameAndStats() : Crée les instances de Game et GameStats, charge les données sauvegardées.
|
* Gère également le cycle de vie de l'application et la persistance de l'état du jeu.
|
||||||
- setupListeners() : Attache les listeners aux boutons et au plateau de jeu (pour les swipes).
|
|
||||||
- updateUI(), updateBoard(), updateScores() : Met à jour l'affichage du jeu (plateau, scores).
|
|
||||||
- setTileStyle() : Applique le style visuel à une tuile individuelle.
|
|
||||||
- handleSwipe() : Traite un geste de swipe, appelle la logique de Game, met à jour les stats et l'UI.
|
|
||||||
- startNewGame() : Initialise une nouvelle partie.
|
|
||||||
- showRestartConfirmationDialog(), showGameWonDialog(), showGameOverDialog(), showMenu(), showMultiplayerScreen() : Affiche diverses boîtes de dialogue.
|
|
||||||
- toggleStatistics(), updateStatisticsTextViews() : Gère l'affichage et la mise à jour du panneau de statistiques (via ViewStub).
|
|
||||||
- Cycle de vie : onCreate(), onResume(), onPause() pour initialiser, reprendre le timer, et sauvegarder l'état/stats.
|
|
||||||
- Persistance : Utilise SharedPreferences pour sauvegarder/charger l'état du jeu (via Game.toString/deserialize) et le meilleur score. Les stats détaillées sont gérées par GameStats.
|
|
||||||
|
|
||||||
Relations :
|
|
||||||
- Game : Instance contenant la logique pure du jeu (plateau, score, mouvements).
|
|
||||||
- GameStats : Instance contenant la logique et les données des statistiques.
|
|
||||||
- OnSwipeTouchListener : Détecte les swipes sur le plateau.
|
|
||||||
- Layout XML (activity_main.xml, stats_layout.xml, etc.) : Définit la structure de l'UI.
|
|
||||||
- Ressources (strings, colors, dimens, styles, drawables) : Utilisées pour l'apparence de l'UI.
|
|
||||||
- SharedPreferences : Pour la sauvegarde des données persistantes.
|
|
||||||
*/
|
*/
|
||||||
package legion.muyue.best2048;
|
package legion.muyue.best2048;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
@ -46,7 +28,7 @@ import android.widget.Button;
|
|||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
// --- UI Elements ---
|
|
||||||
private GridLayout boardGridLayout;
|
private GridLayout boardGridLayout;
|
||||||
private TextView currentScoreTextView;
|
private TextView currentScoreTextView;
|
||||||
private TextView highestScoreTextView;
|
private TextView highestScoreTextView;
|
||||||
@ -55,25 +37,25 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private Button statisticsButton;
|
private Button statisticsButton;
|
||||||
private Button menuButton;
|
private Button menuButton;
|
||||||
private ViewStub statisticsViewStub;
|
private ViewStub statisticsViewStub;
|
||||||
private View inflatedStatsView; // Référence à la vue des stats une fois gonflée
|
private View inflatedStatsView;
|
||||||
|
|
||||||
|
|
||||||
// --- Game Logic & Stats ---
|
|
||||||
private Game game;
|
private Game game;
|
||||||
private GameStats gameStats; // Instance pour gérer les stats
|
private GameStats gameStats;
|
||||||
private static final int BOARD_SIZE = 4;
|
private static final int BOARD_SIZE = 4;
|
||||||
|
|
||||||
// --- State Management ---
|
|
||||||
private boolean statisticsVisible = false;
|
|
||||||
private enum GameFlowState { PLAYING, WON_DIALOG_SHOWN, GAME_OVER } // Nouvel état de jeu
|
|
||||||
private GameFlowState currentGameState = GameFlowState.PLAYING; // Initialisation
|
|
||||||
|
|
||||||
// --- Preferences ---
|
private boolean statisticsVisible = false;
|
||||||
|
private enum GameFlowState { PLAYING, WON_DIALOG_SHOWN, GAME_OVER }
|
||||||
|
private GameFlowState currentGameState = GameFlowState.PLAYING;
|
||||||
|
|
||||||
|
|
||||||
private SharedPreferences preferences;
|
private SharedPreferences preferences;
|
||||||
private static final String PREFS_NAME = "Best2048_Prefs";
|
private static final String PREFS_NAME = "Best2048_Prefs";
|
||||||
private static final String HIGH_SCORE_KEY = "high_score";
|
private static final String HIGH_SCORE_KEY = "high_score";
|
||||||
private static final String GAME_STATE_KEY = "game_state";
|
private static final String GAME_STATE_KEY = "game_state";
|
||||||
|
|
||||||
// --- Activity Lifecycle ---
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -81,26 +63,25 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
findViews(); // Récupère les vues
|
findViews();
|
||||||
initializeGameAndStats(); // Initialise Game, GameStats et charge les données
|
initializeGameAndStats();
|
||||||
setupListeners(); // Attache les listeners
|
setupListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
// Redémarre le chrono pour la partie en cours SI elle n'est pas finie
|
|
||||||
if (game != null && gameStats != null && !game.isGameOver() && !game.isGameWon()) {
|
if (game != null && gameStats != null && !game.isGameOver() && !game.isGameWon()) {
|
||||||
gameStats.setCurrentGameStartTimeMs(System.currentTimeMillis()); // Utilise setter de GameStats
|
gameStats.setCurrentGameStartTimeMs(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
// Gère le réaffichage potentiel des stats si l'activité reprend
|
|
||||||
if (statisticsVisible) {
|
if (statisticsVisible) {
|
||||||
if (inflatedStatsView != null) { // Si déjà gonflé
|
if (inflatedStatsView != null) {
|
||||||
updateStatisticsTextViews(); // Met à jour les données affichées
|
updateStatisticsTextViews();
|
||||||
inflatedStatsView.setVisibility(View.VISIBLE);
|
inflatedStatsView.setVisibility(View.VISIBLE);
|
||||||
multiplayerButton.setVisibility(View.GONE);
|
multiplayerButton.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
// Si pas encore gonflé (cas rare mais possible), on le fait afficher
|
|
||||||
toggleStatistics();
|
toggleStatistics();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,18 +90,18 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
// Sauvegarde l'état et les stats si le jeu existe
|
|
||||||
if (game != null && gameStats != null) {
|
if (game != null && gameStats != null) {
|
||||||
// Met à jour le temps total SI la partie n'est pas terminée
|
|
||||||
if (!game.isGameOver() && !game.isGameWon()) {
|
if (!game.isGameOver() && !game.isGameWon()) {
|
||||||
gameStats.addPlayTime(System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs()); // Utilise méthode GameStats
|
gameStats.addPlayTime(System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs());
|
||||||
}
|
}
|
||||||
saveGame(); // Sauvegarde l'état du jeu (plateau + score courant) et le HS
|
saveGame();
|
||||||
gameStats.saveStats(); // Sauvegarde toutes les stats via GameStats
|
gameStats.saveStats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Initialisation ---
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les références des vues du layout principal via leur ID.
|
* Récupère les références des vues du layout principal via leur ID.
|
||||||
@ -144,20 +125,20 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private void initializeGameAndStats() {
|
private void initializeGameAndStats() {
|
||||||
preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
|
preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
|
||||||
gameStats = new GameStats(this);
|
gameStats = new GameStats(this);
|
||||||
loadGame(); // Charge jeu et met à jour high score
|
loadGame();
|
||||||
updateUI();
|
updateUI();
|
||||||
if (game == null) {
|
if (game == null) {
|
||||||
startNewGame(); // Assure une partie valide si chargement échoue
|
startNewGame();
|
||||||
} else {
|
} else {
|
||||||
// Détermine l'état initial basé sur le jeu chargé
|
|
||||||
if (game.isGameOver()) {
|
if (game.isGameOver()) {
|
||||||
currentGameState = GameFlowState.GAME_OVER;
|
currentGameState = GameFlowState.GAME_OVER;
|
||||||
} else if (game.isGameWon()) {
|
} else if (game.isGameWon()) {
|
||||||
// Si on charge une partie déjà gagnée, on considère qu'on a déjà vu la dialog
|
|
||||||
currentGameState = GameFlowState.WON_DIALOG_SHOWN;
|
currentGameState = GameFlowState.WON_DIALOG_SHOWN;
|
||||||
} else {
|
} else {
|
||||||
currentGameState = GameFlowState.PLAYING;
|
currentGameState = GameFlowState.PLAYING;
|
||||||
// Redémarre le timer dans onResume
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,20 +153,20 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
});
|
});
|
||||||
statisticsButton.setOnClickListener(v -> {
|
statisticsButton.setOnClickListener(v -> {
|
||||||
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
|
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
|
||||||
toggleStatistics(); // Affiche/masque les stats
|
toggleStatistics();
|
||||||
});
|
});
|
||||||
menuButton.setOnClickListener(v -> {
|
menuButton.setOnClickListener(v -> {
|
||||||
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
|
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
|
||||||
showMenu(); // Affiche dialogue placeholder
|
showMenu();
|
||||||
});
|
});
|
||||||
multiplayerButton.setOnClickListener(v -> {
|
multiplayerButton.setOnClickListener(v -> {
|
||||||
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
|
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
|
||||||
showMultiplayerScreen(); // Affiche dialogue placeholder
|
showMultiplayerScreen();
|
||||||
});
|
});
|
||||||
setupSwipeListener(); // Attache le listener de swipe
|
setupSwipeListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Mise à jour UI ---
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Met à jour complètement l'interface utilisateur (plateau et scores).
|
* Met à jour complètement l'interface utilisateur (plateau et scores).
|
||||||
@ -205,16 +186,16 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||||
TextView tileTextView = new TextView(this);
|
TextView tileTextView = new TextView(this);
|
||||||
int value = game.getCellValue(row, col);
|
int value = game.getCellValue(row, col);
|
||||||
setTileStyle(tileTextView, value); // Applique le style
|
setTileStyle(tileTextView, value);
|
||||||
// Définit les LayoutParams pour que la tuile remplisse la cellule du GridLayout
|
|
||||||
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||||||
params.width = 0; params.height = 0; // Poids gère la taille
|
params.width = 0; params.height = 0;
|
||||||
params.rowSpec = GridLayout.spec(row, 1f); // Prend 1 fraction de l'espace en hauteur
|
params.rowSpec = GridLayout.spec(row, 1f);
|
||||||
params.columnSpec = GridLayout.spec(col, 1f); // Prend 1 fraction de l'espace en largeur
|
params.columnSpec = GridLayout.spec(col, 1f);
|
||||||
int margin = (int) getResources().getDimension(R.dimen.tile_margin);
|
int margin = (int) getResources().getDimension(R.dimen.tile_margin);
|
||||||
params.setMargins(margin, margin, margin, margin); // Applique les marges
|
params.setMargins(margin, margin, margin, margin);
|
||||||
tileTextView.setLayoutParams(params);
|
tileTextView.setLayoutParams(params);
|
||||||
boardGridLayout.addView(tileTextView); // Ajoute la tuile au GridLayout
|
boardGridLayout.addView(tileTextView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,7 +214,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
* @param value La valeur numérique de la tuile (0 pour vide).
|
* @param value La valeur numérique de la tuile (0 pour vide).
|
||||||
*/
|
*/
|
||||||
private void setTileStyle(TextView tileTextView, int value) {
|
private void setTileStyle(TextView tileTextView, int value) {
|
||||||
// Code de styling (inchangé par rapport à la correction précédente)
|
|
||||||
tileTextView.setText(value > 0 ? String.valueOf(value) : "");
|
tileTextView.setText(value > 0 ? String.valueOf(value) : "");
|
||||||
tileTextView.setGravity(Gravity.CENTER);
|
tileTextView.setGravity(Gravity.CENTER);
|
||||||
tileTextView.setTypeface(null, android.graphics.Typeface.BOLD);
|
tileTextView.setTypeface(null, android.graphics.Typeface.BOLD);
|
||||||
@ -260,7 +241,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Gestion des Actions Utilisateur ---
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure le listener pour détecter les swipes sur le plateau de jeu.
|
* Configure le listener pour détecter les swipes sur le plateau de jeu.
|
||||||
@ -289,20 +270,19 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
* @param direction La direction du swipe détecté (UP, DOWN, LEFT, RIGHT).
|
* @param direction La direction du swipe détecté (UP, DOWN, LEFT, RIGHT).
|
||||||
*/
|
*/
|
||||||
private void handleSwipe(Direction direction) {
|
private void handleSwipe(Direction direction) {
|
||||||
// Si le jeu n'est pas initialisé ou s'il est DÉJÀ terminé, ignorer le swipe.
|
|
||||||
if (game == null || gameStats == null || currentGameState == GameFlowState.GAME_OVER) {
|
if (game == null || gameStats == null || currentGameState == GameFlowState.GAME_OVER) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stocker le score avant le mouvement pour calculer le delta
|
|
||||||
int scoreBefore = game.getCurrentScore();
|
int scoreBefore = game.getCurrentScore();
|
||||||
// Indique si le mouvement a effectivement changé l'état du plateau
|
|
||||||
boolean boardChanged = false;
|
boolean boardChanged = false;
|
||||||
|
|
||||||
// --- 1. Tenter d'effectuer le mouvement ---
|
|
||||||
// Les méthodes pushX() de l'objet Game contiennent la logique de déplacement/fusion
|
|
||||||
// et appellent en interne checkWinCondition() et checkGameOverCondition()
|
|
||||||
// pour mettre à jour les états isGameWon() et isGameOver().
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case UP:
|
case UP:
|
||||||
boardChanged = game.pushUp();
|
boardChanged = game.pushUp();
|
||||||
@ -318,63 +298,63 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 2. Traiter les conséquences SI le plateau a changé ---
|
|
||||||
if (boardChanged) {
|
if (boardChanged) {
|
||||||
// Mettre à jour les statistiques liées au mouvement réussi
|
|
||||||
gameStats.recordMove();
|
gameStats.recordMove();
|
||||||
int scoreAfter = game.getCurrentScore();
|
int scoreAfter = game.getCurrentScore();
|
||||||
int scoreDelta = scoreAfter - scoreBefore;
|
int scoreDelta = scoreAfter - scoreBefore;
|
||||||
if (scoreDelta > 0) {
|
if (scoreDelta > 0) {
|
||||||
// Supposition simpliste : une augmentation de score implique au moins une fusion
|
|
||||||
gameStats.recordMerge(1);
|
gameStats.recordMerge(1);
|
||||||
// Vérifier et mettre à jour le meilleur score si nécessaire
|
|
||||||
if (scoreAfter > game.getHighestScore()) {
|
if (scoreAfter > game.getHighestScore()) {
|
||||||
game.setHighestScore(scoreAfter); // Met à jour dans l'objet Game
|
game.setHighestScore(scoreAfter);
|
||||||
gameStats.setHighestScore(scoreAfter); // Met à jour et sauvegarde dans GameStats
|
gameStats.setHighestScore(scoreAfter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Mettre à jour la tuile la plus haute atteinte
|
|
||||||
gameStats.updateHighestTile(game.getHighestTileValue());
|
gameStats.updateHighestTile(game.getHighestTileValue());
|
||||||
|
|
||||||
// Ajouter une nouvelle tuile aléatoire sur le plateau
|
|
||||||
game.addNewTile();
|
game.addNewTile();
|
||||||
|
|
||||||
// Mettre à jour l'affichage complet du plateau et des scores
|
|
||||||
updateUI();
|
updateUI();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 3. Vérifier l'état final du jeu (Gagné / Perdu) ---
|
|
||||||
// Cette vérification est faite APRÈS la tentative de mouvement,
|
|
||||||
// On vérifie aussi qu'on n'a pas DÉJÀ traité la fin de partie dans ce même appel.
|
|
||||||
if (currentGameState != GameFlowState.GAME_OVER) {
|
if (currentGameState != GameFlowState.GAME_OVER) {
|
||||||
|
|
||||||
// a) Condition de Victoire (atteindre 2048 ou plus)
|
|
||||||
// On vérifie aussi qu'on était en train de jouer normalement (pas déjà gagné et décidé de continuer)
|
|
||||||
if (game.isGameWon() && currentGameState == GameFlowState.PLAYING) {
|
if (game.isGameWon() && currentGameState == GameFlowState.PLAYING) {
|
||||||
currentGameState = GameFlowState.WON_DIALOG_SHOWN; // Mettre à jour l'état de flux
|
currentGameState = GameFlowState.WON_DIALOG_SHOWN;
|
||||||
// Enregistrer les statistiques de victoire
|
|
||||||
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
|
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
|
||||||
gameStats.recordWin(timeTaken);
|
gameStats.recordWin(timeTaken);
|
||||||
// Afficher la boîte de dialogue de victoire
|
|
||||||
showGameWonKeepPlayingDialog();
|
showGameWonKeepPlayingDialog();
|
||||||
|
|
||||||
// b) Condition de Défaite (Game Over - pas de case vide ET pas de fusion possible)
|
|
||||||
// Cette condition est vérifiée seulement si on n'a pas déjà gagné.
|
|
||||||
} else if (game.isGameOver()) {
|
} else if (game.isGameOver()) {
|
||||||
currentGameState = GameFlowState.GAME_OVER; // Mettre à jour l'état de flux
|
currentGameState = GameFlowState.GAME_OVER;
|
||||||
// Enregistrer les statistiques de défaite et finaliser la partie
|
|
||||||
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
|
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
|
||||||
gameStats.recordLoss();
|
gameStats.recordLoss();
|
||||||
gameStats.endGame(timeTaken); // Finalise le temps, etc.
|
gameStats.endGame(timeTaken);
|
||||||
// Afficher la boîte de dialogue de Game Over
|
|
||||||
showGameOverDialog();
|
showGameOverDialog();
|
||||||
|
|
||||||
if (!boardChanged) {
|
if (!boardChanged) {
|
||||||
updateUI(); // Assure que le score final affiché est correct.
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// c) Ni gagné, ni perdu : Le jeu continue. L'état reste PLAYING ou WON_DIALOG_SHOWN.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,11 +376,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
* crée un nouvel objet Game, synchronise le meilleur score et met à jour l'UI.
|
* crée un nouvel objet Game, synchronise le meilleur score et met à jour l'UI.
|
||||||
*/
|
*/
|
||||||
private void startNewGame() {
|
private void startNewGame() {
|
||||||
gameStats.startGame(); // Réinitialise stats de partie
|
gameStats.startGame();
|
||||||
game = new Game(); // Crée un nouveau jeu
|
game = new Game();
|
||||||
game.setHighestScore(gameStats.getOverallHighScore()); // Applique HS global
|
game.setHighestScore(gameStats.getOverallHighScore());
|
||||||
currentGameState = GameFlowState.PLAYING; // Définit l'état à JOUER
|
currentGameState = GameFlowState.PLAYING;
|
||||||
updateUI(); // Met à jour affichage
|
updateUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -410,24 +390,24 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private void showGameWonKeepPlayingDialog() {
|
private void showGameWonKeepPlayingDialog() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
LayoutInflater inflater = getLayoutInflater();
|
||||||
View dialogView = inflater.inflate(R.layout.dialog_game_won, null); // Gonfle le layout personnalisé
|
View dialogView = inflater.inflate(R.layout.dialog_game_won, null);
|
||||||
builder.setView(dialogView);
|
builder.setView(dialogView);
|
||||||
builder.setCancelable(false); // Empêche de fermer sans choisir
|
builder.setCancelable(false);
|
||||||
|
|
||||||
|
|
||||||
// Récupère les boutons DANS la vue gonflée
|
|
||||||
Button keepPlayingButton = dialogView.findViewById(R.id.dialogKeepPlayingButton);
|
Button keepPlayingButton = dialogView.findViewById(R.id.dialogKeepPlayingButton);
|
||||||
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonWon);
|
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonWon);
|
||||||
|
|
||||||
final AlertDialog dialog = builder.create();
|
final AlertDialog dialog = builder.create();
|
||||||
|
|
||||||
keepPlayingButton.setOnClickListener(v -> {
|
keepPlayingButton.setOnClickListener(v -> {
|
||||||
// L'état est déjà WON_DIALOG_SHOWN, on ne fait rien de spécial, le jeu continue.
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
newGameButton.setOnClickListener(v -> {
|
newGameButton.setOnClickListener(v -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
startNewGame(); // Démarre une nouvelle partie
|
startNewGame();
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
@ -440,35 +420,35 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private void showGameOverDialog() {
|
private void showGameOverDialog() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
LayoutInflater inflater = getLayoutInflater();
|
||||||
View dialogView = inflater.inflate(R.layout.dialog_game_over, null); // Gonfle le layout personnalisé
|
View dialogView = inflater.inflate(R.layout.dialog_game_over, null);
|
||||||
builder.setView(dialogView);
|
builder.setView(dialogView);
|
||||||
builder.setCancelable(false); // Empêche de fermer sans choisir
|
builder.setCancelable(false);
|
||||||
|
|
||||||
|
|
||||||
// Récupère les vues DANS la vue gonflée
|
|
||||||
TextView messageTextView = dialogView.findViewById(R.id.dialogMessageGameOver);
|
TextView messageTextView = dialogView.findViewById(R.id.dialogMessageGameOver);
|
||||||
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonGameOver);
|
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonGameOver);
|
||||||
Button quitButton = dialogView.findViewById(R.id.dialogQuitButtonGameOver);
|
Button quitButton = dialogView.findViewById(R.id.dialogQuitButtonGameOver);
|
||||||
|
|
||||||
// Met à jour le message avec le score final
|
|
||||||
messageTextView.setText(getString(R.string.game_over_message, game.getCurrentScore()));
|
messageTextView.setText(getString(R.string.game_over_message, game.getCurrentScore()));
|
||||||
|
|
||||||
final AlertDialog dialog = builder.create();
|
final AlertDialog dialog = builder.create();
|
||||||
|
|
||||||
newGameButton.setOnClickListener(v -> {
|
newGameButton.setOnClickListener(v -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
startNewGame(); // Démarre une nouvelle partie
|
startNewGame();
|
||||||
});
|
});
|
||||||
|
|
||||||
quitButton.setOnClickListener(v -> {
|
quitButton.setOnClickListener(v -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
finish(); // Ferme l'application
|
finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Gestion des Statistiques (UI) ---
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Affiche ou masque le panneau de statistiques.
|
* Affiche ou masque le panneau de statistiques.
|
||||||
@ -477,20 +457,20 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private void toggleStatistics() {
|
private void toggleStatistics() {
|
||||||
statisticsVisible = !statisticsVisible;
|
statisticsVisible = !statisticsVisible;
|
||||||
if (statisticsVisible) {
|
if (statisticsVisible) {
|
||||||
if (inflatedStatsView == null) { // Gonfle si pas encore fait
|
if (inflatedStatsView == null) {
|
||||||
inflatedStatsView = statisticsViewStub.inflate();
|
inflatedStatsView = statisticsViewStub.inflate();
|
||||||
// Attache listener au bouton Back une fois la vue gonflée
|
|
||||||
Button backButton = inflatedStatsView.findViewById(R.id.backButton);
|
Button backButton = inflatedStatsView.findViewById(R.id.backButton);
|
||||||
backButton.setOnClickListener(v -> toggleStatistics()); // Recliquer sur Back re-appelle toggle
|
backButton.setOnClickListener(v -> toggleStatistics());
|
||||||
}
|
}
|
||||||
updateStatisticsTextViews(); // Remplit les champs avec les données actuelles
|
updateStatisticsTextViews();
|
||||||
inflatedStatsView.setVisibility(View.VISIBLE); // Affiche
|
inflatedStatsView.setVisibility(View.VISIBLE);
|
||||||
multiplayerButton.setVisibility(View.GONE); // Masque bouton multi
|
multiplayerButton.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
if (inflatedStatsView != null) { // Masque si la vue existe
|
if (inflatedStatsView != null) {
|
||||||
inflatedStatsView.setVisibility(View.GONE);
|
inflatedStatsView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
multiplayerButton.setVisibility(View.VISIBLE); // Réaffiche bouton multi
|
multiplayerButton.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,7 +481,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private void updateStatisticsTextViews() {
|
private void updateStatisticsTextViews() {
|
||||||
if (inflatedStatsView == null || gameStats == null) return;
|
if (inflatedStatsView == null || gameStats == null) return;
|
||||||
|
|
||||||
// Récupération des TextViews dans la vue gonflée (idem)
|
|
||||||
TextView highScoreStatsLabel = inflatedStatsView.findViewById(R.id.high_score_stats_label);
|
TextView highScoreStatsLabel = inflatedStatsView.findViewById(R.id.high_score_stats_label);
|
||||||
TextView totalGamesPlayedLabel = inflatedStatsView.findViewById(R.id.total_games_played_label);
|
TextView totalGamesPlayedLabel = inflatedStatsView.findViewById(R.id.total_games_played_label);
|
||||||
TextView totalGamesStartedLabel = inflatedStatsView.findViewById(R.id.total_games_started_label);
|
TextView totalGamesStartedLabel = inflatedStatsView.findViewById(R.id.total_games_started_label);
|
||||||
@ -522,12 +502,12 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
TextView multiplayerWinRateLabel = inflatedStatsView.findViewById(R.id.multiplayer_win_rate_label);
|
TextView multiplayerWinRateLabel = inflatedStatsView.findViewById(R.id.multiplayer_win_rate_label);
|
||||||
TextView multiplayerBestWinningStreakLabel = inflatedStatsView.findViewById(R.id.multiplayer_best_winning_streak_label);
|
TextView multiplayerBestWinningStreakLabel = inflatedStatsView.findViewById(R.id.multiplayer_best_winning_streak_label);
|
||||||
TextView multiplayerAverageScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_average_score_label);
|
TextView multiplayerAverageScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_average_score_label);
|
||||||
TextView averageTimePerGameMultiLabel = inflatedStatsView.findViewById(R.id.average_time_per_game_label); // ID toujours potentiellement dupliqué
|
TextView averageTimePerGameMultiLabel = inflatedStatsView.findViewById(R.id.average_time_per_game_label);
|
||||||
TextView totalMultiplayerLossesLabel = inflatedStatsView.findViewById(R.id.total_multiplayer_losses_label);
|
TextView totalMultiplayerLossesLabel = inflatedStatsView.findViewById(R.id.total_multiplayer_losses_label);
|
||||||
TextView multiplayerHighScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_high_score_label);
|
TextView multiplayerHighScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_high_score_label);
|
||||||
TextView mergesThisGameLabel = inflatedStatsView.findViewById(R.id.merges_this_game);
|
TextView mergesThisGameLabel = inflatedStatsView.findViewById(R.id.merges_this_game);
|
||||||
|
|
||||||
// MAJ textes avec getters de gameStats
|
|
||||||
highScoreStatsLabel.setText(getString(R.string.high_score_stats, gameStats.getOverallHighScore()));
|
highScoreStatsLabel.setText(getString(R.string.high_score_stats, gameStats.getOverallHighScore()));
|
||||||
totalGamesPlayedLabel.setText(getString(R.string.total_games_played, gameStats.getTotalGamesPlayed()));
|
totalGamesPlayedLabel.setText(getString(R.string.total_games_played, gameStats.getTotalGamesPlayed()));
|
||||||
totalGamesStartedLabel.setText(getString(R.string.total_games_started, gameStats.getTotalGamesStarted()));
|
totalGamesStartedLabel.setText(getString(R.string.total_games_started, gameStats.getTotalGamesStarted()));
|
||||||
@ -545,13 +525,13 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
totalMultiplayerLossesLabel.setText(getString(R.string.total_multiplayer_losses, gameStats.getTotalMultiplayerLosses()));
|
totalMultiplayerLossesLabel.setText(getString(R.string.total_multiplayer_losses, gameStats.getTotalMultiplayerLosses()));
|
||||||
multiplayerHighScoreLabel.setText(getString(R.string.multiplayer_high_score, gameStats.getMultiplayerHighestScore()));
|
multiplayerHighScoreLabel.setText(getString(R.string.multiplayer_high_score, gameStats.getMultiplayerHighestScore()));
|
||||||
|
|
||||||
// Calculs Pourcentages
|
|
||||||
String winPercentage = (gameStats.getTotalGamesStarted() > 0) ? String.format("%.2f%%", ((double) gameStats.getNumberOfTimesObjectiveReached() / gameStats.getTotalGamesStarted()) * 100) : "N/A";
|
String winPercentage = (gameStats.getTotalGamesStarted() > 0) ? String.format("%.2f%%", ((double) gameStats.getNumberOfTimesObjectiveReached() / gameStats.getTotalGamesStarted()) * 100) : "N/A";
|
||||||
winPercentageLabel.setText(getString(R.string.win_percentage, winPercentage));
|
winPercentageLabel.setText(getString(R.string.win_percentage, winPercentage));
|
||||||
String multiplayerWinRate = (gameStats.getMultiplayerGamesPlayed() > 0) ? String.format("%.2f%%", ((double) gameStats.getMultiplayerGamesWon() / gameStats.getMultiplayerGamesPlayed()) * 100) : "N/A";
|
String multiplayerWinRate = (gameStats.getMultiplayerGamesPlayed() > 0) ? String.format("%.2f%%", ((double) gameStats.getMultiplayerGamesWon() / gameStats.getMultiplayerGamesPlayed()) * 100) : "N/A";
|
||||||
multiplayerWinRateLabel.setText(getString(R.string.multiplayer_win_rate, multiplayerWinRate));
|
multiplayerWinRateLabel.setText(getString(R.string.multiplayer_win_rate, multiplayerWinRate));
|
||||||
|
|
||||||
// Calculs Temps
|
|
||||||
totalPlayTimeLabel.setText(getString(R.string.total_play_time, GameStats.formatTime(gameStats.getTotalPlayTimeMs())));
|
totalPlayTimeLabel.setText(getString(R.string.total_play_time, GameStats.formatTime(gameStats.getTotalPlayTimeMs())));
|
||||||
long currentDuration = (game != null && !game.isGameOver() && !game.isGameWon() && gameStats != null) ? System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs() : 0;
|
long currentDuration = (game != null && !game.isGameOver() && !game.isGameWon() && gameStats != null) ? System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs() : 0;
|
||||||
currentGameTimeLabel.setText(getString(R.string.current_game_time, GameStats.formatTime(currentDuration)));
|
currentGameTimeLabel.setText(getString(R.string.current_game_time, GameStats.formatTime(currentDuration)));
|
||||||
@ -561,32 +541,32 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
worstWinningTimeLabel.setText(getString(R.string.worst_winning_time, (gameStats.getWorstWinningTimeMs() != 0) ? GameStats.formatTime(gameStats.getWorstWinningTimeMs()) : "N/A"));
|
worstWinningTimeLabel.setText(getString(R.string.worst_winning_time, (gameStats.getWorstWinningTimeMs() != 0) ? GameStats.formatTime(gameStats.getWorstWinningTimeMs()) : "N/A"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Dialogues / Placeholders ---
|
|
||||||
|
|
||||||
/** Affiche un dialogue placeholder pour le menu. */
|
/** Affiche un dialogue placeholder pour le menu. */
|
||||||
private void showMenu() { /* ... (inchangé) ... */
|
private void showMenu() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle("Menu").setMessage("Fonctionnalité de menu à venir !").setPositiveButton("OK", null);
|
builder.setTitle("Menu").setMessage("Fonctionnalité de menu à venir !").setPositiveButton("OK", null);
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Affiche un dialogue placeholder pour le multijoueur. */
|
/** Affiche un dialogue placeholder pour le multijoueur. */
|
||||||
private void showMultiplayerScreen() { /* ... (inchangé) ... */
|
private void showMultiplayerScreen() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle("Multijoueur").setMessage("Fonctionnalité multijoueur à venir !").setPositiveButton("OK", null);
|
builder.setTitle("Multijoueur").setMessage("Fonctionnalité multijoueur à venir !").setPositiveButton("OK", null);
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Sauvegarde / Chargement ---
|
|
||||||
|
|
||||||
/** Sauvegarde l'état du jeu et le meilleur score via SharedPreferences. */
|
/** Sauvegarde l'état du jeu et le meilleur score via SharedPreferences. */
|
||||||
private void saveGame() {
|
private void saveGame() {
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
editor.putString(GAME_STATE_KEY, game.toString()); // Sérialise Game (plateau + score courant)
|
editor.putString(GAME_STATE_KEY, game.toString());
|
||||||
editor.putInt(HIGH_SCORE_KEY, game.getHighestScore()); // Sauvegarde le HS contenu dans Game
|
editor.putInt(HIGH_SCORE_KEY, game.getHighestScore());
|
||||||
} else {
|
} else {
|
||||||
editor.remove(GAME_STATE_KEY); // Optionnel: nettoyer si pas de jeu
|
editor.remove(GAME_STATE_KEY);
|
||||||
}
|
}
|
||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
@ -594,28 +574,27 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
/** Charge l'état du jeu depuis SharedPreferences et synchronise le meilleur score. */
|
/** Charge l'état du jeu depuis SharedPreferences et synchronise le meilleur score. */
|
||||||
private void loadGame() {
|
private void loadGame() {
|
||||||
String gameStateString = preferences.getString(GAME_STATE_KEY, null);
|
String gameStateString = preferences.getString(GAME_STATE_KEY, null);
|
||||||
int savedHighScore = preferences.getInt(HIGH_SCORE_KEY, 0); // HS lu depuis prefs
|
int savedHighScore = preferences.getInt(HIGH_SCORE_KEY, 0);
|
||||||
|
|
||||||
if (gameStats != null) { // S'assure que GameStats a le HS correct
|
if (gameStats != null) {
|
||||||
gameStats.setHighestScore(savedHighScore);
|
gameStats.setHighestScore(savedHighScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameStateString != null) {
|
if (gameStateString != null) {
|
||||||
game = Game.deserialize(gameStateString);
|
game = Game.deserialize(gameStateString);
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
game.setHighestScore(savedHighScore); // Applique HS à Game
|
game.setHighestScore(savedHighScore);
|
||||||
// Détermine l'état basé sur le jeu chargé
|
|
||||||
if (game.isGameOver()) currentGameState = GameFlowState.GAME_OVER;
|
|
||||||
else if (game.isGameWon()) currentGameState = GameFlowState.WON_DIALOG_SHOWN; // Si gagné avant, on continue
|
|
||||||
else currentGameState = GameFlowState.PLAYING;
|
|
||||||
} else { game = null; } // Échec désérialisation
|
|
||||||
} else { game = null; } // Pas de sauvegarde
|
|
||||||
|
|
||||||
if (game == null) { // Si pas de jeu chargé ou erreur
|
if (game.isGameOver()) currentGameState = GameFlowState.GAME_OVER;
|
||||||
|
else if (game.isGameWon()) currentGameState = GameFlowState.WON_DIALOG_SHOWN;
|
||||||
|
else currentGameState = GameFlowState.PLAYING;
|
||||||
|
} else { game = null; }
|
||||||
|
} else { game = null; }
|
||||||
|
|
||||||
|
if (game == null) {
|
||||||
game = new Game();
|
game = new Game();
|
||||||
game.setHighestScore(savedHighScore);
|
game.setHighestScore(savedHighScore);
|
||||||
currentGameState = GameFlowState.PLAYING;
|
currentGameState = GameFlowState.PLAYING;
|
||||||
// Pas besoin d'appeler gameStats.startGame() ici, sera fait dans initializeGame OU startNewGame si nécessaire
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,151 +1,125 @@
|
|||||||
// Fichier OnSwipeTouchListener.java
|
// Fichier OnSwipeTouchListener.java
|
||||||
// Classe utilitaire pour détecter les gestes de balayage (swipe).
|
/**
|
||||||
/*
|
* Listener de vue personnalisé qui détecte les gestes de balayage (swipe)
|
||||||
Fonctions principales :
|
* dans les quatre directions cardinales et notifie un listener externe.
|
||||||
- Utilise GestureDetector pour détecter les swipes.
|
* Utilise {@link GestureDetector} pour l'analyse des gestes.
|
||||||
- Définit une interface SwipeListener pour notifier la classe appelante (MainActivity).
|
|
||||||
- onFling() : Détecte la direction du swipe (haut, bas, gauche, droite).
|
|
||||||
- SWIPE_THRESHOLD, SWIPE_VELOCITY_THRESHOLD : Constantes pour la sensibilité du swipe.
|
|
||||||
|
|
||||||
Relations :
|
|
||||||
- MainActivity : MainActivity crée une instance et est notifiée des swipes via l'interface SwipeListener.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package legion.muyue.best2048;
|
package legion.muyue.best2048;
|
||||||
|
|
||||||
import android.annotation.SuppressLint; // Ajout
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
// import android.location.GnssAntennaInfo; // Import inutile trouvé dans le dump
|
|
||||||
import android.view.GestureDetector;
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.NonNull; // Ajout
|
|
||||||
|
|
||||||
public class OnSwipeTouchListener implements View.OnTouchListener {
|
public class OnSwipeTouchListener implements View.OnTouchListener {
|
||||||
|
|
||||||
|
/** Détecteur de gestes standard d'Android. */
|
||||||
private final GestureDetector gestureDetector;
|
private final GestureDetector gestureDetector;
|
||||||
|
/** Listener externe à notifier lors de la détection d'un swipe. */
|
||||||
private final SwipeListener listener;
|
private final SwipeListener listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface pour les événements de swipe. Toute classe qui veut réagir aux swipes
|
* Interface à implémenter par les classes souhaitant réagir aux événements de swipe.
|
||||||
* doit implémenter cette interface et passer une instance à OnSwipeTouchListener.
|
|
||||||
*/
|
*/
|
||||||
public interface SwipeListener {
|
public interface SwipeListener {
|
||||||
/**
|
/** Appelée lorsqu'un swipe vers le haut est détecté. */
|
||||||
* Appelée lorsqu'un swipe vers le haut est détecté.
|
|
||||||
*/
|
|
||||||
void onSwipeTop();
|
void onSwipeTop();
|
||||||
|
/** Appelée lorsqu'un swipe vers le bas est détecté. */
|
||||||
/**
|
|
||||||
* Appelée lorsqu'un swipe vers le bas est détecté.
|
|
||||||
*/
|
|
||||||
void onSwipeBottom();
|
void onSwipeBottom();
|
||||||
|
/** Appelée lorsqu'un swipe vers la gauche est détecté. */
|
||||||
/**
|
|
||||||
* Appelée lorsqu'un swipe vers la gauche est détecté.
|
|
||||||
*/
|
|
||||||
void onSwipeLeft();
|
void onSwipeLeft();
|
||||||
|
/** Appelée lorsqu'un swipe vers la droite est détecté. */
|
||||||
/**
|
|
||||||
* Appelée lorsqu'un swipe vers la droite est détecté.
|
|
||||||
*/
|
|
||||||
void onSwipeRight();
|
void onSwipeRight();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur de la classe OnSwipeTouchListener.
|
* Constructeur.
|
||||||
*
|
* @param context Contexte applicatif, nécessaire pour `GestureDetector`.
|
||||||
* @param ctx Le contexte de l'application. Nécessaire pour GestureDetector.
|
* @param listener Instance qui recevra les notifications de swipe. Ne doit pas être null.
|
||||||
* @param listener L'instance qui écoutera les événements de swipe.
|
|
||||||
*/
|
*/
|
||||||
public OnSwipeTouchListener(Context ctx, SwipeListener listener) {
|
public OnSwipeTouchListener(Context context, @NonNull SwipeListener listener) {
|
||||||
gestureDetector = new GestureDetector(ctx, new GestureListener());
|
this.gestureDetector = new GestureDetector(context, new GestureListener());
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode appelée lorsqu'un événement tactile se produit sur la vue attachée.
|
* Intercepte les événements tactiles sur la vue associée et les délègue
|
||||||
* Elle transmet l'événement au GestureDetector pour analyse.
|
* au {@link GestureDetector} pour analyse.
|
||||||
*
|
* @param v La vue touchée.
|
||||||
* @param v La vue sur laquelle l'événement tactile s'est produit.
|
* @param event L'événement tactile.
|
||||||
* @param event L'objet MotionEvent décrivant l'événement tactile.
|
* @return true si le geste a été consommé par le détecteur, false sinon.
|
||||||
* @return true si l'événement a été géré, false sinon.
|
|
||||||
*/
|
*/
|
||||||
@SuppressLint("ClickableViewAccessibility") // Ajout
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
// Passe l'événement au GestureDetector. Si ce dernier le gère (ex: détecte un onFling),
|
||||||
|
// il retournera true, et l'événement ne sera pas propagé davantage.
|
||||||
return gestureDetector.onTouchEvent(event);
|
return gestureDetector.onTouchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classe interne qui étend GestureDetector.SimpleOnGestureListener pour gérer
|
* Classe interne implémentant l'écouteur de gestes pour détecter le 'fling' (balayage rapide).
|
||||||
* spécifiquement les gestes de balayage (fling).
|
|
||||||
*/
|
*/
|
||||||
private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||||
|
|
||||||
private static final int SWIPE_THRESHOLD = 100; // Distance minimale du swipe (en pixels).
|
/** Distance minimale (en pixels) pour qu'un mouvement soit considéré comme un swipe. */
|
||||||
private static final int SWIPE_VELOCITY_THRESHOLD = 100; // Vitesse minimale du swipe (en pixels par seconde).
|
private static final int SWIPE_THRESHOLD = 100;
|
||||||
|
/** Vitesse minimale (en pixels/sec) pour qu'un mouvement soit considéré comme un swipe. */
|
||||||
|
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode appelée lorsqu'un appui initial sur l'écran est détecté.
|
* Toujours retourner true pour onDown garantit que les événements suivants
|
||||||
* On retourne toujours 'true' pour indiquer qu'on gère cet événement.
|
* (comme onFling) seront bien reçus par ce listener.
|
||||||
*
|
|
||||||
* @param e L'objet MotionEvent décrivant l'appui initial.
|
|
||||||
* @return true, car on gère toujours l'événement 'onDown'.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onDown(@NonNull MotionEvent e) { // Ajout @NonNull
|
public boolean onDown(@NonNull MotionEvent e) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode appelée lorsqu'un geste de balayage (fling) est détecté.
|
* Appelée quand un geste de 'fling' (balayage rapide) est détecté.
|
||||||
*
|
* Analyse la direction et la vitesse pour déterminer s'il s'agit d'un swipe valide
|
||||||
* @param e1 L'objet MotionEvent du premier appui (début du swipe). Peut être null.
|
* et notifie le {@link SwipeListener} externe.
|
||||||
* @param e2 L'objet MotionEvent de la fin du swipe.
|
|
||||||
* @param velocityX La vitesse du swipe en pixels par seconde sur l'axe X.
|
|
||||||
* @param velocityY La vitesse du swipe en pixels par seconde sur l'axe Y.
|
|
||||||
* @return true si le geste de balayage a été géré, false sinon.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { // Ajout @NonNull
|
public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
|
||||||
// Vérification de nullité pour e1, nécessaire car onFling peut être appelé même si onDown retourne false ou si le geste est complexe.
|
if (e1 == null) return false; // Point de départ est nécessaire
|
||||||
if (e1 == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
try {
|
try {
|
||||||
float diffY = e2.getY() - e1.getY(); // Différence de position sur l'axe Y.
|
float diffY = e2.getY() - e1.getY();
|
||||||
float diffX = e2.getX() - e1.getX(); // Différence de position sur l'axe X.
|
float diffX = e2.getX() - e1.getX();
|
||||||
|
|
||||||
// Détermine si le swipe est plutôt horizontal ou vertical.
|
// Priorité au mouvement le plus ample (horizontal ou vertical)
|
||||||
if (Math.abs(diffX) > Math.abs(diffY)) {
|
if (Math.abs(diffX) > Math.abs(diffY)) {
|
||||||
// Swipe horizontal.
|
// Mouvement principalement horizontal
|
||||||
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
|
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
|
||||||
if (diffX > 0) {
|
if (diffX > 0) {
|
||||||
listener.onSwipeRight(); // Swipe vers la droite.
|
listener.onSwipeRight();
|
||||||
} else {
|
} else {
|
||||||
listener.onSwipeLeft(); // Swipe vers la gauche.
|
listener.onSwipeLeft();
|
||||||
}
|
}
|
||||||
result = true;
|
result = true; // Geste horizontal traité
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Swipe vertical.
|
// Mouvement principalement vertical
|
||||||
if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
|
if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
|
||||||
if (diffY > 0) {
|
if (diffY > 0) {
|
||||||
listener.onSwipeBottom(); // Swipe vers le bas.
|
listener.onSwipeBottom();
|
||||||
} else {
|
} else {
|
||||||
listener.onSwipeTop(); // Swipe vers le haut.
|
listener.onSwipeTop();
|
||||||
}
|
}
|
||||||
result = true;
|
result = true; // Geste vertical traité
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
exception.fillInStackTrace(); // Gestion des erreurs (journalisation).
|
// En cas d'erreur inattendue, on logue discrètement.
|
||||||
|
System.err.println("Erreur dans OnSwipeTouchListener.onFling: " + exception.getMessage());
|
||||||
|
// Ne pas crasher l'application pour une erreur de détection de geste.
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // Fin OnSwipeTouchListener
|
Loading…
x
Reference in New Issue
Block a user