Files
fairhopper/README.md
2023-03-25 16:07:53 +01:00

380 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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",
"name": "Pero",
"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",
"name": "Pero",
"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",
"name": "Pero",
"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": 21,
"height": 21
},
"destination": {
"position": {
"x": 10,
"y": 10
}
},
"players": [
{
"uuid": "test-player-id",
"name": "Pero",
"active": true,
"position": {
"x": 2,
"y": 2
},
"move_count": 3,
"move_attempt_count": 3
},
{
"uuid": "95962b49-0003-4bf2-b205-71f2590f2318",
"name": "Mirko",
"active": true,
"position": {
"x": 0,
"y": 0
},
"move_count": 15,
"move_attempt_count": 20
}
],
"layers": [
{
"name": "obstacles",
"objects": [
{
"type": "OBSTACLE",
"position": {
"x": 4,
"y": 2
}
},
{
"type": "OBSTACLE",
"position": {
"x": 4,
"y": 13
}
},
{
"type": "OBSTACLE",
"position": {
"x": 18,
"y": 18
}
},
{
"type": "OBSTACLE",
"position": {
"x": 5,
"y": 4
}
},
{
"type": "OBSTACLE",
"position": {
"x": 7,
"y": 10
}
}
]
},
{
"name": "destination",
"objects": [
{
"type": "DESTINATION",
"position": {
"x": 10,
"y": 10
}
}
]
},
{
"name": "players",
"objects": [
{
"type": "PLAYER",
"position": {
"x": 2,
"y": 2
}
},
{
"type": "PLAYER",
"position": {
"x": 0,
"y": 0
}
}
]
}
]
}
```