Feat/104 add call command on server side #268

Merged
jona.walpert merged 2 commits from feat/104-add-call-command-on-server-side into main 2026-04-12 01:12:51 +02:00
4 changed files with 172 additions and 0 deletions
Showing only changes of commit 6d8f1787a9 - Show all commits
@@ -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",
@@ -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()));
}
}
@@ -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);
}
}
@@ -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;
}
}