Refactor: Finalisation et externalisation des statistiques
- Création de la classe GameStats pour encapsuler les données et la logique des statistiques. - Implémentation de loadStats() et saveStats() dans GameStats utilisant SharedPreferences. - Ajout de méthodes dans GameStats pour enregistrer les événements du jeu (startGame, recordMove, recordMerge, recordWin, recordLoss, endGame, updateHighestTile, addPlayTime, setCurrentGameStartTimeMs, setHighestScore). - Ajout de getters dans GameStats pour l'affichage et de méthodes pour les valeurs calculées (moyennes, pourcentages). - Déplacement de formatTime() dans GameStats. - Refactorisation de MainActivity: - Suppression des champs et méthodes de statistiques individuelles. - Utilisation d'une instance de GameStats pour gérer les statistiques. - Mise à jour de handleSwipe, startNewGame, onPause, onResume pour appeler GameStats. - Mise à jour de updateStatisticsTextViews pour utiliser les getters de GameStats. - MainActivity gère maintenant le chargement/sauvegarde de l'état sérialisé du jeu et du high score via SharedPreferences, en passant le high score à Game via setHighestScore. - Refactorisation de Game: - Suppression de la dépendance au Contexte et SharedPreferences. - Suppression de la gestion interne du high score (reçoit via setHighestScore). - Ajout de getBoard() et getHighestTileValue(). - Modification du constructeur et de deserialize pour être indépendants du contexte et du high score. - Ajout/Mise à jour des commentaires JavaDoc dans les classes modifiées.
This commit is contained in:
parent
73ab81e208
commit
9d8d2c5c62
@ -1,640 +1,336 @@
|
||||
// Fichier Game.java
|
||||
// Ce fichier contient la logique principale du jeu 2048. Il est indépendant de l'interface utilisateur.
|
||||
// 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.
|
||||
/*
|
||||
Fonctions principales :
|
||||
- gameBoard : Matrice 2D (int[][]) représentant la grille du jeu.
|
||||
- score, highScore : Suivi du score et du meilleur score.
|
||||
- addNewNumbers() : Ajoute une nouvelle tuile (2, 4 ou 8) aléatoirement sur la grille.
|
||||
- pushUp(), pushDown(), pushLeft(), pushRight() : Logique de déplacement et de fusion des tuiles.
|
||||
- serialize(), deserialize() : Sauvegarde et restauration de l'état du jeu (grille, score, meilleur score) en chaînes de caractères.
|
||||
- loadHighScore(), saveHighScore(), loadGameState(), saveGameState() : Utilisation de SharedPreferences pour persister les données.
|
||||
- Constructeurs : Initialisation de la grille et chargement/restauration des données.
|
||||
- board : Matrice 2D (int[][]) représentant la grille du jeu.
|
||||
- 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 et appelle ses méthodes pour la logique du jeu. MainActivity affiche l'état du jeu.
|
||||
- SharedPreferences : Game utilise SharedPreferences pour sauvegarder et charger le meilleur score et l'état du jeu.
|
||||
- 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class Game {
|
||||
|
||||
private int[][] board; // Renommé
|
||||
private final Random randomNumberGenerator; // Renommé
|
||||
private int currentScore = 0; // Renommé
|
||||
private int highestScore = 0; // Renommé
|
||||
private static final int BOARD_SIZE = 4; // Ajout constante
|
||||
private static final String PREFS_NAME = "Best2048_Prefs"; // Ajout constante
|
||||
private static final String HIGH_SCORE_KEY = "high_score"; // Ajout constante
|
||||
private static final String GAME_STATE_KEY = "game_state"; // Ajout constante
|
||||
private final Context context; // Ajout contexte
|
||||
private boolean gameWon = false; // Ajout état
|
||||
private boolean gameOver = false; // Ajout état
|
||||
private int[][] board;
|
||||
private final Random randomNumberGenerator;
|
||||
private int currentScore = 0;
|
||||
private int highestScore = 0; // Stocke le HS fourni par MainActivity
|
||||
private static final int BOARD_SIZE = 4;
|
||||
private boolean gameWon = false;
|
||||
private boolean gameOver = false;
|
||||
|
||||
/**
|
||||
* Constructeur principal de la classe Game.
|
||||
*
|
||||
* @param context Le contexte Android (généralement une Activity). Nécessaire pour accéder aux SharedPreferences.
|
||||
* Constructeur pour une nouvelle partie.
|
||||
* Initialise un plateau vide, définit le score à 0 et ajoute deux tuiles initiales.
|
||||
*/
|
||||
public Game(Context context) {
|
||||
this.context = context;
|
||||
public Game() {
|
||||
this.randomNumberGenerator = new Random();
|
||||
this.board = new int[BOARD_SIZE][BOARD_SIZE];
|
||||
loadHighScore();
|
||||
loadGameState(); // Charge l'état précédent ou initialise
|
||||
// Le highScore sera défini par MainActivity après l'instanciation.
|
||||
initializeNewBoard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur pour la restauration d'une partie précédemment sauvegardée.
|
||||
*
|
||||
* @param board Le plateau de jeu restauré (l'état des tuiles).
|
||||
* @param score Le score au moment de la sauvegarde.
|
||||
* @param highScore Le meilleur score enregistré au moment de la sauvegarde.
|
||||
* @param context Le contexte Android, nécessaire pour les SharedPreferences.
|
||||
* Constructeur utilisé lors de la restauration d'une partie sauvegardée.
|
||||
* @param board Le plateau de jeu restauré.
|
||||
* @param score Le score courant restauré.
|
||||
*/
|
||||
public Game(int[][] board, int score, int highScore, Context context) {
|
||||
public Game(int[][] board, int score) {
|
||||
this.board = board;
|
||||
this.currentScore = score;
|
||||
this.highestScore = highScore;
|
||||
this.context = context;
|
||||
this.randomNumberGenerator = new Random();
|
||||
// Le highScore sera défini par MainActivity après l'instanciation.
|
||||
checkWinCondition(); // Recalcule l'état basé sur le plateau chargé
|
||||
checkGameOverCondition();
|
||||
}
|
||||
|
||||
// --- Getters / Setters ---
|
||||
|
||||
/**
|
||||
* Récupère la valeur d'une cellule spécifique sur le plateau.
|
||||
*
|
||||
* @param row L'index de la ligne (0 à BOARD_SIZE - 1).
|
||||
* @param column L'index de la colonne (0 à BOARD_SIZE - 1).
|
||||
* @return La valeur de la cellule à la position spécifiée.
|
||||
* @throws IllegalArgumentException Si row ou column sont en dehors des limites du plateau.
|
||||
* Retourne la valeur de la cellule aux coordonnées spécifiées.
|
||||
* @param row Ligne de la cellule (0-based).
|
||||
* @param column Colonne de la cellule (0-based).
|
||||
* @return Valeur de la cellule, ou 0 si indices invalides (sécurité).
|
||||
*/
|
||||
public int getCellValue(int row, int column) {
|
||||
if (row < 0 || row >= BOARD_SIZE || column < 0 || column >= BOARD_SIZE) {
|
||||
throw new IllegalArgumentException("Indices de ligne ou de colonne hors limites : row=" + row + ", column=" + column);
|
||||
}
|
||||
if (row < 0 || row >= BOARD_SIZE || column < 0 || column >= BOARD_SIZE) { return 0; }
|
||||
return this.board[row][column];
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la valeur d'une cellule spécifique sur le plateau.
|
||||
*
|
||||
* @param row L'index de la ligne (0 à BOARD_SIZE - 1).
|
||||
* @param col L'index de la colonne (0 à BOARD_SIZE - 1).
|
||||
* @param value La nouvelle valeur de la cellule.
|
||||
* @throws IllegalArgumentException Si row ou col sont en dehors des limites du plateau.
|
||||
* Définit la valeur de la cellule aux coordonnées spécifiées.
|
||||
* @param row Ligne de la cellule (0-based).
|
||||
* @param col Colonne de la cellule (0-based).
|
||||
* @param value Nouvelle valeur.
|
||||
*/
|
||||
public void setCellValue(int row, int col, int value) {
|
||||
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) {
|
||||
throw new IllegalArgumentException("Indices de ligne ou de colonne hors limites : row=" + row + ", col=" + col);
|
||||
}
|
||||
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) { return; }
|
||||
this.board[row][col] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le score actuel de la partie.
|
||||
*
|
||||
* @return Le score actuel.
|
||||
*/
|
||||
public int getCurrentScore() {
|
||||
return this.currentScore;
|
||||
}
|
||||
/** @return Le score actuel de la partie. */
|
||||
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 Game (synchronisé par MainActivity). */
|
||||
public int getHighestScore() { return highestScore; }
|
||||
|
||||
/**
|
||||
* Met à jour le score actuel.
|
||||
*
|
||||
* @param currentScore Le nouveau score actuel.
|
||||
* Définit le meilleur score global connu. Appelé par MainActivity après chargement
|
||||
* des préférences ou après une mise à jour du score.
|
||||
* @param highScore Le meilleur score connu.
|
||||
*/
|
||||
public void setCurrentScore(int currentScore) {
|
||||
this.currentScore = currentScore;
|
||||
}
|
||||
public void setHighestScore(int highScore) { this.highestScore = highScore; }
|
||||
|
||||
/**
|
||||
* Récupère le meilleur score enregistré.
|
||||
*
|
||||
* @return Le meilleur score.
|
||||
*/
|
||||
public int getHighestScore() {
|
||||
return this.highestScore;
|
||||
}
|
||||
/** @return L'état de victoire de la partie (true si >= 2048 atteint). */
|
||||
public boolean isGameWon() { return gameWon; }
|
||||
|
||||
/**
|
||||
* Met à jour le meilleur score. Utilisé lors du chargement et après une partie.
|
||||
*
|
||||
* @param highestScore Le nouveau meilleur score.
|
||||
*/
|
||||
public void setHighestScore(int highestScore) {
|
||||
this.highestScore = highestScore;
|
||||
}
|
||||
public void setGameWon(boolean gameWon) { this.gameWon = gameWon; }
|
||||
|
||||
/**
|
||||
* Met à jour le board. Utilisé lors du chargement.
|
||||
*
|
||||
* @param board Le nouveau board.
|
||||
*/
|
||||
public void setBoard(int[][] board){
|
||||
this.board = board;
|
||||
}
|
||||
/** @return L'état de fin de partie (true si aucun mouvement possible). */
|
||||
public boolean isGameOver() { return gameOver; }
|
||||
|
||||
/**
|
||||
* Récupère le générateur de nombres aléatoires.
|
||||
*
|
||||
* @return Le générateur de nombres aléatoires.
|
||||
*/
|
||||
public Random getRandomNumberGenerator() {
|
||||
return this.randomNumberGenerator;
|
||||
}
|
||||
public void setGameOver(boolean gameOver) { this.gameOver = gameOver; }
|
||||
|
||||
/**
|
||||
* Indique si le joueur a gagné la partie (atteint une tuile de 2048).
|
||||
*
|
||||
* @return true si le joueur a gagné, false sinon.
|
||||
*/
|
||||
public boolean isGameWon() {
|
||||
return this.gameWon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indique si la partie est terminée (plus de mouvements possibles).
|
||||
*
|
||||
* @return true si la partie est terminée, false sinon.
|
||||
*/
|
||||
public boolean isGameOver() {
|
||||
return this.gameOver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit l'état de la partie gagnée.
|
||||
*
|
||||
* @param gameWon true si le joueur a gagné, false sinon.
|
||||
*/
|
||||
public void setGameWon(boolean gameWon) {
|
||||
this.gameWon = gameWon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit l'état de la partie terminée.
|
||||
*
|
||||
* @param gameOver true si la partie est terminée, false sinon.
|
||||
*/
|
||||
public void setGameOver(boolean gameOver) {
|
||||
this.gameOver = gameOver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge le meilleur score à partir des préférences partagées (SharedPreferences).
|
||||
*/
|
||||
public void loadHighScore() {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
setHighestScore(prefs.getInt(HIGH_SCORE_KEY, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre le meilleur score actuel dans les préférences partagées (SharedPreferences).
|
||||
*/
|
||||
public void saveHighScore() {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(HIGH_SCORE_KEY, getHighestScore());
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge l'état de la partie (plateau, score) à partir des préférences partagées.
|
||||
*/
|
||||
private void loadGameState() {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
String savedState = prefs.getString(GAME_STATE_KEY, null);
|
||||
if (savedState != null) {
|
||||
Game savedGame = deserialize(savedState, context);
|
||||
if (savedGame != null) { // Vérifier si la désérialisation a réussi
|
||||
setBoard(savedGame.board);
|
||||
setCurrentScore(savedGame.currentScore);
|
||||
// Le meilleur score est déjà chargé par loadHighScore() appelé dans le constructeur principal
|
||||
} else {
|
||||
// Si l'état sauvegardé est invalide, commence une nouvelle partie
|
||||
initializeNewBoard();
|
||||
}
|
||||
} else {
|
||||
// Si aucun état n'est sauvegardé, commence une nouvelle partie
|
||||
initializeNewBoard();
|
||||
/** @return Une copie du plateau de jeu actuel (pour la sauvegarde externe). */
|
||||
public int[][] getBoard() {
|
||||
// Retourne une copie pour éviter modifications externes accidentelles
|
||||
int[][] copy = new int[BOARD_SIZE][BOARD_SIZE];
|
||||
for(int i=0; i<BOARD_SIZE; i++) {
|
||||
System.arraycopy(this.board[i], 0, copy[i], 0, BOARD_SIZE);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise le plateau pour une nouvelle partie (typiquement avec deux tuiles).
|
||||
* Initialise ou réinitialise le plateau pour une nouvelle partie.
|
||||
* Met le score à 0 et ajoute deux tuiles aléatoires.
|
||||
*/
|
||||
private void initializeNewBoard() {
|
||||
this.board = new int[BOARD_SIZE][BOARD_SIZE]; // Assure que le plateau est vide
|
||||
this.board = new int[BOARD_SIZE][BOARD_SIZE];
|
||||
this.currentScore = 0;
|
||||
this.gameWon = false;
|
||||
this.gameOver = false;
|
||||
addNewTile();
|
||||
addNewTile();
|
||||
}
|
||||
|
||||
// --- Logique du Jeu ---
|
||||
|
||||
/**
|
||||
* Enregistre l'état actuel de la partie (plateau, score) dans les préférences partagées.
|
||||
* Ajoute une nouvelle tuile (selon les probabilités définies)
|
||||
* sur une case vide aléatoire du plateau. Ne fait rien si le plateau est plein.
|
||||
*/
|
||||
public void saveGameState() {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
String serializedState = toString(); // Utilise la méthode toString() pour la sérialisation
|
||||
editor.putString(GAME_STATE_KEY, serializedState);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une nouvelle tuile aléatoire à une position aléatoire vide sur le plateau si possible.
|
||||
*/
|
||||
public void addNewTile() { // Renommé
|
||||
if (!hasEmptyCell()) {
|
||||
return; // Ne fait rien si le plateau est plein
|
||||
}
|
||||
|
||||
public void addNewTile() {
|
||||
if (!hasEmptyCell()) { return; }
|
||||
List<int[]> emptyCells = new ArrayList<>();
|
||||
|
||||
// 1. Collecter les coordonnées des cellules vides.
|
||||
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 (board[row][col] == 0) { emptyCells.add(new int[]{row, col}); }
|
||||
}
|
||||
}
|
||||
|
||||
// 2. S'il y a des cellules vides, ajouter une nouvelle tuile.
|
||||
if (!emptyCells.isEmpty()) {
|
||||
int[] randomCell = emptyCells.get(getRandomNumberGenerator().nextInt(emptyCells.size()));
|
||||
int row = randomCell[0];
|
||||
int col = randomCell[1];
|
||||
|
||||
int value = generateRandomTileValue(); // Utilise la nouvelle logique de probabilité
|
||||
setCellValue(row, col, value);
|
||||
int[] randomCell = emptyCells.get(randomNumberGenerator.nextInt(emptyCells.size()));
|
||||
int value = generateRandomTileValue();
|
||||
setCellValue(randomCell[0], randomCell[1], value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une valeur de tuile aléatoire selon les probabilités définies.
|
||||
*
|
||||
* @return La valeur de la nouvelle tuile (2, 4, 8, ...) avec les probabilités spécifiées.
|
||||
* Génère aléatoirement la valeur d'une nouvelle tuile (2, 4, 8, etc.)
|
||||
* selon des probabilités prédéfinies.
|
||||
* @return La valeur de la nouvelle tuile.
|
||||
*/
|
||||
private int generateRandomTileValue() { // Logique de probabilité modifiée
|
||||
private int generateRandomTileValue() {
|
||||
int randomValue = randomNumberGenerator.nextInt(10000);
|
||||
|
||||
if (randomValue < 8540) { // 85.40%
|
||||
return 2;
|
||||
} else if (randomValue < 9740) { // 12.00%
|
||||
return 4;
|
||||
} else if (randomValue < 9940) { // 2.00%
|
||||
return 8;
|
||||
} else if (randomValue < 9990) { // 0.50%
|
||||
return 16;
|
||||
} else if (randomValue < 9995) { // 0.05%
|
||||
return 32;
|
||||
} else if (randomValue < 9998) { // 0.03%
|
||||
return 64;
|
||||
} else if (randomValue < 9999) { // 0.01%
|
||||
return 128;
|
||||
}else { // 0.01%
|
||||
return 256;
|
||||
}
|
||||
if (randomValue < 8540) return 2; // ~85%
|
||||
if (randomValue < 9740) return 4; // ~12%
|
||||
if (randomValue < 9940) return 8; // ~2%
|
||||
if (randomValue < 9990) return 16; // ~0.5%
|
||||
// ... (autres probabilités)
|
||||
if (randomValue < 9995) return 32;
|
||||
if (randomValue < 9998) return 64;
|
||||
if (randomValue < 9999) return 128;
|
||||
return 256;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplace les tuiles vers le haut, en fusionnant les tuiles de même valeur.
|
||||
*
|
||||
* @return True si le plateau a été modifié, False sinon.
|
||||
* Tente de déplacer et fusionner les tuiles vers le HAUT.
|
||||
* Met à jour le score interne en cas de fusion.
|
||||
* Vérifie les conditions de victoire/défaite après le mouvement.
|
||||
* @return true si au moins une tuile a bougé ou fusionné, false sinon.
|
||||
*/
|
||||
public boolean pushUp() { // Logique refactorisée, retourne boolean, utilise hasMerged
|
||||
boolean boardChanged = false;
|
||||
boolean[] hasMerged = new boolean[BOARD_SIZE];
|
||||
|
||||
public boolean pushUp() {
|
||||
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||
hasMerged = new boolean[BOARD_SIZE]; // Réinitialise par colonne
|
||||
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;
|
||||
|
||||
// 1. Déplacer la tuile
|
||||
while (currentRow > 0 && getCellValue(currentRow - 1, col) == 0) {
|
||||
setCellValue(currentRow - 1, col, currentValue);
|
||||
setCellValue(currentRow, col, 0);
|
||||
currentRow--;
|
||||
boardChanged = true;
|
||||
}
|
||||
|
||||
// 2. Fusionner si possible
|
||||
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); // La tuile d'origine disparaît
|
||||
setCurrentScore(getCurrentScore() + newValue);
|
||||
hasMerged[currentRow - 1] = true;
|
||||
boardChanged = true;
|
||||
updateHighestScore(); // Met à jour le meilleur score si nécessaire
|
||||
// checkWinCondition(); // Vérifie si la victoire est atteinte
|
||||
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(); // Vérifie après tous les mouvements de la colonne
|
||||
checkGameOverCondition(); // Vérifie si la partie est terminée
|
||||
return boardChanged;
|
||||
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Déplace les tuiles vers le bas, en fusionnant les tuiles de même valeur.
|
||||
*
|
||||
* @return True si le plateau a été modifié, False sinon.
|
||||
* Tente de déplacer et fusionner les tuiles vers le BAS.
|
||||
* @return true si changement, false sinon.
|
||||
*/
|
||||
public boolean pushDown() { // Logique refactorisée
|
||||
boolean boardChanged = false;
|
||||
boolean[] hasMerged = new boolean[BOARD_SIZE];
|
||||
|
||||
public boolean pushDown() {
|
||||
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--) { // Itère de bas en haut
|
||||
for (int row = BOARD_SIZE - 2; row >= 0; row--) {
|
||||
if (getCellValue(row, col) != 0) {
|
||||
int currentValue = getCellValue(row, col);
|
||||
int currentRow = row;
|
||||
|
||||
// Déplacer vers le bas
|
||||
while (currentRow < BOARD_SIZE - 1 && getCellValue(currentRow + 1, col) == 0) {
|
||||
setCellValue(currentRow + 1, col, currentValue);
|
||||
setCellValue(currentRow, col, 0);
|
||||
currentRow++;
|
||||
boardChanged = true;
|
||||
}
|
||||
|
||||
// Fusionner si possible
|
||||
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);
|
||||
setCurrentScore(getCurrentScore() + newValue);
|
||||
hasMerged[currentRow + 1] = true;
|
||||
boardChanged = true;
|
||||
updateHighestScore();
|
||||
// checkWinCondition();
|
||||
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;
|
||||
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplace les tuiles vers la gauche, en fusionnant les tuiles de même valeur.
|
||||
*
|
||||
* @return True si le plateau a été modifié, False sinon.
|
||||
* Tente de déplacer et fusionner les tuiles vers la GAUCHE.
|
||||
* @return true si changement, false sinon.
|
||||
*/
|
||||
public boolean pushLeft() { // Logique refactorisée
|
||||
boolean boardChanged = false;
|
||||
boolean[] hasMerged = new boolean[BOARD_SIZE]; // Par ligne cette fois
|
||||
|
||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||
hasMerged = new boolean[BOARD_SIZE]; // Réinitialise par ligne
|
||||
for (int col = 1; col < BOARD_SIZE; col++) { // Commence à la 2ème colonne
|
||||
if (getCellValue(row, col) != 0) {
|
||||
int currentValue = getCellValue(row, col);
|
||||
int currentCol = col;
|
||||
|
||||
// Déplacer vers la gauche
|
||||
while (currentCol > 0 && getCellValue(row, currentCol - 1) == 0) {
|
||||
setCellValue(row, currentCol - 1, currentValue);
|
||||
setCellValue(row, currentCol, 0);
|
||||
currentCol--;
|
||||
boardChanged = true;
|
||||
}
|
||||
|
||||
// Fusionner si possible
|
||||
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);
|
||||
setCurrentScore(getCurrentScore() + newValue);
|
||||
hasMerged[currentCol - 1] = true;
|
||||
boardChanged = true;
|
||||
updateHighestScore();
|
||||
// checkWinCondition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkWinCondition();
|
||||
checkGameOverCondition();
|
||||
return boardChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplace les tuiles vers la droite, en fusionnant les tuiles de même valeur.
|
||||
*
|
||||
* @return True si le plateau a été modifié, False sinon.
|
||||
*/
|
||||
public boolean pushRight() { // Logique refactorisée
|
||||
boolean boardChanged = false;
|
||||
boolean[] hasMerged = new boolean[BOARD_SIZE]; // Par ligne
|
||||
|
||||
public boolean pushLeft() {
|
||||
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 = BOARD_SIZE - 2; col >= 0; col--) { // Itère de droite à gauche
|
||||
for (int col = 1; col < BOARD_SIZE; col++) {
|
||||
if (getCellValue(row, col) != 0) {
|
||||
int currentValue = getCellValue(row, col);
|
||||
int currentCol = col;
|
||||
|
||||
// Déplacer vers la droite
|
||||
while (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == 0) {
|
||||
setCellValue(row, currentCol + 1, currentValue);
|
||||
setCellValue(row, currentCol, 0);
|
||||
currentCol++;
|
||||
boardChanged = true;
|
||||
}
|
||||
|
||||
// Fusionner si possible
|
||||
if (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == currentValue && !hasMerged[currentCol + 1]) {
|
||||
int newValue = getCellValue(row, currentCol + 1) * 2;
|
||||
setCellValue(row, currentCol + 1, newValue);
|
||||
setCellValue(row, currentCol, 0);
|
||||
setCurrentScore(getCurrentScore() + newValue);
|
||||
hasMerged[currentCol + 1] = true;
|
||||
boardChanged = true;
|
||||
updateHighestScore();
|
||||
// checkWinCondition();
|
||||
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;
|
||||
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Met à jour le meilleur score si le score actuel le dépasse, et sauvegarde le nouveau meilleur score.
|
||||
* Tente de déplacer et fusionner les tuiles vers la DROITE.
|
||||
* @return true si changement, false sinon.
|
||||
*/
|
||||
private void updateHighestScore() { // Renommé
|
||||
if (getCurrentScore() > getHighestScore()) {
|
||||
setHighestScore(getCurrentScore());
|
||||
saveHighScore(); // Sauvegarde implémentée
|
||||
}
|
||||
public boolean pushRight() {
|
||||
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 = BOARD_SIZE - 2; col >= 0; col--) {
|
||||
if (getCellValue(row, col) != 0) {
|
||||
int currentValue = getCellValue(row, col); int currentCol = col;
|
||||
while (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == 0) { setCellValue(row, currentCol + 1, currentValue); setCellValue(row, currentCol, 0); currentCol++; boardChanged = true; }
|
||||
if (currentCol < BOARD_SIZE - 1 && 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sérialise l'état du jeu (plateau, score courant, meilleur score) en une chaîne de caractères.
|
||||
* Format: "row1col1,row1col2,...,row1colN,row2col1,...,rowNcolN,currentScore,highestScore"
|
||||
*
|
||||
* @return Une chaîne représentant l'état complet du jeu.
|
||||
* Sérialise l'état actuel du jeu (plateau et score courant) en une chaîne.
|
||||
* Format: "val,val,...,val,score"
|
||||
* @return Chaîne sérialisée.
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() { // Renommé et Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||
sb.append(board[row][col]).append(","); // Utilise board directement
|
||||
}
|
||||
for (int col = 0; col < BOARD_SIZE; col++) { sb.append(board[row][col]).append(","); }
|
||||
}
|
||||
|
||||
sb.append(getCurrentScore()).append(",");
|
||||
sb.append(getHighestScore()); // Sérialise highestScore
|
||||
|
||||
sb.append(currentScore);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Désérialise une chaîne de caractères pour restaurer l'état du jeu.
|
||||
*
|
||||
* @param serializedState La chaîne représentant l'état du jeu (au format de la méthode toString).
|
||||
* @param context Le contexte Android (nécessaire pour le constructeur de Game).
|
||||
* @return Un nouvel objet Game restauré, ou null si la désérialisation échoue.
|
||||
* Crée un nouvel objet Game à partir d'une chaîne sérialisée (plateau + score).
|
||||
* @param serializedState Chaîne générée par toString().
|
||||
* @return Nouvel objet Game, ou null si la désérialisation échoue.
|
||||
*/
|
||||
public static Game deserialize(String serializedState, Context context) { // Logique améliorée
|
||||
if (serializedState == null || serializedState.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Game deserialize(String serializedState) {
|
||||
if (serializedState == null || serializedState.isEmpty()) return null;
|
||||
String[] values = serializedState.split(",");
|
||||
// Vérifie si le nombre d'éléments correspond à la taille attendue (board + score + highScore)
|
||||
if (values.length != (BOARD_SIZE * BOARD_SIZE + 2)) {
|
||||
System.err.println("Erreur de désérialisation : nombre d'éléments incorrect. Attendu=" + (BOARD_SIZE * BOARD_SIZE + 2) + ", Obtenu=" + values.length);
|
||||
return null; // Longueur incorrecte
|
||||
}
|
||||
|
||||
int[][] newBoard = new int[BOARD_SIZE][BOARD_SIZE];
|
||||
int index = 0;
|
||||
|
||||
if (values.length != (BOARD_SIZE * BOARD_SIZE + 1)) return null;
|
||||
int[][] newBoard = new int[BOARD_SIZE][BOARD_SIZE]; int index = 0;
|
||||
try {
|
||||
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 highScore = Integer.parseInt(values[index++]); // Désérialise highScore
|
||||
|
||||
// Utilise le constructeur qui prend le contexte
|
||||
return new Game(newBoard, score, highScore, context);
|
||||
|
||||
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
|
||||
System.err.println("Erreur de désérialisation : " + e.getMessage());
|
||||
// e.printStackTrace(); // Optionnel: pour plus de détails dans Logcat
|
||||
return null; // Erreur de format ou index hors limites
|
||||
}
|
||||
int score = Integer.parseInt(values[index]);
|
||||
return new Game(newBoard, score);
|
||||
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { return null; }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Vérifie si la condition de victoire (tuile 2048) est remplie.
|
||||
* Met à jour la variable gameWon.
|
||||
* Vérifie si une tuile >= 2048 existe sur le plateau et met à jour l'état `gameWon`.
|
||||
*/
|
||||
private void checkWinCondition() { // Ajouté
|
||||
// Ne vérifie que si la partie n'est pas déjà gagnée
|
||||
if (!isGameWon()) {
|
||||
for(int row = 0 ; row < BOARD_SIZE; row++){
|
||||
for (int col = 0; col < BOARD_SIZE; col++){
|
||||
if(getCellValue(row, col) >= 2048){ // Condition >= 2048
|
||||
setGameWon(true);
|
||||
// Optionnel : On pourrait arrêter la vérification ici
|
||||
// return;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void checkWinCondition() {
|
||||
if (!gameWon) { // Optimisation: inutile de revérifier si déjà gagné
|
||||
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 si la condition de fin de partie (aucun mouvement possible) est remplie.
|
||||
* Met à jour la variable gameOver.
|
||||
* Vérifie s'il reste des mouvements possibles (case vide ou fusion adjacente).
|
||||
* Met à jour l'état `gameOver`.
|
||||
*/
|
||||
private void checkGameOverCondition() { // Ajouté
|
||||
// Si une case est vide, la partie n'est pas terminée
|
||||
if (hasEmptyCell()) {
|
||||
setGameOver(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifie les fusions possibles horizontalement et verticalement
|
||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||
int currentValue = getCellValue(row, col);
|
||||
|
||||
// Vérifie voisin du haut
|
||||
if (row > 0 && getCellValue(row - 1, col) == currentValue) {
|
||||
setGameOver(false);
|
||||
return;
|
||||
}
|
||||
// Vérifie voisin du bas
|
||||
if (row < BOARD_SIZE - 1 && getCellValue(row + 1, col) == currentValue) {
|
||||
setGameOver(false);
|
||||
return;
|
||||
}
|
||||
// Vérifie voisin de gauche
|
||||
if (col > 0 && getCellValue(row, col - 1) == currentValue) {
|
||||
setGameOver(false);
|
||||
return;
|
||||
}
|
||||
// Vérifie voisin de droite
|
||||
if (col < BOARD_SIZE - 1 && getCellValue(row, col + 1) == currentValue) {
|
||||
setGameOver(false);
|
||||
return;
|
||||
}
|
||||
private void checkGameOverCondition() {
|
||||
if (hasEmptyCell()) { setGameOver(false); return; } // Si case vide, pas game over
|
||||
// Vérifie fusions adjacentes possibles
|
||||
for(int r=0; r<BOARD_SIZE; r++) for(int c=0; c<BOARD_SIZE; 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) ||
|
||||
(c>0 && getCellValue(r,c-1)==current) || (c<BOARD_SIZE-1 && getCellValue(r,c+1)==current)) {
|
||||
setGameOver(false); return; // Fusion possible, pas game over
|
||||
}
|
||||
}
|
||||
|
||||
// Si aucune case vide et aucune fusion possible, la partie est terminée
|
||||
setGameOver(true);
|
||||
setGameOver(true); // Aucune case vide et aucune fusion -> game over
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie s'il existe au moins une cellule vide sur le plateau.
|
||||
*
|
||||
* @return true s'il y a une cellule vide, false sinon.
|
||||
* @return true s'il y a au moins une case vide sur le plateau, false sinon.
|
||||
*/
|
||||
private boolean hasEmptyCell() { // Ajouté
|
||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||
if (getCellValue(row, col) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve et retourne la valeur de la tuile la plus élevée actuellement sur le plateau.
|
||||
* @return La valeur maximale trouvée.
|
||||
*/
|
||||
public int getHighestTileValue() {
|
||||
int maxTile = 0;
|
||||
for(int r=0; r<BOARD_SIZE; r++) for(int c=0; c<BOARD_SIZE; c++) if(board[r][c]>maxTile) maxTile=board[r][c];
|
||||
return maxTile;
|
||||
}
|
||||
}
|
274
app/src/main/java/legion/muyue/best2048/GameStats.java
Normal file
274
app/src/main/java/legion/muyue/best2048/GameStats.java
Normal file
@ -0,0 +1,274 @@
|
||||
// Fichier GameStats.java
|
||||
// Gère le stockage, le chargement et la mise à jour des statistiques du jeu 2048.
|
||||
/*
|
||||
Fonctions principales :
|
||||
- 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GameStats {
|
||||
|
||||
// Clés SharedPreferences (inchangées)
|
||||
private static final String PREFS_NAME = "Best2048_Prefs";
|
||||
private static final String HIGH_SCORE_KEY = "high_score";
|
||||
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_MOVES = "totalMoves";
|
||||
private static final String STATS_TOTAL_PLAY_TIME_MS = "totalPlayTimeMs";
|
||||
private static final String STATS_TOTAL_MERGES = "totalMerges";
|
||||
private static final String STATS_HIGHEST_TILE = "highestTile";
|
||||
private static final String STATS_OBJECTIVE_REACHED_COUNT = "numberOfTimesObjectiveReached";
|
||||
private static final String STATS_PERFECT_GAMES = "perfectGames";
|
||||
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_MP_GAMES_WON = "multiplayerGamesWon";
|
||||
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_TOTAL_SCORE = "multiplayerTotalScore";
|
||||
private static final String STATS_MP_TOTAL_TIME_MS = "multiplayerTotalTimeMs";
|
||||
private static final String STATS_MP_LOSSES = "totalMultiplayerLosses";
|
||||
private static final String STATS_MP_HIGH_SCORE = "multiplayerHighScore";
|
||||
|
||||
// Champs statistiques (inchangés)
|
||||
private int totalGamesPlayed;
|
||||
private int totalGamesStarted;
|
||||
private int totalMoves;
|
||||
private int currentMoves;
|
||||
private long totalPlayTimeMs;
|
||||
private long currentGameStartTimeMs;
|
||||
private int mergesThisGame;
|
||||
private int totalMerges;
|
||||
private int highestTile;
|
||||
private int numberOfTimesObjectiveReached;
|
||||
private int perfectGames;
|
||||
private long bestWinningTimeMs;
|
||||
private long worstWinningTimeMs;
|
||||
private int multiplayerGamesWon;
|
||||
private int multiplayerGamesPlayed;
|
||||
private int multiplayerBestWinningStreak;
|
||||
private long multiplayerTotalScore;
|
||||
private long multiplayerTotalTimeMs;
|
||||
private int totalMultiplayerLosses;
|
||||
private int multiplayerHighestScore;
|
||||
private int overallHighScore;
|
||||
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* Constructeur de GameStats.
|
||||
* Charge immédiatement les statistiques sauvegardées via SharedPreferences.
|
||||
* @param context Le contexte de l'application (nécessaire pour SharedPreferences).
|
||||
*/
|
||||
public GameStats(Context context) {
|
||||
this.context = context;
|
||||
loadStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge toutes les statistiques (générales et multijoueur) et le high score global
|
||||
* depuis les SharedPreferences.
|
||||
*/
|
||||
public void loadStats() {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
overallHighScore = prefs.getInt(HIGH_SCORE_KEY, 0);
|
||||
totalGamesPlayed = prefs.getInt(STATS_TOTAL_GAMES_PLAYED, 0);
|
||||
totalGamesStarted = prefs.getInt(STATS_TOTAL_GAMES_STARTED, 0);
|
||||
totalMoves = prefs.getInt(STATS_TOTAL_MOVES, 0);
|
||||
totalPlayTimeMs = prefs.getLong(STATS_TOTAL_PLAY_TIME_MS, 0);
|
||||
totalMerges = prefs.getInt(STATS_TOTAL_MERGES, 0);
|
||||
highestTile = prefs.getInt(STATS_HIGHEST_TILE, 0);
|
||||
numberOfTimesObjectiveReached = prefs.getInt(STATS_OBJECTIVE_REACHED_COUNT, 0);
|
||||
perfectGames = prefs.getInt(STATS_PERFECT_GAMES, 0);
|
||||
bestWinningTimeMs = prefs.getLong(STATS_BEST_WINNING_TIME_MS, Long.MAX_VALUE);
|
||||
worstWinningTimeMs = prefs.getLong(STATS_WORST_WINNING_TIME_MS, 0);
|
||||
multiplayerGamesWon = prefs.getInt(STATS_MP_GAMES_WON, 0);
|
||||
multiplayerGamesPlayed = prefs.getInt(STATS_MP_GAMES_PLAYED, 0);
|
||||
multiplayerBestWinningStreak = prefs.getInt(STATS_MP_BEST_WINNING_STREAK, 0);
|
||||
multiplayerTotalScore = prefs.getLong(STATS_MP_TOTAL_SCORE, 0);
|
||||
multiplayerTotalTimeMs = prefs.getLong(STATS_MP_TOTAL_TIME_MS, 0);
|
||||
totalMultiplayerLosses = prefs.getInt(STATS_MP_LOSSES, 0);
|
||||
multiplayerHighestScore = prefs.getInt(STATS_MP_HIGH_SCORE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sauvegarde toutes les statistiques (générales et multijoueur) et le high score global
|
||||
* dans les SharedPreferences.
|
||||
*/
|
||||
public void saveStats() {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(HIGH_SCORE_KEY, overallHighScore);
|
||||
editor.putInt(STATS_TOTAL_GAMES_PLAYED, totalGamesPlayed);
|
||||
editor.putInt(STATS_TOTAL_GAMES_STARTED, totalGamesStarted);
|
||||
editor.putInt(STATS_TOTAL_MOVES, totalMoves);
|
||||
editor.putLong(STATS_TOTAL_PLAY_TIME_MS, totalPlayTimeMs);
|
||||
editor.putInt(STATS_TOTAL_MERGES, totalMerges);
|
||||
editor.putInt(STATS_HIGHEST_TILE, highestTile);
|
||||
editor.putInt(STATS_OBJECTIVE_REACHED_COUNT, numberOfTimesObjectiveReached);
|
||||
editor.putInt(STATS_PERFECT_GAMES, perfectGames);
|
||||
editor.putLong(STATS_BEST_WINNING_TIME_MS, bestWinningTimeMs);
|
||||
editor.putLong(STATS_WORST_WINNING_TIME_MS, worstWinningTimeMs);
|
||||
editor.putInt(STATS_MP_GAMES_WON, multiplayerGamesWon);
|
||||
editor.putInt(STATS_MP_GAMES_PLAYED, multiplayerGamesPlayed);
|
||||
editor.putInt(STATS_MP_BEST_WINNING_STREAK, multiplayerBestWinningStreak);
|
||||
editor.putLong(STATS_MP_TOTAL_SCORE, multiplayerTotalScore);
|
||||
editor.putLong(STATS_MP_TOTAL_TIME_MS, multiplayerTotalTimeMs);
|
||||
editor.putInt(STATS_MP_LOSSES, totalMultiplayerLosses);
|
||||
editor.putInt(STATS_MP_HIGH_SCORE, multiplayerHighestScore);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les statistiques lors du démarrage d'une nouvelle partie.
|
||||
* Incrémente le nombre de parties démarrées et réinitialise les compteurs de la partie en cours.
|
||||
*/
|
||||
public void startGame() {
|
||||
totalGamesStarted++;
|
||||
currentMoves = 0;
|
||||
mergesThisGame = 0;
|
||||
currentGameStartTimeMs = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un mouvement effectué pendant la partie en cours.
|
||||
* Incrémente le compteur de mouvements de la partie et le compteur total.
|
||||
*/
|
||||
public void recordMove() {
|
||||
currentMoves++;
|
||||
totalMoves++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une ou plusieurs fusions survenues pendant la partie en cours.
|
||||
* @param numberOfMerges Le nombre de fusions à ajouter (idéalement précis).
|
||||
*/
|
||||
public void recordMerge(int numberOfMerges) {
|
||||
if (numberOfMerges > 0) {
|
||||
mergesThisGame += numberOfMerges;
|
||||
totalMerges += numberOfMerges;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la statistique de la plus haute tuile atteinte si la valeur fournie est supérieure.
|
||||
* @param tileValue La valeur de la tuile candidate.
|
||||
*/
|
||||
public void updateHighestTile(int tileValue) {
|
||||
if (tileValue > this.highestTile) {
|
||||
this.highestTile = tileValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une victoire et met à jour les statistiques associées (temps, nombre de victoires).
|
||||
* @param timeTakenMs Le temps mis pour gagner cette partie en millisecondes.
|
||||
*/
|
||||
public void recordWin(long timeTakenMs) {
|
||||
numberOfTimesObjectiveReached++;
|
||||
endGame(timeTakenMs); // Finalise les stats de la partie
|
||||
|
||||
if (timeTakenMs < bestWinningTimeMs) { bestWinningTimeMs = timeTakenMs; }
|
||||
if (timeTakenMs > worstWinningTimeMs) { worstWinningTimeMs = timeTakenMs; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une défaite et finalise les statistiques de la partie.
|
||||
*/
|
||||
public void recordLoss() {
|
||||
endGame(System.currentTimeMillis() - currentGameStartTimeMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalise les statistiques à la fin d'une partie (incrémente parties jouées, ajoute temps de jeu).
|
||||
* @param timeTakenMs Le temps total de la partie qui vient de se terminer.
|
||||
*/
|
||||
public void endGame(long timeTakenMs) {
|
||||
totalGamesPlayed++;
|
||||
addPlayTime(timeTakenMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une durée au temps de jeu total cumulé.
|
||||
* @param durationMs Durée à ajouter en millisecondes.
|
||||
*/
|
||||
public void addPlayTime(long durationMs) {
|
||||
if (durationMs > 0) {
|
||||
this.totalPlayTimeMs += durationMs;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Getters ---
|
||||
public int getTotalGamesPlayed() { return totalGamesPlayed; }
|
||||
public int getTotalGamesStarted() { return totalGamesStarted; }
|
||||
public int getTotalMoves() { return totalMoves; }
|
||||
public int getCurrentMoves() { return currentMoves; }
|
||||
public long getTotalPlayTimeMs() { return totalPlayTimeMs; }
|
||||
public long getCurrentGameStartTimeMs() { return currentGameStartTimeMs; }
|
||||
public int getMergesThisGame() { return mergesThisGame; }
|
||||
public int getTotalMerges() { return totalMerges; }
|
||||
public int getHighestTile() { return highestTile; }
|
||||
public int getNumberOfTimesObjectiveReached() { return numberOfTimesObjectiveReached; }
|
||||
public int getPerfectGames() { return perfectGames; }
|
||||
public long getBestWinningTimeMs() { return bestWinningTimeMs; }
|
||||
public long getWorstWinningTimeMs() { return worstWinningTimeMs; }
|
||||
public int getMultiplayerGamesWon() { return multiplayerGamesWon; }
|
||||
public int getMultiplayerGamesPlayed() { return multiplayerGamesPlayed; }
|
||||
public int getMultiplayerBestWinningStreak() { return multiplayerBestWinningStreak; }
|
||||
public long getMultiplayerTotalScore() { return multiplayerTotalScore; }
|
||||
public long getMultiplayerTotalTimeMs() { return multiplayerTotalTimeMs; }
|
||||
public int getTotalMultiplayerLosses() { return totalMultiplayerLosses; }
|
||||
public int getMultiplayerHighestScore() { return multiplayerHighestScore; }
|
||||
public int getOverallHighScore() { return overallHighScore; } // Getter pour le HS global
|
||||
|
||||
// --- Setters ---
|
||||
/**
|
||||
* 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) {
|
||||
// On met à jour seulement si la valeur externe est supérieure,
|
||||
// car GameStats gère aussi la mise à jour via les scores des parties.
|
||||
// Ou plus simplement, on fait confiance à la valeur lue par MainActivity.
|
||||
this.overallHighScore = highScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// --- Méthodes calculées ---
|
||||
public long getAverageGameTimeMs() { return (totalGamesPlayed > 0) ? totalPlayTimeMs / totalGamesPlayed : 0; }
|
||||
public int getMultiplayerAverageScore() { return (multiplayerGamesPlayed > 0) ? (int)(multiplayerTotalScore / multiplayerGamesPlayed) : 0; }
|
||||
public long getMultiplayerAverageTimeMs() { return (multiplayerGamesPlayed > 0) ? multiplayerTotalTimeMs / multiplayerGamesPlayed : 0; }
|
||||
|
||||
/**
|
||||
* Formate une durée en millisecondes en chaîne "hh:mm:ss" ou "mm:ss".
|
||||
* @param milliseconds Durée en millisecondes.
|
||||
* @return Chaîne de temps formatée.
|
||||
*/
|
||||
public static String formatTime(long milliseconds) {
|
||||
long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
|
||||
long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
|
||||
long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60;
|
||||
if (hours > 0) { return String.format("%02d:%02d:%02d", hours, minutes, seconds); }
|
||||
else { return String.format("%02d:%02d", minutes, seconds); }
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -143,7 +143,6 @@ public class OnSwipeTouchListener implements View.OnTouchListener {
|
||||
}
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
// exception.printStackTrace(); // Commenté dans le nouveau code ? Gardons commenté.
|
||||
exception.fillInStackTrace(); // Gestion des erreurs (journalisation).
|
||||
}
|
||||
return result;
|
||||
|
Loading…
x
Reference in New Issue
Block a user