Skip to content

Commit

Permalink
Merge pull request #62 from milospi/61-ensure-documentation-is-correct
Browse files Browse the repository at this point in the history
Ensure documentation is correct
  • Loading branch information
odaakj authored Apr 21, 2026
2 parents b2e84ae + 6d31fa1 commit b95a7ca
Show file tree
Hide file tree
Showing 20 changed files with 88 additions and 119 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions core/src/main/java/group07/beatbattle/BeatBattle.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Player> 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);
}

Expand Down Expand Up @@ -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};

Expand Down Expand Up @@ -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<Player> players, int rounds) {
numRounds = rounds;
game.getMusicService().fetchTracks(numRounds * QuestionBuilder.OPTIONS_PER_Q, new MusicServiceCallback() {
Expand Down Expand Up @@ -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<FirebaseGateway.QuestionData> questionDataList,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/group07/beatbattle/ecs/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Entity> entities = new ArrayList<>();
Expand Down
7 changes: 3 additions & 4 deletions core/src/main/java/group07/beatbattle/ecs/RoundManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class<? extends Component>, Component> components = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,7 +29,7 @@ public void setAudioPlayer(AudioPlayer audioPlayer) {

@Override
public void update(List<Entity> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Entity> entities, float delta);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit b95a7ca

Please sign in to comment.