Feat/104 add call command on server side #268
@@ -63,6 +63,7 @@ This document describes the protocol for client-server communication in our appl
|
|||||||
- [Example Request](#example-request)
|
- [Example Request](#example-request)
|
||||||
- [Example Response](#example-response)
|
- [Example Response](#example-response)
|
||||||
- [RAISE command](#raise-command)
|
- [RAISE command](#raise-command)
|
||||||
|
- [CALL command](#call-command)
|
||||||
- [BET command](#bet-command)
|
- [BET command](#bet-command)
|
||||||
- [SEND_MESSAGE command](#send_message-command)
|
- [SEND_MESSAGE command](#send_message-command)
|
||||||
- [Required pre-execution checks](#required-pre-execution-checks)
|
- [Required pre-execution checks](#required-pre-execution-checks)
|
||||||
@@ -779,6 +780,61 @@ END
|
|||||||
END
|
END
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## CALL command
|
||||||
|
|
||||||
|
The `CALL` command lets the currently logged-in player match the current bet (call) 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: `PlayerCallParser` — accepts optional `GAME_ID`.
|
||||||
|
- Handler: `PlayerCallHandler` — 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 call when not their turn |
|
||||||
|
| `INSUFFICIENT_FUNDS` | Player does not have enough chips |
|
||||||
|
| `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
|
||||||
|
|
||||||
|
```
|
||||||
|
CALL 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
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -196,6 +196,20 @@ public class ServerApp {
|
|||||||
.PlayerRaiseHandler(
|
.PlayerRaiseHandler(
|
||||||
responseDispatcher, userRegistry, lobbyManager));
|
responseDispatcher, userRegistry, lobbyManager));
|
||||||
|
|
||||||
|
// CALL registration
|
||||||
|
parserDispatcher.register(
|
||||||
|
"CALL",
|
||||||
|
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call
|
||||||
|
.PlayerCallParser());
|
||||||
|
commandRouter.register(
|
||||||
|
ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call.PlayerCallRequest
|
||||||
|
.class,
|
||||||
|
(ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler<
|
||||||
|
ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call
|
||||||
|
.PlayerCallRequest>)
|
||||||
|
new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call
|
||||||
|
.PlayerCallHandler(responseDispatcher, userRegistry, lobbyManager));
|
||||||
|
|
||||||
// GET_LOBBY_STATUS registration
|
// GET_LOBBY_STATUS registration
|
||||||
parserDispatcher.register(
|
parserDispatcher.register(
|
||||||
"GET_LOBBY_STATUS",
|
"GET_LOBBY_STATUS",
|
||||||
|
|||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call;
|
||||||
|
|
||||||
|
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 `CALL` command.
|
||||||
|
*
|
||||||
|
* <p>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 call action to the {@code
|
||||||
|
* GameController}.
|
||||||
|
*/
|
||||||
|
public class PlayerCallHandler extends CommandHandler<PlayerCallRequest> {
|
||||||
|
private final UserRegistry userRegistry;
|
||||||
|
private final LobbyManager lobbyManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code PlayerCallHandler}.
|
||||||
|
*
|
||||||
|
* @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 PlayerCallHandler(
|
||||||
|
ResponseDispatcher responseDispatcher,
|
||||||
|
UserRegistry userRegistry,
|
||||||
|
LobbyManager lobbyManager) {
|
||||||
|
super(responseDispatcher);
|
||||||
|
this.userRegistry = userRegistry;
|
||||||
|
this.lobbyManager = lobbyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the call request: resolve lobby by `GAME_ID` if present, otherwise by session user,
|
||||||
|
* verify game is running, and forward the call to the game controller.
|
||||||
|
*
|
||||||
|
* @param request the parsed {@link PlayerCallRequest}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void execute(PlayerCallRequest request) {
|
||||||
|
Optional<ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User> 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.playerCall(PlayerId.of(username));
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
responseDispatcher.dispatch(
|
||||||
|
new ErrorResponse(request.getContext(), "GAME_ACTION_FAILED", e.getMessage()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call;
|
||||||
|
|
||||||
|
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 `CALL` command.
|
||||||
|
*
|
||||||
|
* <p>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 PlayerCallParser implements CommandParser<PlayerCallRequest> {
|
||||||
|
@Override
|
||||||
|
public PlayerCallRequest 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 errors handled by framework
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayerCallRequest(primitiveRequest.context(), gameId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call;
|
||||||
|
|
||||||
|
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 `CALL` command.
|
||||||
|
*
|
||||||
|
* <p>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 PlayerCallRequest extends Request {
|
||||||
|
private final Integer gameId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code PlayerCallRequest}.
|
||||||
|
*
|
||||||
|
* @param context the request context
|
||||||
|
* @param gameId optional game id of the targeted lobby (may be {@code null})
|
||||||
|
*/
|
||||||
|
public PlayerCallRequest(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user