Compare commits

..

9 Commits

Author SHA1 Message Date
Lars Simon Winzer 7cba7ac1dd Merge branch 'chore/113-finalize-presentation-for-fourth-milestone' into 'main'
Finalize presentation for fourth milestone

Closes #113, #83, #82, #78, #76, #70, and #67

See merge request cs108-fs26/Gruppe-13!130
2026-04-15 16:15:06 +02:00
Lars Simon Winzer c83a360fec Docs: Remove images containing code from slides 2026-04-15 16:11:58 +02:00
Lars Simon Winzer 2631791330 Docs: Finalize presentation with exported pdf 2026-04-15 13:54:49 +02:00
Jona Walpert 497820db05 Merge branch 'fix/lobby-auto-cleanup' into 'main'
Feat: Auto-cleanup lobbies when game finishes

See merge request cs108-fs26/Gruppe-13!129
2026-04-14 21:35:43 +00:00
Jona Walpert 88c9431bf0 Feat: Auto-cleanup lobbies when game finishes
- Add countNonFoldedPlayers() to GameState for fold-only victory detection
- Auto-transition to FINISHED phase when betting ends
- Clean up lobby (remove players, reset gameController) when game finishes
- Lobby now switches from RUNNING to CREATED and disappears immediately

Fixes: lobby not disappearing after game ends
2026-04-14 23:33:01 +02:00
Jona Walpert 5a53a14933 Merge branch 'feat/use-real-lobby-id' into 'main'
Feat: uses real lobby id and not hardcoded id

See merge request cs108-fs26/Gruppe-13!128
2026-04-14 21:31:12 +00:00
Jona Walpert f7f66ec5db Feat: uses real lobby id and not hardcoded id 2026-04-14 23:15:39 +02:00
Jona Walpert d85b3d21ee Merge branch 'feat/117-send-winner-with-get-gamestate-command' into 'main'
Feat: Winner is now sent to the frontend with get_gamestate

Closes #117

See merge request cs108-fs26/Gruppe-13!127
2026-04-14 21:13:57 +00:00
Jona Walpert 382383d38b Feat: Winenr is now sent to the frontend with get_gamestate 2026-04-14 23:09:48 +02:00
8 changed files with 102 additions and 26 deletions
Binary file not shown.
Binary file not shown.
@@ -114,8 +114,11 @@ public class CasinoGameUI extends Application {
Parent root = fxmlLoader.load();
CasinoGameController controller = fxmlLoader.getController();
int gameId = 1; // TODO echte gameId einsetzen
GameClient gameClient = new GameClient(clientService, gameId);
if (lobbyId <= 0) {
throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()");
}
GameClient gameClient = new GameClient(clientService, lobbyId);
GameService gameService = new GameService(gameClient);
controller.setGameService(gameService);
@@ -1,6 +1,7 @@
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.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.LobbyId;
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);
Lobby lobby;
LobbyId lobbyId;
if (gameId != null) {
lobby = lobbyManager.getLobby(LobbyId.of(gameId));
lobbyId = LobbyId.of(gameId);
lobby = lobbyManager.getLobby(lobbyId);
if (lobby == null) {
responseDispatcher.dispatch(
new ErrorResponse(
@@ -59,6 +62,7 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
request.getContext(), "NOT_IN_LOBBY", "User not in a lobby"));
return;
}
lobbyId = lobby.getId();
}
var game = lobby.getGameController();
@@ -69,9 +73,30 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
return;
}
if (game.getState().getPhase() == GamePhase.FINISHED) {
cleanupLobby(lobby, lobbyId);
}
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) {
String username = request.getUsername();
if (username != null && !username.isBlank()) {
@@ -6,6 +6,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Rank;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Suit;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GamePhase;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
@@ -33,11 +34,13 @@ public class GetGameStateResponse extends SuccessResponse {
super(
context,
buildBody(
game,
Objects.requireNonNull(game, "game must not be null").getState(),
requestingUsername));
}
private static ResponseBody buildBody(GameState state, String requestingUsername) {
private static ResponseBody buildBody(
GameController game, GameState state, String requestingUsername) {
Objects.requireNonNull(state, "state must not be null");
ResponseBodyBuilder builder = ResponseBody.builder();
@@ -47,6 +50,7 @@ public class GetGameStateResponse extends SuccessResponse {
builder.param("CURRENT_BET", computeGlobalCurrentBet(state));
builder.param("DEALER", state.getDealerIndex());
builder.param("ACTIVE_PLAYER", state.getCurrentPlayerIndex());
builder.param("WINNER", computeWinnerIndex(state, game));
appendCommunityCards(builder, state);
@@ -55,6 +59,28 @@ public class GetGameStateResponse extends SuccessResponse {
return builder.build();
}
private static int computeWinnerIndex(GameState state, GameController game) {
GamePhase phase = state.getPhase();
if (phase != GamePhase.SHOWDOWN && phase != GamePhase.FINISHED) {
return -1;
}
PlayerId winnerId = game.determineWinner();
if (winnerId == null) {
return -1;
}
int index = 0;
for (Player p : state.getPlayers()) {
if (p != null && winnerId.equals(p.getId())) {
return index;
}
index++;
}
return -1;
}
private static int computeGlobalCurrentBet(GameState state) {
int globalCurrentBet = 0;
for (Player p : state.getPlayers()) {
@@ -254,4 +254,12 @@ public class GameController {
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);
}
if (state.countNonFoldedPlayers() == 1) {
state.setPhase(GamePhase.FINISHED);
return;
}
if (isBettingRoundFinished(state)) {
advancePhase(state);
}
@@ -81,8 +86,9 @@ public class RoundManager {
case FLOP -> dealTurn(state);
case TURN -> dealRiver(state);
case RIVER -> showdown(state);
case SHOWDOWN -> {
/* nothing */
case SHOWDOWN -> state.setPhase(GamePhase.FINISHED);
case FINISHED -> {
/* game is over */
}
}
}
@@ -11,19 +11,15 @@ import java.util.List;
import java.util.Map;
/**
* The GameState class encapsulates the entire state of a poker game at any
* given moment.
* The GameState class encapsulates the entire state of a poker game at any given moment.
*
* <p>
* Important invariants this implementation maintains:
* <p>Important invariants this implementation maintains:
*
* <ul>
* <li>{@code playerOrder} defines the stable seating/turn order.
* <li>{@code currentBets} always contains an entry for every player in
* {@code playerOrder}.
* <li>{@code holeCards} maps every player to a mutable list; if not present, it
* is created on
* demand.
* <li>{@code playerOrder} defines the stable seating/turn order.
* <li>{@code currentBets} always contains an entry for every player in {@code playerOrder}.
* <li>{@code holeCards} maps every player to a mutable list; if not present, it is created on
* demand.
* </ul>
*/
public class GameState {
@@ -188,8 +184,7 @@ public class GameState {
}
/**
* Reset bets to 0 for ALL players (do not clear the map). Also resets table
* current bet to 0.
* Reset bets to 0 for ALL players (do not clear the map). Also resets table current bet to 0.
*/
public void resetBets() {
for (PlayerId id : playerOrder) {
@@ -232,8 +227,7 @@ public class GameState {
// Cards
/**
* Gives (overwrites) the two hole cards for the given player. Ensures stable
* list identity
* Gives (overwrites) the two hole cards for the given player. Ensures stable list identity
* (important if other code holds references).
*/
public void giveHoleCards(PlayerId playerId, Card c1, Card c2) {
@@ -293,14 +287,13 @@ public class GameState {
/**
* Starts a new hand by resetting the game state for the next round of poker.
*
* <p>
* This:
* <p>This:
*
* <ul>
* <li>sets {@code phase=PREFLOP}
* <li>resets pot/bets/commitments
* <li>clears community + hole cards
* <li>creates &amp; shuffles a new deck
* <li>sets {@code phase=PREFLOP}
* <li>resets pot/bets/commitments
* <li>clears community + hole cards
* <li>creates &amp; shuffles a new deck
* </ul>
*/
public void startNewHand() {
@@ -336,4 +329,19 @@ public class GameState {
public boolean isFolded(PlayerId playerId) {
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;
}
}