From 0556767d400e6eaca093ad3423045043f93ddcf0 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sun, 12 Apr 2026 00:47:24 +0200 Subject: [PATCH 1/3] Feat: Add Raise command on backend side --- .../dbis/cs108/casono/server/ServerApp.java | 15 +++ .../game/raise/PlayerRaiseHandler.java | 120 ++++++++++++++++++ .../game/raise/PlayerRaiseParser.java | 35 +++++ .../game/raise/PlayerRaiseRequest.java | 47 +++++++ 4 files changed, 217 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.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 7ca3406..e2e6f2a 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 @@ -181,6 +181,21 @@ public class ServerApp { new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet .PlayerBetHandler(responseDispatcher, userRegistry, lobbyManager)); + // RAISE registration + parserDispatcher.register( + "RAISE", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise + .PlayerRaiseParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise.PlayerRaiseRequest + .class, + (ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler< + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise + .PlayerRaiseRequest>) + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise + .PlayerRaiseHandler( + 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/raise/PlayerRaiseHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseHandler.java new file mode 100644 index 0000000..4d7b647 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseHandler.java @@ -0,0 +1,120 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.GameController; +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 `RAISE` 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 PlayerRaiseHandler extends CommandHandler { + private final UserRegistry userRegistry; + private final LobbyManager lobbyManager; + + /** + * Creates a new {@code PlayerRaiseHandler}. + * + * @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 PlayerRaiseHandler( + ResponseDispatcher responseDispatcher, + UserRegistry userRegistry, + LobbyManager lobbyManager) { + super(responseDispatcher); + this.userRegistry = userRegistry; + this.lobbyManager = lobbyManager; + } + + /** + * Execute the raise request: validate parameters, resolve lobby and game, and + * forward the raise + * action to the game controller. Sends an {@link ErrorResponse} on failure or + * an {@link + * OkResponse} on success. + * + * @param request the parsed {@link PlayerRaiseRequest} + */ + @Override + public void execute(PlayerRaiseRequest request) { + int amount = request.getAmount(); + + if (amount < 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; + } + + GameController game = lobby.getGameController(); + if (game == null) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), "GAME_NOT_STARTED", "Game not started")); + return; + } + + try { + game.playerRaise(PlayerId.of(username), amount); + } catch (RuntimeException 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/raise/PlayerRaiseParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseParser.java new file mode 100644 index 0000000..69aac59 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseParser.java @@ -0,0 +1,35 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise; + +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 `RAISE` command. + * + *

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

+ */ +public class PlayerRaiseParser implements CommandParser { + @Override + public PlayerRaiseRequest 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 PlayerRaiseRequest(primitiveRequest.context(), gameId, amount); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.java new file mode 100644 index 0000000..65e5f78 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.java @@ -0,0 +1,47 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise; + +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 `RAISE` command. + * + *

+ * Contains the optional target `gameId` and the `amount` the player wants to + * raise. + */ +public class PlayerRaiseRequest extends Request { + private final Integer gameId; + private final int amount; + + /** + * Creates a new {@code PlayerRaiseRequest}. + * + * @param context the request context + * @param gameId optional game id of the targeted lobby (may be {@code null}) + * @param amount the raise amount (non-negative) + */ + public PlayerRaiseRequest(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 raise amount. + * + * @return the raise amount + */ + public int getAmount() { + return amount; + } +} -- 2.52.0 From dac4dcfdbe25e2396164c1c6cfd54ec2a9301405 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sun, 12 Apr 2026 00:47:41 +0200 Subject: [PATCH 2/3] Docs: Add documentation for Raise command --- .../networking/commands/protocol-document.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/documents/docs/networking/commands/protocol-document.md b/documents/docs/networking/commands/protocol-document.md index d22cbb2..f29b3dd 100644 --- a/documents/docs/networking/commands/protocol-document.md +++ b/documents/docs/networking/commands/protocol-document.md @@ -62,6 +62,7 @@ This document describes the protocol for client-server communication in our appl - [Success Response](#success-response) - [Example Request](#example-request) - [Example Response](#example-response) + - [RAISE command](#raise-command) - [BET command](#bet-command) - [SEND_MESSAGE command](#send_message-command) - [Required pre-execution checks](#required-pre-execution-checks) @@ -721,6 +722,63 @@ END END ``` +## RAISE command + +The `RAISE` command lets the currently logged-in player increase the current bet in the ongoing game for their lobby. + +### Required pre-execution checks + +- [`UserLoggedInCheck`](#userloggedincheck) + +### Request Parameters + +| Parameter Name | Type | Optional | Description | +| :------------- | :--- | :------: | :---------- | +| `GAME_ID` | `int` | yes | Numeric id of the lobby/game to target. If omitted the server resolves the lobby by the requesting session's user. | +| `AMOUNT` | `int` | no | Amount the player wants to raise | + +### Implementation notes + +- Parser: `PlayerRaiseParser` — reads `AMOUNT` and optionally `GAME_ID`. +- Handler: `PlayerRaiseHandler` — validates the session, lobby (by id when provided or else by session), player's turn and balance and forwards to `GameController`. + +### Success Response + +No additional response fields. Server replies with `+OK` on success. + +### Error Response + +| Code | Description | +| :--- | :---------- | +| `NOT_YOUR_TURN` | The player attempted to raise when not their turn | +| `INSUFFICIENT_FUNDS` | Player does not have enough chips | +| `INVALID_AMOUNT` | Amount parameter is invalid | +| `GAME_NOT_STARTED` | No game is running in the lobby | +| `NOT_IN_LOBBY` | Requesting user is not a member of the lobby | +| `LOBBY_NOT_FOUND` | The specified `GAME_ID` does not exist | + +### Example Request + +``` +RAISE GAME_ID=1 AMOUNT=100 +``` + +### Example Response (success) + +``` ++OK +END +``` + +### Example Response (error) + +``` +-ERR + CODE=INSUFFICIENT_FUNDS + MSG=Not enough chips +END +``` + ## GET_LOBBY_STATUS command The `GET_LOBBY_STATUS` command requests the server to return the current state of a lobby, including the list of players and their ready state. -- 2.52.0 From ca818d98922eb426a77e186377765d86dc5e57d9 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sun, 12 Apr 2026 00:54:10 +0200 Subject: [PATCH 3/3] Fix: Checkstyle Line length for MR Pipeline --- .../game/raise/PlayerRaiseHandler.java | 42 +++++++------------ .../game/raise/PlayerRaiseParser.java | 11 +++-- .../game/raise/PlayerRaiseRequest.java | 8 ++-- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseHandler.java index 4d7b647..6fbaaca 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseHandler.java @@ -14,20 +14,13 @@ import java.util.Optional; /** * Handler for the `RAISE` 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 + *

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}. + *

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 PlayerRaiseHandler extends CommandHandler { private final UserRegistry userRegistry; @@ -36,11 +29,9 @@ public class PlayerRaiseHandler extends CommandHandler { /** * Creates a new {@code PlayerRaiseHandler}. * - * @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 + * @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 PlayerRaiseHandler( ResponseDispatcher responseDispatcher, @@ -52,10 +43,8 @@ public class PlayerRaiseHandler extends CommandHandler { } /** - * Execute the raise request: validate parameters, resolve lobby and game, and - * forward the raise - * action to the game controller. Sends an {@link ErrorResponse} on failure or - * an {@link + * Execute the raise request: validate parameters, resolve lobby and game, and forward the raise + * action to the game controller. Sends an {@link ErrorResponse} on failure or an {@link * OkResponse} on success. * * @param request the parsed {@link PlayerRaiseRequest} @@ -71,8 +60,8 @@ public class PlayerRaiseHandler extends CommandHandler { return; } - Optional opt = userRegistry - .getBySessionId(request.getSessionId()); + Optional opt = + userRegistry.getBySessionId(request.getSessionId()); if (opt.isEmpty()) { responseDispatcher.dispatch( new ErrorResponse(request.getContext(), "NOT_LOGGED_IN", "User not logged in")); @@ -82,9 +71,10 @@ public class PlayerRaiseHandler extends CommandHandler { String username = opt.get().getName(); Integer gameId = request.getGameId(); - var lobby = (gameId != null) - ? lobbyManager.getLobby(LobbyId.of(gameId)) - : lobbyManager.getLobbyByUsername(username); + var lobby = + (gameId != null) + ? lobbyManager.getLobby(LobbyId.of(gameId)) + : lobbyManager.getLobbyByUsername(username); if (lobby == null) { if (gameId != null) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseParser.java index 69aac59..efbdfe7 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseParser.java @@ -7,19 +7,18 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor. /** * Parser for the `RAISE` command. * - *

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

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

*/ public class PlayerRaiseParser implements CommandParser { @Override public PlayerRaiseRequest parse(PrimitiveRequest primitiveRequest) { - RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters()); + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); Integer gameId = null; try { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.java index 65e5f78..15aeb79 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/raise/PlayerRaiseRequest.java @@ -6,9 +6,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestCo /** * Request for the `RAISE` command. * - *

- * Contains the optional target `gameId` and the `amount` the player wants to - * raise. + *

Contains the optional target `gameId` and the `amount` the player wants to raise. */ public class PlayerRaiseRequest extends Request { private final Integer gameId; @@ -18,8 +16,8 @@ public class PlayerRaiseRequest extends Request { * Creates a new {@code PlayerRaiseRequest}. * * @param context the request context - * @param gameId optional game id of the targeted lobby (may be {@code null}) - * @param amount the raise amount (non-negative) + * @param gameId optional game id of the targeted lobby (may be {@code null}) + * @param amount the raise amount (non-negative) */ public PlayerRaiseRequest(RequestContext context, Integer gameId, int amount) { super(context); -- 2.52.0