Feat/100 add start game command on server side #265
@@ -82,6 +82,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)
|
||||
@@ -547,8 +548,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
|
||||
|
||||
@@ -266,5 +266,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));
|
||||
}
|
||||
}
|
||||
|
||||
+113
@@ -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<StartGameRequest> {
|
||||
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<ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User> 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"));
|
||||
}
|
||||
}
|
||||
+27
@@ -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.
|
||||
*
|
||||
* <p>Expected request parameters:
|
||||
*
|
||||
* <ul>
|
||||
* <li>`ID` (int) — numeric lobby identifier (required)
|
||||
* </ul>
|
||||
*
|
||||
* <p>The parser builds a {@link StartGameRequest} containing the parsed lobby id and the original
|
||||
* request context.
|
||||
*/
|
||||
public class StartGameParser implements CommandParser<StartGameRequest> {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
+35
@@ -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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user