Skip to content

Fix/syncingbug #39

Merged
merged 3 commits into from
Apr 10, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,19 @@ public void listenToSessionState(String sessionId, SessionStateListener listener
public void updatePlayerScore(String sessionId, String playerId, int score, SimpleCallback callback) {
sessionRepository.updatePlayerScore(sessionId, playerId, score, callback);
}

@Override
public void updateRoundState(String sessionId, String state, int roundIndex, SimpleCallback callback) {
sessionRepository.updateRoundState(sessionId, state, roundIndex, callback);
}

@Override
public void submitAnswer(String sessionId, int roundIndex, String playerId, SimpleCallback callback) {
sessionRepository.submitAnswer(sessionId, roundIndex, playerId, callback);
}

@Override
public void listenToRoundAnswerCount(String sessionId, int roundIndex, AnswerCountCallback callback) {
sessionRepository.listenToRoundAnswerCount(sessionId, roundIndex, callback);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,69 @@ public void listenToSessionState(
}
if (snapshot == null || !snapshot.exists()) return;
String state = snapshot.getString("state");
Long roundLong = snapshot.getLong("currentRound");
int currentRound = roundLong != null ? roundLong.intValue() : 0;
if (state != null) {
listener.onStateChanged(state);
listener.onStateChanged(state, currentRound);
}
});
}

public void submitAnswer(
String sessionId,
int roundIndex,
String playerId,
FirebaseGateway.SimpleCallback callback
) {
Map<String, Object> data = new HashMap<>();
data.put("roundIndex", roundIndex);
data.put("playerId", playerId);

firestore.collection(SESSIONS)
.document(sessionId)
.collection("Answers")
.document(roundIndex + "_" + playerId)
.set(data)
.addOnSuccessListener(unused -> callback.onSuccess())
.addOnFailureListener(callback::onFailure);
}

public void listenToRoundAnswerCount(
String sessionId,
int roundIndex,
FirebaseGateway.AnswerCountCallback callback
) {
firestore.collection(SESSIONS)
.document(sessionId)
.collection("Answers")
.whereEqualTo("roundIndex", roundIndex)
.addSnapshotListener((snapshot, error) -> {
if (error != null) {
callback.onFailure(error);
return;
}
int count = snapshot != null ? snapshot.size() : 0;
callback.onCountChanged(count);
});
}

public void updateRoundState(
String sessionId,
String state,
int roundIndex,
FirebaseGateway.SimpleCallback callback
) {
Map<String, Object> update = new HashMap<>();
update.put("state", state);
update.put("currentRound", roundIndex);

firestore.collection(SESSIONS)
.document(sessionId)
.update(update)
.addOnSuccessListener(unused -> callback.onSuccess())
.addOnFailureListener(callback::onFailure);
}

public void updatePlayerScore(
String sessionId,
String playerId,
Expand Down
6 changes: 5 additions & 1 deletion core/src/main/java/group07/beatbattle/BeatBattle.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import group07.beatbattle.firebase.FirebaseGateway;
import group07.beatbattle.controller.LobbyController;
import group07.beatbattle.ecs.Engine;
import group07.beatbattle.model.GameSession;
import group07.beatbattle.service.MusicService;
import group07.beatbattle.states.StartState;
import group07.beatbattle.states.StateManager;
Expand All @@ -17,7 +18,6 @@
import group07.beatbattle.ui.components.BackButton;
import group07.beatbattle.ui.components.JoinCreateButton;
import group07.beatbattle.ui.style.InputFieldStyles;
import java.util.Random;

public class BeatBattle extends Game {

Expand Down Expand Up @@ -62,6 +62,10 @@ public void setLocalSession(String sessionId, boolean isHost) {
public String getLocalSessionId() { return localSessionId; }
public boolean isLocalHost() { return localIsHost; }

private GameSession currentSession;
public void setCurrentSession(GameSession session) { this.currentSession = session; }
public GameSession getCurrentSession() { return currentSession; }

@Override
public void create() {
montserratFont = loadFont("fonts/Montserrat-Regular.ttf", 54);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package group07.beatbattle.controller;

import com.badlogic.gdx.Gdx;

import group07.beatbattle.BeatBattle;
import group07.beatbattle.firebase.FirebaseGateway;
import group07.beatbattle.model.GameSession;
import group07.beatbattle.model.Leaderboard;
import group07.beatbattle.model.LeaderboardEntry;
Expand Down Expand Up @@ -50,8 +53,38 @@ public boolean hasMoreRounds() {
return session.hasMoreRounds();
}

public boolean isHost() {
return game.isLocalHost();
}

public void onNextRound() {
session.advanceRound();

String sessionId = game.getLocalSessionId();
if (game.isLocalHost() && sessionId != null && !sessionId.isBlank()) {
final int newRound = session.getCurrentRoundIndex();
game.getFirebaseGateway().updateRoundState(
sessionId,
"in_round",
newRound,
new FirebaseGateway.SimpleCallback() {
@Override
public void onSuccess() {
Gdx.app.postRunnable(LeaderboardController.this::launchNextRound);
}

@Override
public void onFailure(Exception e) {
Gdx.app.postRunnable(LeaderboardController.this::launchNextRound);
}
}
);
} else {
launchNextRound();
}
}

private void launchNextRound() {
RoundController roundController = new RoundController(game, session.getCurrentQuestion(), session);
StateManager.getInstance().setState(new InRoundState(game, roundController));
}
Expand All @@ -61,6 +94,29 @@ public void onBackToMenu() {
}

public void onGameOver() {
StateManager.getInstance().setState(new GameOverState(game, this));
String sessionId = game.getLocalSessionId();
if (game.isLocalHost() && sessionId != null && !sessionId.isBlank()) {
game.getFirebaseGateway().updateSessionState(
sessionId,
"game_over",
new FirebaseGateway.SimpleCallback() {
@Override
public void onSuccess() {
Gdx.app.postRunnable(() ->
StateManager.getInstance().setState(new GameOverState(game, LeaderboardController.this))
);
}

@Override
public void onFailure(Exception e) {
Gdx.app.postRunnable(() ->
StateManager.getInstance().setState(new GameOverState(game, LeaderboardController.this))
);
}
}
);
} else {
StateManager.getInstance().setState(new GameOverState(game, this));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
import group07.beatbattle.model.Song;
import group07.beatbattle.model.services.LobbyService;
import group07.beatbattle.service.MusicServiceCallback;
import group07.beatbattle.model.Leaderboard;
import group07.beatbattle.states.GameOverState;
import group07.beatbattle.states.InRoundState;
import group07.beatbattle.states.JoinCreateState;
import group07.beatbattle.states.LeaderboardState;
import group07.beatbattle.states.LobbyState;
import group07.beatbattle.states.StartState;
import group07.beatbattle.states.StateManager;
Expand Down Expand Up @@ -247,10 +250,96 @@ public void onGameStarted(
session.addQuestion(question);
}

game.setCurrentSession(session);

// Start persistent listener to follow host through all game states
startJoinerSyncListener(sessionId, session);

RoundController roundController = new RoundController(game, session.getCurrentQuestion(), session);
StateManager.getInstance().setState(new InRoundState(game, roundController));
}

/**
* Joiners: single Firestore listener that routes through leaderboard →
* next round → game over, driven by what the host writes.
*/
private void startJoinerSyncListener(String sessionId, GameSession session) {
// Track the last state+round we handled to avoid duplicate transitions.
// Round 0 is already handled by onGameStarted, so start there.
final String[] lastHandled = {"in_round:0"};

game.getFirebaseGateway().listenToSessionState(
sessionId,
new FirebaseGateway.SessionStateListener() {
@Override
public void onStateChanged(String state, int currentRound) {
String key = state + ":" + currentRound;
if (key.equals(lastHandled[0])) return;
lastHandled[0] = key;

if ("leaderboard".equals(state)) {
joinerTransitionToLeaderboard(sessionId, session);
} else if ("in_round".equals(state)) {
joinerTransitionToRound(session, currentRound);
} else if ("game_over".equals(state)) {
Gdx.app.postRunnable(() -> {
Leaderboard lb = new Leaderboard(session.getPlayers());
LeaderboardController lc = new LeaderboardController(game, session, lb);
StateManager.getInstance().setState(new GameOverState(game, lc));
});
}
}

@Override
public void onFailure(Exception exception) {
Gdx.app.error("LobbyController",
"Joiner sync error: " + exception.getMessage());
}
}
);
}

private void joinerTransitionToLeaderboard(String sessionId, GameSession session) {
game.getFirebaseGateway().fetchPlayers(
sessionId,
new FirebaseGateway.PlayersListenerCallback() {
@Override
public void onPlayersChanged(List<Player> updated) {
Gdx.app.postRunnable(() -> {
for (Player u : updated) {
for (Player local : session.getPlayers()) {
if (local.getId().equals(u.getId())) local.setScore(u.getScore());
}
}
Leaderboard lb = new Leaderboard(session.getPlayers());
LeaderboardController lc = new LeaderboardController(game, session, lb);
StateManager.getInstance().setState(new LeaderboardState(game, lc));
});
}

@Override
public void onFailure(Exception e) {
Gdx.app.postRunnable(() -> {
Leaderboard lb = new Leaderboard(session.getPlayers());
LeaderboardController lc = new LeaderboardController(game, session, lb);
StateManager.getInstance().setState(new LeaderboardState(game, lc));
});
}
}
);
}

private void joinerTransitionToRound(GameSession session, int roundIndex) {
if (roundIndex >= session.getQuestions().size()) return;
Gdx.app.postRunnable(() -> {
session.setCurrentRoundIndex(roundIndex);
Question question = session.getCurrentQuestion();
if (question == null) return;
RoundController rc = new RoundController(game, question, session);
StateManager.getInstance().setState(new InRoundState(game, rc));
});
}

private void buildAndStartSession(String sessionId, List<Player> sessionPlayers, List<Song> songs) {
GameSession session = new GameSession(sessionId, game.getLocalPlayerId(), numRounds);

Expand Down Expand Up @@ -294,6 +383,8 @@ private void buildAndStartSession(String sessionId, List<Player> sessionPlayers,
));
}

game.setCurrentSession(session);

// Store questions → update state → launch round
game.getFirebaseGateway().storeQuestions(
sessionId,
Expand Down
Loading
Loading