From 23debb18bce23245f1277f94f73fe256ee43db58 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 23:17:39 +0200 Subject: [PATCH 1/2] Feat: Add start_Game command to server --- .../dbis/cs108/casono/server/ServerApp.java | 14 +++ .../lobby/start_game/StartGameHandler.java | 113 ++++++++++++++++++ .../lobby/start_game/StartGameParser.java | 27 +++++ .../lobby/start_game/StartGameRequest.java | 35 ++++++ .../lobby/start_game/StartGameResponse.java | 34 ++++++ 5 files changed, 223 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameRequest.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameResponse.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..cec1b2a 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 @@ -197,5 +197,19 @@ public class ServerApp { .JoinLobbyRequest>) new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby .JoinLobbyHandler(responseDispatcher, lobbyManager, userRegistry)); + + // START_GAME registration + parserDispatcher.register( + "START_GAME", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game + .StartGameParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game + .StartGameRequest.class, + (ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler< + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game + .StartGameRequest>) + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game + .StartGameHandler(responseDispatcher, lobbyManager, userRegistry)); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameHandler.java new file mode 100644 index 0000000..eb1f335 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameHandler.java @@ -0,0 +1,113 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game; + +import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.GameController; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.GameEngine; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.RoundManager; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.TurnManager; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState; +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.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; +import java.util.ArrayList; +import java.util.Optional; + +/** + * Handler for START_GAME: creates a GameEngine+GameController for the lobby and starts the game. + */ +public class StartGameHandler extends CommandHandler { + private final LobbyManager lobbyManager; + private final UserRegistry userRegistry; + private static final int DEFAULT_START_CHIPS = 20000; + + public StartGameHandler( + ResponseDispatcher responseDispatcher, + LobbyManager lobbyManager, + UserRegistry userRegistry) { + super(responseDispatcher); + this.lobbyManager = lobbyManager; + this.userRegistry = userRegistry; + addCheck(new UserLoggedInCheck(userRegistry)); + } + + @Override + public void execute(StartGameRequest request) { + Optional opt = + userRegistry.getBySessionId(request.getSessionId()); + if (opt.isEmpty()) { + // Guard: UserLoggedInCheck should normally handle this + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), "USER_NOT_LOGGED_IN", "User not logged in")); + return; + } + + String username = opt.get().getName(); + + // Resolve lobby by provided ID (required) + LobbyId lid = LobbyId.of(request.getId()); + Lobby lobby = lobbyManager.getLobby(lid); + if (lobby == null) { + responseDispatcher.dispatch( + new ErrorResponse(request.getContext(), "LOBBY_NOT_FOUND", "Lobby not found")); + return; + } + + // Ensure requester is part of the lobby + if (!lobby.getPlayerNames().contains(username)) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), "NOT_IN_LOBBY", "User not in specified lobby")); + return; + } + + // Prevent double-start + if (lobby.getGameController() != null) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), + "ALREADY_STARTED", + "Game has already been started for this lobby")); + return; + } + + // Basic player count check + if (lobby.getPlayerNames().size() < 2) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), + "NOT_ENOUGH_PLAYERS", + "Not enough players to start the game")); + return; + } + + // Create engine + controller + GameState state = new GameState(); + GameEngine engine = + new GameEngine( + state, + new RuleEngine(new ArrayList<>()), + new RoundManager(), + new TurnManager()); + GameController game = new GameController(engine); + + // Add all players from lobby + for (String playerName : lobby.getPlayerNames()) { + game.addPlayer(PlayerId.of(playerName), DEFAULT_START_CHIPS); + } + + // Start the game and attach to lobby + game.startGame(); + lobby.initGame(game); + + responseDispatcher.dispatch( + new StartGameResponse( + request.getContext(), lid.value(), "Game started successfully")); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameParser.java new file mode 100644 index 0000000..807dfd8 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameParser.java @@ -0,0 +1,27 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game; + +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 `START_GAME` command. + * + *

Expected request parameters: + * + *

    + *
  • `ID` (int) — numeric lobby identifier (required) + *
+ * + *

The parser builds a {@link StartGameRequest} containing the parsed lobby id and the original + * request context. + */ +public class StartGameParser implements CommandParser { + @Override + public StartGameRequest parse(PrimitiveRequest primitiveRequest) { + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); + int id = accessor.require("ID", Integer::parseInt); + return new StartGameRequest(primitiveRequest.context(), id); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameRequest.java new file mode 100644 index 0000000..616b6f9 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameRequest.java @@ -0,0 +1,35 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game; + +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 to start a game in a specific lobby. + * + *

The `START_GAME` request requires the numeric lobby id parameter `ID` identifying the target + * lobby. The request carries the usual {@link RequestContext} (session id, request id) via the base + * class. + */ +public class StartGameRequest extends Request { + private final int id; + + /** + * Create a new {@link StartGameRequest}. + * + * @param context the request context + * @param id numeric lobby id to start the game in + */ + public StartGameRequest(RequestContext context, int id) { + super(context); + this.id = id; + } + + /** + * Returns the numeric lobby id provided by the client. + * + * @return lobby id + */ + public int getId() { + return id; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameResponse.java new file mode 100644 index 0000000..35ae0c0 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/start_game/StartGameResponse.java @@ -0,0 +1,34 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.start_game; + +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; + +/** + * Success response for {@code START_GAME} containing the started game's identifier and an + * informational message. + */ +public class StartGameResponse extends SuccessResponse { + /** + * Create a standard start-game response with a default success message. + * + * @param context the request context + * @param gameId numeric id of the started game (uses lobby id) + */ + public StartGameResponse(RequestContext context, int gameId) { + this(context, gameId, "Game started successfully"); + } + + /** + * Create a start-game response with a custom message. + * + * @param context the request context + * @param gameId numeric id of the started game (uses lobby id) + * @param message informational message for the client + */ + public StartGameResponse(RequestContext context, int gameId, String message) { + super( + context, + ResponseBody.builder().param("GAME", gameId).param("MESSAGE", message).build()); + } +} From 371e6afe5ce2a70e482cd35e0509223a684e2580 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 23:18:22 +0200 Subject: [PATCH 2/2] Docs: add documentation of new start_game command --- .../networking/commands/protocol-document.md | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/documents/docs/networking/commands/protocol-document.md b/documents/docs/networking/commands/protocol-document.md index 8ac0631..378940a 100644 --- a/documents/docs/networking/commands/protocol-document.md +++ b/documents/docs/networking/commands/protocol-document.md @@ -78,6 +78,7 @@ This document describes the protocol for client-server communication in our appl - [Required pre-execution checks](#required-pre-execution-checks) - [Request Parameters](#request-parameters) - [Success Response](#success-response) + - [START_GAME command](#start_game-command) - [GET_LOBBY_STATUS command](#get_lobby_status-command) - [Required pre-execution checks](#required-pre-execution-checks) - [Request Parameters](#request-parameters) @@ -543,8 +544,64 @@ END ``` -ERR CODE=LOBBY_NOT_FOUND - MESSAGE=Lobby not found + MSG=Lobby not found END + +## START_GAME command + +The `START_GAME` command requests the server to start a new game in the specified lobby. The +server requires an explicit numeric `ID` parameter identifying the target lobby. + +### Required pre-execution checks +- [`UserLoggedInCheck`](#userloggedincheck) + +### Request Parameters +| Parameter Name | Type | Optional | Description | +| :------------- | :--- | :------: | :---------- | +| `ID` | `int` | no | Numeric id of the target lobby | + +### Implementation notes + +- Parser: `StartGameParser` — reads the required `ID` parameter and builds `StartGameRequest`. +- Handler: `StartGameHandler` — resolves the lobby by id, checks the requester is a member of the + lobby, verifies a game is not already running and that enough players are present, initializes + `GameController` and attaches it to the lobby. On success a structured `START_GAME` success + response containing `GAME` and `MESSAGE` fields is returned. + +### Success Response +| Field | Type | Description | +| :------- | :------ | :-------------------------------- | +| `GAME` | `int` | Numeric id of the started game (uses lobby id) | +| `MESSAGE`| `String`| Human-readable status message | + +### Error Response +| Code | Description | +| :--------------- | :---------------------------------------- | +| `MISSING_PARAMETER` | Required parameter `ID` missing (parser) | +| `LOBBY_NOT_FOUND` | The specified lobby id does not exist | +| `NOT_IN_LOBBY` | Requesting user is not a member of the lobby | +| `NOT_ENOUGH_PLAYERS` | Not enough players to start the game | +| `ALREADY_STARTED` | A game is already running in the lobby | +| `USER_NOT_LOGGED_IN` | No user associated with the session | + +### Example Request + +``` + +START_GAME ID=1 + +``` + +### Example Response (success) + +``` + ++OK + GAME=1 + MESSAGE=Game started successfully +END + +``` ``` ## GET_LOBBY_LIST command