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:
parent
a190263cf5
commit
73ab81e208
@ -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"
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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
|
|
||||||
}
|
}
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
270
app/src/main/res/layout/stats_layout.xml
Normal file
270
app/src/main/res/layout/stats_layout.xml
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user