From f5a62c6e934a382b993d4ab8742999d92dbe24df Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sun, 12 Apr 2026 00:11:11 +0200 Subject: [PATCH] Feat: add bet command on server side --- .../dbis/cs108/casono/server/ServerApp.java | 12 ++ .../commands/game/bet/PlayerBetHandler.java | 107 ++++++++++++++++++ .../commands/game/bet/PlayerBetParser.java | 40 +++++++ .../commands/game/bet/PlayerBetRequest.java | 45 ++++++++ 4 files changed, 204 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetRequest.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index f9007eb..7ca3406 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -169,6 +169,18 @@ public class ServerApp { .GetGameStateHandler( responseDispatcher, lobbyManager, userRegistry)); + // BET registration + parserDispatcher.register( + "BET", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet.PlayerBetParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet.PlayerBetRequest.class, + (ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler< + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet + .PlayerBetRequest>) + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet + .PlayerBetHandler(responseDispatcher, userRegistry, lobbyManager)); + // GET_LOBBY_STATUS registration parserDispatcher.register( "GET_LOBBY_STATUS", diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetHandler.java new file mode 100644 index 0000000..eeca227 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetHandler.java @@ -0,0 +1,107 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId; +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.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.OkResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +import java.util.Optional; + +/** + * Handler for the `BET` command. + * + *

This handler validates the request (amount >= 0), resolves the user and target lobby (by + * `GAME_ID` when provided or by session username otherwise), checks that a game is running and then + * forwards the action to the {@code GameController}. + * + *

On failure the handler dispatches an {@link ErrorResponse} with an appropriate error code + * (e.g. {@code INVALID_AMOUNT}, {@code NOT_IN_LOBBY}, {@code LOBBY_NOT_FOUND}, {@code + * GAME_NOT_STARTED}, {@code GAME_ACTION_FAILED}). On success it dispatches an {@link OkResponse}. + */ +public class PlayerBetHandler extends CommandHandler { + private final UserRegistry userRegistry; + private final LobbyManager lobbyManager; + + /** + * Creates a new {@code PlayerBetHandler}. + * + * @param responseDispatcher dispatcher used to send responses back to the client + * @param userRegistry registry to resolve users from session ids + * @param lobbyManager manager used to lookup lobbies and their game controllers + */ + public PlayerBetHandler( + ResponseDispatcher responseDispatcher, + UserRegistry userRegistry, + LobbyManager lobbyManager) { + super(responseDispatcher); + this.userRegistry = userRegistry; + this.lobbyManager = lobbyManager; + } + + /** + * Execute the bet request: validate parameters, resolve lobby and game, and forward the bet + * action to the game controller. Sends an {@link ErrorResponse} on failure or an {@link + * OkResponse} on success. + * + * @param request the parsed {@link PlayerBetRequest} + */ + @Override + public void execute(PlayerBetRequest request) { + if (request.getAmount() < 0) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), "INVALID_AMOUNT", "Amount must be non-negative")); + return; + } + + Optional opt = + userRegistry.getBySessionId(request.getSessionId()); + if (opt.isEmpty()) { + responseDispatcher.dispatch( + new ErrorResponse(request.getContext(), "NOT_LOGGED_IN", "User not logged in")); + return; + } + + String username = opt.get().getName(); + + Integer gameId = request.getGameId(); + var lobby = + (gameId != null) + ? lobbyManager.getLobby(LobbyId.of(gameId)) + : lobbyManager.getLobbyByUsername(username); + + if (lobby == null) { + if (gameId != null) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), "LOBBY_NOT_FOUND", "Lobby not found")); + } else { + 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; + } + + try { + game.playerBet(PlayerId.of(username), request.getAmount()); + } catch (Exception e) { + responseDispatcher.dispatch( + new ErrorResponse(request.getContext(), "GAME_ACTION_FAILED", e.getMessage())); + return; + } + + responseDispatcher.dispatch(new OkResponse(request.getContext())); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetParser.java new file mode 100644 index 0000000..0ed9a1d --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetParser.java @@ -0,0 +1,40 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet; + +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; + +/** + * Parser for the `BET` command. + * + *

Parses the request parameters and builds a {@link PlayerBetRequest}. Expected parameters: + * + *

+ */ +public class PlayerBetParser implements CommandParser { + /** + * Parse a primitive request into a {@link PlayerBetRequest}. + * + * @param primitiveRequest the raw parsed request containing parameters and context + * @return a {@link PlayerBetRequest} with the parsed values (gameId may be null) + */ + @Override + public PlayerBetRequest parse(PrimitiveRequest primitiveRequest) { + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); + + Integer gameId = null; + try { + gameId = accessor.optional("GAME_ID", null, Integer::parseInt); + } catch (Exception e) { + // parse handled by framework + } + + int amount = accessor.require("AMOUNT", Integer::parseInt); + + return new PlayerBetRequest(primitiveRequest.context(), gameId, amount); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetRequest.java new file mode 100644 index 0000000..02c70a8 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/bet/PlayerBetRequest.java @@ -0,0 +1,45 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet; + +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 for the `BET` command. + * + *

Contains the optional target `gameId` and the `amount` the player wants to bet. + */ +public class PlayerBetRequest extends Request { + private final Integer gameId; + private final int amount; + + /** + * Creates a new {@code PlayerBetRequest}. + * + * @param context the request context + * @param gameId optional game id of the targeted lobby (may be {@code null}) + * @param amount the bet amount (non-negative) + */ + public PlayerBetRequest(RequestContext context, Integer gameId, int amount) { + super(context); + this.gameId = gameId; + this.amount = amount; + } + + /** + * Returns the optional target game id. + * + * @return the game id or {@code null} if not provided + */ + public Integer getGameId() { + return gameId; + } + + /** + * Returns the requested bet amount. + * + * @return the bet amount + */ + public int getAmount() { + return amount; + } +}