Project rename and restructure
This commit is contained in:
316
README.md
Normal file
316
README.md
Normal file
@ -0,0 +1,316 @@
|
||||
# FairHopper
|
||||
|
||||
## Game
|
||||
|
||||
### Overview
|
||||
|
||||
- Rectangle board W × H
|
||||
- Destination: center of a board (W / 2, H / 2)
|
||||
- Initial player position: Random on board border
|
||||
- Available moves:
|
||||
- left
|
||||
- right
|
||||
- up
|
||||
- down
|
||||
- Optional on-board obstacles
|
||||
|
||||
### Rules
|
||||
- Goal: Reach the goal destination
|
||||
- Player can't move out of board
|
||||
- Player can't move if destination position contains obstacle
|
||||
- Move timeout: 10s. Game is finished if timeout ocurrs.
|
||||
|
||||
|
||||
## FairHopper Game Server
|
||||
|
||||
Requirements:
|
||||
- Python 3.10+
|
||||
|
||||
### Install virtual envirnonment
|
||||
|
||||
Project uses [Poetry](https://python-poetry.org), ultimate dependency management software for Python.
|
||||
|
||||
Install Poetry:
|
||||
```sh
|
||||
pip install poetry
|
||||
```
|
||||
|
||||
Install virtual environment:
|
||||
```sh
|
||||
poetry install
|
||||
```
|
||||
|
||||
### Setting up
|
||||
|
||||
Copy `settings_template.py` to `settings.py`.
|
||||
|
||||
Edit `settings.py` and customize application.
|
||||
|
||||
|
||||
### Starting FairHopper Game Server
|
||||
|
||||
```sh
|
||||
make run
|
||||
```
|
||||
|
||||
By default, JFK runs on port **8010**. To run on other port, start `uvicorn` directly:
|
||||
```sh
|
||||
poetry run uvicorn main:app --host 0.0.0.0 --port 8010 --workers=1
|
||||
```
|
||||
|
||||
To activate virtual environment:
|
||||
```sh
|
||||
poetry shell
|
||||
```
|
||||
|
||||
|
||||
## System overview
|
||||
|
||||
### Architecture
|
||||
|
||||
```plantuml
|
||||
actor "Player 1" as P1
|
||||
actor "Player 2" as P2
|
||||
actor "Player 3" as P3
|
||||
|
||||
package Masterpiece {
|
||||
usecase JFK as "JFK Game Server"
|
||||
usecase WS as "WS Server"
|
||||
usecase Vis as "Visualisation\nService"
|
||||
}
|
||||
|
||||
P1 -left-> JFK: REST API
|
||||
P2 -left-> JFK: REST API
|
||||
P3 -left-> JFK: REST API
|
||||
JFK --> WS: WebSockets
|
||||
WS --> Vis: WebSockets
|
||||
```
|
||||
|
||||
### WebSockets
|
||||
|
||||
```plantuml
|
||||
participant JFK as "JFK Game Server"
|
||||
participant WS as "WS Server"
|
||||
participant Client1 as "Visualisation\nClient 1"
|
||||
participant Client2 as "Visualisation\nClient 2"
|
||||
|
||||
JFK ->o WS: Server Connect
|
||||
activate WS #coral
|
||||
WS -> JFK: Get game state
|
||||
activate JFK #yellow
|
||||
JFK -> WS: Game state
|
||||
deactivate
|
||||
deactivate
|
||||
|
||||
Client1 ->o WS: Client Connect
|
||||
activate WS #coral
|
||||
WS -> Client1: Game state
|
||||
deactivate
|
||||
|
||||
Client2 ->o WS: Client Connect
|
||||
activate WS #coral
|
||||
WS -> Client2: Game state
|
||||
deactivate
|
||||
|
||||
loop #lightyellow On game state change
|
||||
JFK ->o WS: Game state
|
||||
activate WS #coral
|
||||
WS o-> Client1: Game state
|
||||
WS o-> Client2: Game state
|
||||
deactivate
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## REST API
|
||||
|
||||
- Start game
|
||||
- Move left
|
||||
- Move right
|
||||
- Move up
|
||||
- Move down
|
||||
- Get current position
|
||||
- Get board info
|
||||
|
||||
Check REST API interface on [FastAPI docs](http://localhost:8010/docs).
|
||||
|
||||
### Start game
|
||||
|
||||
**Endpoint**: POST `/game`
|
||||
|
||||
Request body:
|
||||
```json
|
||||
{
|
||||
"player_name": "Pero"
|
||||
}
|
||||
```
|
||||
|
||||
Response body:
|
||||
```json
|
||||
{
|
||||
"board": {
|
||||
"width": 101,
|
||||
"height": 101
|
||||
},
|
||||
"destination": {
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 50
|
||||
},
|
||||
},
|
||||
"player": {
|
||||
"uuid": "75bba7cd-a4c1-4b50-b0b5-6382c2822a25",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 10
|
||||
},
|
||||
"move_count": 0,
|
||||
"move_attempt_count": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Player Move
|
||||
|
||||
POST `/player/{uuid}/move/left`
|
||||
POST `/player/{uuid}/move/right`
|
||||
POST `/player/{uuid}/move/up`
|
||||
POST `/player/{uuid}/move/down`
|
||||
|
||||
Request body: None
|
||||
|
||||
Response code:
|
||||
- 200 OK: Destination reached
|
||||
- 201 Created: Player moved successfully
|
||||
- 403 Forbidden: Player uuid not valid, probably timeout
|
||||
- 409 Conflict: Invalid move, obstacle or position out of board
|
||||
- 422 Unprocessable Content: Validation error
|
||||
|
||||
Response body:
|
||||
```json
|
||||
{
|
||||
"player": {
|
||||
"uuid": "string",
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 50
|
||||
},
|
||||
"move_count": 10,
|
||||
"move_attempt_count": 12
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Player Info
|
||||
|
||||
GET `/player/{{uuid}}`
|
||||
|
||||
Request body: None
|
||||
|
||||
Response body:
|
||||
```json
|
||||
{
|
||||
"player": {
|
||||
"uuid": "string",
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 50
|
||||
},
|
||||
"move_count": 10,
|
||||
"move_attempt_count": 12
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Game Info
|
||||
|
||||
GET `/game`
|
||||
|
||||
Response body:
|
||||
```json
|
||||
{
|
||||
"playerId": "75bba7cd-a4c1-4b50-b0b5-6382c2822a25",
|
||||
"board": {
|
||||
"width": 101,
|
||||
"height": 101
|
||||
},
|
||||
"destinationPosition": {
|
||||
"x": 50,
|
||||
"y": 50
|
||||
},
|
||||
"playerPosition": {
|
||||
"x": 0,
|
||||
"y": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## WebSockets
|
||||
|
||||
### WS Data format
|
||||
- json
|
||||
|
||||
General data format:
|
||||
```json
|
||||
{
|
||||
"command": "command",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Game info structure
|
||||
|
||||
Command: `gameInfo`
|
||||
|
||||
Data:
|
||||
```json
|
||||
{
|
||||
"board": {
|
||||
"width": 101,
|
||||
"height": 101
|
||||
},
|
||||
"destinationPosition": {
|
||||
"x": 50,
|
||||
"y": 50
|
||||
},
|
||||
"players": [
|
||||
{
|
||||
"id": "75bba7cd-a4c1-4b50-b0b5-6382c2822a25",
|
||||
"name": "Pero",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "04793b36-0785-4bf3-9396-3585c358cbac",
|
||||
"name": "Mirko",
|
||||
"position": {
|
||||
"x": 11,
|
||||
"y": 12
|
||||
}
|
||||
}
|
||||
],
|
||||
"layers": [
|
||||
{
|
||||
"name": "obstacles",
|
||||
"objects": [
|
||||
{
|
||||
"type": "obstacle",
|
||||
"position": {
|
||||
"x": 15,
|
||||
"y": 25
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "obstacle",
|
||||
"position": {
|
||||
"x": 33,
|
||||
"y": 44
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user