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.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; // Assurez-vous que cet import est présent
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.util.TypedValue; import android.util.TypedValue;
@ -28,7 +29,7 @@ import android.widget.Button;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
// --- UI Elements ---
private GridLayout boardGridLayout; private GridLayout boardGridLayout;
private TextView currentScoreTextView; private TextView currentScoreTextView;
private TextView highestScoreTextView; private TextView highestScoreTextView;
@ -39,23 +40,23 @@ public class MainActivity extends AppCompatActivity {
private ViewStub statisticsViewStub; private ViewStub statisticsViewStub;
private View inflatedStatsView; private View inflatedStatsView;
// --- Game Logic & Stats ---
private Game game; private Game game;
private GameStats gameStats; private GameStats gameStats;
private static final int BOARD_SIZE = 4; private static final int BOARD_SIZE = 4;
// --- State Management ---
private boolean statisticsVisible = false; private boolean statisticsVisible = false;
private enum GameFlowState { PLAYING, WON_DIALOG_SHOWN, GAME_OVER } private enum GameFlowState { PLAYING, WON_DIALOG_SHOWN, GAME_OVER }
private GameFlowState currentGameState = GameFlowState.PLAYING; private GameFlowState currentGameState = GameFlowState.PLAYING;
// --- Preferences ---
private SharedPreferences preferences; private SharedPreferences preferences;
private static final String PREFS_NAME = "Best2048_Prefs"; private static final String PREFS_NAME = "Best2048_Prefs";
private static final String HIGH_SCORE_KEY = "high_score"; private static final String HIGH_SCORE_KEY = "high_score";
private static final String GAME_STATE_KEY = "game_state"; private static final String GAME_STATE_KEY = "game_state";
// --- Activity Lifecycle ---
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -71,17 +72,18 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
protected void onResume() { protected void onResume() {
super.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()); gameStats.setCurrentGameStartTimeMs(System.currentTimeMillis());
} }
// Gère le réaffichage potentiel des stats si l'activité reprend
if (statisticsVisible) { if (statisticsVisible) {
if (inflatedStatsView != null) { if (inflatedStatsView != null) { // Si déjà gonflé
updateStatisticsTextViews(); updateStatisticsTextViews(); // Met à jour les données affichées
inflatedStatsView.setVisibility(View.VISIBLE); inflatedStatsView.setVisibility(View.VISIBLE);
multiplayerButton.setVisibility(View.GONE); multiplayerButton.setVisibility(View.GONE);
} else { } else {
// Si pas encore gonflé (cas rare mais possible), on le fait afficher
toggleStatistics(); toggleStatistics();
} }
} }
@ -90,18 +92,18 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
// Sauvegarde l'état et les stats si le jeu existe
if (game != null && gameStats != null) { if (game != null && gameStats != null) {
// Met à jour le temps total SI la partie était en cours
if (!game.isGameOver() && !game.isGameWon()) { if (currentGameState == GameFlowState.PLAYING) {
gameStats.addPlayTime(System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs()); gameStats.addPlayTime(System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs()); // Utilise méthode GameStats
} }
saveGame(); saveGame(); // Sauvegarde l'état du jeu (plateau + score courant) et le HS
gameStats.saveStats(); gameStats.saveStats(); // Sauvegarde toutes les stats via GameStats
} }
} }
// --- Initialisation ---
/** /**
* Récupère les références des vues du layout principal via leur ID. * 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() { private void initializeGameAndStats() {
preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
gameStats = new GameStats(this); gameStats = new GameStats(this);
loadGame(); loadGame(); // Charge jeu et met à jour high score
updateUI(); updateUI();
if (game == null) { if (game == null) {
// Si loadGame échoue ou aucune sauvegarde, startNewGame gère l'initialisation
startNewGame(); 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() { private void setupListeners() {
newGameButton.setOnClickListener(v -> { newGameButton.setOnClickListener(v -> {
@ -155,18 +149,19 @@ public class MainActivity extends AppCompatActivity {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press)); v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
toggleStatistics(); toggleStatistics();
}); });
// Modifié pour appeler la nouvelle méthode showMenu()
menuButton.setOnClickListener(v -> { menuButton.setOnClickListener(v -> {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press)); v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
showMenu(); showMenu(); // Appelle la méthode du menu
}); });
multiplayerButton.setOnClickListener(v -> { multiplayerButton.setOnClickListener(v -> {
v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press)); v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_press));
showMultiplayerScreen(); showMultiplayerScreen(); // Affiche dialogue placeholder
}); });
setupSwipeListener(); setupSwipeListener();
} }
// --- Mise à jour UI ---
/** /**
* Met à jour complètement l'interface utilisateur (plateau et scores). * Met à jour complètement l'interface utilisateur (plateau et scores).
@ -187,11 +182,11 @@ public class MainActivity extends AppCompatActivity {
TextView tileTextView = new TextView(this); TextView tileTextView = new TextView(this);
int value = game.getCellValue(row, col); int value = game.getCellValue(row, col);
setTileStyle(tileTextView, value); setTileStyle(tileTextView, value);
// Définit les LayoutParams pour que la tuile remplisse la cellule du GridLayout
GridLayout.LayoutParams params = new GridLayout.LayoutParams(); GridLayout.LayoutParams params = new GridLayout.LayoutParams();
params.width = 0; params.height = 0; params.width = 0; params.height = 0; // Poids gère la taille
params.rowSpec = GridLayout.spec(row, 1f); params.rowSpec = GridLayout.spec(row, 1f); // Prend 1 fraction de l'espace en hauteur
params.columnSpec = GridLayout.spec(col, 1f); params.columnSpec = GridLayout.spec(col, 1f); // Prend 1 fraction de l'espace en largeur
int margin = (int) getResources().getDimension(R.dimen.tile_margin); int margin = (int) getResources().getDimension(R.dimen.tile_margin);
params.setMargins(margin, margin, margin, margin); params.setMargins(margin, margin, margin, margin);
tileTextView.setLayoutParams(params); tileTextView.setLayoutParams(params);
@ -214,7 +209,6 @@ public class MainActivity extends AppCompatActivity {
* @param value La valeur numérique de la tuile (0 pour vide). * @param value La valeur numérique de la tuile (0 pour vide).
*/ */
private void setTileStyle(TextView tileTextView, int value) { private void setTileStyle(TextView tileTextView, int value) {
tileTextView.setText(value > 0 ? String.valueOf(value) : ""); tileTextView.setText(value > 0 ? String.valueOf(value) : "");
tileTextView.setGravity(Gravity.CENTER); tileTextView.setGravity(Gravity.CENTER);
tileTextView.setTypeface(null, android.graphics.Typeface.BOLD); 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. * Configure le listener pour détecter les swipes sur le plateau de jeu.
@ -258,130 +252,104 @@ public class MainActivity extends AppCompatActivity {
/** /**
* Traite un geste de swipe de l'utilisateur sur le plateau de jeu. * Traite un geste de swipe de l'utilisateur sur le plateau de jeu.
* 1. Tente d'effectuer le mouvement dans l'objet Game. * Met à jour le jeu, les statistiques et l'UI, et vérifie les conditions de fin de partie.
* 2. Si le mouvement a modifié le plateau (boardChanged == true) : * @param direction La direction du swipe détecté.
* - 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).
*/ */
private void handleSwipe(Direction direction) { private void handleSwipe(Direction direction) {
if (game == null || gameStats == null || currentGameState == GameFlowState.GAME_OVER) { if (game == null || gameStats == null || currentGameState == GameFlowState.GAME_OVER) {
return; return; // Ignore swipe si jeu terminé ou non initialisé
} }
int scoreBefore = game.getCurrentScore(); int scoreBefore = game.getCurrentScore();
boolean boardChanged = false; boolean boardChanged = false;
// Tente d'effectuer le mouvement dans l'objet Game
switch (direction) { switch (direction) {
case UP: case UP: boardChanged = game.pushUp(); break;
boardChanged = game.pushUp(); case DOWN: boardChanged = game.pushDown(); break;
break; case LEFT: boardChanged = game.pushLeft(); break;
case DOWN: case RIGHT: boardChanged = game.pushRight(); break;
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) { if (boardChanged) {
gameStats.recordMove(); gameStats.recordMove();
int scoreAfter = game.getCurrentScore(); int scoreAfter = game.getCurrentScore();
int scoreDelta = scoreAfter - scoreBefore; int scoreDelta = scoreAfter - scoreBefore;
if (scoreDelta > 0) { if (scoreDelta > 0) {
gameStats.recordMerge(1); // Simplification: compte comme 1 fusion si score augmente
gameStats.recordMerge(1);
if (scoreAfter > game.getHighestScore()) { if (scoreAfter > game.getHighestScore()) {
game.setHighestScore(scoreAfter); game.setHighestScore(scoreAfter);
gameStats.setHighestScore(scoreAfter); gameStats.setHighestScore(scoreAfter); // Met à jour aussi dans GameStats pour sauvegarde
} }
} }
gameStats.updateHighestTile(game.getHighestTileValue()); gameStats.updateHighestTile(game.getHighestTileValue());
game.addNewTile(); // Ajoute une nouvelle tuile
updateUI(); // Rafraîchit l'affichage
game.addNewTile();
updateUI();
} }
// Vérifie l'état final après le mouvement (même si boardChanged est false)
if (currentGameState != GameFlowState.GAME_OVER) { if (currentGameState != GameFlowState.GAME_OVER) {
if (game.isGameWon() && currentGameState == GameFlowState.PLAYING) { if (game.isGameWon() && currentGameState == GameFlowState.PLAYING) {
currentGameState = GameFlowState.WON_DIALOG_SHOWN; currentGameState = GameFlowState.WON_DIALOG_SHOWN;
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs(); long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
gameStats.recordWin(timeTaken); gameStats.recordWin(timeTaken);
showGameWonKeepPlayingDialog(); showGameWonKeepPlayingDialog();
} else if (game.isGameOver()) { } else if (game.isGameOver()) {
currentGameState = GameFlowState.GAME_OVER; currentGameState = GameFlowState.GAME_OVER;
long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs(); long timeTaken = System.currentTimeMillis() - gameStats.getCurrentGameStartTimeMs();
gameStats.recordLoss(); gameStats.recordLoss();
gameStats.endGame(timeTaken); gameStats.endGame(timeTaken); // Finalise temps, etc.
showGameOverDialog(); showGameOverDialog();
// Met à jour l'UI pour afficher le score final si Game Over atteint sans mouvement (rare)
if (!boardChanged) { if (!boardChanged) updateUI();
updateUI(); }
} }
} }
} /** Énumération pour les directions de swipe. */
} private enum Direction { UP, DOWN, LEFT, RIGHT }
enum Direction { UP, DOWN, LEFT, RIGHT } // --- Dialogues ---
/** /**
* Affiche la boîte de dialogue demandant confirmation avant de redémarrer. * Affiche la boîte de dialogue demandant confirmation avant de redémarrer.
*/ */
private void showRestartConfirmationDialog() { private void showRestartConfirmationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = getLayoutInflater(); View dialogView = inflater.inflate(R.layout.dialog_restart_confirm, null); LayoutInflater inflater = getLayoutInflater();
builder.setView(dialogView); Button cancelButton = dialogView.findViewById(R.id.dialogCancelButton); Button confirmButton = dialogView.findViewById(R.id.dialogConfirmButton); View dialogView = inflater.inflate(R.layout.dialog_restart_confirm, null);
final AlertDialog dialog = builder.create(); cancelButton.setOnClickListener(v -> dialog.dismiss()); builder.setView(dialogView);
confirmButton.setOnClickListener(v -> { dialog.dismiss(); startNewGame(); }); dialog.show(); 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, * Démarre une nouvelle partie logiquement et rafraîchit l'UI.
* crée un nouvel objet Game, synchronise le meilleur score et met à jour 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() { private void startNewGame() {
gameStats.startGame(); if (gameStats == null) gameStats = new GameStats(this); // Précaution si initialisation a échoué avant
game = new Game(); gameStats.startGame(); // Réinitialise stats de partie (temps, mouvements, etc.)
game.setHighestScore(gameStats.getOverallHighScore()); game = new Game(); // Crée un nouveau jeu logique
currentGameState = GameFlowState.PLAYING; game.setHighestScore(gameStats.getOverallHighScore()); // Applique le meilleur score global au nouveau jeu
updateUI(); 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é. * Affiche la boîte de dialogue quand 2048 est atteint, en utilisant un layout personnalisé.
@ -394,22 +362,18 @@ public class MainActivity extends AppCompatActivity {
builder.setView(dialogView); builder.setView(dialogView);
builder.setCancelable(false); builder.setCancelable(false);
Button keepPlayingButton = dialogView.findViewById(R.id.dialogKeepPlayingButton); Button keepPlayingButton = dialogView.findViewById(R.id.dialogKeepPlayingButton);
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonWon); Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonWon);
final AlertDialog dialog = builder.create(); final AlertDialog dialog = builder.create();
keepPlayingButton.setOnClickListener(v -> { keepPlayingButton.setOnClickListener(v -> {
// L'état est déjà WON_DIALOG_SHOWN, on ne fait rien de spécial, le jeu continue.
dialog.dismiss(); dialog.dismiss();
}); });
newGameButton.setOnClickListener(v -> { newGameButton.setOnClickListener(v -> {
dialog.dismiss(); dialog.dismiss();
startNewGame(); startNewGame();
}); });
dialog.show(); dialog.show();
} }
@ -424,31 +388,102 @@ public class MainActivity extends AppCompatActivity {
builder.setView(dialogView); builder.setView(dialogView);
builder.setCancelable(false); builder.setCancelable(false);
TextView messageTextView = dialogView.findViewById(R.id.dialogMessageGameOver); TextView messageTextView = dialogView.findViewById(R.id.dialogMessageGameOver);
Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonGameOver); Button newGameButton = dialogView.findViewById(R.id.dialogNewGameButtonGameOver);
Button quitButton = dialogView.findViewById(R.id.dialogQuitButtonGameOver); Button quitButton = dialogView.findViewById(R.id.dialogQuitButtonGameOver);
messageTextView.setText(getString(R.string.game_over_message, game.getCurrentScore())); messageTextView.setText(getString(R.string.game_over_message, game.getCurrentScore()));
final AlertDialog dialog = builder.create(); final AlertDialog dialog = builder.create();
newGameButton.setOnClickListener(v -> { newGameButton.setOnClickListener(v -> {
dialog.dismiss(); dialog.dismiss();
startNewGame(); startNewGame();
}); });
quitButton.setOnClickListener(v -> { quitButton.setOnClickListener(v -> {
dialog.dismiss(); dialog.dismiss();
finish(); finish(); // Ferme l'application
}); });
dialog.show(); 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. * Affiche ou masque le panneau de statistiques.
@ -457,20 +492,20 @@ public class MainActivity extends AppCompatActivity {
private void toggleStatistics() { private void toggleStatistics() {
statisticsVisible = !statisticsVisible; statisticsVisible = !statisticsVisible;
if (statisticsVisible) { if (statisticsVisible) {
if (inflatedStatsView == null) { if (inflatedStatsView == null) { // Gonfle si pas encore fait
inflatedStatsView = statisticsViewStub.inflate(); inflatedStatsView = statisticsViewStub.inflate();
// Attache listener au bouton Back une fois la vue gonflée
Button backButton = inflatedStatsView.findViewById(R.id.backButton); Button backButton = inflatedStatsView.findViewById(R.id.backButton);
backButton.setOnClickListener(v -> toggleStatistics()); backButton.setOnClickListener(v -> toggleStatistics()); // Recliquer sur Back re-appelle toggle
} }
updateStatisticsTextViews(); updateStatisticsTextViews(); // Remplit les champs avec les données actuelles
inflatedStatsView.setVisibility(View.VISIBLE); inflatedStatsView.setVisibility(View.VISIBLE); // Affiche
multiplayerButton.setVisibility(View.GONE); multiplayerButton.setVisibility(View.GONE); // Masque bouton multi
} else { } else {
if (inflatedStatsView != null) { if (inflatedStatsView != null) { // Masque si la vue existe
inflatedStatsView.setVisibility(View.GONE); 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() { private void updateStatisticsTextViews() {
if (inflatedStatsView == null || gameStats == null) return; 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 highScoreStatsLabel = inflatedStatsView.findViewById(R.id.high_score_stats_label);
TextView totalGamesPlayedLabel = inflatedStatsView.findViewById(R.id.total_games_played_label); TextView totalGamesPlayedLabel = inflatedStatsView.findViewById(R.id.total_games_played_label);
TextView totalGamesStartedLabel = inflatedStatsView.findViewById(R.id.total_games_started_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 multiplayerWinRateLabel = inflatedStatsView.findViewById(R.id.multiplayer_win_rate_label);
TextView multiplayerBestWinningStreakLabel = inflatedStatsView.findViewById(R.id.multiplayer_best_winning_streak_label); TextView multiplayerBestWinningStreakLabel = inflatedStatsView.findViewById(R.id.multiplayer_best_winning_streak_label);
TextView multiplayerAverageScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_average_score_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 totalMultiplayerLossesLabel = inflatedStatsView.findViewById(R.id.total_multiplayer_losses_label);
TextView multiplayerHighScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_high_score_label); TextView multiplayerHighScoreLabel = inflatedStatsView.findViewById(R.id.multiplayer_high_score_label);
TextView mergesThisGameLabel = inflatedStatsView.findViewById(R.id.merges_this_game); 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())); highScoreStatsLabel.setText(getString(R.string.high_score_stats, gameStats.getOverallHighScore()));
totalGamesPlayedLabel.setText(getString(R.string.total_games_played, gameStats.getTotalGamesPlayed())); totalGamesPlayedLabel.setText(getString(R.string.total_games_played, gameStats.getTotalGamesPlayed()));
totalGamesStartedLabel.setText(getString(R.string.total_games_started, gameStats.getTotalGamesStarted())); 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())); totalMultiplayerLossesLabel.setText(getString(R.string.total_multiplayer_losses, gameStats.getTotalMultiplayerLosses()));
multiplayerHighScoreLabel.setText(getString(R.string.multiplayer_high_score, gameStats.getMultiplayerHighestScore())); 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"; String winPercentage = (gameStats.getTotalGamesStarted() > 0) ? String.format("%.2f%%", ((double) gameStats.getNumberOfTimesObjectiveReached() / gameStats.getTotalGamesStarted()) * 100) : "N/A";
winPercentageLabel.setText(getString(R.string.win_percentage, winPercentage)); winPercentageLabel.setText(getString(R.string.win_percentage, winPercentage));
String multiplayerWinRate = (gameStats.getMultiplayerGamesPlayed() > 0) ? String.format("%.2f%%", ((double) gameStats.getMultiplayerGamesWon() / gameStats.getMultiplayerGamesPlayed()) * 100) : "N/A"; 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)); multiplayerWinRateLabel.setText(getString(R.string.multiplayer_win_rate, multiplayerWinRate));
// Calculs Temps
totalPlayTimeLabel.setText(getString(R.string.total_play_time, GameStats.formatTime(gameStats.getTotalPlayTimeMs()))); 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; // Calcule le temps de la partie en cours seulement si elle n'est pas finie
currentGameTimeLabel.setText(getString(R.string.current_game_time, GameStats.formatTime(currentDuration))); 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()))); 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")); 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")); worstWinningTimeLabel.setText(getString(R.string.worst_winning_time, (gameStats.getWorstWinningTimeMs() != 0) ? GameStats.formatTime(gameStats.getWorstWinningTimeMs()) : "N/A"));
} }
// --- Placeholders Multi ---
/** 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();
}
/** Affiche un dialogue placeholder pour le multijoueur. */ /** Affiche un dialogue placeholder pour le multijoueur. */
private void showMultiplayerScreen() { private void showMultiplayerScreen() {
@ -557,45 +591,60 @@ public class MainActivity extends AppCompatActivity {
builder.create().show(); builder.create().show();
} }
// --- Sauvegarde / Chargement ---
/** Sauvegarde l'état du jeu et le meilleur score via SharedPreferences. */ /** Sauvegarde l'état du jeu et le meilleur score via SharedPreferences. */
private void saveGame() { private void saveGame() {
SharedPreferences.Editor editor = preferences.edit(); SharedPreferences.Editor editor = preferences.edit();
if (game != null) { 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()); editor.putInt(HIGH_SCORE_KEY, game.getHighestScore());
} else { } 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. */ /** Charge l'état du jeu depuis SharedPreferences et synchronise le meilleur score. */
private void loadGame() { private void loadGame() {
String gameStateString = preferences.getString(GAME_STATE_KEY, null); 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); int savedHighScore = preferences.getInt(HIGH_SCORE_KEY, 0);
if (gameStats != null) { // Assure que GameStats charge son état (y compris le HS global)
gameStats.setHighestScore(savedHighScore); 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) { if (gameStateString != null) {
game = Game.deserialize(gameStateString); loadedGame = Game.deserialize(gameStateString);
if (game != null) { }
game.setHighestScore(savedHighScore);
if (game.isGameOver()) currentGameState = GameFlowState.GAME_OVER; if (loadedGame != null) {
else if (game.isGameWon()) currentGameState = GameFlowState.WON_DIALOG_SHOWN; game = loadedGame;
else currentGameState = GameFlowState.PLAYING; game.setHighestScore(savedHighScore); // Applique le HS synchronisé
} else { game = null; } // Détermine l'état basé sur le jeu chargé
} else { game = null; } if (game.isGameOver()) {
currentGameState = GameFlowState.GAME_OVER;
if (game == null) { } else if (game.isGameWon()) {
game = new Game(); // Si on charge une partie déjà gagnée, on considère qu'on a déjà vu la dialog
game.setHighestScore(savedHighScore); currentGameState = GameFlowState.WON_DIALOG_SHOWN;
} else {
currentGameState = GameFlowState.PLAYING; 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_title">Game over!</string>
<string name="game_over_message">No move possible.\nFinal score: %d</string> <string name="game_over_message">No move possible.\nFinal score: %d</string>
<string name="quit">To leave</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> </resources>