Feat: Auto-cleanup lobbies when game finishes #285

Merged
jona.walpert merged 1 commits from fix/lobby-auto-cleanup into main 2026-04-14 23:35:44 +02:00
4 changed files with 70 additions and 23 deletions
@@ -1,6 +1,7 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state; package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state;
import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck; import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GamePhase;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.Lobby; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.Lobby;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyId; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyId;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager;
@@ -31,8 +32,10 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
String username = resolveUsername(request); String username = resolveUsername(request);
Lobby lobby; Lobby lobby;
LobbyId lobbyId;
if (gameId != null) { if (gameId != null) {
lobby = lobbyManager.getLobby(LobbyId.of(gameId)); lobbyId = LobbyId.of(gameId);
lobby = lobbyManager.getLobby(lobbyId);
if (lobby == null) { if (lobby == null) {
responseDispatcher.dispatch( responseDispatcher.dispatch(
new ErrorResponse( new ErrorResponse(
@@ -59,6 +62,7 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
request.getContext(), "NOT_IN_LOBBY", "User not in a lobby")); request.getContext(), "NOT_IN_LOBBY", "User not in a lobby"));
return; return;
} }
lobbyId = lobby.getId();
} }
var game = lobby.getGameController(); var game = lobby.getGameController();
@@ -69,9 +73,30 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
return; return;
} }
if (game.getState().getPhase() == GamePhase.FINISHED) {
cleanupLobby(lobby, lobbyId);
}
responseDispatcher.dispatch(new GetGameStateResponse(request.getContext(), game, username)); responseDispatcher.dispatch(new GetGameStateResponse(request.getContext(), game, username));
} }
/**
* Cleans up a lobby by removing all players and resetting the game controller. This is called
* when the game reaches FINISHED phase.
*/
private void cleanupLobby(Lobby lobby, LobbyId lobbyId) {
try {
// Remove all players from the lobby
for (String playerName : lobby.getPlayerNames()) {
lobbyManager.removePlayer(playerName);
}
// Reset the game controller so the lobby returns to CREATED state
lobby.initGame(null);
} catch (RuntimeException e) {
// Log silently to avoid disrupting game state response
}
}
private String resolveUsername(GetGameStateRequest request) { private String resolveUsername(GetGameStateRequest request) {
String username = request.getUsername(); String username = request.getUsername();
if (username != null && !username.isBlank()) { if (username != null && !username.isBlank()) {
@@ -254,4 +254,12 @@ public class GameController {
return bestPlayer; return bestPlayer;
} }
/**
* Ends the current game by setting the phase to FINISHED. This should be called when the
* showdown is complete and a winner has been determined.
*/
public void endGame() {
engine.getState().setPhase(GamePhase.FINISHED);
}
} }
@@ -49,6 +49,11 @@ public class RoundManager {
state.setPhase(GamePhase.PREFLOP); state.setPhase(GamePhase.PREFLOP);
} }
if (state.countNonFoldedPlayers() == 1) {
state.setPhase(GamePhase.FINISHED);
return;
}
if (isBettingRoundFinished(state)) { if (isBettingRoundFinished(state)) {
advancePhase(state); advancePhase(state);
} }
@@ -81,8 +86,9 @@ public class RoundManager {
case FLOP -> dealTurn(state); case FLOP -> dealTurn(state);
case TURN -> dealRiver(state); case TURN -> dealRiver(state);
case RIVER -> showdown(state); case RIVER -> showdown(state);
case SHOWDOWN -> { case SHOWDOWN -> state.setPhase(GamePhase.FINISHED);
/* nothing */ case FINISHED -> {
/* game is over */
} }
} }
} }
@@ -11,18 +11,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* The GameState class encapsulates the entire state of a poker game at any * The GameState class encapsulates the entire state of a poker game at any given moment.
* given moment.
* *
* <p> * <p>Important invariants this implementation maintains:
* Important invariants this implementation maintains:
* *
* <ul> * <ul>
* <li>{@code playerOrder} defines the stable seating/turn order. * <li>{@code playerOrder} defines the stable seating/turn order.
* <li>{@code currentBets} always contains an entry for every player in * <li>{@code currentBets} always contains an entry for every player in {@code playerOrder}.
* {@code playerOrder}. * <li>{@code holeCards} maps every player to a mutable list; if not present, it is created on
* <li>{@code holeCards} maps every player to a mutable list; if not present, it
* is created on
* demand. * demand.
* </ul> * </ul>
*/ */
@@ -188,8 +184,7 @@ public class GameState {
} }
/** /**
* Reset bets to 0 for ALL players (do not clear the map). Also resets table * Reset bets to 0 for ALL players (do not clear the map). Also resets table current bet to 0.
* current bet to 0.
*/ */
public void resetBets() { public void resetBets() {
for (PlayerId id : playerOrder) { for (PlayerId id : playerOrder) {
@@ -232,8 +227,7 @@ public class GameState {
// Cards // Cards
/** /**
* Gives (overwrites) the two hole cards for the given player. Ensures stable * Gives (overwrites) the two hole cards for the given player. Ensures stable list identity
* list identity
* (important if other code holds references). * (important if other code holds references).
*/ */
public void giveHoleCards(PlayerId playerId, Card c1, Card c2) { public void giveHoleCards(PlayerId playerId, Card c1, Card c2) {
@@ -293,8 +287,7 @@ public class GameState {
/** /**
* Starts a new hand by resetting the game state for the next round of poker. * Starts a new hand by resetting the game state for the next round of poker.
* *
* <p> * <p>This:
* This:
* *
* <ul> * <ul>
* <li>sets {@code phase=PREFLOP} * <li>sets {@code phase=PREFLOP}
@@ -336,4 +329,19 @@ public class GameState {
public boolean isFolded(PlayerId playerId) { public boolean isFolded(PlayerId playerId) {
return getPlayer(playerId).isFolded(); return getPlayer(playerId).isFolded();
} }
/**
* Counts the number of players who have not folded.
*
* @return The number of non-folded players
*/
public int countNonFoldedPlayers() {
int count = 0;
for (Player p : players.values()) {
if (p != null && !p.isFolded()) {
count++;
}
}
return count;
}
} }