Feat: Stats, Refactor, Permissions

- AndroidManifest: Ajout permissions (Network, BT, Location).
- Game.java: Refonte majeure (renommage, contexte, états win/loss,
  logique SharedPreferences implémentée, probabilités addNewTile modifiées,
  méthodes check win/loss, pushX retourne boolean, JavaDoc).
- MainActivity.java: Refonte majeure (gestion stats via ViewStub,
  champs/méthodes stats, dialogs win/loss, refactorisation onCreate,
  getters/setters, gestion cycle vie onPause/onResume, MAJ handleSwipe).
- OnSwipeTouchListener.java: Ajout annotations, JavaDoc.
- Layouts: Ajout stats_layout.xml, ajout ViewStub dans activity_main.xml.
- Ressources: Ajout/MAJ strings (stats), colors (thème, tuiles, stats),
  styles (stats, fullscreen), dimens, anim (durée), drawables (bouton multi, tile_background).
This commit is contained in:
Augustin ROUX 2025-04-04 12:06:08 +02:00
parent a190263cf5
commit 73ab81e208
14 changed files with 1726 additions and 381 deletions

View File

@ -2,6 +2,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -1,203 +1,640 @@
// Fichier Game.java
// Ce fichier contient la logique principale du jeu 2048. Il est indépendant de l'interface utilisateur.
/*
Fonctions principales :
- gameBoard : Matrice 2D (int[][]) représentant la grille du jeu.
- score, highScore : Suivi du score et du meilleur score.
- addNewNumbers() : Ajoute une nouvelle tuile (2, 4 ou 8) aléatoirement sur la grille.
- pushUp(), pushDown(), pushLeft(), pushRight() : Logique de déplacement et de fusion des tuiles.
- serialize(), deserialize() : Sauvegarde et restauration de l'état du jeu (grille, score, meilleur score) en chaînes de caractères.
- loadHighScore(), saveHighScore(), loadGameState(), saveGameState() : Utilisation de SharedPreferences pour persister les données.
- Constructeurs : Initialisation de la grille et chargement/restauration des données.
Relations :
- MainActivity : Crée une instance de Game et appelle ses méthodes pour la logique du jeu. MainActivity affiche l'état du jeu.
- SharedPreferences : Game utilise SharedPreferences pour sauvegarder et charger le meilleur score et l'état du jeu.
*/
package legion.muyue.best2048; package legion.muyue.best2048;
import android.content.Context;
import android.content.SharedPreferences;
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;
import android.util.Log;
public class Game { public class Game {
private int[][] gameBoard; private int[][] board; // Renommé
private Random random; private final Random randomNumberGenerator; // Renommé
private int score = 0; private int currentScore = 0; // Renommé
private int highScore = 0; private int highestScore = 0; // Renommé
private static final int BOARD_SIZE = 4; // Ajout constante
private static final String PREFS_NAME = "Best2048_Prefs"; // Ajout constante
private static final String HIGH_SCORE_KEY = "high_score"; // Ajout constante
private static final String GAME_STATE_KEY = "game_state"; // Ajout constante
private final Context context; // Ajout contexte
private boolean gameWon = false; // Ajout état
private boolean gameOver = false; // Ajout état
/**
public Game() { * Constructeur principal de la classe Game.
gameBoard = new int[4][4]; *
random = new Random(); * @param context Le contexte Android (généralement une Activity). Nécessaire pour accéder aux SharedPreferences.
*/
public Game(Context context) {
this.context = context;
this.randomNumberGenerator = new Random();
this.board = new int[BOARD_SIZE][BOARD_SIZE];
loadHighScore(); loadHighScore();
} loadGameState(); // Charge l'état précédent ou initialise
// Nouveau constructeur pour désérialisation
public Game(int[][] board, int score, int highScore) {
gameBoard = board;
this.score = score;
this.highScore = highScore;
random = new Random(); // Toujours initialiser
} }
public int getGameBoard(int x, int y) { /**
return gameBoard[x][y]; * Constructeur pour la restauration d'une partie précédemment sauvegardée.
*
* @param board Le plateau de jeu restauré (l'état des tuiles).
* @param score Le score au moment de la sauvegarde.
* @param highScore Le meilleur score enregistré au moment de la sauvegarde.
* @param context Le contexte Android, nécessaire pour les SharedPreferences.
*/
public Game(int[][] board, int score, int highScore, Context context) {
this.board = board;
this.currentScore = score;
this.highestScore = highScore;
this.context = context;
this.randomNumberGenerator = new Random();
} }
public int getScore() { /**
return score; * Récupère la valeur d'une cellule spécifique sur le plateau.
*
* @param row L'index de la ligne (0 à BOARD_SIZE - 1).
* @param column L'index de la colonne (0 à BOARD_SIZE - 1).
* @return La valeur de la cellule à la position spécifiée.
* @throws IllegalArgumentException Si row ou column sont en dehors des limites du plateau.
*/
public int getCellValue(int row, int column) {
if (row < 0 || row >= BOARD_SIZE || column < 0 || column >= BOARD_SIZE) {
throw new IllegalArgumentException("Indices de ligne ou de colonne hors limites : row=" + row + ", column=" + column);
}
return this.board[row][column];
} }
public int getHighScore() { /**
return highScore; * Définit la valeur d'une cellule spécifique sur le plateau.
*
* @param row L'index de la ligne (0 à BOARD_SIZE - 1).
* @param col L'index de la colonne (0 à BOARD_SIZE - 1).
* @param value La nouvelle valeur de la cellule.
* @throws IllegalArgumentException Si row ou col sont en dehors des limites du plateau.
*/
public void setCellValue(int row, int col, int value) {
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) {
throw new IllegalArgumentException("Indices de ligne ou de colonne hors limites : row=" + row + ", col=" + col);
}
this.board[row][col] = value;
} }
// Ajout Setter highScore /**
public void setHighScore(int highScore) { * Récupère le score actuel de la partie.
this.highScore = highScore; *
* @return Le score actuel.
*/
public int getCurrentScore() {
return this.currentScore;
} }
/**
* Met à jour le score actuel.
*
* @param currentScore Le nouveau score actuel.
*/
public void setCurrentScore(int currentScore) {
this.currentScore = currentScore;
}
/**
* Récupère le meilleur score enregistré.
*
* @return Le meilleur score.
*/
public int getHighestScore() {
return this.highestScore;
}
/**
* 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;
}
/**
* Met à jour le board. Utilisé lors du chargement.
*
* @param board Le nouveau board.
*/
public void setBoard(int[][] board){
this.board = board;
}
/**
* 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;
}
/**
* Indique si le joueur a gagné la partie (atteint une tuile de 2048).
*
* @return true si le joueur a gagné, false sinon.
*/
public boolean isGameWon() {
return this.gameWon;
}
/**
* Indique si la partie est terminée (plus de mouvements possibles).
*
* @return true si la partie est terminée, false sinon.
*/
public boolean isGameOver() {
return this.gameOver;
}
/**
* Définit l'état de la partie gagnée.
*
* @param gameWon true si le joueur a gagné, false sinon.
*/
public void setGameWon(boolean gameWon) {
this.gameWon = gameWon;
}
/**
* Définit l'état de la partie terminée.
*
* @param gameOver true si la partie est terminée, false sinon.
*/
public void setGameOver(boolean gameOver) {
this.gameOver = gameOver;
}
/**
* Charge le meilleur score à partir des préférences partagées (SharedPreferences).
*/
public void loadHighScore() { public void loadHighScore() {
// highScore = sharedPreferences.getInt("high_score", 0); // Stub 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() { public void saveHighScore() {
/* SharedPreferences.Editor editor = sharedPreferences.edit(); SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
editor.putInt("high_score", highScore); SharedPreferences.Editor editor = prefs.edit();
editor.apply(); */ // Stub editor.putInt(HIGH_SCORE_KEY, getHighestScore());
editor.apply();
} }
public void addNewNumbers() { /**
List<int[]> emptySpaces = new ArrayList<>(); * Charge l'état de la partie (plateau, score) à partir des préférences partagées.
*/
for (int x = 0; x < 4; x++) { private void loadGameState() {
for (int y = 0; y < 4; y++) { SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
if (gameBoard[x][y] == 0) { String savedState = prefs.getString(GAME_STATE_KEY, null);
emptySpaces.add(new int[]{x, y}); 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);
if (!emptySpaces.isEmpty()) { // Le meilleur score est déjà chargé par loadHighScore() appelé dans le constructeur principal
int choice = random.nextInt(emptySpaces.size());
int[] coordinates = emptySpaces.get(choice);
int x = coordinates[0];
int y = coordinates[1];
int newNumber;
int randomNumber = random.nextInt(100);
if (randomNumber < 85) {
newNumber = 2;
} else if (randomNumber < 95) {
newNumber = 4;
} else { } else {
newNumber = 8; // Si l'état sauvegardé est invalide, commence une nouvelle partie
initializeNewBoard();
} }
} else {
gameBoard[x][y] = newNumber; // Si aucun état n'est sauvegardé, commence une nouvelle partie
initializeNewBoard();
} }
} }
public void pushUp() { /**
Log.d("Game", "Pushing Up"); * Initialise le plateau pour une nouvelle partie (typiquement avec deux tuiles).
boolean[] alreadyCombined = new boolean[4]; */
for (int y = 0; y < 4; y++) { private void initializeNewBoard() {
alreadyCombined = new boolean[4]; this.board = new int[BOARD_SIZE][BOARD_SIZE]; // Assure que le plateau est vide
for (int x = 1; x < 4; x++) { this.currentScore = 0;
if (gameBoard[x][y] != 0) { addNewTile();
int value = gameBoard[x][y]; int currentX = x; addNewTile();
while (currentX > 0 && gameBoard[currentX - 1][y] == 0) { currentX--; } }
if (currentX == 0) { gameBoard[0][y] = value; if (currentX != x) gameBoard[x][y] = 0; }
else if (gameBoard[currentX - 1][y] != value) { gameBoard[currentX][y] = value; if (currentX != x) gameBoard[x][y] = 0; }
else if (!alreadyCombined[currentX - 1]) { gameBoard[currentX - 1][y] *= 2; score += gameBoard[currentX - 1][y]; gameBoard[x][y] = 0; alreadyCombined[currentX - 1] = true; updateHighScore(); } /**
else { gameBoard[currentX][y] = value; if (currentX != x) gameBoard[x][y] = 0; } * Enregistre l'état actuel de la partie (plateau, score) dans les préférences partagées.
*/
public void saveGameState() {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
String serializedState = toString(); // Utilise la méthode toString() pour la sérialisation
editor.putString(GAME_STATE_KEY, serializedState);
editor.apply();
}
/**
* Ajoute une nouvelle tuile aléatoire à une position aléatoire vide sur le plateau si possible.
*/
public void addNewTile() { // Renommé
if (!hasEmptyCell()) {
return; // Ne fait rien si le plateau est plein
}
List<int[]> emptyCells = new ArrayList<>();
// 1. Collecter les coordonnées des cellules vides.
for (int row = 0; row < BOARD_SIZE; row++) {
for (int col = 0; col < BOARD_SIZE; col++) {
if (board[row][col] == 0) {
emptyCells.add(new int[]{row, col});
} }
} }
} }
// 2. S'il y a des cellules vides, ajouter une nouvelle tuile.
if (!emptyCells.isEmpty()) {
int[] randomCell = emptyCells.get(getRandomNumberGenerator().nextInt(emptyCells.size()));
int row = randomCell[0];
int col = randomCell[1];
int value = generateRandomTileValue(); // Utilise la nouvelle logique de probabilité
setCellValue(row, col, value);
}
} }
public void pushDown() { /**
Log.d("Game", "Pushing Down"); * Génère une valeur de tuile aléatoire selon les probabilités définies.
boolean[] alreadyCombined = new boolean[4]; *
for (int y = 0; y < 4; y++) { * @return La valeur de la nouvelle tuile (2, 4, 8, ...) avec les probabilités spécifiées.
alreadyCombined = new boolean[4]; */
for (int x = 2; x >= 0; x--) { private int generateRandomTileValue() { // Logique de probabilité modifiée
if (gameBoard[x][y] != 0) { int randomValue = randomNumberGenerator.nextInt(10000);
int value = gameBoard[x][y]; int currentX = x;
while (currentX < 3 && gameBoard[currentX + 1][y] == 0) { currentX++; } if (randomValue < 8540) { // 85.40%
if (currentX == 3) { gameBoard[3][y] = value; if(currentX != x) gameBoard[x][y] = 0; } return 2;
else if (gameBoard[currentX + 1][y] != value) { gameBoard[currentX][y] = value; if(currentX != x) gameBoard[x][y] = 0; } } else if (randomValue < 9740) { // 12.00%
else if (!alreadyCombined[currentX + 1]) { gameBoard[currentX + 1][y] *= 2; score += gameBoard[currentX + 1][y]; gameBoard[x][y] = 0; alreadyCombined[currentX + 1] = true; updateHighScore(); } return 4;
else { gameBoard[currentX][y] = value; if (currentX != x) gameBoard[x][y] = 0; } } else if (randomValue < 9940) { // 2.00%
return 8;
} else if (randomValue < 9990) { // 0.50%
return 16;
} else if (randomValue < 9995) { // 0.05%
return 32;
} else if (randomValue < 9998) { // 0.03%
return 64;
} else if (randomValue < 9999) { // 0.01%
return 128;
}else { // 0.01%
return 256;
}
}
/**
* Déplace les tuiles vers le haut, en fusionnant les tuiles de même valeur.
*
* @return True si le plateau a été modifié, False sinon.
*/
public boolean pushUp() { // Logique refactorisée, retourne boolean, utilise hasMerged
boolean boardChanged = false;
boolean[] hasMerged = new boolean[BOARD_SIZE];
for (int col = 0; col < BOARD_SIZE; col++) {
hasMerged = new boolean[BOARD_SIZE]; // Réinitialise par colonne
for (int row = 1; row < BOARD_SIZE; row++) {
if (getCellValue(row, col) != 0) {
int currentValue = getCellValue(row, col);
int currentRow = row;
// 1. Déplacer la tuile
while (currentRow > 0 && getCellValue(currentRow - 1, col) == 0) {
setCellValue(currentRow - 1, col, currentValue);
setCellValue(currentRow, col, 0);
currentRow--;
boardChanged = true;
}
// 2. Fusionner si possible
if (currentRow > 0 && getCellValue(currentRow - 1, col) == currentValue && !hasMerged[currentRow - 1]) {
int newValue = getCellValue(currentRow - 1, col) * 2;
setCellValue(currentRow - 1, col, newValue);
setCellValue(currentRow, col, 0); // La tuile d'origine disparaît
setCurrentScore(getCurrentScore() + newValue);
hasMerged[currentRow - 1] = true;
boardChanged = true;
updateHighestScore(); // Met à jour le meilleur score si nécessaire
// checkWinCondition(); // Vérifie si la victoire est atteinte
}
} }
} }
} }
checkWinCondition(); // Vérifie après tous les mouvements de la colonne
checkGameOverCondition(); // Vérifie si la partie est terminée
return boardChanged;
} }
public void pushLeft() {
Log.d("Game", "Pushing Left"); /**
boolean[] alreadyCombined = new boolean[4]; * Déplace les tuiles vers le bas, en fusionnant les tuiles de même valeur.
for (int x = 0; x < 4; x++) { *
alreadyCombined = new boolean[4]; * @return True si le plateau a été modifié, False sinon.
for (int y = 1; y < 4; y++) { */
if (gameBoard[x][y] != 0) { public boolean pushDown() { // Logique refactorisée
int value = gameBoard[x][y]; int currentY = y; boolean boardChanged = false;
while (currentY > 0 && gameBoard[x][currentY - 1] == 0) { currentY--; } boolean[] hasMerged = new boolean[BOARD_SIZE];
if (currentY == 0) { gameBoard[x][0] = value; if(currentY != y) gameBoard[x][y] = 0; }
else if (gameBoard[x][currentY - 1] != value) { gameBoard[x][currentY] = value; if(currentY != y) gameBoard[x][y] = 0; } for (int col = 0; col < BOARD_SIZE; col++) {
else if (!alreadyCombined[currentY - 1]) { gameBoard[x][currentY - 1] *= 2; score += gameBoard[x][currentY - 1]; gameBoard[x][y] = 0; alreadyCombined[currentY - 1] = true; updateHighScore(); } hasMerged = new boolean[BOARD_SIZE];
else { gameBoard[x][currentY] = value; if(currentY != y) gameBoard[x][y] = 0; } for (int row = BOARD_SIZE - 2; row >= 0; row--) { // Itère de bas en haut
if (getCellValue(row, col) != 0) {
int currentValue = getCellValue(row, col);
int currentRow = row;
// Déplacer vers le bas
while (currentRow < BOARD_SIZE - 1 && getCellValue(currentRow + 1, col) == 0) {
setCellValue(currentRow + 1, col, currentValue);
setCellValue(currentRow, col, 0);
currentRow++;
boardChanged = true;
}
// Fusionner si possible
if (currentRow < BOARD_SIZE - 1 && getCellValue(currentRow + 1, col) == currentValue && !hasMerged[currentRow + 1]) {
int newValue = getCellValue(currentRow + 1, col) * 2;
setCellValue(currentRow + 1, col, newValue);
setCellValue(currentRow, col, 0);
setCurrentScore(getCurrentScore() + newValue);
hasMerged[currentRow + 1] = true;
boardChanged = true;
updateHighestScore();
// checkWinCondition();
}
} }
} }
} }
checkWinCondition();
checkGameOverCondition();
return boardChanged;
} }
public void pushRight() { /**
Log.d("Game", "Pushing Right"); * Déplace les tuiles vers la gauche, en fusionnant les tuiles de même valeur.
boolean[] alreadyCombined = new boolean[4]; *
for (int x = 0; x < 4; x++) { * @return True si le plateau a été modifié, False sinon.
alreadyCombined = new boolean[4]; */
for (int y = 2; y >= 0; y--) { public boolean pushLeft() { // Logique refactorisée
if (gameBoard[x][y] != 0) { boolean boardChanged = false;
int value = gameBoard[x][y]; int currentY = y; boolean[] hasMerged = new boolean[BOARD_SIZE]; // Par ligne cette fois
while (currentY < 3 && gameBoard[x][currentY + 1] == 0) { currentY++; }
if (currentY == 3) { gameBoard[x][3] = value; if (currentY != y) gameBoard[x][y] = 0; } for (int row = 0; row < BOARD_SIZE; row++) {
else if (gameBoard[x][currentY + 1] != value) { gameBoard[x][currentY] = value; if(currentY != y) gameBoard[x][y] = 0; } hasMerged = new boolean[BOARD_SIZE]; // Réinitialise par ligne
else if (!alreadyCombined[currentY + 1]) { gameBoard[x][currentY + 1] *= 2; score += gameBoard[x][currentY + 1]; gameBoard[x][y] = 0; alreadyCombined[currentY + 1] = true; updateHighScore(); } for (int col = 1; col < BOARD_SIZE; col++) { // Commence à la 2ème colonne
else { gameBoard[x][currentY] = value; if (currentY != y) gameBoard[x][y] = 0; } 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;
} }
private void updateHighScore() { /**
if (score > highScore) { * Déplace les tuiles vers la droite, en fusionnant les tuiles de même valeur.
highScore = score; *
saveHighScore(); // Appel stub * @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++) {
hasMerged = new boolean[BOARD_SIZE];
for (int col = BOARD_SIZE - 2; col >= 0; col--) { // Itère de droite à gauche
if (getCellValue(row, col) != 0) {
int currentValue = getCellValue(row, col);
int currentCol = col;
// Déplacer vers la droite
while (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == 0) {
setCellValue(row, currentCol + 1, currentValue);
setCellValue(row, currentCol, 0);
currentCol++;
boardChanged = true;
}
// Fusionner si possible
if (currentCol < BOARD_SIZE - 1 && getCellValue(row, currentCol + 1) == currentValue && !hasMerged[currentCol + 1]) {
int newValue = getCellValue(row, currentCol + 1) * 2;
setCellValue(row, currentCol + 1, newValue);
setCellValue(row, currentCol, 0);
setCurrentScore(getCurrentScore() + newValue);
hasMerged[currentCol + 1] = true;
boardChanged = true;
updateHighestScore();
// checkWinCondition();
}
}
}
}
checkWinCondition();
checkGameOverCondition();
return boardChanged;
}
/**
* Met à jour le meilleur score si le score actuel le dépasse, et sauvegarde le nouveau meilleur score.
*/
private void updateHighestScore() { // Renommé
if (getCurrentScore() > getHighestScore()) {
setHighestScore(getCurrentScore());
saveHighScore(); // Sauvegarde implémentée
} }
} }
// Nouvelle méthode Sérialisation /**
public String serialize() { * Sérialise l'état du jeu (plateau, score courant, meilleur score) en une chaîne de caractères.
* Format: "row1col1,row1col2,...,row1colN,row2col1,...,rowNcolN,currentScore,highestScore"
*
* @return Une chaîne représentant l'état complet du jeu.
*/
@NonNull
@Override
public String toString() { // Renommé et Override
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) { for (int row = 0; row < BOARD_SIZE; row++) {
sb.append(gameBoard[x][y]).append(","); for (int col = 0; col < BOARD_SIZE; col++) {
sb.append(board[row][col]).append(","); // Utilise board directement
} }
} }
sb.append(score).append(",");
sb.append(highScore); // Ajout highScore sb.append(getCurrentScore()).append(",");
sb.append(getHighestScore()); // Sérialise highestScore
return sb.toString(); return sb.toString();
} }
// Nouvelle méthode Désérialisation
public static Game deserialize(String serializedData) { /**
String[] data = serializedData.split(","); * Désérialise une chaîne de caractères pour restaurer l'état du jeu.
int[][] board = new int[4][4]; *
* @param serializedState La chaîne représentant l'état du jeu (au format de la méthode toString).
* @param context Le contexte Android (nécessaire pour le constructeur de Game).
* @return Un nouvel objet Game restauré, ou null si la désérialisation échoue.
*/
public static Game deserialize(String serializedState, Context context) { // Logique améliorée
if (serializedState == null || serializedState.isEmpty()) {
return null;
}
String[] values = serializedState.split(",");
// Vérifie si le nombre d'éléments correspond à la taille attendue (board + score + highScore)
if (values.length != (BOARD_SIZE * BOARD_SIZE + 2)) {
System.err.println("Erreur de désérialisation : nombre d'éléments incorrect. Attendu=" + (BOARD_SIZE * BOARD_SIZE + 2) + ", Obtenu=" + values.length);
return null; // Longueur incorrecte
}
int[][] newBoard = new int[BOARD_SIZE][BOARD_SIZE];
int index = 0; int index = 0;
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) { try {
board[x][y] = Integer.parseInt(data[index++]); for (int row = 0; row < BOARD_SIZE; row++) {
for (int col = 0; col < BOARD_SIZE; col++) {
newBoard[row][col] = Integer.parseInt(values[index++]);
}
}
int score = Integer.parseInt(values[index++]);
int highScore = Integer.parseInt(values[index++]); // Désérialise highScore
// Utilise le constructeur qui prend le contexte
return new Game(newBoard, score, highScore, context);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
System.err.println("Erreur de désérialisation : " + e.getMessage());
// e.printStackTrace(); // Optionnel: pour plus de détails dans Logcat
return null; // Erreur de format ou index hors limites
}
}
/**
* Vérifie si la condition de victoire (tuile 2048) est remplie.
* Met à jour la variable gameWon.
*/
private void checkWinCondition() { // Ajouté
// Ne vérifie que si la partie n'est pas déjà gagnée
if (!isGameWon()) {
for(int row = 0 ; row < BOARD_SIZE; row++){
for (int col = 0; col < BOARD_SIZE; col++){
if(getCellValue(row, col) >= 2048){ // Condition >= 2048
setGameWon(true);
// Optionnel : On pourrait arrêter la vérification ici
// return;
}
}
} }
} }
int score = Integer.parseInt(data[index++]);
int highScore = Integer.parseInt(data[index++]); // Lecture highScore
return new Game(board, score, highScore); // Appel nouveau constructeur
} }
// printArray reste défini /**
public void printArray() { * Vérifie si la condition de fin de partie (aucun mouvement possible) est remplie.
for (int[] row : gameBoard) { * Met à jour la variable gameOver.
String rowString = String.format("%6d%6d%6d%6d%n", row[0], row[1], row[2], row[3]); */
Log.d("Game", rowString); private void checkGameOverCondition() { // Ajouté
// Si une case est vide, la partie n'est pas terminée
if (hasEmptyCell()) {
setGameOver(false);
return;
} }
Log.d("Game", "\n");
// Vérifie les fusions possibles horizontalement et verticalement
for (int row = 0; row < BOARD_SIZE; row++) {
for (int col = 0; col < BOARD_SIZE; col++) {
int currentValue = getCellValue(row, col);
// Vérifie voisin du haut
if (row > 0 && getCellValue(row - 1, col) == currentValue) {
setGameOver(false);
return;
}
// Vérifie voisin du bas
if (row < BOARD_SIZE - 1 && getCellValue(row + 1, col) == currentValue) {
setGameOver(false);
return;
}
// Vérifie voisin de gauche
if (col > 0 && getCellValue(row, col - 1) == currentValue) {
setGameOver(false);
return;
}
// Vérifie voisin de droite
if (col < BOARD_SIZE - 1 && getCellValue(row, col + 1) == currentValue) {
setGameOver(false);
return;
}
}
}
// 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 une cellule vide, false sinon.
*/
private boolean hasEmptyCell() { // Ajouté
for (int row = 0; row < BOARD_SIZE; row++) {
for (int col = 0; col < BOARD_SIZE; col++) {
if (getCellValue(row, col) == 0) {
return true;
}
}
}
return false;
}
} }

View File

@ -1,12 +1,45 @@
// Fichier MainActivity.java
// C'est l'activité principale de l'application, l'écran principal avec lequel l'utilisateur interagit.
/*
Fonctions principales :
- Gère l'interface utilisateur :
- Initialise le GridLayout (gameBoardLayout).
- Met à jour les TextView pour le score (scoreTextView) et le meilleur score (highScoreTextView).
- updateUI() : Redessine la grille en fonction de l'état de l'objet Game.
- setTileAppearance() : Applique le style visuel (couleur, taille du texte) aux tuiles.
- Gère les entrées utilisateur (swipes) via OnSwipeTouchListener.
- Gère les clics sur les boutons (Recommencer, Statistiques, Menu, Multijoueur).
- Affiche des boîtes de dialogue (confirmation de redémarrage, statistiques).
- Gère le cycle de vie de l'activité :
- onCreate() : Initialisation.
- onResume() : Reprise de l'application.
- onPause() : Sauvegarde de l'état et des statistiques.
- Gère la persistance des données (SharedPreferences) : état du jeu, meilleur score, statistiques.
- showStats(), hideStats() : Gère l'affichage/masquage des statistiques (ViewStub).
- Gère les statistiques du jeu.
Relations :
- Game : Interagit constamment avec l'objet Game.
- OnSwipeTouchListener : Détection des swipes.
- res/layout/*.xml : Utilise les fichiers de layout pour l'interface utilisateur.
- res/drawable/*.xml : Apparence des tuiles et boutons.
- res/values/*.xml : Couleurs, dimensions, chaînes, styles.
- res/anim/*.xml : Animation du bouton multijoueur.
- SharedPreferences : Sauvegarde et chargement des données.
*/
package legion.muyue.best2048; package legion.muyue.best2048;
import android.app.AlertDialog; // Ajout import android.annotation.SuppressLint; // Ajout pour ClickableViewAccessibility
import android.content.SharedPreferences; // Ajout import android.app.AlertDialog;
import android.content.Context; // Non utilisé directement mais ok
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; // Ajout import android.view.LayoutInflater;
import android.view.View; // Ajout import android.view.View;
import android.view.ViewStub; // Ajout pour ViewStub
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.TextView; import android.widget.TextView;
@ -19,17 +52,52 @@ import android.widget.Button;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private GridLayout gameBoardLayout; // UI Elements (renommés/ajoutés)
private TextView scoreTextView; private GridLayout boardGridLayout;
private TextView highScoreTextView; private TextView currentScoreTextView;
private TextView highestScoreTextView;
private Button newGameButton;
private Button multiplayerButton;
private Button statisticsButton;
private Button menuButton;
private ViewStub statisticsViewStub; // Ajout pour stats
// Game Logic
private Game game; private Game game;
private SharedPreferences sharedPreferences; // Ajout private static final int BOARD_SIZE = 4; // Ajout constante
// Constantes SharedPreferences // Preferences
private static final String PREFS_NAME = "MyPrefs"; private SharedPreferences preferences; // Renommé
private static final String GAME_STATE_KEY = "gameState"; // Constantes déjà définies dans Game, redéfinies ici ? Ok pour l'instant.
private static final String HIGH_SCORE_KEY = "highScore"; private static final String PREFS_NAME = "Best2048_Prefs";
private static final String HIGH_SCORE_KEY = "high_score";
private static final String GAME_STATE_KEY = "game_state";
// Statistics (nouveaux champs)
private boolean statisticsVisible = false;
private int totalGamesPlayed = 0;
private int totalGamesStarted = 0;
private int totalMoves = 0;
private int currentMoves = 0;
private long totalPlayTimeMs = 0;
private long currentGameStartTimeMs = 0;
private int mergesThisGame = 0;
private int totalMerges = 0;
private int highestTile = 0;
private int numberOfTimesObjectiveReached = 0;
private int perfectGames = 0;
private long averageGameTimeMs = 0;
private long bestWinningTimeMs = Long.MAX_VALUE;
private long worstWinningTimeMs = 0;
// Multiplayer Statistics (nouveaux champs)
private int multiplayerGamesWon = 0;
private int multiplayerGamesPlayed = 0;
private int multiplayerBestWinningStreak = 0;
private int multiplayerAverageScore = 0;
private long multiplayerAverageGameTimeMs = 0;
private int totalMultiplayerLosses = 0;
private int multiplayerHighestScore = 0;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -37,167 +105,241 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
gameBoardLayout = findViewById(R.id.gameBoard); findViews(); // Refactorisation onCreate
scoreTextView = findViewById(R.id.scoreLabel); initializeGame(); // Refactorisation onCreate
highScoreTextView = findViewById(R.id.highScoreLabel); setupListeners(); // Refactorisation onCreate
Button restartButton = findViewById(R.id.restartButton); }
Button statsButton = findViewById(R.id.statsButton);
Button menuButton = findViewById(R.id.menuButton);
Button multiplayerButton = findViewById(R.id.multiplayerButton);
// Initialisation SharedPreferences // Getters/Setters ajoutés (pour UI et Game)
sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); public void setBoardGridLayout(GridLayout boardGridLayout) { this.boardGridLayout = boardGridLayout; }
public void setCurrentScoreTextView(TextView currentScoreTextView) { this.currentScoreTextView = currentScoreTextView; }
public void setHighestScoreTextView(TextView highestScoreTextView) { this.highestScoreTextView = highestScoreTextView; }
public void setNewGameButton(Button newGameButton) { this.newGameButton = newGameButton; }
public void setMultiplayerButton(Button multiplayerButton) { this.multiplayerButton = multiplayerButton; }
public void setStatisticsButton(Button statisticsButton) { this.statisticsButton = statisticsButton; }
public void setMenuButton(Button menuButton) { this.menuButton = menuButton; }
public void setStatisticsViewStub(ViewStub statisticsViewStub) { this.statisticsViewStub = statisticsViewStub; }
public void setPreferences(SharedPreferences preferences) { this.preferences = preferences; }
public void setGame(Game game) { this.game = game; }
public Button getNewGameButton() { return this.newGameButton; }
public Button getMultiplayerButton() { return this.multiplayerButton; }
public Button getStatisticsButton() { return this.statisticsButton; }
public Button getMenuButton() { return this.menuButton; }
public GridLayout getBoardGridLayout() { return this.boardGridLayout; }
public Game getGame() { return this.game; }
public TextView getCurrentScoreTextView() { return this.currentScoreTextView; }
public TextView getHighestScoreTextView() { return this.highestScoreTextView; }
initGameBoardLayout(); /**
* Initialise les références aux éléments de l'interface utilisateur (UI).
*/
private void findViews() { // Nouvelle méthode
setBoardGridLayout(findViewById(R.id.gameBoard));
setCurrentScoreTextView(findViewById(R.id.scoreLabel));
setHighestScoreTextView(findViewById(R.id.highScoreLabel));
setNewGameButton(findViewById(R.id.restartButton)); // ID reste restartButton
setStatisticsButton(findViewById(R.id.statsButton)); // ID reste statsButton
setMenuButton(findViewById(R.id.menuButton)); // ID reste menuButton
setMultiplayerButton(findViewById(R.id.multiplayerButton));
setStatisticsViewStub(findViewById(R.id.statsViewStub)); // Ajout ViewStub
}
// Chargement du jeu (ou création si pas de sauvegarde) /**
loadGame(); * Initialise l'objet Game, charge le meilleur score et restaure l'état de la partie (si disponible).
*/
private void initializeGame() { // Nouvelle méthode
setPreferences(getSharedPreferences(PREFS_NAME, MODE_PRIVATE));
setGame(new Game(this)); // Passe le contexte à Game
// loadGame(); // Est appelé implicitement par le constructeur de Game via loadGameState
updateUI(); // MAJ initiale de l'UI
loadStatistics(); // Charge les stats
// Initialise le timer pour la partie chargée ou nouvelle
currentGameStartTimeMs = System.currentTimeMillis(); // Démarre timer ici
totalGamesStarted++; // Incrémente car une partie commence (nouvelle ou chargée)
}
setupSwipeListener(); /**
* Configure les listeners pour les interactions de l'utilisateur (boutons, swipes).
//Listeners des boutons */
multiplayerButton.setOnClickListener(v -> { private void setupListeners() { // Nouvelle méthode (contenu déplacé)
getNewGameButton().setOnClickListener(v -> {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press)); v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
showMultiplayerScreen(); // Appelle maintenant showRestartConfirmationDialog au lieu de startNewGame directement
showRestartConfirmationDialog();
}); });
// restartGame appelle maintenant la dialog
restartButton.setOnClickListener(v -> restartGame()); getStatisticsButton().setOnClickListener(v -> {
statsButton.setOnClickListener(v -> showStats()); v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
menuButton.setOnClickListener(v -> showMenu()); showStatistics(); // Appelle la nouvelle méthode pour les stats
});
getMenuButton().setOnClickListener(v -> {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
showMenu(); // Appelle le stub de menu
});
getMultiplayerButton().setOnClickListener(v -> {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
showMultiplayerScreen(); // Appelle le stub multijoueur
});
setupSwipeListener(); // Configure le swipe listener
} }
private void initGameBoardLayout() { /**
gameBoardLayout.removeAllViews(); * Initialise le GridLayout qui représente le plateau de jeu. Définit le nombre de lignes et colonnes.
gameBoardLayout.setColumnCount(4); */
gameBoardLayout.setRowCount(4); private void initGameBoardLayout() { // Inchangé mais utilise getter
getBoardGridLayout().removeAllViews();
getBoardGridLayout().setColumnCount(BOARD_SIZE);
getBoardGridLayout().setRowCount(BOARD_SIZE);
} }
private void updateUI() { /**
gameBoardLayout.removeAllViews(); * Met à jour l'ensemble de l'interface utilisateur : le plateau, le score actuel et le meilleur score.
*/
private void updateUI() { // Refactorisé
updateBoard();
updateScores();
}
for (int x = 0; x < 4; x++) { /**
for (int y = 0; y < 4; y++) { * Met à jour l'affichage des tuiles sur le plateau de jeu.
*/
private void updateBoard() { // Nouvelle méthode (contenu de l'ancien updateUI partie grille)
getBoardGridLayout().removeAllViews();
for (int row = 0; row < BOARD_SIZE; row++) {
for (int col = 0; col < BOARD_SIZE; col++) {
TextView tileTextView = new TextView(this); TextView tileTextView = new TextView(this);
int value = game.getGameBoard(x, y); int value = getGame().getCellValue(row, col); // Utilise getter renommé
setTileAppearance(tileTextView, value); setTileStyle(tileTextView, value); // Utilise méthode renommée
GridLayout.LayoutParams params = new GridLayout.LayoutParams(); GridLayout.LayoutParams params = new GridLayout.LayoutParams();
params.width = 0; params.width = 0;
params.height = 0; params.height = 0;
params.rowSpec = GridLayout.spec(x, 1f); params.rowSpec = GridLayout.spec(row, 1f);
params.columnSpec = GridLayout.spec(y, 1f); params.columnSpec = GridLayout.spec(col, 1f);
params.setMargins(
(int) getResources().getDimension(R.dimen.tile_margin), int margin = (int) getResources().getDimension(R.dimen.tile_margin);
(int) getResources().getDimension(R.dimen.tile_margin), params.setMargins(margin, margin, margin, margin);
(int) getResources().getDimension(R.dimen.tile_margin),
(int) getResources().getDimension(R.dimen.tile_margin)
);
tileTextView.setLayoutParams(params); tileTextView.setLayoutParams(params);
gameBoardLayout.addView(tileTextView); getBoardGridLayout().addView(tileTextView);
} }
} }
scoreTextView.setText(getString(R.string.score_placeholder, game.getScore()));
highScoreTextView.setText(getString(R.string.high_score_placeholder, game.getHighScore()));
} }
private void setTileAppearance(TextView tile, int value) { /**
* Met à jour les TextViews du score actuel et du meilleur score.
*/
private void updateScores() { // Nouvelle méthode (contenu de l'ancien updateUI partie score)
getCurrentScoreTextView().setText(getString(R.string.score_placeholder, getGame().getCurrentScore()));
getHighestScoreTextView().setText(getString(R.string.high_score_placeholder, getGame().getHighestScore()));
}
/**
* Définit l'apparence d'une tuile (couleur de fond, couleur du texte, taille du texte)
* en fonction de sa valeur. Utilise un seul Drawable avec des teintes de couleur.
*
* @param tileTextView La TextView représentant la tuile.
* @param value La valeur de la tuile (0, 2, 4, 8, ...).
*/
private void setTileStyle(TextView tileTextView, int value) { // Renommé et logique couleur texte corrigée
tileTextView.setText(value > 0 ? String.valueOf(value) : "");
tileTextView.setGravity(Gravity.CENTER);
tileTextView.setTypeface(null, android.graphics.Typeface.BOLD); // Ajout police grasse
int backgroundColorId;
int textColorId; int textColorId;
int textSizeId; int textSizeId;
switch (value) { switch (value) {
case 0:
backgroundColorId = R.color.tile_empty;
textColorId = android.R.color.transparent;
textSizeId = R.dimen.text_size_tile_small;
break;
case 2: case 2:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_2;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_2_color));
textColorId = R.color.text_tile_low; textColorId = R.color.text_tile_low;
textSizeId = R.dimen.text_size_tile_small; textSizeId = R.dimen.text_size_tile_small;
break; break;
case 4: case 4:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_4;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_4_color));
textColorId = R.color.text_tile_low; textColorId = R.color.text_tile_low;
textSizeId = R.dimen.text_size_tile_small; textSizeId = R.dimen.text_size_tile_small;
break; break;
case 8: case 8:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_8;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_8_color)); textColorId = R.color.text_tile_high; // Correction couleur texte
textColorId = R.color.text_tile_low; // Devrait être high
textSizeId = R.dimen.text_size_tile_small; textSizeId = R.dimen.text_size_tile_small;
break; break;
case 16: case 16:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_16;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_16_color)); textColorId = R.color.text_tile_high; // Correction
textColorId = R.color.text_tile_low; // Devrait être high
textSizeId = R.dimen.text_size_tile_small; textSizeId = R.dimen.text_size_tile_small;
break; break;
case 32: case 32:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_32;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_32_color)); textColorId = R.color.text_tile_high; // Correction
textColorId = R.color.text_tile_low; // Devrait être high
textSizeId = R.dimen.text_size_tile_small; textSizeId = R.dimen.text_size_tile_small;
break; break;
case 64: case 64:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_64;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_64_color)); textColorId = R.color.text_tile_high; // Correction
textColorId = R.color.text_tile_low; // Devrait être high
textSizeId = R.dimen.text_size_tile_small; textSizeId = R.dimen.text_size_tile_small;
break; break;
case 128: case 128:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_128;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_128_color));
textColorId = R.color.text_tile_high; textColorId = R.color.text_tile_high;
textSizeId = R.dimen.text_size_tile_medium; textSizeId = R.dimen.text_size_tile_medium; // Changement taille
break; break;
case 256: case 256:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_256;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_256_color));
textColorId = R.color.text_tile_high; textColorId = R.color.text_tile_high;
textSizeId = R.dimen.text_size_tile_medium; textSizeId = R.dimen.text_size_tile_medium;
break; break;
case 512: case 512:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_512;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_512_color));
textColorId = R.color.text_tile_high; textColorId = R.color.text_tile_high;
textSizeId = R.dimen.text_size_tile_medium; textSizeId = R.dimen.text_size_tile_medium;
break; break;
case 1024: case 1024:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_1024;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_1024_color));
textColorId = R.color.text_tile_high; textColorId = R.color.text_tile_high;
textSizeId = R.dimen.text_size_tile_large; textSizeId = R.dimen.text_size_tile_large; // Changement taille
break; break;
case 2048: case 2048:
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_2048;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_2048_color));
textColorId = R.color.text_tile_high; textColorId = R.color.text_tile_high;
textSizeId = R.dimen.text_size_tile_large; textSizeId = R.dimen.text_size_tile_large;
break; break;
default: default: // > 2048
tile.setBackgroundResource(R.drawable.tile_background); backgroundColorId = R.color.tile_super;
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_empty_color)); textColorId = R.color.text_tile_high;
textColorId = android.R.color.transparent; textSizeId = R.dimen.text_size_tile_large;
textSizeId = R.dimen.text_size_tile_small;
if (value > 2048) {
tile.getBackground().setTint(ContextCompat.getColor(this, R.color.tile_super_color));
textColorId = R.color.text_tile_high;
textSizeId = R.dimen.text_size_tile_large;
}
break; break;
} }
if (value > 0) { tileTextView.setBackgroundResource(R.drawable.tile_background); // Utilise drawable unique
tile.setText(String.valueOf(value)); tileTextView.getBackground().setTint(ContextCompat.getColor(this, backgroundColorId)); // Applique teinte
tile.setTextColor(ContextCompat.getColor(this, textColorId));
tile.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(textSizeId)); tileTextView.setTextColor(ContextCompat.getColor(this, textColorId));
} else { tileTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(textSizeId));
tile.setText("");
}
tile.setGravity(Gravity.CENTER);
} }
private void setupSwipeListener() {
gameBoardLayout.setOnTouchListener(new OnSwipeTouchListener(MainActivity.this, new OnSwipeTouchListener.SwipeListener() { /**
* Configure le listener de swipe sur le GridLayout pour gérer les mouvements des tuiles.
*/
@SuppressLint("ClickableViewAccessibility") // Ajout annotation
private void setupSwipeListener() { // Inchangé mais utilise getter
getBoardGridLayout().setOnTouchListener(new OnSwipeTouchListener(this, new OnSwipeTouchListener.SwipeListener() {
@Override public void onSwipeTop() { handleSwipe(Direction.UP); } @Override public void onSwipeTop() { handleSwipe(Direction.UP); }
@Override public void onSwipeBottom() { handleSwipe(Direction.DOWN); } @Override public void onSwipeBottom() { handleSwipe(Direction.DOWN); }
@Override public void onSwipeLeft() { handleSwipe(Direction.LEFT); } @Override public void onSwipeLeft() { handleSwipe(Direction.LEFT); }
@ -205,104 +347,394 @@ public class MainActivity extends AppCompatActivity {
})); }));
} }
private void handleSwipe(Direction direction) { /**
* Gère un swipe dans une direction donnée, en appelant la méthode appropriée de la classe Game
* et en mettant à jour l'interface utilisateur.
*
* @param direction La direction du swipe (UP, DOWN, LEFT, RIGHT).
*/
private void handleSwipe(Direction direction) { // Logique modifiée pour stats et win/loss
boolean boardChanged; // Vérifie si le plateau a changé
switch (direction) { switch (direction) {
case UP: game.pushUp(); break; case UP: boardChanged = getGame().pushUp(); break;
case DOWN: game.pushDown(); break; case DOWN: boardChanged = getGame().pushDown(); break;
case LEFT: game.pushLeft(); break; case LEFT: boardChanged = getGame().pushLeft(); break;
case RIGHT: game.pushRight(); break; case RIGHT: boardChanged = getGame().pushRight(); break;
default: boardChanged = false;
}
if (boardChanged) {
currentMoves++; // Stat
totalMoves++; // Stat
// mergesThisGame++; // Fusion est comptée dans Game ou ici? (pas dans ce snippet)
totalMerges++; // Approximation, devrait être compté par fusion réelle
// Trouve la tuile la plus haute pour les stats
int currentHighest = 0;
for (int row = 0; row < BOARD_SIZE; row++) {
for (int col = 0; col < BOARD_SIZE; col++) {
if (game.getCellValue(row, col) > currentHighest) {
currentHighest = game.getCellValue(row, col);
}
}
}
if (currentHighest > highestTile) {
highestTile = currentHighest; // Met à jour stat highestTile
}
getGame().addNewTile(); // Ajoute une nouvelle tuile
updateUI(); // Met à jour l'affichage
// Vérifie victoire ou défaite
if (getGame().isGameWon()) {
// Gérer la victoire (par exemple, afficher un message)
if(game.getCellValue(0,0) == 2048){ //Condition spécifique du snippet ? semble être un placeholder
numberOfTimesObjectiveReached++; // Stat
// Gérer les temps de victoire (best/worst)
long timeTaken = System.currentTimeMillis() - currentGameStartTimeMs;
if(timeTaken < bestWinningTimeMs){
bestWinningTimeMs = timeTaken;
}
if(timeTaken > worstWinningTimeMs){
worstWinningTimeMs = timeTaken;
}
}
showGameWonDialog(); // Affiche dialog victoire
} else if (getGame().isGameOver()) {
// Gérer la défaite
totalGamesPlayed++; // Stat
showGameOverDialog(); // Affiche dialog défaite
}
} }
game.addNewNumbers();
updateUI();
} }
// Modifié pour montrer la dialog
private void restartGame() {
showRestartConfirmationDialog();
}
// Nouvelle méthode pour la dialog /**
private void showRestartConfirmationDialog() { * Énumération représentant les directions possibles de swipe.
*/
enum Direction { UP, DOWN, LEFT, RIGHT }
/**
* Affiche une boîte de dialogue de confirmation avant de recommencer la partie.
* Utilise un layout XML personnalisé (dialog_restart_confirm.xml).
*/
private void showRestartConfirmationDialog() { // Inchangé mais appelle startNewGame
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = getLayoutInflater(); LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_restart_confirm, null); View dialogView = inflater.inflate(R.layout.dialog_restart_confirm, null);
builder.setView(dialogView); builder.setView(dialogView);
// Références vues dialog TextView dialogTitle = dialogView.findViewById(R.id.dialogTitle);
TextView dialogTitle = dialogView.findViewById(R.id.dialogTitle); // Non utilisé mais présent dans snippet TextView dialogMessage = dialogView.findViewById(R.id.dialogMessage);
TextView dialogMessage = dialogView.findViewById(R.id.dialogMessage); // Non utilisé mais présent dans snippet
Button cancelButton = dialogView.findViewById(R.id.dialogCancelButton); Button cancelButton = dialogView.findViewById(R.id.dialogCancelButton);
Button confirmButton = dialogView.findViewById(R.id.dialogConfirmButton); Button confirmButton = dialogView.findViewById(R.id.dialogConfirmButton);
final AlertDialog dialog = builder.create(); final AlertDialog dialog = builder.create();
cancelButton.setOnClickListener(v -> { cancelButton.setOnClickListener(v -> dialog.dismiss());
dialog.dismiss();
});
confirmButton.setOnClickListener(v -> { confirmButton.setOnClickListener(v -> {
dialog.dismiss(); dialog.dismiss();
game = new Game(); // Création nouveau jeu startNewGame(); // Appelle la nouvelle méthode
game.addNewNumbers(); // Ajout tuiles initiales
game.addNewNumbers();
updateUI(); // Mise à jour UI
}); });
dialog.show(); dialog.show();
} }
/**
private void showStats() { * Commence une nouvelle partie : réinitialise le jeu et les statistiques de la partie en cours.
//A faire */
} private void startNewGame() { // Nouvelle méthode (anciennement restartGame) + reset stats
private void showMenu() { totalGamesStarted++; // Stat
//A faire currentMoves = 0; // Reset stat partie
mergesThisGame = 0; // Reset stat partie
currentGameStartTimeMs = System.currentTimeMillis(); // Reset timer partie
setGame(new Game(this)); // Crée un nouveau jeu
// game.addNewTile(); // Le constructeur de Game s'en charge maintenant
// game.addNewTile();
updateUI(); // Met à jour l'affichage
} }
private void showMultiplayerScreen() {
//A faire
}
// Ajout onPause /**
@Override * Affiche les statistiques du jeu en utilisant un ViewStub pour le chargement différé.
protected void onPause() { */
super.onPause(); private void showStatistics() { // Nouvelle méthode
saveGame(); // Sauvegarde en quittant statisticsVisible = !statisticsVisible;
}
// Nouvelle méthode sauvegarde if (statisticsVisible) {
private void saveGame() { if (statisticsViewStub.getParent() != null) {
SharedPreferences.Editor editor = sharedPreferences.edit(); statisticsViewStub.inflate();
editor.putString(GAME_STATE_KEY, game.serialize()); // Sauvegarde état sérialisé }
editor.putInt(HIGH_SCORE_KEY, game.getHighScore()); // Sauvegarde high score updateStatisticsTextViews(); // Met à jour les textes des stats
editor.apply(); // Appliquer findViewById(R.id.statistics_layout).setVisibility(View.VISIBLE); // Rend visible le layout gonflé
} getMultiplayerButton().setVisibility(View.GONE); // Cache bouton multi
// Nouvelle méthode chargement
private void loadGame() {
String gameState = sharedPreferences.getString(GAME_STATE_KEY, null); // Récupère état
if (gameState != null) {
game = Game.deserialize(gameState); // Restaure si existe
// game.loadHighScore(); // Pas dans le snippet loadGame
} else { } else {
game = new Game(); // Nouveau jeu sinon hideStatistics(); // Cache si on reclique
game.addNewNumbers();
game.addNewNumbers();
} }
game.loadHighScore(); // Présent dans le snippet loadGame
updateUI(); // MAJ UI
// Logique high score séparée comme dans snippet
int savedHighScore = sharedPreferences.getInt(HIGH_SCORE_KEY, 0);
if (savedHighScore > game.getHighScore()) {
game.setHighScore(savedHighScore);
// updateUI(); // Pas de second updateUI dans le snippet ici
}
} }
private enum Direction { /**
UP, DOWN, LEFT, RIGHT * Met à jour tous les TextViews à l'intérieur du layout des statistiques (qui doit déjà être gonflé).
*/
private void updateStatisticsTextViews() { // Nouvelle méthode
View inflatedStatsView = findViewById(R.id.statistics_layout); // Récupère la vue gonflée
if (inflatedStatsView != null) { // Vérifie si la vue existe
TextView highScoreStatsLabel = inflatedStatsView.findViewById(R.id.high_score_stats_label);
TextView totalGamesPlayedLabel = inflatedStatsView.findViewById(R.id.total_games_played_label);
TextView totalGamesStartedLabel = inflatedStatsView.findViewById(R.id.total_games_started_label);
TextView winPercentageLabel = inflatedStatsView.findViewById(R.id.win_percentage_label);
TextView totalPlayTimeLabel = inflatedStatsView.findViewById(R.id.total_play_time_label);
TextView totalMovesLabel = inflatedStatsView.findViewById(R.id.total_moves_label);
TextView currentMovesLabel = inflatedStatsView.findViewById(R.id.current_moves_label);
TextView currentGameTimeLabel = inflatedStatsView.findViewById(R.id.current_game_time_label);
TextView averageGameTimeLabel = inflatedStatsView.findViewById(R.id.average_game_time_label); // Pour solo
TextView bestWinningTimeLabel = inflatedStatsView.findViewById(R.id.best_winning_time_label);
TextView worstWinningTimeLabel = inflatedStatsView.findViewById(R.id.worst_winning_time_label);
TextView totalMergesLabel = inflatedStatsView.findViewById(R.id.total_merges_label);
TextView highestTileLabel = inflatedStatsView.findViewById(R.id.highest_tile_label);
TextView objectiveReachedLabel = inflatedStatsView.findViewById(R.id.number_of_time_objective_reached_label);
TextView perfectGameLabel = inflatedStatsView.findViewById(R.id.perfect_game_label);
TextView multiplayerGamesWonLabel = inflatedStatsView.findViewById(R.id.multiplayer_games_won_label);
TextView multiplayerGamesPlayedLabel = inflatedStatsView.findViewById(R.id.multiplayer_games_played_label);
TextView multiplayerWinRateLabel = inflatedStatsView.findViewById(R.id.multiplayer_win_rate_label);
TextView multiplayerBestWinningStreakLabel = inflatedStatsView.findViewById(R.id.multiplayer_best_winning_streak_label);
TextView multiplayerAverageScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_average_score_label);
TextView averageTimePerGameMultiLabel = inflatedStatsView.findViewById(R.id.average_time_per_game_label); // ID dupliqué? Utilisons le 2eme
TextView totalMultiplayerLossesLabel = inflatedStatsView.findViewById(R.id.total_multiplayer_losses_label);
TextView multiplayerHighScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_high_score_label);
TextView mergesThisGameLabel = inflatedStatsView.findViewById(R.id.merges_this_game);
// MAJ Textes
highScoreStatsLabel.setText(getString(R.string.high_score_stats, game.getHighestScore()));
totalGamesPlayedLabel.setText(getString(R.string.total_games_played, totalGamesPlayed));
totalGamesStartedLabel.setText(getString(R.string.total_games_started, totalGamesStarted));
totalMovesLabel.setText(getString(R.string.total_moves, totalMoves));
currentMovesLabel.setText(getString(R.string.current_moves, currentMoves));
mergesThisGameLabel.setText(getString(R.string.merges_this_game_label, mergesThisGame));
totalMergesLabel.setText(getString(R.string.total_merges, totalMerges));
highestTileLabel.setText(getString(R.string.highest_tile, highestTile));
objectiveReachedLabel.setText(getString(R.string.number_of_time_objective_reached, numberOfTimesObjectiveReached));
perfectGameLabel.setText(getString(R.string.perfect_games, perfectGames)); // Il manque la stat perfectGames dans les champs
multiplayerGamesWonLabel.setText(getString(R.string.multiplayer_games_won, multiplayerGamesWon));
multiplayerGamesPlayedLabel.setText(getString(R.string.multiplayer_games_played, multiplayerGamesPlayed));
multiplayerBestWinningStreakLabel.setText(getString(R.string.multiplayer_best_winning_streak, multiplayerBestWinningStreak));
multiplayerAverageScoreLabel.setText(getString(R.string.multiplayer_average_score, multiplayerGamesPlayed > 0 ? (totalMerges / multiplayerGamesPlayed) : 0)); // Calcul approximatif
totalMultiplayerLossesLabel.setText(getString(R.string.total_multiplayer_losses, totalMultiplayerLosses));
multiplayerHighScoreLabel.setText(getString(R.string.multiplayer_high_score, multiplayerHighestScore));
// Calculs Pourcentages et Temps
String winPercentage = (totalGamesStarted > 0) ? String.format("%.2f%%", ((double) numberOfTimesObjectiveReached / totalGamesStarted) * 100) : "N/A"; // Utilise numberOfTimesObjectiveReached
winPercentageLabel.setText(getString(R.string.win_percentage, winPercentage));
String multiplayerWinRate = (multiplayerGamesPlayed > 0) ? String.format("%.2f%%", ((double) multiplayerGamesWon / multiplayerGamesPlayed) * 100) : "N/A";
multiplayerWinRateLabel.setText(getString(R.string.multiplayer_win_rate, multiplayerWinRate));
totalPlayTimeLabel.setText(getString(R.string.total_play_time, formatTime(totalPlayTimeMs)));
// Calcule le temps de la partie en cours dynamiquement si elle n'est pas finie
long currentDuration = (game != null && !game.isGameOver() && !game.isGameWon()) ? System.currentTimeMillis() - currentGameStartTimeMs : 0;
currentGameTimeLabel.setText(getString(R.string.current_game_time, formatTime(currentDuration)));
long avgTimeSolo = (numberOfTimesObjectiveReached > 0) ? totalPlayTimeMs / numberOfTimesObjectiveReached : 0; // Basé sur victoires? Ou parties jouées?
averageGameTimeLabel.setText(getString(R.string.average_time_per_game, formatTime(avgTimeSolo))); // ID 1
long avgTimeMulti = (multiplayerGamesPlayed > 0) ? multiplayerAverageGameTimeMs / multiplayerGamesPlayed : 0;
averageTimePerGameMultiLabel.setText(getString(R.string.average_time_per_game_label, formatTime(avgTimeMulti))); // ID 2
bestWinningTimeLabel.setText(getString(R.string.best_winning_time, (bestWinningTimeMs != Long.MAX_VALUE) ? formatTime(bestWinningTimeMs) : "N/A"));
worstWinningTimeLabel.setText(getString(R.string.worst_winning_time, (worstWinningTimeMs != 0) ? formatTime(worstWinningTimeMs) : "N/A"));
// Listener bouton Back dans les stats
Button backButton = inflatedStatsView.findViewById(R.id.backButton);
backButton.setOnClickListener(v -> hideStatistics());
}
}
/**
* Masque la vue des statistiques et réaffiche le bouton multijoueur.
*/
private void hideStatistics() { // Nouvelle méthode
View inflatedStatsView = findViewById(R.id.statistics_layout);
if (inflatedStatsView != null) {
inflatedStatsView.setVisibility(View.GONE);
}
getMultiplayerButton().setVisibility(View.VISIBLE); // Réaffiche bouton multi
statisticsVisible = false; // Met à jour l'état
}
/**
* Charge les statistiques depuis les SharedPreferences.
*/
private void loadStatistics() { // Nouvelle méthode
totalGamesPlayed = preferences.getInt("totalGamesPlayed", 0);
totalGamesStarted = preferences.getInt("totalGamesStarted", 0);
totalMoves = preferences.getInt("totalMoves", 0);
// currentMoves n'est pas chargé, il est spécifique à la session
totalPlayTimeMs = preferences.getLong("totalPlayTimeMs", 0);
// currentGameStartTimeMs est initialisé dans initializeGame ou repris
// mergesThisGame n'est pas chargé
totalMerges = preferences.getInt("totalMerges", 0);
highestTile = preferences.getInt("highestTile", 0);
numberOfTimesObjectiveReached = preferences.getInt("numberOfTimesObjectiveReached", 0);
perfectGames = preferences.getInt("perfectGames", 0); // Chargement stat
// averageGameTimeMs n'est pas chargé, recalculé
bestWinningTimeMs = preferences.getLong("bestWinningTimeMs", Long.MAX_VALUE);
worstWinningTimeMs = preferences.getLong("worstWinningTimeMs", 0);
multiplayerGamesWon = preferences.getInt("multiplayerGamesWon", 0);
multiplayerGamesPlayed = preferences.getInt("multiplayerGamesPlayed", 0);
multiplayerBestWinningStreak = preferences.getInt("multiplayerBestWinningStreak", 0);
// multiplayerAverageScore recalculé
multiplayerAverageGameTimeMs = preferences.getLong("multiplayerAverageGameTimeMs", 0);
totalMultiplayerLosses = preferences.getInt("totalMultiplayerLosses", 0);
multiplayerHighestScore = preferences.getInt("multiplayerHighScore", 0);
}
/**
* Sauvegarde les statistiques dans les SharedPreferences.
*/
private void saveStatistics() { // Nouvelle méthode
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("totalGamesPlayed", totalGamesPlayed);
editor.putInt("totalGamesStarted", totalGamesStarted);
editor.putInt("totalMoves", totalMoves);
// currentMoves non sauvegardé
editor.putLong("totalPlayTimeMs", totalPlayTimeMs);
// currentGameStartTimeMs non sauvegardé directement, utilisé pour calculer totalPlayTimeMs
// mergesThisGame non sauvegardé
editor.putInt("totalMerges", totalMerges);
editor.putInt("highestTile", highestTile);
editor.putInt("numberOfTimesObjectiveReached", numberOfTimesObjectiveReached);
editor.putInt("perfectGames", perfectGames); // Sauvegarde stat
// averageGameTimeMs non sauvegardé
editor.putLong("bestWinningTimeMs", bestWinningTimeMs);
editor.putLong("worstWinningTimeMs", worstWinningTimeMs);
editor.putInt("multiplayerGamesWon", multiplayerGamesWon);
editor.putInt("multiplayerGamesPlayed", multiplayerGamesPlayed);
editor.putInt("multiplayerBestWinningStreak", multiplayerBestWinningStreak);
// multiplayerAverageScore non sauvegardé
editor.putLong("multiplayerAverageGameTimeMs", multiplayerAverageGameTimeMs);
editor.putInt("totalMultiplayerLosses", totalMultiplayerLosses);
editor.putInt("multiplayerHighScore", multiplayerHighestScore);
editor.apply();
}
/**
* Formate une durée en millisecondes en une chaîne de caractères (hh:mm:ss ou mm:ss).
*/
private String formatTime(long milliseconds) { // Nouvelle méthode
long seconds = (milliseconds / 1000) % 60;
long minutes = (milliseconds / (1000 * 60)) % 60;
long hours = milliseconds / (1000 * 60 * 60);
if (hours > 0) {
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
} else {
return String.format("%02d:%02d", minutes, seconds);
}
}
/**
* Affiche l'écran du menu (placeholder avec AlertDialog).
*/
private void showMenu() { // Modifié pour utiliser AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Menu")
.setMessage("Fonctionnalité de menu à venir !")
.setPositiveButton("OK", (dialog, id) -> { /* Rien */ });
AlertDialog dialog = builder.create();
dialog.show();
}
/**
* Affiche l'écran du mode multijoueur (placeholder avec AlertDialog).
*/
private void showMultiplayerScreen() { // Modifié pour utiliser AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Multijoueur")
.setMessage("Fonctionnalité multijoueur à venir !")
.setPositiveButton("OK", (dialog, id) -> { /* Rien */ });
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
protected void onResume() { // Ajouté/Modifié
super.onResume();
// Ne redémarre le chrono que si la partie n'est pas finie
if (game != null && !game.isGameOver() && !game.isGameWon()) {
currentGameStartTimeMs = System.currentTimeMillis();
}
// Réaffiche les stats si elles étaient visibles
if (statisticsVisible) {
// Assure-toi que la vue est prête avant de mettre à jour
if (findViewById(R.id.statistics_layout) != null) {
updateStatisticsTextViews();
findViewById(R.id.statistics_layout).setVisibility(View.VISIBLE);
getMultiplayerButton().setVisibility(View.GONE);
} else {
// Si la vue n'est pas encore gonflée, on la montre (ce qui la gonflera et mettra à jour)
showStatistics();
}
}
}
@Override
protected void onPause() { // Modifié pour calculer temps et sauver stats
super.onPause();
if (game != null) {
// Ajoute le temps écoulé seulement si la partie n'est pas finie
if (!game.isGameOver() && !game.isGameWon()) {
totalPlayTimeMs += System.currentTimeMillis() - currentGameStartTimeMs;
}
saveGame(); // Sauvegarde état jeu
saveStatistics(); // Sauvegarde stats
}
}
private void saveGame() {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(GAME_STATE_KEY, game.toString());
editor.putInt(HIGH_SCORE_KEY, game.getHighestScore());
editor.apply();
}
/**
* Affiche une boîte de dialogue lorsque le joueur gagne la partie (atteint 2048).
*/
private void showGameWonDialog() { // Ajouté
// Met à jour les stats de victoire avant d'afficher
numberOfTimesObjectiveReached++;
long timeTaken = System.currentTimeMillis() - currentGameStartTimeMs;
if(timeTaken < bestWinningTimeMs){ bestWinningTimeMs = timeTaken; }
if(timeTaken > worstWinningTimeMs){ worstWinningTimeMs = timeTaken; }
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Vous avez gagné !")
.setMessage("Félicitations ! Vous avez atteint 2048 !")
.setPositiveButton("Nouvelle partie", (dialog, which) -> startNewGame())
.setNegativeButton("Quitter", (dialog, which) -> finish())
.setCancelable(false)
.show();
}
/**
* Affiche une boîte de dialogue lorsque le joueur perd la partie (aucun mouvement possible).
*/
private void showGameOverDialog() { // Ajouté
totalGamesPlayed++; // Met à jour stat avant d'afficher
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Partie terminée !")
.setMessage("Aucun mouvement possible. Votre score final est : " + game.getCurrentScore())
.setPositiveButton("Nouvelle partie", (dialog, which) -> startNewGame())
.setNegativeButton("Quitter", (dialog, which) -> finish())
.setCancelable(false)
.show();
} }
} }

View File

@ -1,72 +1,152 @@
// Fichier OnSwipeTouchListener.java
// Classe utilitaire pour détecter les gestes de balayage (swipe).
/*
Fonctions principales :
- Utilise GestureDetector pour détecter les swipes.
- Définit une interface SwipeListener pour notifier la classe appelante (MainActivity).
- onFling() : Détecte la direction du swipe (haut, bas, gauche, droite).
- SWIPE_THRESHOLD, SWIPE_VELOCITY_THRESHOLD : Constantes pour la sensibilité du swipe.
Relations :
- MainActivity : MainActivity crée une instance et est notifiée des swipes via l'interface SwipeListener.
*/
package legion.muyue.best2048; package legion.muyue.best2048;
import android.annotation.SuppressLint; // Ajout
import android.content.Context; import android.content.Context;
// import android.location.GnssAntennaInfo; // Import inutile trouvé dans le dump
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; // Ajout
public class OnSwipeTouchListener implements View.OnTouchListener { public class OnSwipeTouchListener implements View.OnTouchListener {
private final GestureDetector gestureDetector; private final GestureDetector gestureDetector;
private final SwipeListener listener; // Ajout de l'interface listener private final SwipeListener listener;
// Interface pour les événements de swipe /**
* Interface pour les événements de swipe. Toute classe qui veut réagir aux swipes
* doit implémenter cette interface et passer une instance à OnSwipeTouchListener.
*/
public interface SwipeListener { public interface SwipeListener {
/**
* Appelée lorsqu'un swipe vers le haut est détecté.
*/
void onSwipeTop(); void onSwipeTop();
/**
* Appelée lorsqu'un swipe vers le bas est détecté.
*/
void onSwipeBottom(); void onSwipeBottom();
/**
* Appelée lorsqu'un swipe vers la gauche est détecté.
*/
void onSwipeLeft(); void onSwipeLeft();
/**
* Appelée lorsqu'un swipe vers la droite est détecté.
*/
void onSwipeRight(); void onSwipeRight();
} }
// Constructeur modifié pour accepter le listener /**
* Constructeur de la classe OnSwipeTouchListener.
*
* @param ctx Le contexte de l'application. Nécessaire pour GestureDetector.
* @param listener L'instance qui écoutera les événements de swipe.
*/
public OnSwipeTouchListener(Context ctx, SwipeListener listener) { public OnSwipeTouchListener(Context ctx, SwipeListener listener) {
gestureDetector = new GestureDetector(ctx, new GestureListener()); gestureDetector = new GestureDetector(ctx, new GestureListener());
this.listener = listener; this.listener = listener;
} }
/**
* Méthode appelée lorsqu'un événement tactile se produit sur la vue attachée.
* Elle transmet l'événement au GestureDetector pour analyse.
*
* @param v La vue sur laquelle l'événement tactile s'est produit.
* @param event L'objet MotionEvent décrivant l'événement tactile.
* @return true si l'événement a été géré, false sinon.
*/
@SuppressLint("ClickableViewAccessibility") // Ajout
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event); return gestureDetector.onTouchEvent(event);
} }
/**
* Classe interne qui étend GestureDetector.SimpleOnGestureListener pour gérer
* spécifiquement les gestes de balayage (fling).
*/
private final class GestureListener extends GestureDetector.SimpleOnGestureListener { private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
private static final int SWIPE_THRESHOLD = 100; private static final int SWIPE_THRESHOLD = 100; // Distance minimale du swipe (en pixels).
private static final int SWIPE_VELOCITY_THRESHOLD = 100; private static final int SWIPE_VELOCITY_THRESHOLD = 100; // Vitesse minimale du swipe (en pixels par seconde).
/**
* Méthode appelée lorsqu'un appui initial sur l'écran est détecté.
* On retourne toujours 'true' pour indiquer qu'on gère cet événement.
*
* @param e L'objet MotionEvent décrivant l'appui initial.
* @return true, car on gère toujours l'événement 'onDown'.
*/
@Override @Override
public boolean onDown(MotionEvent e) { public boolean onDown(@NonNull MotionEvent e) { // Ajout @NonNull
return true; return true;
} }
/**
* Méthode appelée lorsqu'un geste de balayage (fling) est détecté.
*
* @param e1 L'objet MotionEvent du premier appui (début du swipe). Peut être null.
* @param e2 L'objet MotionEvent de la fin du swipe.
* @param velocityX La vitesse du swipe en pixels par seconde sur l'axe X.
* @param velocityY La vitesse du swipe en pixels par seconde sur l'axe Y.
* @return true si le geste de balayage a été géré, false sinon.
*/
@Override @Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { // Ajout @NonNull
// Vérification de nullité pour e1, nécessaire car onFling peut être appelé même si onDown retourne false ou si le geste est complexe.
if (e1 == null) {
return false;
}
boolean result = false; boolean result = false;
try { try {
float diffY = e2.getY() - e1.getY(); float diffY = e2.getY() - e1.getY(); // Différence de position sur l'axe Y.
float diffX = e2.getX() - e1.getX(); float diffX = e2.getX() - e1.getX(); // Différence de position sur l'axe X.
// Détermine si le swipe est plutôt horizontal ou vertical.
if (Math.abs(diffX) > Math.abs(diffY)) { if (Math.abs(diffX) > Math.abs(diffY)) {
// Swipe horizontal.
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) { if (diffX > 0) {
listener.onSwipeRight(); // Appel via listener listener.onSwipeRight(); // Swipe vers la droite.
} else { } else {
listener.onSwipeLeft(); // Appel via listener listener.onSwipeLeft(); // Swipe vers la gauche.
} }
result = true; result = true;
} }
} else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) { } else {
if (diffY > 0) { // Swipe vertical.
listener.onSwipeBottom(); // Appel via listener if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
} else { if (diffY > 0) {
listener.onSwipeTop(); // Appel via listener listener.onSwipeBottom(); // Swipe vers le bas.
} else {
listener.onSwipeTop(); // Swipe vers le haut.
}
result = true;
} }
result = true;
} }
} catch (Exception exception) { } catch (Exception exception) {
exception.printStackTrace(); // exception.printStackTrace(); // Commenté dans le nouveau code ? Gardons commenté.
exception.fillInStackTrace(); // Gestion des erreurs (journalisation).
} }
return result; return result;
} }
} }
// Les méthodes locales vides ne sont plus nécessaires car on utilise l'interface
} }

View File

@ -1,15 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"> android:interpolator="@android:anim/accelerate_decelerate_interpolator">
<scale <scale
android:duration="150"
android:fromXScale="1.0" android:fromXScale="1.0"
android:toXScale="0.95"
android:fromYScale="1.0" android:fromYScale="1.0"
android:toYScale="0.95"
android:pivotX="50%" android:pivotX="50%"
android:pivotY="50%" android:pivotY="50%"
android:duration="100" /> android:toXScale="0.95"
android:toYScale="0.95" />
<alpha <alpha
android:duration="150"
android:fromAlpha="1.0" android:fromAlpha="1.0"
android:toAlpha="0.8" android:toAlpha="0.8" />
android:duration="100" />
</set> </set>

View File

@ -1,15 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"> android:interpolator="@android:anim/accelerate_decelerate_interpolator">
<scale <scale
android:duration="150"
android:fromXScale="0.95" android:fromXScale="0.95"
android:toXScale="1.0"
android:fromYScale="0.95" android:fromYScale="0.95"
android:toYScale="1.0"
android:pivotX="50%" android:pivotX="50%"
android:pivotY="50%" android:pivotY="50%"
android:duration="100" /> android:toXScale="1.0"
android:toYScale="1.0" />
<alpha <alpha
android:duration="150"
android:fromAlpha="0.8" android:fromAlpha="0.8"
android:toAlpha="1.0" android:toAlpha="1.0" />
android:duration="100" />
</set> </set>

View File

@ -1,6 +1,35 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <selector xmlns:android="http://schemas.android.com/apk/res/android">
android:shape="rectangle"> <item android:state_pressed="true">
<solid android:color="@color/button_background"/> <shape android:shape="rectangle">
<corners android:radius="4dp"/> <gradient
</shape> android:startColor="@color/multiplayer_button_pressed_start"
android:endColor="@color/multiplayer_button_pressed_end"
android:angle="270" />
<corners android:radius="30dp" /> <padding
android:left="16dp"
android:top="8dp"
android:right="16dp"
android:bottom="8dp" />
<stroke
android:width="2dp"
android:color="@color/multiplayer_button_stroke" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<gradient
android:startColor="@color/multiplayer_button_start"
android:endColor="@color/multiplayer_button_end"
android:angle="270" />
<corners android:radius="30dp" /> <padding
android:left="16dp"
android:top="8dp"
android:right="16dp"
android:bottom="8dp" />
<stroke
android:width="2dp"
android:color="@color/multiplayer_button_stroke" />
</shape>
</item>
</selector>

View File

@ -2,7 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="@color/white"/> <solid android:color="@color/white"/>
<corners android:radius="12dp"/> <corners android:radius="32dp"/>
<stroke android:width="1dp"
android:color="@color/game_board_background"/>
</shape> </shape>

View File

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#CDC1B4"/>
<corners android:radius="@dimen/corner_radius"/> <solid android:color="@color/tile_empty" />
<corners android:radius="@dimen/corner_radius" />
<padding android:left="@dimen/tile_margin"
android:top="@dimen/tile_margin"
android:right="@dimen/tile_margin"
android:bottom="@dimen/tile_margin" />
</shape> </shape>

View File

@ -1,23 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/background_color" android:background="@color/background_color"
android:fitsSystemWindows="false"
tools:context=".MainActivity"> tools:context=".MainActivity">
<LinearLayout <LinearLayout
android:id="@+id/northPanel" android:id="@+id/northPanel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:baselineAligned="false"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_marginTop="32dp"
android:padding="@dimen/padding_general" android:padding="@dimen/padding_general"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -57,13 +55,14 @@
<TextView <TextView
android:id="@+id/scoreLabel" android:id="@+id/scoreLabel"
style="@style/ScoreLabelStyle" style="@style/ScoreLabelStyle"
android:text="@string/score" /> android:text="@string/score_placeholder" />
<TextView <TextView
android:id="@+id/highScoreLabel" android:id="@+id/highScoreLabel"
style="@style/ScoreLabelStyle" style="@style/ScoreLabelStyle"
android:layout_marginTop="@dimen/margin_between_elements" android:layout_marginTop="@dimen/margin_between_elements"
android:text="@string/high_score" /> android:text="@string/high_score_placeholder" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -96,8 +95,7 @@
<Button <Button
android:id="@+id/multiplayerButton" android:id="@+id/multiplayerButton"
style="@style/LargeButtonStyle" style="@style/LargeButtonStyle"
android:layout_width="match_parent" android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content"
android:text="@string/multiplayer" android:text="@string/multiplayer"
app:layout_constraintBottom_toTopOf="@+id/buttons_layout" app:layout_constraintBottom_toTopOf="@+id/buttons_layout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -109,10 +107,18 @@
android:id="@+id/buttons_layout" android:id="@+id/buttons_layout"
layout="@layout/bottom_buttons_layout" layout="@layout/bottom_buttons_layout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent"
/>
<ViewStub
android:id="@+id/statsViewStub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inflatedId="@+id/statistics_layout"
android:layout="@layout/stats_layout" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,270 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_color"
tools:context=".MainActivity">
<TextView
android:id="@+id/statsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/stats_title"
android:textSize="36sp"
android:textStyle="bold"
android:textColor="#333333"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statsTitle"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="@style/SectionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/general_section" />
<LinearLayout
style="@style/SectionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/high_score_stats_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/high_score_stats" />
<TextView
android:id="@+id/total_games_played_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/total_games_played" />
<TextView
android:id="@+id/total_games_started_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/total_games_started" />
<TextView
android:id="@+id/win_percentage_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/win_percentage" />
<TextView
android:id="@+id/total_play_time_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/total_play_time" />
<TextView
android:id="@+id/total_moves_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/total_moves" />
</LinearLayout>
<TextView
style="@style/SectionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/current_game_section" />
<LinearLayout
style="@style/SectionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/current_moves_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/current_moves" />
<TextView
android:id="@+id/current_game_time_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/current_game_time" />
<TextView
android:id="@+id/merges_this_game"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/merges_this_game_label" />
</LinearLayout>
<TextView
style="@style/SectionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/single_player_section" />
<LinearLayout
style="@style/SectionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/average_game_time_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/average_time_per_game" />
<TextView
android:id="@+id/best_winning_time_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/best_winning_time" />
<TextView
android:id="@+id/worst_winning_time_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/worst_winning_time" />
<TextView
android:id="@+id/total_merges_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/total_merges" />
<TextView
android:id="@+id/highest_tile_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/highest_tile" />
<TextView
android:id="@+id/number_of_time_objective_reached_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/number_of_time_objective_reached" />
<TextView
android:id="@+id/perfect_game_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/perfect_games" />
</LinearLayout>
<TextView
style="@style/SectionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multiplayer_section" />
<LinearLayout
style="@style/SectionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/multiplayer_games_won_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multiplayer_games_won" />
<TextView
android:id="@+id/multiplayer_games_played_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multiplayer_games_played" />
<TextView
android:id="@+id/multiplayer_win_rate_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multiplayer_win_rate" />
<TextView
android:id="@+id/multiplayer_best_winning_streak_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multiplayer_best_winning_streak" />
<TextView
android:id="@+id/multiplayer_average_score_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multiplayer_average_score" />
<TextView
android:id="@+id/average_time_per_game_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/average_time_per_game_label" />
<TextView
android:id="@+id/total_multiplayer_losses_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/total_multiplayer_losses" />
<TextView
android:id="@+id/multiplayer_high_score_label"
style="@style/StatLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multiplayer_high_score" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/backButton"
style="@style/ButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/back_button_label"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,24 +2,35 @@
<resources> <resources>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="text_tile_low">#776E65</color>
<color name="text_tile_high">#F9F6F2</color> <color name="text_tile_low">#776e65</color>
<color name="background_color">#FCFAEE</color> <color name="text_tile_high">#f9f6f2</color>
<color name="game_label_background">#EDCC5F</color>
<color name="score_label_background">#F75E3E</color> <color name="background_color">#faf8ef</color>
<color name="game_board_background">#A3937D</color> <color name="game_board_background">#bbada0</color>
<color name="button_background">#4A443C</color> <color name="score_label_background">#f65e3b</color>
<color name="tile_empty_color">#C9BCAC</color> <color name="game_label_background">#edc22e</color>
<color name="tile_2_color">#EEE4DA</color> <color name="button_background">#8f7a66</color>
<color name="tile_4_color">#EDE0C8</color> <color name="button_text_color">#f9f6f2</color>
<color name="tile_8_color">#F2B179</color> <color name="tile_empty">#cdc1b4</color>
<color name="tile_16_color">#F59563</color> <color name="tile_2">#eee4da</color>
<color name="tile_32_color">#F67C5F</color> <color name="tile_4">#ede0c8</color>
<color name="tile_64_color">#F65E3B</color> <color name="tile_8">#f2b179</color>
<color name="tile_128_color">#EDCF72</color> <color name="tile_16">#f59563</color>
<color name="tile_256_color">#EDCC61</color> <color name="tile_32">#f67c5f</color>
<color name="tile_512_color">#EDC850</color> <color name="tile_64">#f65e3b</color>
<color name="tile_1024_color">#EEC43F</color> <color name="tile_128">#edcf72</color>
<color name="tile_2048_color">#EEC43F</color> <color name="tile_256">#edcc61</color>
<color name="tile_super_color">#3C3A32</color> <color name="tile_512">#edc850</color>
<color name="tile_1024">#edc53f</color>
<color name="tile_2048">#edc22e</color>
<color name="tile_super">#3c3a32</color>
<color name="multiplayer_button_start">#6200ee</color>
<color name="multiplayer_button_end">#3700b3</color>
<color name="multiplayer_button_pressed_start">#3700b3</color>
<color name="multiplayer_button_pressed_end">#03dac6</color>
<color name="multiplayer_button_stroke">#b0bec5</color>
<color name="stats_background">#875932</color>
</resources> </resources>

View File

@ -13,4 +13,35 @@
<string name="restart_confirm_message">Are you sure you want to restart the game ?</string> <string name="restart_confirm_message">Are you sure you want to restart the game ?</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="confirm">Confirm</string> <string name="confirm">Confirm</string>
<string name="high_score_stats">High Score: %d</string>
<string name="total_games_played">Total Games Played: %d</string>
<string name="total_games_started">Total Games Started: %d</string>
<string name="win_percentage">Win Percentage: %s</string>
<string name="total_play_time">Total Play Time: %s</string>
<string name="total_moves">Total Moves: %d</string>
<string name="current_moves">Current Moves: %d</string>
<string name="current_game_time">Current Game Time: %s</string>
<string name="merges_this_game_label">Merges: %d</string>
<string name="average_time_per_game">Average Game Time: %s</string>
<string name="best_winning_time">Best Winning Time: %s</string>
<string name="worst_winning_time">Worst Winning Time: %s</string>
<string name="total_merges">Total Merges: %d</string>
<string name="highest_tile">Highest Tile: %d</string>
<string name="number_of_time_objective_reached">Number of time objective reached: %d</string>
<string name="perfect_games">Perfect game : %d</string>
<string name="multiplayer_games_won">Multiplayer game won : %d</string>
<string name="multiplayer_games_played">Multiplayer game played : %d</string>
<string name="multiplayer_win_rate">Multiplayer win rate : %s</string>
<string name="multiplayer_best_winning_streak">Best winning streak : %d</string>
<string name="multiplayer_average_score">Multiplayer Average Score: %d</string>
<string name="average_time_per_game_label">Average time per game: %s</string>
<string name="total_multiplayer_losses">Total Multiplayer losses: %d</string>
<string name="multiplayer_high_score">Multiplayer High Score: %d</string>
<string name="stats_button_label">Stats</string>
<string name="stats_title">Statistics</string>
<string name="general_section">General</string>
<string name="current_game_section">Current Game</string>
<string name="single_player_section">Single Player</string>
<string name="multiplayer_section">Multiplayer</string>
<string name="back_button_label">Back</string>
</resources> </resources>

View File

@ -1,9 +1,7 @@
<resources> <resources>
<style name="Base.Theme.Best2048" parent="Theme.Material3.DayNight.NoActionBar"> <style name="Theme.Best2048" parent="Theme.AppCompat.Light.NoActionBar">
</style> </style>
<style name="Theme.Best2048" parent="Base.Theme.Best2048" />
<style name="ScoreLabelStyle"> <style name="ScoreLabelStyle">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">0dp</item> <item name="android:layout_height">0dp</item>
@ -24,16 +22,47 @@
<item name="android:layout_marginTop">@dimen/padding_general</item> <item name="android:layout_marginTop">@dimen/padding_general</item>
<item name="android:layout_marginStart">@dimen/padding_general</item> <item name="android:layout_marginStart">@dimen/padding_general</item>
<item name="android:layout_marginEnd">@dimen/padding_general</item> <item name="android:layout_marginEnd">@dimen/padding_general</item>
<item name="android:buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item> <item name="android:buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
</style> </style>
<style name="LargeButtonStyle" parent="ButtonStyle"> <style name="LargeButtonStyle">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">@dimen/padding_general</item>
<item name="android:layout_marginEnd">@dimen/padding_general</item>
<item name="android:buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
<item name="android:layout_marginTop">@dimen/padding_general</item> <item name="android:layout_marginTop">@dimen/padding_general</item>
<item name="android:layout_marginBottom">@dimen/padding_general</item> <item name="android:layout_marginBottom">@dimen/padding_general</item>
<item name="android:background">@drawable/button_multiplayer_background</item> <item name="android:textColor">@color/white</item> <item name="android:background">@drawable/button_multiplayer_background</item>
<item name="android:textColor">@color/white</item>
<item name="android:textAllCaps">true</item> <item name="android:textAllCaps">true</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
<item name="android:textSize">20sp</item> <item name="android:textSize">20sp</item>
</style> </style>
<style name="SectionTitle">
<item name="android:textSize">18sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/text_tile_high</item> <item name="android:layout_marginBottom">4dp</item>
</style>
<style name="SectionContainer">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:orientation">vertical</item>
<item name="android:background">@drawable/dialog_background</item> <item name="android:padding">12dp</item>
<item name="android:layout_marginBottom">16dp</item>
</style>
<style name="StatLabel">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">@color/text_tile_low</item> <item name="android:textSize">16sp</item>
<item name="android:layout_marginBottom">4dp</item>
</style>
<style name="Theme.AppCompat.Light.NoActionBar.FullScreen" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
</resources> </resources>