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:
Augustin ROUX 2025-04-04 13:12:59 +02:00
parent 73ab81e208
commit 9d8d2c5c62
4 changed files with 803 additions and 1053 deletions

View File

@ -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;
}
} }

View 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

View File

@ -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;