Feat: Implémentation menu principal via AlertDialog

- Ajout des chaînes de caractères pour le menu et ses options dans strings.xml.
- Modification de MainActivity :
  - Implémentation de showMenu() pour créer et afficher la liste d'options grâce à un layout personnalisé (dialog_main_menu.xml).
  - Ajout de showHowToPlayDialog() affichant les règles de base.
  - Ajout de showAboutDialog() affichant des informations sur l'app.
  - Ajout de showSettingsDialog() comme placeholder pour les futurs paramètres.
This commit is contained in:
Augustin ROUX 2025-04-04 14:28:41 +02:00
parent 21ff127536
commit 2a782950cd
3 changed files with 292 additions and 168 deletions

View File

@ -9,6 +9,7 @@ package legion.muyue.best2048;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface; // Assurez-vous que cet import est présent
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.TypedValue;
@ -28,7 +29,7 @@ import android.widget.Button;
public class MainActivity extends AppCompatActivity {
// --- UI Elements ---
private GridLayout boardGridLayout;
private TextView currentScoreTextView;
private TextView highestScoreTextView;
@ -39,23 +40,23 @@ public class MainActivity extends AppCompatActivity {
private ViewStub statisticsViewStub;
private View inflatedStatsView;
// --- Game Logic & Stats ---
private Game game;
private GameStats gameStats;
private static final int BOARD_SIZE = 4;
// --- State Management ---
private boolean statisticsVisible = false;
private enum GameFlowState { PLAYING, WON_DIALOG_SHOWN, GAME_OVER }
private GameFlowState currentGameState = GameFlowState.PLAYING;
// --- Preferences ---
private SharedPreferences preferences;
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";
// --- Activity Lifecycle ---
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -71,17 +72,18 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onResume() {
super.onResume();
if (game != null && gameStats != null && !game.isGameOver() && !game.isGameWon()) {
// Redémarre le timer seulement si le jeu est en cours (pas gagné/perdu)
if (game != null && gameStats != null && currentGameState == GameFlowState.PLAYING) {
gameStats.setCurrentGameStartTimeMs(System.currentTimeMillis());
}
// Gère le réaffichage potentiel des stats si l'activité reprend
if (statisticsVisible) {
if (inflatedStatsView != null) {
updateStatisticsTextViews();
if (inflatedStatsView != null) { // Si déjà gonflé
updateStatisticsTextViews(); // Met à jour les données affichées
inflatedStatsView.setVisibility(View.VISIBLE);
multiplayerButton.setVisibility(View.GONE);
} else {
// Si pas encore gonflé (cas rare mais possible), on le fait afficher
toggleStatistics();
}
}
@ -90,18 +92,18 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onPause() {
super.onPause();
// Sauvegarde l'état et les stats si le jeu existe
if (game != null && gameStats != null) {
if (!game.isGameOver() && !game.isGameWon()) {
gameStats.addPlayTime(System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs());
// Met à jour le temps total SI la partie était en cours
if (currentGameState == GameFlowState.PLAYING) {
gameStats.addPlayTime(System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs()); // Utilise méthode GameStats
}
saveGame();
gameStats.saveStats();
saveGame(); // Sauvegarde l'état du jeu (plateau + score courant) et le HS
gameStats.saveStats(); // Sauvegarde toutes les stats via GameStats
}
}
// --- Initialisation ---
/**
* Récupère les références des vues du layout principal via leur ID.
@ -125,26 +127,18 @@ public class MainActivity extends AppCompatActivity {
private void initializeGameAndStats() {
preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
gameStats = new GameStats(this);
loadGame();
loadGame(); // Charge jeu et met à jour high score
updateUI();
if (game == null) {
// Si loadGame échoue ou aucune sauvegarde, startNewGame gère l'initialisation
startNewGame();
} else {
if (game.isGameOver()) {
currentGameState = GameFlowState.GAME_OVER;
} else if (game.isGameWon()) {
currentGameState = GameFlowState.WON_DIALOG_SHOWN;
} else {
currentGameState = GameFlowState.PLAYING;
}
}
// L'état (currentGameState) est défini dans loadGame ou startNewGame
}
/**
* Attache les listeners aux boutons et configure le listener de swipe sur le plateau.
* Configure les listeners pour les boutons et le plateau de jeu (swipes).
* Mise à jour pour le bouton Menu.
*/
private void setupListeners() {
newGameButton.setOnClickListener(v -> {
@ -155,18 +149,19 @@ public class MainActivity extends AppCompatActivity {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
toggleStatistics();
});
// Modifié pour appeler la nouvelle méthode showMenu()
menuButton.setOnClickListener(v -> {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
showMenu();
showMenu(); // Appelle la méthode du menu
});
multiplayerButton.setOnClickListener(v -> {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
showMultiplayerScreen();
showMultiplayerScreen(); // Affiche dialogue placeholder
});
setupSwipeListener();
}
// --- Mise à jour UI ---
/**
* Met à jour complètement l'interface utilisateur (plateau et scores).
@ -187,11 +182,11 @@ public class MainActivity extends AppCompatActivity {
TextView tileTextView = new TextView(this);
int value = game.getCellValue(row, col);
setTileStyle(tileTextView, value);
// Définit les LayoutParams pour que la tuile remplisse la cellule du GridLayout
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
params.width = 0; params.height = 0;
params.rowSpec = GridLayout.spec(row, 1f);
params.columnSpec = GridLayout.spec(col, 1f);
params.width = 0; params.height = 0; // Poids gère la taille
params.rowSpec = GridLayout.spec(row, 1f); // Prend 1 fraction de l'espace en hauteur
params.columnSpec = GridLayout.spec(col, 1f); // Prend 1 fraction de l'espace en largeur
int margin = (int) getResources().getDimension(R.dimen.tile_margin);
params.setMargins(margin, margin, margin, margin);
tileTextView.setLayoutParams(params);
@ -214,7 +209,6 @@ public class MainActivity extends AppCompatActivity {
* @param value La valeur numérique de la tuile (0 pour vide).
*/
private void setTileStyle(TextView tileTextView, int value) {
tileTextView.setText(value > 0 ? String.valueOf(value) : "");
tileTextView.setGravity(Gravity.CENTER);
tileTextView.setTypeface(null, android.graphics.Typeface.BOLD);
@ -241,7 +235,7 @@ public class MainActivity extends AppCompatActivity {
}
// --- Gestion des Actions Utilisateur ---
/**
* Configure le listener pour détecter les swipes sur le plateau de jeu.
@ -258,131 +252,105 @@ public class MainActivity extends AppCompatActivity {
/**
* Traite un geste de swipe de l'utilisateur sur le plateau de jeu.
* 1. Tente d'effectuer le mouvement dans l'objet Game.
* 2. Si le mouvement a modifié le plateau (boardChanged == true) :
* - Met à jour les statistiques (mouvements, fusions, score, etc.).
* - Ajoute une nouvelle tuile aléatoire.
* - Met à jour l'affichage (UI).
* 3. Vérifie l'état du jeu (gagné ou perdu) APRÈS la tentative de mouvement,
* que le plateau ait changé ou non. C'est la correction clé.
* 4. Affiche les boîtes de dialogue appropriées (victoire, défaite).
*
* @param direction La direction du swipe détecté (UP, DOWN, LEFT, RIGHT).
* Met à jour le jeu, les statistiques et l'UI, et vérifie les conditions de fin de partie.
* @param direction La direction du swipe détecté.
*/
private void handleSwipe(Direction direction) {
if (game == null || gameStats == null || currentGameState == GameFlowState.GAME_OVER) {
return;
return; // Ignore swipe si jeu terminé ou non initialisé
}
int scoreBefore = game.getCurrentScore();
boolean boardChanged = false;
// Tente d'effectuer le mouvement dans l'objet Game
switch (direction) {
case UP:
boardChanged = game.pushUp();
break;
case DOWN:
boardChanged = game.pushDown();
break;
case LEFT:
boardChanged = game.pushLeft();
break;
case RIGHT:
boardChanged = game.pushRight();
break;
case UP: boardChanged = game.pushUp(); break;
case DOWN: boardChanged = game.pushDown(); break;
case LEFT: boardChanged = game.pushLeft(); break;
case RIGHT: boardChanged = game.pushRight(); break;
}
// Si le mouvement a modifié le plateau
if (boardChanged) {
gameStats.recordMove();
int scoreAfter = game.getCurrentScore();
int scoreDelta = scoreAfter - scoreBefore;
if (scoreDelta > 0) {
gameStats.recordMerge(1);
gameStats.recordMerge(1); // Simplification: compte comme 1 fusion si score augmente
if (scoreAfter > game.getHighestScore()) {
game.setHighestScore(scoreAfter);
gameStats.setHighestScore(scoreAfter);
gameStats.setHighestScore(scoreAfter); // Met à jour aussi dans GameStats pour sauvegarde
}
}
gameStats.updateHighestTile(game.getHighestTileValue());
game.addNewTile();
updateUI();
game.addNewTile(); // Ajoute une nouvelle tuile
updateUI(); // Rafraîchit l'affichage
}
// Vérifie l'état final après le mouvement (même si boardChanged est false)
if (currentGameState != GameFlowState.GAME_OVER) {
if (game.isGameWon() && currentGameState == GameFlowState.PLAYING) {
currentGameState = GameFlowState.WON_DIALOG_SHOWN;
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
gameStats.recordWin(timeTaken);
showGameWonKeepPlayingDialog();
} else if (game.isGameOver()) {
currentGameState = GameFlowState.GAME_OVER;
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
gameStats.recordLoss();
gameStats.endGame(timeTaken);
gameStats.endGame(timeTaken); // Finalise temps, etc.
showGameOverDialog();
if (!boardChanged) {
updateUI();
}
// Met à jour l'UI pour afficher le score final si Game Over atteint sans mouvement (rare)
if (!boardChanged) updateUI();
}
}
}
enum Direction { UP, DOWN, LEFT, RIGHT }
/** Énumération pour les directions de swipe. */
private enum Direction { UP, DOWN, LEFT, RIGHT }
// --- Dialogues ---
/**
* Affiche la boîte de dialogue demandant confirmation avant de redémarrer.
*/
private void showRestartConfirmationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = getLayoutInflater(); View dialogView = inflater.inflate(R.layout.dialog_restart_confirm, null);
builder.setView(dialogView); Button cancelButton = dialogView.findViewById(R.id.dialogCancelButton); Button confirmButton = dialogView.findViewById(R.id.dialogConfirmButton);
final AlertDialog dialog = builder.create(); cancelButton.setOnClickListener(v -> dialog.dismiss());
confirmButton.setOnClickListener(v -> { dialog.dismiss(); startNewGame(); }); dialog.show();
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_restart_confirm, null);
builder.setView(dialogView);
Button cancelButton = dialogView.findViewById(R.id.dialogCancelButton);
Button confirmButton = dialogView.findViewById(R.id.dialogConfirmButton);
final AlertDialog dialog = builder.create();
cancelButton.setOnClickListener(v -> dialog.dismiss());
confirmButton.setOnClickListener(v -> { dialog.dismiss(); startNewGame(); });
dialog.show();
}
/**
* Démarre une nouvelle partie : initialise GameStats pour une nouvelle session,
* crée un nouvel objet Game, synchronise le meilleur score et met à jour l'UI.
* Démarre une nouvelle partie logiquement et rafraîchit l'UI.
* Réinitialise les stats de la partie en cours via `gameStats.startGame()`.
* Ferme le panneau de statistiques s'il est ouvert.
*/
private void startNewGame() {
gameStats.startGame();
game = new Game();
game.setHighestScore(gameStats.getOverallHighScore());
currentGameState = GameFlowState.PLAYING;
updateUI();
if (gameStats == null) gameStats = new GameStats(this); // Précaution si initialisation a échoué avant
gameStats.startGame(); // Réinitialise stats de partie (temps, mouvements, etc.)
game = new Game(); // Crée un nouveau jeu logique
game.setHighestScore(gameStats.getOverallHighScore()); // Applique le meilleur score global au nouveau jeu
currentGameState = GameFlowState.PLAYING; // Définit l'état à JOUER
// Ferme le panneau de statistiques s'il était ouvert
if (statisticsVisible) {
toggleStatistics(); // Utilise la méthode existante pour masquer proprement
}
// Assure que le bouton multijoueur est visible (pourrait être masqué par les stats)
multiplayerButton.setVisibility(View.VISIBLE);
updateUI(); // Met à jour l'affichage (plateau, scores)
}
/**
* Affiche la boîte de dialogue quand 2048 est atteint, en utilisant un layout personnalisé.
* Propose de continuer à jouer ou de commencer une nouvelle partie.
@ -394,22 +362,18 @@ public class MainActivity extends AppCompatActivity {
builder.setView(dialogView);
builder.setCancelable(false);
Button keepPlayingButton = dialogView.findViewById(R.id.dialogKeepPlayingButton);
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonWon);
final AlertDialog dialog = builder.create();
keepPlayingButton.setOnClickListener(v -> {
// L'état est déjà WON_DIALOG_SHOWN, on ne fait rien de spécial, le jeu continue.
dialog.dismiss();
});
newGameButton.setOnClickListener(v -> {
dialog.dismiss();
startNewGame();
});
dialog.show();
}
@ -424,31 +388,102 @@ public class MainActivity extends AppCompatActivity {
builder.setView(dialogView);
builder.setCancelable(false);
TextView messageTextView = dialogView.findViewById(R.id.dialogMessageGameOver);
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonGameOver);
Button quitButton = dialogView.findViewById(R.id.dialogQuitButtonGameOver);
messageTextView.setText(getString(R.string.game_over_message, game.getCurrentScore()));
final AlertDialog dialog = builder.create();
newGameButton.setOnClickListener(v -> {
dialog.dismiss();
startNewGame();
});
quitButton.setOnClickListener(v -> {
dialog.dismiss();
finish();
finish(); // Ferme l'application
});
dialog.show();
}
// --- Menu Principal ---
/**
* Affiche la boîte de dialogue du menu principal en utilisant un layout personnalisé.
* Attache les listeners aux boutons pour déclencher les actions correspondantes.
*/
private void showMenu() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_main_menu, null); // Gonfle le layout personnalisé
builder.setView(dialogView);
builder.setCancelable(true);
// Récupère les boutons du layout personnalisé
Button howToPlayButton = dialogView.findViewById(R.id.menuButtonHowToPlay);
Button settingsButton = dialogView.findViewById(R.id.menuButtonSettings);
Button aboutButton = dialogView.findViewById(R.id.menuButtonAbout);
Button returnButton = dialogView.findViewById(R.id.menuButtonReturn);
final AlertDialog dialog = builder.create();
// Attache les listeners aux boutons
howToPlayButton.setOnClickListener(v -> {
dialog.dismiss(); // Ferme le menu
showHowToPlayDialog(); // Ouvre la dialogue "Comment Jouer"
});
settingsButton.setOnClickListener(v -> {
dialog.dismiss(); // Ferme le menu
showSettingsDialog(); // Ouvre la dialogue placeholder "Paramètres"
});
aboutButton.setOnClickListener(v -> {
dialog.dismiss(); // Ferme le menu
showAboutDialog(); // Ouvre la dialogue "À Propos"
});
returnButton.setOnClickListener(v -> {
dialog.dismiss(); // Ferme simplement le menu
});
dialog.show(); // Affiche la boîte de dialogue
}
/**
* Affiche une boîte de dialogue simple expliquant les règles du jeu.
*/
private void showHowToPlayDialog() {
new AlertDialog.Builder(this)
.setTitle(R.string.how_to_play_title)
.setMessage(R.string.how_to_play_instructions)
.setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss())
.show();
}
/**
* Affiche une boîte de dialogue placeholder pour les paramètres.
*/
private void showSettingsDialog() {
new AlertDialog.Builder(this)
.setTitle(R.string.settings_title)
.setMessage(R.string.settings_message) // Message placeholder
.setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss())
.show();
}
/**
* Affiche une boîte de dialogue simple avec les informations "À Propos".
*/
private void showAboutDialog() {
new AlertDialog.Builder(this)
.setTitle(R.string.about_title)
.setMessage(R.string.about_message) // Pensez à personnaliser ceci dans strings.xml
.setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss())
.show();
}
// --- Gestion Stats UI ---
/**
* Affiche ou masque le panneau de statistiques.
@ -457,20 +492,20 @@ public class MainActivity extends AppCompatActivity {
private void toggleStatistics() {
statisticsVisible = !statisticsVisible;
if (statisticsVisible) {
if (inflatedStatsView == null) {
if (inflatedStatsView == null) { // Gonfle si pas encore fait
inflatedStatsView = statisticsViewStub.inflate();
// Attache listener au bouton Back une fois la vue gonflée
Button backButton = inflatedStatsView.findViewById(R.id.backButton);
backButton.setOnClickListener(v -> toggleStatistics());
backButton.setOnClickListener(v -> toggleStatistics()); // Recliquer sur Back re-appelle toggle
}
updateStatisticsTextViews();
inflatedStatsView.setVisibility(View.VISIBLE);
multiplayerButton.setVisibility(View.GONE);
updateStatisticsTextViews(); // Remplit les champs avec les données actuelles
inflatedStatsView.setVisibility(View.VISIBLE); // Affiche
multiplayerButton.setVisibility(View.GONE); // Masque bouton multi
} else {
if (inflatedStatsView != null) {
if (inflatedStatsView != null) { // Masque si la vue existe
inflatedStatsView.setVisibility(View.GONE);
}
multiplayerButton.setVisibility(View.VISIBLE);
multiplayerButton.setVisibility(View.VISIBLE); // Réaffiche bouton multi
}
}
@ -481,7 +516,7 @@ public class MainActivity extends AppCompatActivity {
private void updateStatisticsTextViews() {
if (inflatedStatsView == null || gameStats == null) return;
// Récupération des TextViews dans la vue gonflée
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);
@ -502,12 +537,12 @@ public class MainActivity extends AppCompatActivity {
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);
TextView averageTimePerGameMultiLabel = inflatedStatsView.findViewById(R.id.average_time_per_game_label); // Potentiel ID dupliqué dans layout?
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 avec getters de gameStats
highScoreStatsLabel.setText(getString(R.string.high_score_stats, gameStats.getOverallHighScore()));
totalGamesPlayedLabel.setText(getString(R.string.total_games_played, gameStats.getTotalGamesPlayed()));
totalGamesStartedLabel.setText(getString(R.string.total_games_started, gameStats.getTotalGamesStarted()));
@ -525,30 +560,29 @@ public class MainActivity extends AppCompatActivity {
totalMultiplayerLossesLabel.setText(getString(R.string.total_multiplayer_losses, gameStats.getTotalMultiplayerLosses()));
multiplayerHighScoreLabel.setText(getString(R.string.multiplayer_high_score, gameStats.getMultiplayerHighestScore()));
// Calculs Pourcentages
String winPercentage = (gameStats.getTotalGamesStarted() > 0) ? String.format("%.2f%%", ((double) gameStats.getNumberOfTimesObjectiveReached() / gameStats.getTotalGamesStarted()) * 100) : "N/A";
winPercentageLabel.setText(getString(R.string.win_percentage, winPercentage));
String multiplayerWinRate = (gameStats.getMultiplayerGamesPlayed() > 0) ? String.format("%.2f%%", ((double) gameStats.getMultiplayerGamesWon() / gameStats.getMultiplayerGamesPlayed()) * 100) : "N/A";
multiplayerWinRateLabel.setText(getString(R.string.multiplayer_win_rate, multiplayerWinRate));
// Calculs Temps
totalPlayTimeLabel.setText(getString(R.string.total_play_time, GameStats.formatTime(gameStats.getTotalPlayTimeMs())));
long currentDuration = (game != null && !game.isGameOver() && !game.isGameWon() && gameStats != null) ? System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs() : 0;
currentGameTimeLabel.setText(getString(R.string.current_game_time, GameStats.formatTime(currentDuration)));
// Calcule le temps de la partie en cours seulement si elle n'est pas finie
long currentDurationMs = 0;
if (game != null && gameStats != null && currentGameState == GameFlowState.PLAYING && gameStats.getCurrentGameStartTimeMs() > 0) {
currentDurationMs = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
}
currentGameTimeLabel.setText(getString(R.string.current_game_time, GameStats.formatTime(currentDurationMs)));
averageGameTimeLabel.setText(getString(R.string.average_time_per_game, GameStats.formatTime(gameStats.getAverageGameTimeMs())));
averageTimePerGameMultiLabel.setText(getString(R.string.average_time_per_game_label, GameStats.formatTime(gameStats.getMultiplayerAverageTimeMs())));
averageTimePerGameMultiLabel.setText(getString(R.string.average_time_per_game_label, GameStats.formatTime(gameStats.getMultiplayerAverageTimeMs()))); // Assurez-vous que l'ID R.string.average_time_per_game_label est correct
bestWinningTimeLabel.setText(getString(R.string.best_winning_time, (gameStats.getBestWinningTimeMs() != Long.MAX_VALUE) ? GameStats.formatTime(gameStats.getBestWinningTimeMs()) : "N/A"));
worstWinningTimeLabel.setText(getString(R.string.worst_winning_time, (gameStats.getWorstWinningTimeMs() != 0) ? GameStats.formatTime(gameStats.getWorstWinningTimeMs()) : "N/A"));
}
/** Affiche un dialogue placeholder pour le menu. */
private void showMenu() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Menu").setMessage("Fonctionnalité de menu à venir !").setPositiveButton("OK", null);
builder.create().show();
}
// --- Placeholders Multi ---
/** Affiche un dialogue placeholder pour le multijoueur. */
private void showMultiplayerScreen() {
@ -557,45 +591,60 @@ public class MainActivity extends AppCompatActivity {
builder.create().show();
}
// --- Sauvegarde / Chargement ---
/** Sauvegarde l'état du jeu et le meilleur score via SharedPreferences. */
private void saveGame() {
SharedPreferences.Editor editor = preferences.edit();
if (game != null) {
editor.putString(GAME_STATE_KEY, game.toString());
editor.putString(GAME_STATE_KEY, game.toString()); // Sérialise Game (plateau + score courant)
// Le meilleur score est géré et sauvegardé par GameStats, mais on le sauve aussi ici pour la synchro au chargement
editor.putInt(HIGH_SCORE_KEY, game.getHighestScore());
} else {
editor.remove(GAME_STATE_KEY);
editor.remove(GAME_STATE_KEY); // Optionnel: nettoyer si pas de jeu
}
editor.apply();
editor.apply(); // Utilise apply() pour une sauvegarde asynchrone
}
/** Charge l'état du jeu depuis SharedPreferences et synchronise le meilleur score. */
private void loadGame() {
String gameStateString = preferences.getString(GAME_STATE_KEY, null);
// Charge le meilleur score depuis les préférences (sera aussi chargé par GameStats mais on l'utilise ici pour Game)
int savedHighScore = preferences.getInt(HIGH_SCORE_KEY, 0);
if (gameStats != null) {
gameStats.setHighestScore(savedHighScore);
// Assure que GameStats charge son état (y compris le HS global)
if (gameStats == null) { gameStats = new GameStats(this); } // Précaution
gameStats.loadStats(); // Charge explicitement les stats (ce qui devrait inclure le HS global)
// S'assure que le HS chargé par gameStats est cohérent avec celui des prefs directes
if (savedHighScore > gameStats.getOverallHighScore()) {
gameStats.setHighestScore(savedHighScore); // Assure que GameStats a au moins le HS trouvé ici
} else {
savedHighScore = gameStats.getOverallHighScore(); // Utilise le HS de GameStats s'il est plus grand
}
Game loadedGame = null;
if (gameStateString != null) {
game = Game.deserialize(gameStateString);
if (game != null) {
game.setHighestScore(savedHighScore);
loadedGame = Game.deserialize(gameStateString);
}
if (game.isGameOver()) currentGameState = GameFlowState.GAME_OVER;
else if (game.isGameWon()) currentGameState = GameFlowState.WON_DIALOG_SHOWN;
else currentGameState = GameFlowState.PLAYING;
} else { game = null; }
} else { game = null; }
if (game == null) {
game = new Game();
game.setHighestScore(savedHighScore);
currentGameState = GameFlowState.PLAYING;
if (loadedGame != null) {
game = loadedGame;
game.setHighestScore(savedHighScore); // Applique le HS synchronisé
// Détermine l'état basé sur le jeu chargé
if (game.isGameOver()) {
currentGameState = GameFlowState.GAME_OVER;
} else if (game.isGameWon()) {
// Si on charge une partie déjà gagnée, on considère qu'on a déjà vu la dialog
currentGameState = GameFlowState.WON_DIALOG_SHOWN;
} else {
currentGameState = GameFlowState.PLAYING;
// Le timer sera (re)démarré dans onResume si l'état est PLAYING
}
} else {
// Pas de sauvegarde valide ou erreur de désérialisation -> Commence une nouvelle partie implicitement
game = null; // Sera géré par l'appel à startNewGame dans initializeGameAndStats
}
}
}
} // Fin MainActivity

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:background="@drawable/dialog_background">
<TextView
android:id="@+id/dialogTitleMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/menu_title"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/text_tile_low"
android:gravity="center"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/menuButtonHowToPlay"
style="@style/ButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp" android:layout_marginEnd="0dp"
android:layout_marginTop="4dp" android:layout_marginBottom="4dp"
android:text="@string/menu_option_how_to_play" />
<Button
android:id="@+id/menuButtonSettings"
style="@style/ButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp" android:layout_marginEnd="0dp"
android:layout_marginTop="4dp" android:layout_marginBottom="4dp"
android:text="@string/menu_option_settings" />
<Button
android:id="@+id/menuButtonAbout"
style="@style/ButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp" android:layout_marginEnd="0dp"
android:layout_marginTop="4dp" android:layout_marginBottom="4dp"
android:text="@string/menu_option_about" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/game_board_background"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/menuButtonReturn"
style="@style/ButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp" android:layout_marginEnd="0dp"
android:layout_marginTop="4dp" android:layout_marginBottom="0dp"
android:text="@string/menu_option_return" />
</LinearLayout>

View File

@ -51,4 +51,16 @@
<string name="game_over_title">Game over!</string>
<string name="game_over_message">No move possible.\nFinal score: %d</string>
<string name="quit">To leave</string>
<string name="menu_title">Main Menu</string>
<string name="menu_option_how_to_play">How to Play</string>
<string name="menu_option_settings">Settings</string>
<string name="menu_option_about">About</string>
<string name="menu_option_return">Back</string>
<string name="how_to_play_title">How to Play</string>
<string name="how_to_play_instructions">Swipe the screen (Up, Down, Left, Right) to move all the tiles.\n\nWhen two tiles with the same number touch, they merge into one!\n\nReach the 2048 tile to win.\n\nThe game is over if the board is full and no moves are possible.</string>
<string name="about_title">About Best 2048</string>
<string name="about_message">Version: 1.0 (University Project)\nDeveloped by: La Legion de Muyue\n(Leader: Muyue, Members: 2 others)\n\nBased on the popular game 2048.</string>
<string name="ok">OK</string>
<string name="settings_title">Settings</string>
<string name="settings_message">Settings screen to be implemented.</string>
</resources>