Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 190b30672a | |||
| 497820db05 | |||
| 88c9431bf0 | |||
| 5a53a14933 | |||
| f7f66ec5db | |||
| d85b3d21ee | |||
| 382383d38b |
@@ -1,8 +1,9 @@
|
|||||||
# Protocol Document
|
# Protocol Document
|
||||||
|
|
||||||
This document describes the protocol for client-server communication in our application. It defines the structure of requests and responses, the supported commands along with their request parameters, response formats, and possible errors.
|
This document describes the protocol for client-server communication in our application. It defines the structure of requests and responses, the supported commands along with their request parameters, response formats, and possible errors.
|
||||||
|
|
||||||
|
|
||||||
# Table of Contents
|
# Table of Contents
|
||||||
|
|
||||||
- [Protocol Document](#protocol-document)
|
- [Protocol Document](#protocol-document)
|
||||||
- [Table of Contents](#table-of-contents)
|
- [Table of Contents](#table-of-contents)
|
||||||
- [General structure of requests](#general-structure-of-requests)
|
- [General structure of requests](#general-structure-of-requests)
|
||||||
@@ -36,20 +37,27 @@ This document describes the protocol for client-server communication in our appl
|
|||||||
- [Error Response](#error-response-4)
|
- [Error Response](#error-response-4)
|
||||||
- [Example Request](#example-request-2)
|
- [Example Request](#example-request-2)
|
||||||
- [Example Response](#example-response-2)
|
- [Example Response](#example-response-2)
|
||||||
- [LOGOUT command](#logout-command)
|
- [CHANGE\_USERNAME command](#change_username-command)
|
||||||
- [Required pre-execution checks](#required-pre-execution-checks-3)
|
- [Required pre-execution checks](#required-pre-execution-checks-3)
|
||||||
- [Request Parameters](#request-parameters-3)
|
- [Request Parameters](#request-parameters-3)
|
||||||
- [Success Response](#success-response-3)
|
- [Success Response](#success-response-3)
|
||||||
- [Error Response](#error-response-5)
|
- [Error Response](#error-response-5)
|
||||||
- [Example Request](#example-request-3)
|
- [Example Request](#example-request-3)
|
||||||
- [Example Response](#example-response-3)
|
- [Example Response](#example-response-3)
|
||||||
- [LIST\_USERS command](#list_users-command)
|
- [LOGOUT command](#logout-command)
|
||||||
- [Required pre-execution checks](#required-pre-execution-checks-4)
|
- [Required pre-execution checks](#required-pre-execution-checks-4)
|
||||||
- [Request Parameters](#request-parameters-4)
|
- [Request Parameters](#request-parameters-4)
|
||||||
- [Success Response](#success-response-4)
|
- [Success Response](#success-response-4)
|
||||||
- [Error Response](#error-response-6)
|
- [Error Response](#error-response-6)
|
||||||
- [Example Request](#example-request-4)
|
- [Example Request](#example-request-4)
|
||||||
- [Example Response](#example-response-4)
|
- [Example Response](#example-response-4)
|
||||||
|
- [LIST\_USERS command](#list_users-command)
|
||||||
|
- [Required pre-execution checks](#required-pre-execution-checks-5)
|
||||||
|
- [Request Parameters](#request-parameters-5)
|
||||||
|
- [Success Response](#success-response-5)
|
||||||
|
- [Error Response](#error-response-7)
|
||||||
|
- [Example Request](#example-request-5)
|
||||||
|
- [Example Response](#example-response-5)
|
||||||
- [GET_LOBBY_LIST command](#get_lobby_list-command)
|
- [GET_LOBBY_LIST command](#get_lobby_list-command)
|
||||||
- [Required pre-execution checks](#required-pre-execution-checks)
|
- [Required pre-execution checks](#required-pre-execution-checks)
|
||||||
- [Request Parameters](#request-parameters)
|
- [Request Parameters](#request-parameters)
|
||||||
@@ -91,50 +99,53 @@ This document describes the protocol for client-server communication in our appl
|
|||||||
<!-- Please see the comments for copy ‚ n' paste ready examples -->
|
<!-- Please see the comments for copy ‚ n' paste ready examples -->
|
||||||
|
|
||||||
# General structure of requests
|
# General structure of requests
|
||||||
As mentioned before, our protocol is based on POP3.
|
|
||||||
|
As mentioned before, our protocol is based on POP3.
|
||||||
Each command is represented as a single line of text, starting with the command name followed by parameters. The server responds with a status line indicating success or failure, followed by the body.
|
Each command is represented as a single line of text, starting with the command name followed by parameters. The server responds with a status line indicating success or failure, followed by the body.
|
||||||
|
|
||||||
Requests can have parameters that provide additional information for the command. Parameters are key-value pairs separated by an equal sign (`=`).
|
Requests can have parameters that provide additional information for the command. Parameters are key-value pairs separated by an equal sign (`=`).
|
||||||
|
|
||||||
Responses are collections of key-value pairs, containing either a value or another collection, allowing for nested structures.
|
Responses are collections of key-value pairs, containing either a value or another collection, allowing for nested structures.
|
||||||
Each collection is ended with the `END` keyword.
|
Each collection is ended with the `END` keyword.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Preconditions
|
# Preconditions
|
||||||
|
|
||||||
The serverside pipeline to process incoming requests consists of multiple stages.
|
The serverside pipeline to process incoming requests consists of multiple stages.
|
||||||
Each of these stages can yield an error response if the request does not meet the requirements of that stage.
|
Each of these stages can yield an error response if the request does not meet the requirements of that stage.
|
||||||
|
|
||||||
## Parsing
|
## Parsing
|
||||||
|
|
||||||
One of these stages is the parsing. It is responsible for parsing the raw request into a structured format that can be easily processed by the command handlers. It validates the syntax of the request as well.
|
One of these stages is the parsing. It is responsible for parsing the raw request into a structured format that can be easily processed by the command handlers. It validates the syntax of the request as well.
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :-------------- | :---------------------------------------------------------------------------------------------- |
|
| :-------------- | :---------------------------------------------------------------------------------------------- |
|
||||||
| `PARSING_ERROR` | The body of the request contains syntax errors (see message field of response for more details) |
|
| `PARSING_ERROR` | The body of the request contains syntax errors (see message field of response for more details) |
|
||||||
|
|
||||||
|
|
||||||
## Command dispatching
|
## Command dispatching
|
||||||
|
|
||||||
After the request has been successfully parsed, the next stage is to dispatch the `PrimitiveRequest` to the appropriate `CommandParser`. This is done by the `CommandDispatcherDispatcher`, which uses the command name to determine which parser to use.
|
After the request has been successfully parsed, the next stage is to dispatch the `PrimitiveRequest` to the appropriate `CommandParser`. This is done by the `CommandDispatcherDispatcher`, which uses the command name to determine which parser to use.
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :---------------- | :---------------------------------------------------------------- |
|
| :---------------- | :---------------------------------------------------------------- |
|
||||||
| `UNKNOWN_COMMAND` | The command is unknown to this server. No parser has been defined |
|
| `UNKNOWN_COMMAND` | The command is unknown to this server. No parser has been defined |
|
||||||
|
|
||||||
|
|
||||||
## Command parsing
|
## Command parsing
|
||||||
|
|
||||||
Once the `PrimitiveRequest` has been dispatched to the appropriate `CommandParser`, the parser is responsible for parsing the parameters of the request and creating a `Request` that can be executed by the responsible `CommandHandler`.
|
Once the `PrimitiveRequest` has been dispatched to the appropriate `CommandParser`, the parser is responsible for parsing the parameters of the request and creating a `Request` that can be executed by the responsible `CommandHandler`.
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :------------------ | :------------------------------------------------------------------------------------------------ |
|
| :------------------ | :------------------------------------------------------------------------------------------------ |
|
||||||
| `MISSING_PARAMETER` | A required parameter is missing from the request (see message field of response for more details) |
|
| `MISSING_PARAMETER` | A required parameter is missing from the request (see message field of response for more details) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Pre-execution checks
|
# Pre-execution checks
|
||||||
Pre-execution checks are reusable validation steps that can be registered on command handlers.
|
|
||||||
|
Pre-execution checks are reusable validation steps that can be registered on command handlers.
|
||||||
They are implemented as `HandlerCheck` instances and are executed by the `CommandHandlerExecutor` before the handler's main logic is invoked.
|
They are implemented as `HandlerCheck` instances and are executed by the `CommandHandlerExecutor` before the handler's main logic is invoked.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -148,16 +159,17 @@ Description of the check, what it does and when it should be used.
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
## UserLoggedInCheck
|
## UserLoggedInCheck
|
||||||
|
|
||||||
The `UserLoggedInCheck` is a common pre-execution check that verifies whether the user is logged in (i.e. has a user associated with his session).
|
The `UserLoggedInCheck` is a common pre-execution check that verifies whether the user is logged in (i.e. has a user associated with his session).
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :------------------- | :------------------------ |
|
| :------------------- | :------------------------ |
|
||||||
| `USER_NOT_LOGGED_IN` | The user is not logged in |
|
| `USER_NOT_LOGGED_IN` | The user is not logged in |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Commands
|
# Commands
|
||||||
|
|
||||||
Commands are the core of our protocol, representing the various actions that clients can request from the server. Each command has a unique name and may require specific parameters in addition to pre-execution checks.
|
Commands are the core of our protocol, representing the various actions that clients can request from the server. Each command has a unique name and may require specific parameters in addition to pre-execution checks.
|
||||||
The server processes these commands and responds accordingly.
|
The server processes these commands and responds accordingly.
|
||||||
|
|
||||||
@@ -212,41 +224,50 @@ END
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
## PING command
|
## PING command
|
||||||
|
|
||||||
The `PING` command is a simple command that can be used to check if the server is responsive.
|
The `PING` command is a simple command that can be used to check if the server is responsive.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
No parameters.
|
No parameters.
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
|
|
||||||
No response fields.
|
No response fields.
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
PING
|
PING
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
END
|
END
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## CHECK_USERNAME command
|
## CHECK_USERNAME command
|
||||||
|
|
||||||
The `CHECK_USERNAME` command is used to check if a username is already taken by another user. Additional users can still log in with the same username, but their name will be substituted with a suffix.
|
The `CHECK_USERNAME` command is used to check if a username is already taken by another user. Additional users can still log in with the same username, but their name will be substituted with a suffix.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
| Parameter Name | Type | Optional | Description |
|
| Parameter Name | Type | Optional | Description |
|
||||||
| :------------- | :------- | :------- | :------------------------------------- |
|
| :------------- | :------- | :------- | :------------------------------------- |
|
||||||
| `USERNAME` | `String` | no | The username to check for availability |
|
| `USERNAME` | `String` | no | The username to check for availability |
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
| :------- | :--------------------------- | :---------------------------------------------------------------------- |
|
| :------- | :--------------------------- | :---------------------------------------------------------------------- |
|
||||||
| `STATUS` | `Enum<UsernameAvailability>` | Member of enum indicating if the username is available or already taken |
|
| `STATUS` | `Enum<UsernameAvailability>` | Member of enum indicating if the username is available or already taken |
|
||||||
@@ -257,11 +278,13 @@ None.
|
|||||||
| `TAKEN` | Username is already in use |
|
| `TAKEN` | Username is already in use |
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
CHECK_USERNAME USERNAME='Lars'
|
CHECK_USERNAME USERNAME='Lars'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
STATUS=FREE
|
STATUS=FREE
|
||||||
@@ -269,33 +292,40 @@ END
|
|||||||
```
|
```
|
||||||
|
|
||||||
## LOGIN command
|
## LOGIN command
|
||||||
|
|
||||||
The `LOGIN` command is used to log in a user with a specified username. If the username is already taken by another user, the server will append a suffix to the username to make it unique.
|
The `LOGIN` command is used to log in a user with a specified username. If the username is already taken by another user, the server will append a suffix to the username to make it unique.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
| Parameter Name | Type | Optional | Description |
|
| Parameter Name | Type | Optional | Description |
|
||||||
| :------------- | :------- | :------- | :----------------------------------- |
|
| :------------- | :------- | :------- | :----------------------------------- |
|
||||||
| `USERNAME` | `String` | no | The username to create the user with |
|
| `USERNAME` | `String` | no | The username to create the user with |
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
| :--------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- |
|
| :--------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||||
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
|
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
|
||||||
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
|
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :------------------ | :----------------------------------------------------------------------------- |
|
| :------------------ | :----------------------------------------------------------------------------- |
|
||||||
| `ALREADY_LOGGED_IN` | The session is already associated with a user, logging in again is prohibited. |
|
| `ALREADY_LOGGED_IN` | The session is already associated with a user, logging in again is prohibited. |
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
LOGIN USERNAME='Lars'
|
LOGIN USERNAME='Lars'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
USERNAME='Lars_1234'
|
USERNAME='Lars_1234'
|
||||||
@@ -303,45 +333,100 @@ LOGIN USERNAME='Lars'
|
|||||||
END
|
END
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## CHANGE_USERNAME command
|
||||||
|
|
||||||
## LOGOUT command
|
The `CHANGE_USERNAME` command is used to change the username of an already logged-in user. The request is tied to the current session and updates all affected server-side mappings.
|
||||||
Description of the command, what it does, and when it should be used.
|
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
|
| Parameter Name | Type | Optional | Description |
|
||||||
|
| :------------- | :------- | :------- | :------------------------------------ |
|
||||||
|
| `USERNAME` | `String` | no | The new username for the active user |
|
||||||
|
|
||||||
|
### Success Response
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| :--------- | :---------------------------------------------------------------------- | :-------------------------------------- |
|
||||||
|
| `USERNAME` | `String` | Effective username after rename |
|
||||||
|
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the renamed user |
|
||||||
|
|
||||||
|
### Error Response
|
||||||
|
|
||||||
|
| Code | Description |
|
||||||
|
| :------------------- | :-------------------------------------------------------------------- |
|
||||||
|
| `USER_NOT_LOGGED_IN` | No active user is associated with this session. |
|
||||||
|
| `INVALID_USERNAME` | Username contains disallowed characters or is empty. |
|
||||||
|
| `USERNAME_TAKEN` | Requested username is already used by another user. |
|
||||||
|
| `RENAME_CONFLICT` | Rename could not be propagated to all current lobby/game structures. |
|
||||||
|
|
||||||
|
### Example Request
|
||||||
|
|
||||||
|
```
|
||||||
|
CHANGE_USERNAME USERNAME='Lars_New'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Response
|
||||||
|
|
||||||
|
```
|
||||||
|
+OK
|
||||||
|
USERNAME='Lars_New'
|
||||||
|
ID=e47a671e-2b2a-42df-bb82-953fe2ebd307
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
## LOGOUT command
|
||||||
|
|
||||||
|
Description of the command, what it does, and when it should be used.
|
||||||
|
|
||||||
|
### Required pre-execution checks
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Request Parameters
|
||||||
|
|
||||||
No parameters.
|
No parameters.
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
|
|
||||||
No response fields.
|
No response fields.
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :------------------- | :--------------------------------------------------------------- |
|
| :------------------- | :--------------------------------------------------------------- |
|
||||||
| `NO_USER_ASSOCIATED` | The session has no user associated, logging out is not possible. |
|
| `NO_USER_ASSOCIATED` | The session has no user associated, logging out is not possible. |
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
LOGOUT
|
LOGOUT
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
END
|
END
|
||||||
```
|
```
|
||||||
|
|
||||||
## LIST_USERS command
|
## LIST_USERS command
|
||||||
|
|
||||||
The `LIST_USERS` command is used to retrieve a list of all currently logged-in users.
|
The `LIST_USERS` command is used to retrieve a list of all currently logged-in users.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
No parameters.
|
No parameters.
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
| :------ | :----------------- | :--------------------------------------- |
|
| :------ | :----------------- | :--------------------------------------- |
|
||||||
| `USERS` | `Collection<User>` | Collection of all users currently online |
|
| `USERS` | `Collection<User>` | Collection of all users currently online |
|
||||||
@@ -351,16 +436,18 @@ No parameters.
|
|||||||
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
|
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
|
||||||
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
|
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
|
||||||
|
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
LIST_USERS
|
LIST_USERS
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
USERS
|
USERS
|
||||||
@@ -381,8 +468,11 @@ END
|
|||||||
```
|
```
|
||||||
|
|
||||||
## SEND_MESSAGE command
|
## SEND_MESSAGE command
|
||||||
|
|
||||||
The `SEND_MESSAGE` command is used to transfer the chat message sent by a user to the server.
|
The `SEND_MESSAGE` command is used to transfer the chat message sent by a user to the server.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
@@ -403,28 +493,36 @@ None.
|
|||||||
| `WHISPER` | Message is for the whisper chat |
|
| `WHISPER` | Message is for the whisper chat |
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
|
|
||||||
No response fields.
|
No response fields.
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
SEND_MESSAGE TYPE=GLOBAL GAME=1 USER=player1 TARGET=null TIME='10:30' TEXT='Hello World'
|
SEND_MESSAGE TYPE=GLOBAL GAME=1 USER=player1 TARGET=null TIME='10:30' TEXT='Hello World'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
END
|
END
|
||||||
```
|
```
|
||||||
|
|
||||||
## GET_MESSAGE_COUNT command
|
## GET_MESSAGE_COUNT command
|
||||||
|
|
||||||
The `GET_MESSAGE_COUNT` is used to get the current number of messages that are stored in the queue for a client.
|
The `GET_MESSAGE_COUNT` is used to get the current number of messages that are stored in the queue for a client.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
No parameters.
|
No parameters.
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
@@ -434,17 +532,19 @@ No parameters.
|
|||||||
| `COUNT` | `int` | The current number of messages |
|
| `COUNT` | `int` | The current number of messages |
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :------------------- |:---------------------------------------------------------------------------------|
|
| :------------------- |:---------------------------------------------------------------------------------|
|
||||||
| `NO_USER_ASSOCIATED` | The session has no user associated, there is no queue of messages for the client |
|
| `NO_USER_ASSOCIATED` | The session has no user associated, there is no queue of messages for the client |
|
||||||
|
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
GET_MESSAGE_COUNT
|
GET_MESSAGE_COUNT
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
COUNT=10
|
COUNT=10
|
||||||
@@ -452,14 +552,19 @@ END
|
|||||||
```
|
```
|
||||||
|
|
||||||
## GET_NEXT_MESSAGE command
|
## GET_NEXT_MESSAGE command
|
||||||
|
|
||||||
The `GET_NEXT_MESSAGE` command is used to get the next message stored in a queue for the client.
|
The `GET_NEXT_MESSAGE` command is used to get the next message stored in a queue for the client.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
No parameters.
|
No parameters.
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
|:---------|:----------------|:-------------------------------------------------------------------|
|
|:---------|:----------------|:-------------------------------------------------------------------|
|
||||||
| `TYPE` | `Enum<ChatType` | Member of Enum, indicating with chat type is used |
|
| `TYPE` | `Enum<ChatType` | Member of Enum, indicating with chat type is used |
|
||||||
@@ -476,16 +581,19 @@ No parameters.
|
|||||||
| `WHISPER` | Message is for the whisper chat |
|
| `WHISPER` | Message is for the whisper chat |
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :------------------- |:---------------------------------------------------------------------------------|
|
| :------------------- |:---------------------------------------------------------------------------------|
|
||||||
| `NO_USER_ASSOCIATED` | The session has no user associated, there is no queue of messages for the client |
|
| `NO_USER_ASSOCIATED` | The session has no user associated, there is no queue of messages for the client |
|
||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```
|
```
|
||||||
GET_NEXT_MESSAGE
|
GET_NEXT_MESSAGE
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response
|
### Example Response
|
||||||
|
|
||||||
```
|
```
|
||||||
+OK
|
+OK
|
||||||
TYPE=GLOBAL GAME=-1 USER=player1 TARGET=null TIME=9:30 TEXT="Guten Tag"
|
TYPE=GLOBAL GAME=-1 USER=player1 TARGET=null TIME=9:30 TEXT="Guten Tag"
|
||||||
@@ -613,9 +721,11 @@ END
|
|||||||
The `GET_LOBBY_LIST` command requests the server to return a list of currently available lobbies with basic metadata.
|
The `GET_LOBBY_LIST` command requests the server to return a list of currently available lobbies with basic metadata.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
No parameters.
|
No parameters.
|
||||||
|
|
||||||
### Implementation notes
|
### Implementation notes
|
||||||
@@ -667,9 +777,11 @@ END
|
|||||||
The `GET_GAME_STATE` command returns a snapshot of the current game for a lobby. It includes global fields (`PHASE`, `POT`, `CURRENT_BET`, `DEALER`, `ACTIVE_PLAYER`), repeated `CARD` blocks for community cards and repeated `PLAYER` blocks for per-player information. If the requester is a player in the game, their hole cards are included inside their `PLAYER` block as nested `CARD` blocks.
|
The `GET_GAME_STATE` command returns a snapshot of the current game for a lobby. It includes global fields (`PHASE`, `POT`, `CURRENT_BET`, `DEALER`, `ACTIVE_PLAYER`), repeated `CARD` blocks for community cards and repeated `PLAYER` blocks for per-player information. If the requester is a player in the game, their hole cards are included inside their `PLAYER` block as nested `CARD` blocks.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
- [`UserLoggedInCheck`](#userloggedincheck)
|
- [`UserLoggedInCheck`](#userloggedincheck)
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
| Parameter Name | Type | Optional | Description |
|
| Parameter Name | Type | Optional | Description |
|
||||||
| :------------- | :----- | :------: | :---------- |
|
| :------------- | :----- | :------: | :---------- |
|
||||||
| `GAME_ID` | `int` | yes | Numeric id of the lobby/game to query. If omitted the server will resolve the lobby by the requesting session's associated user.
|
| `GAME_ID` | `int` | yes | Numeric id of the lobby/game to query. If omitted the server will resolve the lobby by the requesting session's associated user.
|
||||||
@@ -1114,9 +1226,11 @@ END
|
|||||||
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.
|
||||||
|
|
||||||
### Required pre-execution checks
|
### Required pre-execution checks
|
||||||
|
|
||||||
None.
|
None.
|
||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
| Parameter Name | Type | Optional | Description |
|
| Parameter Name | Type | Optional | Description |
|
||||||
| :------------- | :--- | :------: | :---------- |
|
| :------------- | :--- | :------: | :---------- |
|
||||||
| `ID` | `int` | no | Numeric id of the target lobby |
|
| `ID` | `int` | no | Numeric id of the target lobby |
|
||||||
@@ -1151,6 +1265,7 @@ END
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| :--- | :---------- |
|
| :--- | :---------- |
|
||||||
| `LOBBY_NOT_FOUND` | The specified lobby id does not exist |
|
| `LOBBY_NOT_FOUND` | The specified lobby id does not exist |
|
||||||
@@ -1222,4 +1337,4 @@ END
|
|||||||
CODE=LOBBIES_FULL
|
CODE=LOBBIES_FULL
|
||||||
MESSAGE=Maximum number of 8 lobbies reached
|
MESSAGE=Maximum number of 8 lobbies reached
|
||||||
END
|
END
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -114,8 +114,11 @@ public class CasinoGameUI extends Application {
|
|||||||
Parent root = fxmlLoader.load();
|
Parent root = fxmlLoader.load();
|
||||||
CasinoGameController controller = fxmlLoader.getController();
|
CasinoGameController controller = fxmlLoader.getController();
|
||||||
|
|
||||||
int gameId = 1; // TODO echte gameId einsetzen
|
if (lobbyId <= 0) {
|
||||||
GameClient gameClient = new GameClient(clientService, gameId);
|
throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()");
|
||||||
|
}
|
||||||
|
|
||||||
|
GameClient gameClient = new GameClient(clientService, lobbyId);
|
||||||
GameService gameService = new GameService(gameClient);
|
GameService gameService = new GameService(gameClient);
|
||||||
controller.setGameService(gameService);
|
controller.setGameService(gameService);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.server;
|
package ch.unibas.dmi.dbis.cs108.casono.server;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.change_username.ChangeUsernameHandler;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.change_username.ChangeUsernameParser;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.change_username.ChangeUsernameRequest;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameHandler;
|
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameHandler;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameParser;
|
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameParser;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameRequest;
|
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameRequest;
|
||||||
@@ -168,6 +171,15 @@ public class ServerApp {
|
|||||||
commandRouter.register(
|
commandRouter.register(
|
||||||
LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry));
|
LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry));
|
||||||
|
|
||||||
|
parserDispatcher.register("CHANGE_USERNAME", new ChangeUsernameParser());
|
||||||
|
commandRouter.register(
|
||||||
|
ChangeUsernameRequest.class,
|
||||||
|
new ChangeUsernameHandler(
|
||||||
|
responseDispatcher,
|
||||||
|
userRegistry,
|
||||||
|
context.lobbyManager(),
|
||||||
|
context.sessionManager()));
|
||||||
|
|
||||||
parserDispatcher.register("LOGOUT", new LogoutParser());
|
parserDispatcher.register("LOGOUT", new LogoutParser());
|
||||||
commandRouter.register(
|
commandRouter.register(
|
||||||
LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry));
|
LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry));
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.change_username;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
|
||||||
|
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.request.RequestContext;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** Handles CHANGE_USERNAME requests for already logged-in users. */
|
||||||
|
public class ChangeUsernameHandler extends CommandHandler<ChangeUsernameRequest> {
|
||||||
|
private static final Pattern VALID_USERNAME = Pattern.compile("[a-zA-Z0-9_-]+");
|
||||||
|
private final UserRegistry userRegistry;
|
||||||
|
private final LobbyManager lobbyManager;
|
||||||
|
private final SessionManager sessionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param responseDispatcher dispatcher used for responses
|
||||||
|
* @param userRegistry registry containing all users
|
||||||
|
* @param lobbyManager lobby manager used to keep lobby/game mappings in sync
|
||||||
|
* @param sessionManager session manager used to broadcast rename events
|
||||||
|
*/
|
||||||
|
public ChangeUsernameHandler(
|
||||||
|
ResponseDispatcher responseDispatcher,
|
||||||
|
UserRegistry userRegistry,
|
||||||
|
LobbyManager lobbyManager,
|
||||||
|
SessionManager sessionManager) {
|
||||||
|
super(responseDispatcher);
|
||||||
|
this.userRegistry = userRegistry;
|
||||||
|
this.lobbyManager = lobbyManager;
|
||||||
|
this.sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(ChangeUsernameRequest request) {
|
||||||
|
Optional<User> user = userRegistry.getBySessionId(request.getSessionId());
|
||||||
|
if (user.isEmpty()) {
|
||||||
|
responseDispatcher.dispatch(
|
||||||
|
new ErrorResponse(
|
||||||
|
request.getContext(),
|
||||||
|
"USER_NOT_LOGGED_IN",
|
||||||
|
"This session is not associated with an active user."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String newUsername = request.getUsername() == null ? "" : request.getUsername().trim();
|
||||||
|
if (newUsername.isEmpty() || !VALID_USERNAME.matcher(newUsername).matches()) {
|
||||||
|
responseDispatcher.dispatch(
|
||||||
|
new ErrorResponse(
|
||||||
|
request.getContext(),
|
||||||
|
"INVALID_USERNAME",
|
||||||
|
"Only letters, numbers, '_' and '-' are allowed."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User currentUser = user.get();
|
||||||
|
String oldUsername = currentUser.getName();
|
||||||
|
boolean changed = userRegistry.changeUsername(currentUser.getId(), newUsername);
|
||||||
|
if (!changed) {
|
||||||
|
responseDispatcher.dispatch(
|
||||||
|
new ErrorResponse(
|
||||||
|
request.getContext(),
|
||||||
|
"USERNAME_TAKEN",
|
||||||
|
"The requested username is already taken."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean lobbySynced =
|
||||||
|
lobbyManager == null || lobbyManager.renamePlayer(oldUsername, newUsername);
|
||||||
|
if (!lobbySynced) {
|
||||||
|
userRegistry.changeUsername(currentUser.getId(), oldUsername);
|
||||||
|
responseDispatcher.dispatch(
|
||||||
|
new ErrorResponse(
|
||||||
|
request.getContext(),
|
||||||
|
"RENAME_CONFLICT",
|
||||||
|
"Could not update username in current lobby/game state."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseDispatcher.dispatch(
|
||||||
|
new ChangeUsernameResponse(
|
||||||
|
request.getContext(), currentUser.getName(), currentUser.getId()));
|
||||||
|
|
||||||
|
broadcastUsernameChanged(oldUsername, currentUser.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastUsernameChanged(String oldUsername, String newUsername) {
|
||||||
|
if (sessionManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Session session : sessionManager.getAllSessions()) {
|
||||||
|
RequestContext ctx = new RequestContext(session.getId(), 0);
|
||||||
|
SuccessResponse ev =
|
||||||
|
new SuccessResponse(
|
||||||
|
ctx,
|
||||||
|
new ResponseBodyBuilder()
|
||||||
|
.param("EVENT", "USERNAME_CHANGED")
|
||||||
|
.param("OLD_USERNAME", oldUsername)
|
||||||
|
.param("NEW_USERNAME", newUsername)
|
||||||
|
.build()) {};
|
||||||
|
responseDispatcher.dispatch(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.change_username;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/** Parses CHANGE_USERNAME requests. */
|
||||||
|
public class ChangeUsernameParser implements CommandParser<ChangeUsernameRequest> {
|
||||||
|
@Override
|
||||||
|
public ChangeUsernameRequest parse(PrimitiveRequest primitiveRequest) {
|
||||||
|
RequestParameterAccessor accessor =
|
||||||
|
new RequestParameterAccessor(primitiveRequest.parameters());
|
||||||
|
return new ChangeUsernameRequest(primitiveRequest.context(), accessor.require("USERNAME"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.change_username;
|
||||||
|
|
||||||
|
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 used to change the username of the current session user. */
|
||||||
|
public class ChangeUsernameRequest extends Request {
|
||||||
|
private final String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context request context for responses
|
||||||
|
* @param username desired new username
|
||||||
|
*/
|
||||||
|
public ChangeUsernameRequest(RequestContext context, String username) {
|
||||||
|
super(context);
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return desired new username
|
||||||
|
*/
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.change_username;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserId;
|
||||||
|
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.ResponseBodyBuilder;
|
||||||
|
|
||||||
|
/** Response for successful username changes. */
|
||||||
|
public class ChangeUsernameResponse extends SuccessResponse {
|
||||||
|
/**
|
||||||
|
* @param context request context
|
||||||
|
* @param username current username after the rename operation
|
||||||
|
* @param id user id of renamed user
|
||||||
|
*/
|
||||||
|
public ChangeUsernameResponse(RequestContext context, String username, UserId id) {
|
||||||
|
super(
|
||||||
|
context,
|
||||||
|
new ResponseBodyBuilder()
|
||||||
|
.param("USERNAME", username)
|
||||||
|
.param("ID", id.value())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-1
@@ -1,6 +1,7 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state;
|
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state;
|
||||||
|
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck;
|
import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GamePhase;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.Lobby;
|
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.LobbyId;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager;
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager;
|
||||||
@@ -31,8 +32,10 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
|
|||||||
String username = resolveUsername(request);
|
String username = resolveUsername(request);
|
||||||
|
|
||||||
Lobby lobby;
|
Lobby lobby;
|
||||||
|
LobbyId lobbyId;
|
||||||
if (gameId != null) {
|
if (gameId != null) {
|
||||||
lobby = lobbyManager.getLobby(LobbyId.of(gameId));
|
lobbyId = LobbyId.of(gameId);
|
||||||
|
lobby = lobbyManager.getLobby(lobbyId);
|
||||||
if (lobby == null) {
|
if (lobby == null) {
|
||||||
responseDispatcher.dispatch(
|
responseDispatcher.dispatch(
|
||||||
new ErrorResponse(
|
new ErrorResponse(
|
||||||
@@ -59,6 +62,7 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
|
|||||||
request.getContext(), "NOT_IN_LOBBY", "User not in a lobby"));
|
request.getContext(), "NOT_IN_LOBBY", "User not in a lobby"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
lobbyId = lobby.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = lobby.getGameController();
|
var game = lobby.getGameController();
|
||||||
@@ -69,9 +73,30 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game.getState().getPhase() == GamePhase.FINISHED) {
|
||||||
|
cleanupLobby(lobby, lobbyId);
|
||||||
|
}
|
||||||
|
|
||||||
responseDispatcher.dispatch(new GetGameStateResponse(request.getContext(), game, username));
|
responseDispatcher.dispatch(new GetGameStateResponse(request.getContext(), game, username));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up a lobby by removing all players and resetting the game controller. This is called
|
||||||
|
* when the game reaches FINISHED phase.
|
||||||
|
*/
|
||||||
|
private void cleanupLobby(Lobby lobby, LobbyId lobbyId) {
|
||||||
|
try {
|
||||||
|
// Remove all players from the lobby
|
||||||
|
for (String playerName : lobby.getPlayerNames()) {
|
||||||
|
lobbyManager.removePlayer(playerName);
|
||||||
|
}
|
||||||
|
// Reset the game controller so the lobby returns to CREATED state
|
||||||
|
lobby.initGame(null);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Log silently to avoid disrupting game state response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveUsername(GetGameStateRequest request) {
|
private String resolveUsername(GetGameStateRequest request) {
|
||||||
String username = request.getUsername();
|
String username = request.getUsername();
|
||||||
if (username != null && !username.isBlank()) {
|
if (username != null && !username.isBlank()) {
|
||||||
|
|||||||
+27
-1
@@ -6,6 +6,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Rank;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Suit;
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Suit;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player;
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GamePhase;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
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.SuccessResponse;
|
||||||
@@ -33,11 +34,13 @@ public class GetGameStateResponse extends SuccessResponse {
|
|||||||
super(
|
super(
|
||||||
context,
|
context,
|
||||||
buildBody(
|
buildBody(
|
||||||
|
game,
|
||||||
Objects.requireNonNull(game, "game must not be null").getState(),
|
Objects.requireNonNull(game, "game must not be null").getState(),
|
||||||
requestingUsername));
|
requestingUsername));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResponseBody buildBody(GameState state, String requestingUsername) {
|
private static ResponseBody buildBody(
|
||||||
|
GameController game, GameState state, String requestingUsername) {
|
||||||
Objects.requireNonNull(state, "state must not be null");
|
Objects.requireNonNull(state, "state must not be null");
|
||||||
|
|
||||||
ResponseBodyBuilder builder = ResponseBody.builder();
|
ResponseBodyBuilder builder = ResponseBody.builder();
|
||||||
@@ -47,6 +50,7 @@ public class GetGameStateResponse extends SuccessResponse {
|
|||||||
builder.param("CURRENT_BET", computeGlobalCurrentBet(state));
|
builder.param("CURRENT_BET", computeGlobalCurrentBet(state));
|
||||||
builder.param("DEALER", state.getDealerIndex());
|
builder.param("DEALER", state.getDealerIndex());
|
||||||
builder.param("ACTIVE_PLAYER", state.getCurrentPlayerIndex());
|
builder.param("ACTIVE_PLAYER", state.getCurrentPlayerIndex());
|
||||||
|
builder.param("WINNER", computeWinnerIndex(state, game));
|
||||||
|
|
||||||
appendCommunityCards(builder, state);
|
appendCommunityCards(builder, state);
|
||||||
|
|
||||||
@@ -55,6 +59,28 @@ public class GetGameStateResponse extends SuccessResponse {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int computeWinnerIndex(GameState state, GameController game) {
|
||||||
|
GamePhase phase = state.getPhase();
|
||||||
|
if (phase != GamePhase.SHOWDOWN && phase != GamePhase.FINISHED) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerId winnerId = game.determineWinner();
|
||||||
|
if (winnerId == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (Player p : state.getPlayers()) {
|
||||||
|
if (p != null && winnerId.equals(p.getId())) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
private static int computeGlobalCurrentBet(GameState state) {
|
private static int computeGlobalCurrentBet(GameState state) {
|
||||||
int globalCurrentBet = 0;
|
int globalCurrentBet = 0;
|
||||||
for (Player p : state.getPlayers()) {
|
for (Player p : state.getPlayers()) {
|
||||||
|
|||||||
@@ -254,4 +254,12 @@ public class GameController {
|
|||||||
|
|
||||||
return bestPlayer;
|
return bestPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends the current game by setting the phase to FINISHED. This should be called when the
|
||||||
|
* showdown is complete and a winner has been determined.
|
||||||
|
*/
|
||||||
|
public void endGame() {
|
||||||
|
engine.getState().setPhase(GamePhase.FINISHED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-2
@@ -49,6 +49,11 @@ public class RoundManager {
|
|||||||
state.setPhase(GamePhase.PREFLOP);
|
state.setPhase(GamePhase.PREFLOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.countNonFoldedPlayers() == 1) {
|
||||||
|
state.setPhase(GamePhase.FINISHED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isBettingRoundFinished(state)) {
|
if (isBettingRoundFinished(state)) {
|
||||||
advancePhase(state);
|
advancePhase(state);
|
||||||
}
|
}
|
||||||
@@ -81,8 +86,9 @@ public class RoundManager {
|
|||||||
case FLOP -> dealTurn(state);
|
case FLOP -> dealTurn(state);
|
||||||
case TURN -> dealRiver(state);
|
case TURN -> dealRiver(state);
|
||||||
case RIVER -> showdown(state);
|
case RIVER -> showdown(state);
|
||||||
case SHOWDOWN -> {
|
case SHOWDOWN -> state.setPhase(GamePhase.FINISHED);
|
||||||
/* nothing */
|
case FINISHED -> {
|
||||||
|
/* game is over */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-20
@@ -11,19 +11,15 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GameState class encapsulates the entire state of a poker game at any
|
* The GameState class encapsulates the entire state of a poker game at any given moment.
|
||||||
* given moment.
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>Important invariants this implementation maintains:
|
||||||
* Important invariants this implementation maintains:
|
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code playerOrder} defines the stable seating/turn order.
|
* <li>{@code playerOrder} defines the stable seating/turn order.
|
||||||
* <li>{@code currentBets} always contains an entry for every player in
|
* <li>{@code currentBets} always contains an entry for every player in {@code playerOrder}.
|
||||||
* {@code playerOrder}.
|
* <li>{@code holeCards} maps every player to a mutable list; if not present, it is created on
|
||||||
* <li>{@code holeCards} maps every player to a mutable list; if not present, it
|
* demand.
|
||||||
* is created on
|
|
||||||
* demand.
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class GameState {
|
public class GameState {
|
||||||
@@ -188,8 +184,7 @@ public class GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset bets to 0 for ALL players (do not clear the map). Also resets table
|
* Reset bets to 0 for ALL players (do not clear the map). Also resets table current bet to 0.
|
||||||
* current bet to 0.
|
|
||||||
*/
|
*/
|
||||||
public void resetBets() {
|
public void resetBets() {
|
||||||
for (PlayerId id : playerOrder) {
|
for (PlayerId id : playerOrder) {
|
||||||
@@ -232,8 +227,7 @@ public class GameState {
|
|||||||
// Cards
|
// Cards
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gives (overwrites) the two hole cards for the given player. Ensures stable
|
* Gives (overwrites) the two hole cards for the given player. Ensures stable list identity
|
||||||
* list identity
|
|
||||||
* (important if other code holds references).
|
* (important if other code holds references).
|
||||||
*/
|
*/
|
||||||
public void giveHoleCards(PlayerId playerId, Card c1, Card c2) {
|
public void giveHoleCards(PlayerId playerId, Card c1, Card c2) {
|
||||||
@@ -293,14 +287,13 @@ public class GameState {
|
|||||||
/**
|
/**
|
||||||
* Starts a new hand by resetting the game state for the next round of poker.
|
* Starts a new hand by resetting the game state for the next round of poker.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>This:
|
||||||
* This:
|
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>sets {@code phase=PREFLOP}
|
* <li>sets {@code phase=PREFLOP}
|
||||||
* <li>resets pot/bets/commitments
|
* <li>resets pot/bets/commitments
|
||||||
* <li>clears community + hole cards
|
* <li>clears community + hole cards
|
||||||
* <li>creates & shuffles a new deck
|
* <li>creates & shuffles a new deck
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public void startNewHand() {
|
public void startNewHand() {
|
||||||
@@ -336,4 +329,19 @@ public class GameState {
|
|||||||
public boolean isFolded(PlayerId playerId) {
|
public boolean isFolded(PlayerId playerId) {
|
||||||
return getPlayer(playerId).isFolded();
|
return getPlayer(playerId).isFolded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of players who have not folded.
|
||||||
|
*
|
||||||
|
* @return The number of non-folded players
|
||||||
|
*/
|
||||||
|
public int countNonFoldedPlayers() {
|
||||||
|
int count = 0;
|
||||||
|
for (Player p : players.values()) {
|
||||||
|
if (p != null && !p.isFolded()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user