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
|
// 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 :
|
Fonctions principales :
|
||||||
- gameBoard : Matrice 2D (int[][]) représentant la grille du jeu.
|
- board : Matrice 2D (int[][]) représentant la grille du jeu.
|
||||||
- score, highScore : Suivi du score et du meilleur score.
|
- currentScore : Suivi du score de la partie en cours.
|
||||||
- addNewNumbers() : Ajoute une nouvelle tuile (2, 4 ou 8) aléatoirement sur la grille.
|
- highestScore : Stocke le meilleur score global (reçu de l'extérieur via setHighestScore).
|
||||||
- pushUp(), pushDown(), pushLeft(), pushRight() : Logique de déplacement et de fusion des tuiles.
|
- addNewTile() : Ajoute une nouvelle tuile aléatoire sur une case vide.
|
||||||
- serialize(), deserialize() : Sauvegarde et restauration de l'état du jeu (grille, score, meilleur score) en chaînes de caractères.
|
- pushUp(), pushDown(), pushLeft(), pushRight() : Gèrent la logique de déplacement et de fusion, retournent un booléen indiquant si le plateau a changé.
|
||||||
- loadHighScore(), saveHighScore(), loadGameState(), saveGameState() : Utilisation de SharedPreferences pour persister les données.
|
- getHighestTileValue() : Retourne la valeur de la plus haute tuile sur le plateau.
|
||||||
- Constructeurs : Initialisation de la grille et chargement/restauration des données.
|
- É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 :
|
Relations :
|
||||||
- MainActivity : Crée une instance de Game et appelle ses méthodes pour la logique du jeu. MainActivity affiche 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.
|
||||||
- SharedPreferences : Game utilise SharedPreferences pour sauvegarder et charger le meilleur score et l'état du jeu.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package legion.muyue.best2048;
|
package legion.muyue.best2048;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
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 {
|
||||||
|
|
||||||
private int[][] board; // Renommé
|
private int[][] board;
|
||||||
private final Random randomNumberGenerator; // Renommé
|
private final Random randomNumberGenerator;
|
||||||
private int currentScore = 0; // Renommé
|
private int currentScore = 0;
|
||||||
private int highestScore = 0; // Renommé
|
private int highestScore = 0; // Stocke le HS fourni par MainActivity
|
||||||
private static final int BOARD_SIZE = 4; // Ajout constante
|
private static final int BOARD_SIZE = 4;
|
||||||
private static final String PREFS_NAME = "Best2048_Prefs"; // Ajout constante
|
private boolean gameWon = false;
|
||||||
private static final String HIGH_SCORE_KEY = "high_score"; // Ajout constante
|
private boolean gameOver = false;
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur principal de la classe Game.
|
* Constructeur pour une nouvelle partie.
|
||||||
*
|
* Initialise un plateau vide, définit le score à 0 et ajoute deux tuiles initiales.
|
||||||
* @param context Le contexte Android (généralement une Activity). Nécessaire pour accéder aux SharedPreferences.
|
|
||||||
*/
|
*/
|
||||||
public Game(Context context) {
|
public Game() {
|
||||||
this.context = context;
|
|
||||||
this.randomNumberGenerator = new Random();
|
this.randomNumberGenerator = new Random();
|
||||||
this.board = new int[BOARD_SIZE][BOARD_SIZE];
|
// Le highScore sera défini par MainActivity après l'instanciation.
|
||||||
loadHighScore();
|
initializeNewBoard();
|
||||||
loadGameState(); // Charge l'état précédent ou initialise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur pour la restauration d'une partie précédemment sauvegardée.
|
* Constructeur utilisé lors de la restauration d'une partie sauvegardée.
|
||||||
*
|
* @param board Le plateau de jeu restauré.
|
||||||
* @param board Le plateau de jeu restauré (l'état des tuiles).
|
* @param score Le score courant restauré.
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
public Game(int[][] board, int score, int highScore, Context context) {
|
public Game(int[][] board, int score) {
|
||||||
this.board = board;
|
this.board = board;
|
||||||
this.currentScore = score;
|
this.currentScore = score;
|
||||||
this.highestScore = highScore;
|
|
||||||
this.context = context;
|
|
||||||
this.randomNumberGenerator = new Random();
|
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.
|
* Retourne la valeur de la cellule aux coordonnées spécifiées.
|
||||||
*
|
* @param row Ligne de la cellule (0-based).
|
||||||
* @param row L'index de la ligne (0 à BOARD_SIZE - 1).
|
* @param column Colonne de la cellule (0-based).
|
||||||
* @param column L'index de la colonne (0 à BOARD_SIZE - 1).
|
* @return Valeur de la cellule, ou 0 si indices invalides (sécurité).
|
||||||
* @return La valeur de la cellule à la position spécifiée.
|
|
||||||
* @throws IllegalArgumentException Si row ou column sont en dehors des limites du plateau.
|
|
||||||
*/
|
*/
|
||||||
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) {
|
if (row < 0 || row >= BOARD_SIZE || column < 0 || column >= BOARD_SIZE) { return 0; }
|
||||||
throw new IllegalArgumentException("Indices de ligne ou de colonne hors limites : row=" + row + ", column=" + column);
|
|
||||||
}
|
|
||||||
return this.board[row][column];
|
return this.board[row][column];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Définit la valeur d'une cellule spécifique sur le plateau.
|
* Définit la valeur de la cellule aux coordonnées spécifiées.
|
||||||
*
|
* @param row Ligne de la cellule (0-based).
|
||||||
* @param row L'index de la ligne (0 à BOARD_SIZE - 1).
|
* @param col Colonne de la cellule (0-based).
|
||||||
* @param col L'index de la colonne (0 à BOARD_SIZE - 1).
|
* @param value Nouvelle valeur.
|
||||||
* @param value La nouvelle valeur de la cellule.
|
|
||||||
* @throws IllegalArgumentException Si row ou col sont en dehors des limites du plateau.
|
|
||||||
*/
|
*/
|
||||||
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) {
|
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) { return; }
|
||||||
throw new IllegalArgumentException("Indices de ligne ou de colonne hors limites : row=" + row + ", col=" + col);
|
|
||||||
}
|
|
||||||
this.board[row][col] = value;
|
this.board[row][col] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @return Le score actuel de la partie. */
|
||||||
* Récupère le score actuel de la partie.
|
public int getCurrentScore() { return currentScore; }
|
||||||
*
|
|
||||||
* @return Le score actuel.
|
// public void setCurrentScore(int currentScore) { this.currentScore = currentScore; } // Setter interne si nécessaire
|
||||||
*/
|
|
||||||
public int getCurrentScore() {
|
/** @return Le meilleur score connu par cet objet Game (synchronisé par MainActivity). */
|
||||||
return this.currentScore;
|
public int getHighestScore() { return highestScore; }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Met à jour le 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 currentScore Le nouveau score actuel.
|
* @param highScore Le meilleur score connu.
|
||||||
*/
|
*/
|
||||||
public void setCurrentScore(int currentScore) {
|
public void setHighestScore(int highScore) { this.highestScore = highScore; }
|
||||||
this.currentScore = currentScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** @return L'état de victoire de la partie (true si >= 2048 atteint). */
|
||||||
* Récupère le meilleur score enregistré.
|
public boolean isGameWon() { return gameWon; }
|
||||||
*
|
|
||||||
* @return Le meilleur score.
|
|
||||||
*/
|
|
||||||
public int getHighestScore() {
|
|
||||||
return this.highestScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void setGameWon(boolean gameWon) { this.gameWon = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** @return L'état de fin de partie (true si aucun mouvement possible). */
|
||||||
* Met à jour le board. Utilisé lors du chargement.
|
public boolean isGameOver() { return gameOver; }
|
||||||
*
|
|
||||||
* @param board Le nouveau board.
|
|
||||||
*/
|
|
||||||
public void setBoard(int[][] board){
|
|
||||||
this.board = board;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void setGameOver(boolean gameOver) { this.gameOver = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** @return Une copie du plateau de jeu actuel (pour la sauvegarde externe). */
|
||||||
* Indique si le joueur a gagné la partie (atteint une tuile de 2048).
|
public int[][] getBoard() {
|
||||||
*
|
// Retourne une copie pour éviter modifications externes accidentelles
|
||||||
* @return true si le joueur a gagné, false sinon.
|
int[][] copy = new int[BOARD_SIZE][BOARD_SIZE];
|
||||||
*/
|
for(int i=0; i<BOARD_SIZE; i++) {
|
||||||
public boolean isGameWon() {
|
System.arraycopy(this.board[i], 0, copy[i], 0, BOARD_SIZE);
|
||||||
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 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() {
|
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.currentScore = 0;
|
||||||
|
this.gameWon = false;
|
||||||
|
this.gameOver = false;
|
||||||
addNewTile();
|
addNewTile();
|
||||||
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() {
|
public void addNewTile() {
|
||||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
if (!hasEmptyCell()) { return; }
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int[]> emptyCells = new ArrayList<>();
|
List<int[]> emptyCells = new ArrayList<>();
|
||||||
|
|
||||||
// 1. Collecter les coordonnées des cellules vides.
|
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||||
if (board[row][col] == 0) {
|
if (board[row][col] == 0) { emptyCells.add(new int[]{row, col}); }
|
||||||
emptyCells.add(new int[]{row, col});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. S'il y a des cellules vides, ajouter une nouvelle tuile.
|
|
||||||
if (!emptyCells.isEmpty()) {
|
if (!emptyCells.isEmpty()) {
|
||||||
int[] randomCell = emptyCells.get(getRandomNumberGenerator().nextInt(emptyCells.size()));
|
int[] randomCell = emptyCells.get(randomNumberGenerator.nextInt(emptyCells.size()));
|
||||||
int row = randomCell[0];
|
int value = generateRandomTileValue();
|
||||||
int col = randomCell[1];
|
setCellValue(randomCell[0], randomCell[1], value);
|
||||||
|
|
||||||
int value = generateRandomTileValue(); // Utilise la nouvelle logique de probabilité
|
|
||||||
setCellValue(row, col, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Génère une valeur de tuile aléatoire selon les probabilités définies.
|
* 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 (2, 4, 8, ...) avec les probabilités spécifiées.
|
* @return La valeur de la nouvelle tuile.
|
||||||
*/
|
*/
|
||||||
private int generateRandomTileValue() { // Logique de probabilité modifiée
|
private int generateRandomTileValue() {
|
||||||
int randomValue = randomNumberGenerator.nextInt(10000);
|
int randomValue = randomNumberGenerator.nextInt(10000);
|
||||||
|
if (randomValue < 8540) return 2; // ~85%
|
||||||
if (randomValue < 8540) { // 85.40%
|
if (randomValue < 9740) return 4; // ~12%
|
||||||
return 2;
|
if (randomValue < 9940) return 8; // ~2%
|
||||||
} else if (randomValue < 9740) { // 12.00%
|
if (randomValue < 9990) return 16; // ~0.5%
|
||||||
return 4;
|
// ... (autres probabilités)
|
||||||
} else if (randomValue < 9940) { // 2.00%
|
if (randomValue < 9995) return 32;
|
||||||
return 8;
|
if (randomValue < 9998) return 64;
|
||||||
} else if (randomValue < 9990) { // 0.50%
|
if (randomValue < 9999) return 128;
|
||||||
return 16;
|
return 256;
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Déplace les tuiles vers le haut, en fusionnant les tuiles de même valeur.
|
* Tente de déplacer et fusionner les tuiles vers le HAUT.
|
||||||
*
|
* Met à jour le score interne en cas de fusion.
|
||||||
* @return True si le plateau a été modifié, False sinon.
|
* 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
|
public boolean pushUp() {
|
||||||
boolean boardChanged = false;
|
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
||||||
boolean[] hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
|
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
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++) {
|
for (int row = 1; row < BOARD_SIZE; row++) {
|
||||||
if (getCellValue(row, col) != 0) {
|
if (getCellValue(row, col) != 0) {
|
||||||
int currentValue = getCellValue(row, col);
|
int currentValue = getCellValue(row, col); int currentRow = row;
|
||||||
int currentRow = row;
|
while (currentRow > 0 && getCellValue(currentRow - 1, col) == 0) { setCellValue(currentRow - 1, col, currentValue); setCellValue(currentRow, col, 0); currentRow--; boardChanged = true; }
|
||||||
|
|
||||||
// 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
|
|
||||||
if (currentRow > 0 && getCellValue(currentRow - 1, col) == currentValue && !hasMerged[currentRow - 1]) {
|
if (currentRow > 0 && getCellValue(currentRow - 1, col) == currentValue && !hasMerged[currentRow - 1]) {
|
||||||
int newValue = getCellValue(currentRow - 1, col) * 2;
|
int newValue = getCellValue(currentRow - 1, col) * 2; setCellValue(currentRow - 1, col, newValue); setCellValue(currentRow, col, 0);
|
||||||
setCellValue(currentRow - 1, col, newValue);
|
currentScore += newValue; hasMerged[currentRow - 1] = true; boardChanged = true;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
||||||
checkWinCondition(); // Vérifie après tous les mouvements de la colonne
|
|
||||||
checkGameOverCondition(); // Vérifie si la partie est terminée
|
|
||||||
return boardChanged;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Déplace les tuiles vers le bas, en fusionnant les tuiles de même valeur.
|
* 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() { // Logique refactorisée
|
public boolean pushDown() {
|
||||||
boolean boardChanged = false;
|
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
||||||
boolean[] hasMerged = new boolean[BOARD_SIZE];
|
|
||||||
|
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||||
hasMerged = new boolean[BOARD_SIZE];
|
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) {
|
if (getCellValue(row, col) != 0) {
|
||||||
int currentValue = getCellValue(row, col);
|
int currentValue = getCellValue(row, col); int currentRow = row;
|
||||||
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; }
|
||||||
|
|
||||||
// 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
|
|
||||||
if (currentRow < BOARD_SIZE - 1 && getCellValue(currentRow + 1, col) == currentValue && !hasMerged[currentRow + 1]) {
|
if (currentRow < BOARD_SIZE - 1 && getCellValue(currentRow + 1, col) == currentValue && !hasMerged[currentRow + 1]) {
|
||||||
int newValue = getCellValue(currentRow + 1, col) * 2;
|
int newValue = getCellValue(currentRow + 1, col) * 2; setCellValue(currentRow + 1, col, newValue); setCellValue(currentRow, col, 0);
|
||||||
setCellValue(currentRow + 1, col, newValue);
|
currentScore += newValue; hasMerged[currentRow + 1] = true; boardChanged = true;
|
||||||
setCellValue(currentRow, col, 0);
|
|
||||||
setCurrentScore(getCurrentScore() + newValue);
|
|
||||||
hasMerged[currentRow + 1] = true;
|
|
||||||
boardChanged = true;
|
|
||||||
updateHighestScore();
|
|
||||||
// checkWinCondition();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} checkWinCondition(); checkGameOverCondition(); return boardChanged;
|
||||||
checkWinCondition();
|
|
||||||
checkGameOverCondition();
|
|
||||||
return boardChanged;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Déplace les tuiles vers la gauche, en fusionnant les tuiles de même valeur.
|
* 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() { // Logique refactorisée
|
public boolean pushLeft() {
|
||||||
boolean boardChanged = false;
|
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
||||||
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
|
|
||||||
|
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||||
hasMerged = new boolean[BOARD_SIZE];
|
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) {
|
if (getCellValue(row, col) != 0) {
|
||||||
int currentValue = getCellValue(row, col);
|
int currentValue = getCellValue(row, col); int currentCol = 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]) {
|
||||||
// Déplacer vers la droite
|
int newValue = getCellValue(row, currentCol - 1) * 2; setCellValue(row, currentCol - 1, newValue); setCellValue(row, currentCol, 0);
|
||||||
while (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == 0) {
|
currentScore += newValue; hasMerged[currentCol - 1] = true; boardChanged = true;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} 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é
|
public boolean pushRight() {
|
||||||
if (getCurrentScore() > getHighestScore()) {
|
boolean boardChanged = false; boolean[] hasMerged = new boolean[BOARD_SIZE];
|
||||||
setHighestScore(getCurrentScore());
|
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||||
saveHighScore(); // Sauvegarde implémentée
|
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.
|
* Sérialise l'état actuel du jeu (plateau et score courant) en une chaîne.
|
||||||
* Format: "row1col1,row1col2,...,row1colN,row2col1,...,rowNcolN,currentScore,highestScore"
|
* Format: "val,val,...,val,score"
|
||||||
*
|
* @return Chaîne sérialisée.
|
||||||
* @return Une chaîne représentant l'état complet du jeu.
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() { // Renommé et Override
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
for (int col = 0; col < BOARD_SIZE; col++) { sb.append(board[row][col]).append(","); }
|
||||||
sb.append(board[row][col]).append(","); // Utilise board directement
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
sb.append(currentScore);
|
||||||
sb.append(getCurrentScore()).append(",");
|
|
||||||
sb.append(getHighestScore()); // Sérialise highestScore
|
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Désérialise une chaîne de caractères pour restaurer l'état du jeu.
|
* 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().
|
||||||
* @param serializedState La chaîne représentant l'état du jeu (au format de la méthode toString).
|
* @return Nouvel objet Game, ou null si la désérialisation échoue.
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
public static Game deserialize(String serializedState, Context context) { // Logique améliorée
|
public static Game deserialize(String serializedState) {
|
||||||
if (serializedState == null || serializedState.isEmpty()) {
|
if (serializedState == null || serializedState.isEmpty()) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] values = serializedState.split(",");
|
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 + 1)) return null;
|
||||||
if (values.length != (BOARD_SIZE * BOARD_SIZE + 2)) {
|
int[][] newBoard = new int[BOARD_SIZE][BOARD_SIZE]; int index = 0;
|
||||||
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;
|
|
||||||
|
|
||||||
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++) {
|
for (int col = 0; col < BOARD_SIZE; col++) { newBoard[row][col] = Integer.parseInt(values[index++]); }
|
||||||
newBoard[row][col] = Integer.parseInt(values[index++]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
int score = Integer.parseInt(values[index]);
|
||||||
int score = Integer.parseInt(values[index++]);
|
return new Game(newBoard, score);
|
||||||
int highScore = Integer.parseInt(values[index++]); // Désérialise highScore
|
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { return null; }
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifie si la condition de victoire (tuile 2048) est remplie.
|
* Vérifie si une tuile >= 2048 existe sur le plateau et met à jour l'état `gameWon`.
|
||||||
* Met à jour la variable gameWon.
|
|
||||||
*/
|
*/
|
||||||
private void checkWinCondition() { // Ajouté
|
private void checkWinCondition() {
|
||||||
// Ne vérifie que si la partie n'est pas déjà gagnée
|
if (!gameWon) { // Optimisation: inutile de revérifier si déjà gagné
|
||||||
if (!isGameWon()) {
|
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 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifie si la condition de fin de partie (aucun mouvement possible) est remplie.
|
* Vérifie s'il reste des mouvements possibles (case vide ou fusion adjacente).
|
||||||
* Met à jour la variable gameOver.
|
* Met à jour l'état `gameOver`.
|
||||||
*/
|
*/
|
||||||
private void checkGameOverCondition() { // Ajouté
|
private void checkGameOverCondition() {
|
||||||
// Si une case est vide, la partie n'est pas terminée
|
if (hasEmptyCell()) { setGameOver(false); return; } // Si case vide, pas game over
|
||||||
if (hasEmptyCell()) {
|
// Vérifie fusions adjacentes possibles
|
||||||
setGameOver(false);
|
for(int r=0; r<BOARD_SIZE; r++) for(int c=0; c<BOARD_SIZE; c++) {
|
||||||
return;
|
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) ||
|
||||||
// Vérifie les fusions possibles horizontalement et verticalement
|
(c>0 && getCellValue(r,c-1)==current) || (c<BOARD_SIZE-1 && getCellValue(r,c+1)==current)) {
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
setGameOver(false); return; // Fusion possible, pas game over
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setGameOver(true); // Aucune case vide et aucune fusion -> game over
|
||||||
// Si aucune case vide et aucune fusion possible, la partie est terminée
|
|
||||||
setGameOver(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifie s'il existe au moins une cellule vide sur le plateau.
|
* @return true s'il y a au moins une case vide sur le plateau, false sinon.
|
||||||
*
|
|
||||||
* @return true s'il y a une cellule vide, false sinon.
|
|
||||||
*/
|
*/
|
||||||
private boolean hasEmptyCell() { // Ajouté
|
private boolean hasEmptyCell() {
|
||||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
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 col = 0; col < BOARD_SIZE; col++) {
|
|
||||||
if (getCellValue(row, col) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
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) {
|
} catch (Exception exception) {
|
||||||
// exception.printStackTrace(); // Commenté dans le nouveau code ? Gardons commenté.
|
|
||||||
exception.fillInStackTrace(); // Gestion des erreurs (journalisation).
|
exception.fillInStackTrace(); // Gestion des erreurs (journalisation).
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user