2023-03-25 17:23:00 +01:00
2023-03-25 17:23:00 +01:00
2023-03-25 13:21:07 +01:00
2023-03-25 16:30:17 +01:00
2023-03-25 17:23:00 +01:00
2023-03-25 13:21:07 +01:00
2023-03-25 13:21:07 +01:00
2023-03-25 16:33:49 +01:00
2023-03-25 16:24:54 +01:00

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, ultimate dependency management software for Python.

Install Poetry:

pip install poetry

Install virtual environment:

poetry install

Setting up

Copy settings_template.py to settings.py.

Edit settings.py and customize application.

Starting FairHopper Game Server

make run

By default, FairHopper runs on port 8010. To run FairHopper on different port, start uvicorn directly:

poetry run uvicorn main:app --host 0.0.0.0 --port 8010 --workers=1

To activate virtual environment:

poetry shell

Starting WebSockets Server

make run-ws

WebSockets server runs on port 8011. To run WS Server on different port, edit settings.py configuration.

System overview

Architecture

actor "Player 1" as P1
actor "Player 2" as P2
actor "Player 3" as P3

package Masterpiece {
    usecase Game as "FairHopper\nGame Server"
    usecase WS as "WS Server"
    usecase Vis as "Visualisation\nService"
}

P1 -left-> Game: REST API
P2 -left-> Game: REST API
P3 -left-> Game: REST API
Game --> WS: WebSockets
WS --> Vis: WebSockets

WebSockets

    participant Game as "FairHopper\nGame Server"
    participant WS as "WS Server"
    participant Client1 as "Visualisation\nClient 1"
    participant Client2 as "Visualisation\nClient 2"

    Game ->o WS: Server Connect
    activate WS #coral
        WS -> Game: Get game state
        activate Game #yellow
            Game -> 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
    Game ->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.

Start game

Endpoint: POST /game

Request body:

{
	"player_name": "Pero"
}

Response body:

{
	"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:

{
	"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:

{
	"player": {
		"uuid": "string",
		"name": "Pero",
		"position": {
			"x": 50,
			"y": 50
		},
		"move_count": 10,
		"move_attempt_count": 12
	}
}

Get Game Info

GET /game

Response body:

{
	"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

Game state structure

URI: /game-state

Data:

{
	"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
					}
				}
			]
		}
	]
}
Description
No description provided
Readme 502 KiB
Languages
Python 75.9%
JavaScript 11%
HTML 5%
Dockerfile 2.8%
Makefile 2.6%
Other 2.7%