Feat: Add get_game_state_command on server side

This commit is contained in:
Jona Walpert
2026-04-11 22:04:47 +02:00
parent 1b6e8ce6d6
commit 54a7be074f
5 changed files with 251 additions and 2 deletions
@@ -154,6 +154,21 @@ public class ServerApp {
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.get_lobby_list new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.get_lobby_list
.GetLobbyListHandler(responseDispatcher, lobbyManager)); .GetLobbyListHandler(responseDispatcher, lobbyManager));
// GET_GAME_STATE registration
parserDispatcher.register(
"GET_GAME_STATE",
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state
.GetGameStateParser());
commandRouter.register(
ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state
.GetGameStateRequest.class,
(ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler<
ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game
.get_game_state.GetGameStateRequest>)
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state
.GetGameStateHandler(
responseDispatcher, lobbyManager, userRegistry));
// GET_LOBBY_STATUS registration // GET_LOBBY_STATUS registration
parserDispatcher.register( parserDispatcher.register(
"GET_LOBBY_STATUS", "GET_LOBBY_STATUS",
@@ -169,7 +184,6 @@ public class ServerApp {
.get_lobby_status.GetLobbyStatusHandler( .get_lobby_status.GetLobbyStatusHandler(
responseDispatcher, lobbyManager, userRegistry)); responseDispatcher, lobbyManager, userRegistry));
// JOIN_LOBBY registration // JOIN_LOBBY registration
parserDispatcher.register( parserDispatcher.register(
"JOIN_LOBBY", "JOIN_LOBBY",
@@ -0,0 +1,74 @@
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.lobby.Lobby;
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.user.User;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
/** Handler for GET_GAME_STATE: returns pot, phase, community cards and per-player info. */
public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
private final LobbyManager lobbyManager;
private final UserRegistry userRegistry;
public GetGameStateHandler(
ResponseDispatcher responseDispatcher,
LobbyManager lobbyManager,
UserRegistry userRegistry) {
super(responseDispatcher);
this.lobbyManager = lobbyManager;
this.userRegistry = userRegistry;
addCheck(new UserLoggedInCheck(userRegistry));
}
@Override
public void execute(GetGameStateRequest request) {
Integer gameId = request.getGameId();
String username = request.getUsername();
Lobby lobby = null;
if (gameId != null) {
lobby = lobbyManager.getLobby(LobbyId.of(gameId));
if (lobby == null) {
responseDispatcher.dispatch(
new ErrorResponse(
request.getContext(), "LOBBY_NOT_FOUND", "Lobby not found"));
return;
}
} else {
if (username == null) {
var maybeUser = userRegistry.getBySessionId(request.getSessionId());
if (maybeUser.isEmpty()) {
responseDispatcher.dispatch(
new ErrorResponse(
request.getContext(), "NOT_LOGGED_IN", "User not logged in"));
return;
}
User u = maybeUser.get();
username = u.getName();
}
lobby = lobbyManager.getLobbyByUsername(username);
if (lobby == null) {
responseDispatcher.dispatch(
new ErrorResponse(
request.getContext(), "NOT_IN_LOBBY", "User not in a lobby"));
return;
}
}
var game = lobby.getGameController();
if (game == null) {
responseDispatcher.dispatch(
new ErrorResponse(
request.getContext(), "GAME_NOT_STARTED", "Game not started"));
return;
}
responseDispatcher.dispatch(new GetGameStateResponse(request.getContext(), game, username));
}
}
@@ -0,0 +1,16 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state;
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
public class GetGameStateParser implements CommandParser<GetGameStateRequest> {
@Override
public GetGameStateRequest parse(PrimitiveRequest primitiveRequest) {
RequestParameterAccessor accessor =
new RequestParameterAccessor(primitiveRequest.parameters());
Integer gameId = accessor.optional("GAME_ID", null, Integer::parseInt);
String username = accessor.optional("USERNAME", null);
return new GetGameStateRequest(primitiveRequest.context(), username, gameId);
}
}
@@ -0,0 +1,31 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
/**
* Request object for `GET_GAME_STATE`.
*
* <p>Either `USERNAME` or `GAME_ID` may be provided by the client. If both are omitted the handler
* will resolve the username from the session.
*/
public class GetGameStateRequest extends Request {
private final String username;
private final Integer gameId;
public GetGameStateRequest(RequestContext context, String username, Integer gameId) {
super(context);
this.username = username;
this.gameId = gameId;
}
/** Returns the optional username provided in the request. */
public String getUsername() {
return username;
}
/** Returns the optional game id provided in the request. */
public Integer getGameId() {
return gameId;
}
}
@@ -0,0 +1,114 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.GameController;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Card;
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.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;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
import java.util.List;
/** Response carrying a snapshot of the current game state using the project's wire format. */
public class GetGameStateResponse extends SuccessResponse {
public GetGameStateResponse(
RequestContext context, GameController game, String requestingUsername) {
super(context, buildBody(game.getState(), requestingUsername));
}
private static ResponseBody buildBody(GameState state, String requestingUsername) {
int globalCurrentBet = computeGlobalCurrentBet(state);
ResponseBodyBuilder builder = ResponseBody.builder();
builder.param("PHASE", state.getPhase() == null ? "UNKNOWN" : state.getPhase().name());
builder.param("POT", state.getPot().getAmount());
builder.param("CURRENT_BET", globalCurrentBet);
builder.param("DEALER", state.getDealerIndex());
builder.param("ACTIVE_PLAYER", state.getCurrentPlayerIndex());
appendCommunityCards(builder, state);
appendPlayers(builder, state, requestingUsername);
return builder.build();
}
private static int computeGlobalCurrentBet(GameState state) {
int globalCurrentBet = 0;
for (Player p : state.getPlayers()) {
int b = state.getCurrentBet(p.getId());
if (b > globalCurrentBet) {
globalCurrentBet = b;
}
}
return globalCurrentBet;
}
private static void appendCommunityCards(ResponseBodyBuilder builder, GameState state) {
for (Card c : state.getCommunityCards()) {
builder.block(
"CARD",
card -> {
card.param("VALUE", rankToWire(c.getRank()));
card.param("SUIT", suitToWire(c.getSuit()));
});
}
}
private static void appendPlayers(
ResponseBodyBuilder builder, GameState state, String requestingUsername) {
for (Player p : state.getPlayers()) {
PlayerId pid = p.getId();
builder.block(
"PLAYER",
pblock -> {
pblock.param("NAME", pid.value());
pblock.param("CHIPS", p.getChips());
pblock.param("BET", state.getCurrentBet(pid));
pblock.param("STATE", p.isFolded() ? "FOLDED" : "ACTIVE");
if (requestingUsername != null && requestingUsername.equals(pid.value())) {
List<Card> hole = state.getHoleCards(pid);
for (Card hc : hole) {
pblock.block(
"CARD",
cblock -> {
cblock.param("VALUE", rankToWire(hc.getRank()));
cblock.param("SUIT", suitToWire(hc.getSuit()));
});
}
}
});
}
}
private static String rankToWire(Rank r) {
return switch (r) {
case TWO -> "2";
case THREE -> "3";
case FOUR -> "4";
case FIVE -> "5";
case SIX -> "6";
case SEVEN -> "7";
case EIGHT -> "8";
case NINE -> "9";
case TEN -> "10";
case JACK -> "J";
case QUEEN -> "Q";
case KING -> "K";
case ACE -> "A";
};
}
private static String suitToWire(Suit s) {
return switch (s) {
case HEARTS -> "H";
case DIAMONDS -> "D";
case CLUBS -> "C";
case SPADES -> "S";
};
}
}