diff --git a/documents/docs/networking/commands/protocol-document.md b/documents/docs/networking/commands/protocol-document.md index 574b28f..2a5aca9 100644 --- a/documents/docs/networking/commands/protocol-document.md +++ b/documents/docs/networking/commands/protocol-document.md @@ -64,6 +64,7 @@ This document describes the protocol for client-server communication in our appl - [Example Response](#example-response) - [RAISE command](#raise-command) - [CALL command](#call-command) + - [FOLD command](#fold-command) - [BET command](#bet-command) - [SEND_MESSAGE command](#send_message-command) - [Required pre-execution checks](#required-pre-execution-checks) @@ -835,6 +836,222 @@ END END ``` +## FOLD command + +The `FOLD` command lets the currently logged-in player leave the current hand (fold) 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. | + +### Implementation notes + +- Parser: `PlayerFoldParser` — accepts optional `GAME_ID`. +- Handler: `PlayerFoldHandler` — validates the session, resolves the lobby by id when provided or by session otherwise 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 fold when not their turn | +| `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 + +``` +FOLD GAME_ID=1 +``` + +### Example Response (success) + +``` ++OK +END +``` + +### Example Response (error) + +``` +-ERR + CODE=NOT_YOUR_TURN + MSG=It is not your turn +END +``` + +## FOLD command + +The `FOLD` command lets the currently logged-in player leave the current hand (fold) 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. | + +### Implementation notes + +- Parser: `PlayerFoldParser` — accepts optional `GAME_ID`. +- Handler: `PlayerFoldHandler` — validates the session, resolves the lobby by id when provided or by session otherwise 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 fold when not their turn | +| `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 + +``` +FOLD GAME_ID=1 +``` + +### Example Response (success) + +``` ++OK +END +``` + +### Example Response (error) + +``` +-ERR + CODE=NOT_YOUR_TURN + MSG=It is not your turn +END +``` + +## FOLD command + +The `FOLD` command lets the currently logged-in player leave the current hand (fold) 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. | + +### Implementation notes + +- Parser: `PlayerFoldParser` — accepts optional `GAME_ID`. +- Handler: `PlayerFoldHandler` — validates the session, resolves the lobby by id when provided or by session otherwise 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 fold when not their turn | +| `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 + +``` +FOLD GAME_ID=1 +``` + +### Example Response (success) + +``` ++OK +END +``` + +### Example Response (error) + +``` +-ERR + CODE=NOT_YOUR_TURN + MSG=It is not your turn +END +``` + +## FOLD command + +The `FOLD` command lets the currently logged-in player leave the current hand (fold) 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. | + +### Implementation notes + +- Parser: `PlayerFoldParser` — accepts optional `GAME_ID`. +- Handler: `PlayerFoldHandler` — validates the session, resolves the lobby by id when provided or by session otherwise 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 fold when not their turn | +| `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 + +``` +FOLD GAME_ID=1 +``` + +### Example Response (success) + +``` ++OK +END +``` + +### Example Response (error) + +``` +-ERR + CODE=NOT_YOUR_TURN + MSG=It is not your turn +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. 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 eef8e31..5dedaf3 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 @@ -210,6 +210,20 @@ public class ServerApp { new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call .PlayerCallHandler(responseDispatcher, userRegistry, lobbyManager)); + // FOLD registration + parserDispatcher.register( + "FOLD", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold + .PlayerFoldParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold.PlayerFoldRequest + .class, + (ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler< + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold + .PlayerFoldRequest>) + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold + .PlayerFoldHandler(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/fold/PlayerFoldHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/fold/PlayerFoldHandler.java new file mode 100644 index 0000000..0db3628 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/fold/PlayerFoldHandler.java @@ -0,0 +1,96 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold; + +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 `FOLD` command. + * + *

This handler validates the session, resolves the lobby (by `GAME_ID` when provided or by + * session username otherwise), checks for a running game and forwards the fold action to the {@code + * GameController}. + */ +public class PlayerFoldHandler extends CommandHandler { + private final UserRegistry userRegistry; + private final LobbyManager lobbyManager; + + /** + * Creates a new {@code PlayerFoldHandler}. + * + * @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 PlayerFoldHandler( + ResponseDispatcher responseDispatcher, + UserRegistry userRegistry, + LobbyManager lobbyManager) { + super(responseDispatcher); + this.userRegistry = userRegistry; + this.lobbyManager = lobbyManager; + } + + /** + * Execute the fold request: resolve lobby by `GAME_ID` if present, otherwise by session user, + * verify game is running, and forward the fold to the game controller. + * + * @param request the parsed {@link PlayerFoldRequest} + */ + @Override + public void execute(PlayerFoldRequest request) { + 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.playerFold(PlayerId.of(username)); + } 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/fold/PlayerFoldParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/fold/PlayerFoldParser.java new file mode 100644 index 0000000..1fe8d30 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/fold/PlayerFoldParser.java @@ -0,0 +1,28 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold; + +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 `FOLD` command. + * + *

Accepts an optional `GAME_ID` parameter. If provided, the handler will target the specified + * lobby; otherwise the server resolves the lobby by the requesting session's user. + */ +public class PlayerFoldParser implements CommandParser { + @Override + public PlayerFoldRequest 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 + } + + return new PlayerFoldRequest(primitiveRequest.context(), gameId); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/fold/PlayerFoldRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/fold/PlayerFoldRequest.java new file mode 100644 index 0000000..536aa5f --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/fold/PlayerFoldRequest.java @@ -0,0 +1,34 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold; + +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 `FOLD` command. + * + *

Contains an optional `gameId` when the client targets a specific lobby, otherwise the server + * will resolve the lobby from the requesting session's user. + */ +public class PlayerFoldRequest extends Request { + private final Integer gameId; + + /** + * Creates a new {@code PlayerFoldRequest}. + * + * @param context the request context + * @param gameId optional game id of the targeted lobby (may be {@code null}) + */ + public PlayerFoldRequest(RequestContext context, Integer gameId) { + super(context); + this.gameId = gameId; + } + + /** + * Returns the optional target game id. + * + * @return the game id or {@code null} if not provided + */ + public Integer getGameId() { + return gameId; + } +}