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;
+ }
+}