diff --git a/README.md b/README.md index b6cdddb..169eb35 100644 --- a/README.md +++ b/README.md @@ -60,18 +60,21 @@ BeatBattle/ │ │ ├── LeaderboardController.java │ │ └── SettingsController.java │ ├── model/ -│ │ ├── domain/ # GameSession, Player, Question, Song, GameRules, … +│ │ ├── domain/ # GameSession, Player, Question, Song, GameRules, AnswerSubmission, … │ │ ├── dto/ # SessionCreationResult, SessionJoinResult -│ │ ├── score/ # ScoreCalculator -│ │ └── services/ # LobbyService, MusicService +│ │ └── services/ # LobbyService, MusicService, QuestionBuilder, SessionSyncService │ ├── ecs/ │ │ ├── Engine.java │ │ ├── components/ # TimerComponent, AudioComponent │ │ ├── systems/ # RoundSystem, AudioSystem │ │ └── entities/ # Entity, RoundFactory -│ ├── states/ # StateManager + Start/Lobby/InRound/Leaderboard/GameOver states +│ ├── states/ # StateManager + Start/JoinCreate/Lobby/InRound/Leaderboard/GameOver states │ ├── view/ # One ScreenAdapter per state -│ ├── ui/ # Reusable Scene2D components & dialogs +│ ├── ui/ +│ │ ├── components/ # Reusable Scene2D buttons and selectors +│ │ ├── dialog/ # AlertDialogs +│ │ └── style/ # InputFieldStyles +│ ├── utils/ # ScoreCalculator, Strings │ ├── firebase/ # FirebaseGateway interface + NoOpFirebaseGateway │ └── audio/ # AudioPlayer interface └── assets/ # Fonts, images diff --git a/core/src/main/java/group07/beatbattle/BeatBattle.java b/core/src/main/java/group07/beatbattle/BeatBattle.java index 5b5be8b..b3668a9 100644 --- a/core/src/main/java/group07/beatbattle/BeatBattle.java +++ b/core/src/main/java/group07/beatbattle/BeatBattle.java @@ -21,11 +21,9 @@ import group07.beatbattle.ui.style.InputFieldStyles; /** - * Root libGDX Game class and central dependency holder for BeatBattle. - * Loads and owns the shared fonts (Montserrat, Orbitron, Oswald). - * Holds platform-injected services: FirebaseGateway, MusicService, AudioPlayer. - * Stores the local player's identity, session membership, and the active GameSession. - * Platform dependencies are injected by AndroidLauncher before create() is called. + * Root LibGDX Game class. Owns shared fonts, platform-injected services + * (FirebaseGateway, MusicService, AudioPlayer), and the local player's session state. + * AndroidLauncher injects platform dependencies before create() is called. */ public class BeatBattle extends Game { diff --git a/core/src/main/java/group07/beatbattle/controller/LeaderboardController.java b/core/src/main/java/group07/beatbattle/controller/LeaderboardController.java index 7eb6e1f..4731f4b 100644 --- a/core/src/main/java/group07/beatbattle/controller/LeaderboardController.java +++ b/core/src/main/java/group07/beatbattle/controller/LeaderboardController.java @@ -18,19 +18,16 @@ /** * Controls the leaderboard screen shown between rounds and at game over. - * Views that need to react to session cancellation should call - * startSessionStateListener() rather than accessing FirebaseGateway directly. * - * Host behaviour: onNextRound() writes the new round index to Firestore before - * launching the next RoundController, so joiners are kept in sync. - * onGameOver() writes "game_over" to Firestore then transitions to GameOverState. + * Host: onNextRound() writes the new round index to Firestore before launching the next round, + * and onGameOver() writes "game_over" before transitioning to GameOverState. * - * Joiner behaviour: joiners do not call onNextRound() or onGameOver() directly — - * they follow the host via the session-state listener started in LobbyController. + * Joiners do not call onNextRound() or onGameOver() directly — they follow the host + * via the session-state listener started in SessionSyncService. */ public class LeaderboardController { - /** Callback for views to react to session cancellation without importing FirebaseGateway. */ + /** Lets views react to session cancellation without accessing FirebaseGateway directly. */ public interface SessionCancelledCallback { void onHostLeft(); void onCancelled(); @@ -85,7 +82,7 @@ public void stopPlayerListener() { onPlayersChangedCallback = null; } - /** Returns the leaderboard entry for the local player, or null if not found. */ + /** Returns the leaderboard entry for the local player, or null if the player is not on the board. */ public LeaderboardEntry getLocalPlayerEntry() { String localId = game.getLocalPlayerId(); if (localId == null && !session.getPlayers().isEmpty()) { @@ -146,10 +143,7 @@ private void launchNextRound() { StateManager.getInstance().setState(new InRoundState(game, roundController)); } - /** - * Registers a Firestore session-state listener on behalf of the view. - * Only registers for the host; joiners are already handled by SessionSyncService. - */ + /** Starts listening for session cancellation on behalf of the view (host only). Joiners are already handled by SessionSyncService. */ public void startSessionStateListener(SessionCancelledCallback callback) { if (!game.isLocalHost()) return; String sessionId = game.getLocalSessionId(); @@ -250,11 +244,7 @@ public void onFailure(Exception exception) { } } - /** - * Schedules deletion of the session document 5 seconds after the host has cancelled it. - * This gives all connected clients time to receive the cancellation event and show - * the "host left" dialog before the Firestore document disappears. - */ + /** Deletes the session from Firestore after a short delay so all clients can receive the cancellation event first. */ private void scheduleSessionDeletion(String sessionId) { Timer.schedule(new Timer.Task() { @Override diff --git a/core/src/main/java/group07/beatbattle/controller/LobbyController.java b/core/src/main/java/group07/beatbattle/controller/LobbyController.java index d09f9d6..16e37c5 100644 --- a/core/src/main/java/group07/beatbattle/controller/LobbyController.java +++ b/core/src/main/java/group07/beatbattle/controller/LobbyController.java @@ -28,34 +28,25 @@ import group07.beatbattle.view.LobbyView; /** - * Orchestrates pre-game and inter-round navigation for both the host and joiners. + * Handles session creation, joining, leaving, and the transition into the first round. * - * Host path: onStartGame() fetches tracks from Deezer, delegates to QuestionBuilder - * which builds questions, stores them in Firestore, and sets state to "in_round". + * Host: onStartGame() fetches tracks from Deezer, builds questions via QuestionBuilder, + * stores them in Firestore, and navigates to the first round. * - * Joiner path: onGameStarted() reads questions from Firestore and delegates to - * SessionSyncService which starts a persistent listener routing the joiner through - * all game states driven by what the host writes to Firestore. + * Joiner: onGameStarted() fetches questions from Firestore and starts SessionSyncService, + * which listens for host state changes and navigates the joiner through all subsequent screens. */ public class LobbyController { private final BeatBattle game; private int numRounds = 5; - /** - * Callback interface that lets LobbyView react to session state changes - * without importing or knowing about FirebaseGateway. - */ + /** Lets LobbyView react to session state changes without accessing FirebaseGateway directly. */ public interface LobbySessionCallback { - /** Returns the current player roster so the controller can pass it to onGameStarted. */ List getCurrentPlayers(); - /** Called when the host cancelled the session. hostLeft=true means the host left explicitly. */ void onHostCancelled(boolean hostLeft); - /** Called when STATE_IN_ROUND is detected and question fetching begins. */ void onGameStarting(); - /** Called when question fetching fails; the listener resets so it can retry. */ void onGameStartFailed(); - /** Called when the Firestore listener itself errors. */ void onConnectionError(String error); } @@ -235,10 +226,7 @@ public void onFailure(Exception exception) { ); } - /** - * Registers a Firestore session-state listener on behalf of LobbyView (joiner only). - * The view is notified through LobbySessionCallback without ever touching FirebaseGateway. - */ + /** Starts listening to session state for a joiner. Notifies the view via LobbySessionCallback when the game starts or is cancelled. */ public void startSessionStateListener(String sessionId, LobbySessionCallback callback) { final boolean[] gameStarted = {false}; @@ -286,7 +274,7 @@ public void onFailure(Exception exception) { ); } - /** Called by the host when they press Start Game. */ + /** Called when the host presses Start Game. Fetches tracks from Deezer and builds the session. */ public void onStartGame(String sessionId, List players, int rounds) { numRounds = rounds; game.getMusicService().fetchTracks(numRounds * QuestionBuilder.OPTIONS_PER_Q, new MusicServiceCallback() { @@ -318,7 +306,7 @@ public void onFailure(String error) { }); } - /** Called by joiners when Firebase signals game has started (via startSessionStateListener). */ + /** Called when Firebase signals the game has started. Builds the local session from Firestore questions and starts round 0. */ public void onGameStarted( String sessionId, List questionDataList, @@ -356,11 +344,7 @@ public void onGameStarted( StateManager.getInstance().setState(new InRoundState(game, roundController)); } - /** - * Schedules deletion of the session document 5 seconds after the host has cancelled it. - * This gives all connected clients time to receive the cancellation event and show - * the "host left" dialog before the Firestore document disappears. - */ + /** Deletes the session from Firestore after a short delay so all clients can receive the cancellation event first. */ private void scheduleSessionDeletion(String sessionId) { Timer.schedule(new Timer.Task() { @Override diff --git a/core/src/main/java/group07/beatbattle/controller/RoundController.java b/core/src/main/java/group07/beatbattle/controller/RoundController.java index d7f7206..9601e42 100644 --- a/core/src/main/java/group07/beatbattle/controller/RoundController.java +++ b/core/src/main/java/group07/beatbattle/controller/RoundController.java @@ -19,21 +19,18 @@ /** * Controls the lifecycle of a single game round. - * On construction creates a RoundManager (which owns the ECS entity), starts audio - * playback, and (if host) starts a Firestore listener that advances the game as soon - * as all players have answered. + * Creates a RoundManager on construction, which starts audio playback via ECS. + * The host also starts a Firestore listener that triggers early leaderboard transition + * when all players have answered. * * Flow: - * 1. Player answers -> onAnswerSubmitted() scores it and records it in Firebase. - * 2. Timer expires (host only) -> onRoundExpired() triggers the leaderboard transition. - * 3. Leaderboard transition fetches fresh scores from Firebase then hands off to LeaderboardController. + * 1. Player answers: onAnswerSubmitted() scores the answer via AnswerSubmission, updates the local player, and records it in Firebase. + * 2. Timer expires (host only): onRoundExpired() triggers transition to the leaderboard. + * 3. Transition fetches updated scores from Firebase before handing off to LeaderboardController. */ public class RoundController { - /** - * Callback for GameRoundView to react to session cancellation without - * knowing about Firebase constants or the gateway. - */ + /** Lets GameRoundView react to session cancellation without accessing FirebaseGateway directly. */ public interface SessionCancelledCallback { void onHostLeft(); void onCancelled(); @@ -87,11 +84,7 @@ public void setMuted(boolean muted) { roundManager.setMuted(muted); } - /** - * Registers a Firestore session-state listener on behalf of GameRoundView. - * Only registers for the host; joiners already have a persistent sync listener - * from SessionSyncService that handles STATE_CANCELLED. - */ + /** Starts listening for session cancellation on behalf of GameRoundView (host only). Joiners are already handled by SessionSyncService. */ public void startSessionStateListener(SessionCancelledCallback callback) { if (!game.isLocalHost()) return; String sessionId = game.getLocalSessionId(); @@ -135,7 +128,7 @@ public void onAnswerSubmitted(String answer) { recordAnswerInFirebase(); } - /** Called by GameRoundView when the timer runs out (host only). */ + /** Called when the round timer runs out. Submits a no-answer if the player never responded, then transitions to the leaderboard. */ public void onRoundExpired() { if (!playerAnswered) { Player localPlayer = findLocalPlayer(); @@ -226,10 +219,7 @@ public void onFailure(Exception exception) { // --- Private helpers --- - /** - * Host listens for the answer count for this round. - * When every player has answered, advance immediately without waiting for the timer. - */ + /** Host listens for the answer count. When all players have answered, advances to the leaderboard without waiting for the timer. */ private void startAllAnsweredListener() { String sessionId = game.getLocalSessionId(); if (sessionId == null || sessionId.isBlank()) return; @@ -383,11 +373,7 @@ private void launchLeaderboard() { StateManager.getInstance().setState(new LeaderboardState(game, leaderboardController)); } - /** - * Schedules deletion of the session document 5 seconds after the host has cancelled it. - * This gives all connected clients time to receive the cancellation event and show - * the "host left" dialog before the Firestore document disappears. - */ + /** Deletes the session from Firestore after a short delay so all clients can receive the cancellation event first. */ private void scheduleSessionDeletion(String sessionId) { Timer.schedule(new Timer.Task() { @Override diff --git a/core/src/main/java/group07/beatbattle/controller/SettingsController.java b/core/src/main/java/group07/beatbattle/controller/SettingsController.java index 37290fb..d026ce9 100644 --- a/core/src/main/java/group07/beatbattle/controller/SettingsController.java +++ b/core/src/main/java/group07/beatbattle/controller/SettingsController.java @@ -9,6 +9,11 @@ import group07.beatbattle.states.StateManager; import group07.beatbattle.view.LobbyView; +/** + * Controls the settings screen. Saves language and round count to persistent preferences + * and reloads the string table when the player applies changes. Navigates back to the + * lobby on back, or to the start screen if no active session exists. + */ public class SettingsController { private final BeatBattle game; diff --git a/core/src/main/java/group07/beatbattle/ecs/Engine.java b/core/src/main/java/group07/beatbattle/ecs/Engine.java index e8e4cbb..b16e2ac 100644 --- a/core/src/main/java/group07/beatbattle/ecs/Engine.java +++ b/core/src/main/java/group07/beatbattle/ecs/Engine.java @@ -5,6 +5,10 @@ import java.util.ArrayList; import java.util.List; +/** + * Singleton ECS engine. Holds all active entities and systems, + * and drives per-frame updates by calling each system's update() method. + */ public class Engine { private static Engine instance; private final List entities = new ArrayList<>(); diff --git a/core/src/main/java/group07/beatbattle/ecs/RoundManager.java b/core/src/main/java/group07/beatbattle/ecs/RoundManager.java index 9fbe1a5..1af7215 100644 --- a/core/src/main/java/group07/beatbattle/ecs/RoundManager.java +++ b/core/src/main/java/group07/beatbattle/ecs/RoundManager.java @@ -6,9 +6,8 @@ import group07.beatbattle.ecs.systems.AudioSystem; /** - * Wraps the ECS lifecycle for a single game round. - * Hides Engine, Entity, and component access from RoundController so - * the controller layer does not depend on ECS internals. + * Manages the ECS entity for a single game round: creates it, starts audio playback, + * and cleans it up when the round ends. Shields RoundController from ECS internals. */ public class RoundManager { @@ -30,7 +29,7 @@ public float getTotalTime() { return timer != null ? timer.totalTime : 30f; } - /** Stops audio playback and removes the entity from the ECS engine. */ + /** Stops audio and removes the round entity from the ECS engine. */ public void stop() { AudioSystem.getInstance().stop(roundEntity); Engine.getInstance().removeEntity(roundEntity); diff --git a/core/src/main/java/group07/beatbattle/ecs/components/AudioComponent.java b/core/src/main/java/group07/beatbattle/ecs/components/AudioComponent.java index 4960b39..593dade 100644 --- a/core/src/main/java/group07/beatbattle/ecs/components/AudioComponent.java +++ b/core/src/main/java/group07/beatbattle/ecs/components/AudioComponent.java @@ -1,5 +1,6 @@ package group07.beatbattle.ecs.components; +/** Holds the song preview URL and playback state for a round entity. */ public class AudioComponent implements Component { public String previewUrl; public boolean isPlaying; diff --git a/core/src/main/java/group07/beatbattle/ecs/components/TimerComponent.java b/core/src/main/java/group07/beatbattle/ecs/components/TimerComponent.java index 880f9ac..133a2f9 100644 --- a/core/src/main/java/group07/beatbattle/ecs/components/TimerComponent.java +++ b/core/src/main/java/group07/beatbattle/ecs/components/TimerComponent.java @@ -1,5 +1,6 @@ package group07.beatbattle.ecs.components; +/** Holds the countdown timer for a round. timeRemaining is decremented each frame by RoundSystem. */ public class TimerComponent implements Component { public float timeRemaining; public float totalTime; diff --git a/core/src/main/java/group07/beatbattle/ecs/entities/Entity.java b/core/src/main/java/group07/beatbattle/ecs/entities/Entity.java index 020df27..dc2a933 100644 --- a/core/src/main/java/group07/beatbattle/ecs/entities/Entity.java +++ b/core/src/main/java/group07/beatbattle/ecs/entities/Entity.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; +/** An ECS entity identified by a string ID. Holds a set of components keyed by their class. */ public class Entity { private final String id; private final Map, Component> components = new HashMap<>(); diff --git a/core/src/main/java/group07/beatbattle/ecs/systems/AudioSystem.java b/core/src/main/java/group07/beatbattle/ecs/systems/AudioSystem.java index 2c1e889..b8c92c5 100644 --- a/core/src/main/java/group07/beatbattle/ecs/systems/AudioSystem.java +++ b/core/src/main/java/group07/beatbattle/ecs/systems/AudioSystem.java @@ -8,9 +8,8 @@ /** * ECS system that controls music preview playback during game rounds. - * Playback is event-driven via play/stop calls rather than running on every tick. - * Mute state is stored here so it persists across round transitions, - * GameRoundView reads it via isMuted() on construction. + * Playback is event-driven: play() and stop() are called explicitly rather than on every tick. + * Mute state persists across round transitions; GameRoundView reads it via isMuted() on construction. */ public class AudioSystem extends EntitySystem { private static AudioSystem instance; @@ -30,7 +29,7 @@ public void setAudioPlayer(AudioPlayer audioPlayer) { @Override public void update(List entities, float delta) { - // playback is event-driven, triggered by play/stop calls + // no per-frame logic — playback is driven by explicit play/stop calls } public void play(Entity round) { diff --git a/core/src/main/java/group07/beatbattle/ecs/systems/EntitySystem.java b/core/src/main/java/group07/beatbattle/ecs/systems/EntitySystem.java index d6e7f09..577a9ed 100644 --- a/core/src/main/java/group07/beatbattle/ecs/systems/EntitySystem.java +++ b/core/src/main/java/group07/beatbattle/ecs/systems/EntitySystem.java @@ -3,6 +3,7 @@ import group07.beatbattle.ecs.entities.Entity; import java.util.List; +/** Base class for all ECS systems. update() is called every frame with the full list of active entities. */ public abstract class EntitySystem { public abstract void update(List entities, float delta); } diff --git a/core/src/main/java/group07/beatbattle/ecs/systems/RoundSystem.java b/core/src/main/java/group07/beatbattle/ecs/systems/RoundSystem.java index d461a2f..15f3dad 100644 --- a/core/src/main/java/group07/beatbattle/ecs/systems/RoundSystem.java +++ b/core/src/main/java/group07/beatbattle/ecs/systems/RoundSystem.java @@ -4,6 +4,10 @@ import group07.beatbattle.ecs.entities.Entity; import java.util.List; +/** + * ECS system that counts down each entity's TimerComponent every frame. + * When a timer reaches zero it fires the RoundExpiredListener once. + */ public class RoundSystem extends EntitySystem { private static RoundSystem instance; private RoundExpiredListener listener; diff --git a/core/src/main/java/group07/beatbattle/firebase/FirebaseGateway.java b/core/src/main/java/group07/beatbattle/firebase/FirebaseGateway.java index f78ee6d..e796b20 100644 --- a/core/src/main/java/group07/beatbattle/firebase/FirebaseGateway.java +++ b/core/src/main/java/group07/beatbattle/firebase/FirebaseGateway.java @@ -5,12 +5,11 @@ import group07.beatbattle.model.domain.Player; /** - * Abstraction layer over the Firebase/Firestore backend. - * All methods are async and deliver results via callback interfaces defined as inner types. - * Keeps Firebase SDK code out of the core module. - * NoOpFirebaseGateway is used for the desktop/LWJGL3 build where Firebase is unavailable. + * Abstraction over the Firestore backend. All methods are asynchronous and return results + * via callback interfaces defined as inner types. Keeping Firebase SDK imports out of core. + * NoOpFirebaseGateway is used for the desktop build where Firebase is not available. * - * Firestore data model: + * Firestore structure: * Sessions/{sessionId} * Players/{playerId} — name, score, isHost * Questions/{roundIndex} — songId, songTitle, songArtist, previewUrl, options[] @@ -85,7 +84,7 @@ void deleteSession( void updateSessionState(String sessionId, String state, SimpleCallback callback); - /** Updates both state and currentRound atomically. */ + /** Updates the session state and the current round index together in a single Firestore write. */ void updateRoundState(String sessionId, String state, int roundIndex, SimpleCallback callback); void listenToSessionState(String sessionId, SessionStateListener listener); @@ -103,11 +102,7 @@ void deleteSession( /** Deletes all answer documents for a session (called on play again to prevent stale counts). */ void clearAnswers(String sessionId, SimpleCallback callback); - /** - * Removes all active Firestore snapshot listeners registered through this gateway. - * Call when leaving a session entirely (e.g. going back to the start screen) to - * prevent memory leaks from orphaned listeners. - */ + /** Removes all active Firestore listeners. Call when leaving a session to avoid memory leaks from orphaned listeners. */ void stopAllListeners(); // --- Callbacks --- @@ -163,8 +158,7 @@ interface GetQuestionsCallback { } interface SessionStateListener { - /** @param state current session state string - * @param currentRound current round index stored in Firebase */ + /** Called whenever the session state, round index, or end reason changes in Firestore. */ void onStateChanged(String state, int currentRound, String endReason); void onFailure(Exception exception); } @@ -174,7 +168,7 @@ interface AnswerCountCallback { void onFailure(Exception exception); } - /** Data transfer object for a question stored in Firebase. */ + /** Raw question data read from and written to Firestore. Used to transfer question state between host and joiners. */ class QuestionData { public final String songId; public final String songTitle; diff --git a/core/src/main/java/group07/beatbattle/model/domain/AnswerSubmission.java b/core/src/main/java/group07/beatbattle/model/domain/AnswerSubmission.java index ca83bde..9383905 100644 --- a/core/src/main/java/group07/beatbattle/model/domain/AnswerSubmission.java +++ b/core/src/main/java/group07/beatbattle/model/domain/AnswerSubmission.java @@ -3,8 +3,9 @@ import group07.beatbattle.utils.ScoreCalculator; /** - * Immutable record of a player's answer for a single round. - * Stores what they answered, when they answered, whether it was correct, and how many points were awarded. + * Immutable record of one player's answer for a single round. + * Use the static factory {@link #create} to build an instance: it checks correctness against + * the question and calculates the score based on how quickly the player answered. */ public class AnswerSubmission { @@ -23,7 +24,7 @@ public AnswerSubmission(String playerId, String selectedAnswer, long timestamp, this.pointsAwarded = pointsAwarded; } - /** Creates an AnswerSubmission by evaluating correctness and calculating score from the given question and answer time. */ + /** Builds an AnswerSubmission by checking the answer against the question and calculating the score for the given answer time. */ public static AnswerSubmission create(String playerId, String selectedAnswer, float answerTime, Question question) { boolean correct = question.isCorrect(selectedAnswer); int points = ScoreCalculator.calculateScore(correct, answerTime); diff --git a/core/src/main/java/group07/beatbattle/model/domain/GameSession.java b/core/src/main/java/group07/beatbattle/model/domain/GameSession.java index 07891a1..f396c3e 100644 --- a/core/src/main/java/group07/beatbattle/model/domain/GameSession.java +++ b/core/src/main/java/group07/beatbattle/model/domain/GameSession.java @@ -6,8 +6,8 @@ /** * Holds all state for an active game session: players, questions, and round progress. - * Created by the host when the game starts and shared (via Firestore) with all joiners. - * currentRoundIndex tracks which question is active and advances via advanceRound(). + * Created by the host when the game starts and reconstructed on each joiner's device + * from questions fetched from Firestore. */ public class GameSession { @@ -47,7 +47,7 @@ public void removePlayer(String playerId) { players.removeIf(p -> p.getId().equals(playerId)); } - /** Sync local player list to match Firebase: remove departed players, update scores. */ + /** Updates the local player list to match Firebase: removes players who left, and applies score changes. */ public void syncPlayers(List updated) { players.removeIf(local -> { for (Player u : updated) { diff --git a/core/src/main/java/group07/beatbattle/model/services/LobbyService.java b/core/src/main/java/group07/beatbattle/model/services/LobbyService.java index b994100..8145f75 100644 --- a/core/src/main/java/group07/beatbattle/model/services/LobbyService.java +++ b/core/src/main/java/group07/beatbattle/model/services/LobbyService.java @@ -11,9 +11,9 @@ /** * Handles creating, joining, and leaving lobby sessions. - * Validates name, game pin, and round count before touching Firebase. - * Game pins are 4-character alphanumeric (A-Z0-9), checked for uniqueness against Firestore. - * Player/host IDs are time-based ("host-") — fine for now but consider UUID for production. + * Validates display name, game pin, and round count before calling Firebase. + * Game pins are 4-character alphanumeric (A-Z0-9) and are checked for uniqueness against Firestore. + * Player and host IDs are UUID-based. */ public class LobbyService { diff --git a/core/src/main/java/group07/beatbattle/model/services/QuestionBuilder.java b/core/src/main/java/group07/beatbattle/model/services/QuestionBuilder.java index 7f644e8..fa6e20f 100644 --- a/core/src/main/java/group07/beatbattle/model/services/QuestionBuilder.java +++ b/core/src/main/java/group07/beatbattle/model/services/QuestionBuilder.java @@ -14,10 +14,9 @@ import group07.beatbattle.model.domain.Song; /** - * Builds quiz questions from a list of songs and stores them in Firebase. - * Extracted from LobbyController to keep question-generation concerns separate. - * - * Flow: buildAndStore() -> clearAnswers -> storeQuestions -> updateRoundState -> callback.onReady() + * Builds quiz questions from a list of songs and stores them in Firestore. + * Called by the host when starting a game. Clears stale answers, stores all questions, + * updates the session state to "in_round", then calls callback.onReady() with the new session. */ public class QuestionBuilder { diff --git a/core/src/main/java/group07/beatbattle/model/services/SessionSyncService.java b/core/src/main/java/group07/beatbattle/model/services/SessionSyncService.java index 3c32590..f02a300 100644 --- a/core/src/main/java/group07/beatbattle/model/services/SessionSyncService.java +++ b/core/src/main/java/group07/beatbattle/model/services/SessionSyncService.java @@ -22,11 +22,10 @@ import group07.beatbattle.states.StateManager; /** - * Manages the persistent Firestore listener that routes joiners through all game states. - * Extracted from LobbyController to keep joiner-sync concerns separate. - * - * Round 0 is already handled by the caller before startJoinerSync() is invoked, - * so the initial lastHandled key is set to "in_round:0:" to avoid re-entering round 0. + * Keeps a joiner in sync with the host by listening to session state changes in Firestore + * and transitioning the joiner to the correct screen when the host advances the game. + * The caller handles round 0 before invoking startJoinerSync(), so the listener + * starts with round 0 already marked as handled to avoid entering it twice. */ public class SessionSyncService {