Compare commits
6 Commits
fd71fa276c
...
ed8f93d7d0
| Author | SHA1 | Date | |
|---|---|---|---|
| ed8f93d7d0 | |||
| dfd582a439 | |||
| 96adaac01a | |||
| fb6d1276e6 | |||
| 6a64709d17 | |||
| 270f742c95 |
@ -10,8 +10,7 @@ Check `demo.js` for usage example.
|
|||||||
Edit `demo.js` and configure FairHopper Game host settings:
|
Edit `demo.js` and configure FairHopper Game host settings:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const FAIRHOPPER_HOST = "http://127.0.0.1";
|
const FAIRHOPPER_HOST = "http://127.0.0.1:8010";
|
||||||
const FAIRHOPPER_PORT = 8010;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Run example using node:
|
Run example using node:
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
const sdk = require("./fh_sdk");
|
const sdk = require("./fh_sdk");
|
||||||
|
|
||||||
const FAIRHOPPER_HOST = "http://127.0.0.1";
|
const FAIRHOPPER_HOST = "https://api.fairhopper.mjerenja.com";
|
||||||
const FAIRHOPPER_PORT = 8010;
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const fh = new sdk.FairHopper(FAIRHOPPER_HOST, FAIRHOPPER_PORT);
|
const fh = new sdk.FairHopper(FAIRHOPPER_HOST);
|
||||||
|
|
||||||
console.log(`Pinging FairHopper server on ${FAIRHOPPER_HOST}:${FAIRHOPPER_PORT}`);
|
console.log(`Pinging FairHopper server on ${FAIRHOPPER_HOST}`);
|
||||||
const pingResult = await fh.ping();
|
const pingResult = await fh.ping();
|
||||||
console.log("Ping result:", pingResult);
|
console.log("Ping result:", pingResult);
|
||||||
console.log();
|
console.log();
|
||||||
|
|||||||
@ -1,3 +1,13 @@
|
|||||||
|
class ValidationError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
if (!message) {
|
||||||
|
message = "ValidationError";
|
||||||
|
}
|
||||||
|
super(message);
|
||||||
|
this.name = "ValidationError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PlayerNotFoundError extends Error {
|
class PlayerNotFoundError extends Error {
|
||||||
constructor(message) {
|
constructor(message) {
|
||||||
if (!message) {
|
if (!message) {
|
||||||
@ -36,9 +46,8 @@ const Direction = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class FairHopper {
|
class FairHopper {
|
||||||
constructor(host, port) {
|
constructor(host) {
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.port = port;
|
|
||||||
|
|
||||||
this.defaultHeaders = {
|
this.defaultHeaders = {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
@ -47,7 +56,7 @@ class FairHopper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatUrl(path) {
|
formatUrl(path) {
|
||||||
return `${this.host}:${this.port}${path}`;
|
return `${this.host}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ping() {
|
async ping() {
|
||||||
@ -66,6 +75,10 @@ class FairHopper {
|
|||||||
headers: this.defaultHeaders,
|
headers: this.defaultHeaders,
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
switch (r.status) {
|
||||||
|
case 422:
|
||||||
|
throw new ValidationError();
|
||||||
|
}
|
||||||
return await r.json();
|
return await r.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +98,8 @@ class FairHopper {
|
|||||||
throw new PlayerInactiveError();
|
throw new PlayerInactiveError();
|
||||||
case 404:
|
case 404:
|
||||||
throw new PlayerNotFoundError();
|
throw new PlayerNotFoundError();
|
||||||
|
case 422:
|
||||||
|
throw new ValidationError();
|
||||||
}
|
}
|
||||||
return await r.json();
|
return await r.json();
|
||||||
}
|
}
|
||||||
@ -118,6 +133,8 @@ class FairHopper {
|
|||||||
throw new PlayerNotFoundError();
|
throw new PlayerNotFoundError();
|
||||||
case 409:
|
case 409:
|
||||||
throw new PositionError();
|
throw new PositionError();
|
||||||
|
case 422:
|
||||||
|
throw new ValidationError();
|
||||||
}
|
}
|
||||||
return await r.json();
|
return await r.json();
|
||||||
}
|
}
|
||||||
@ -129,6 +146,17 @@ class FairHopper {
|
|||||||
return await r.json();
|
return await r.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getProduct(productId) {
|
||||||
|
const r = await fetch(this.formatUrl(`/products/${productId}`), {
|
||||||
|
headers: this.defaultHeaders,
|
||||||
|
});
|
||||||
|
switch (r.status) {
|
||||||
|
case 422:
|
||||||
|
throw new ValidationError();
|
||||||
|
}
|
||||||
|
return await r.json();
|
||||||
|
}
|
||||||
|
|
||||||
async purchaseProduct(playerId, productId) {
|
async purchaseProduct(playerId, productId) {
|
||||||
const url = this.formatUrl(`/player/${playerId}/product/purchase`);
|
const url = this.formatUrl(`/player/${playerId}/product/purchase`);
|
||||||
const postData = {
|
const postData = {
|
||||||
@ -146,6 +174,8 @@ class FairHopper {
|
|||||||
throw new PlayerNotFoundError();
|
throw new PlayerNotFoundError();
|
||||||
case 409:
|
case 409:
|
||||||
throw new PositionError();
|
throw new PositionError();
|
||||||
|
case 422:
|
||||||
|
throw new ValidationError();
|
||||||
}
|
}
|
||||||
return await r.json();
|
return await r.json();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# FairHopper Python SDK
|
# FairHopper Python SDK
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- Python 3.10+
|
- Python 3.7+
|
||||||
|
|
||||||
## Setting up environment
|
## Setting up environment
|
||||||
|
|
||||||
@ -24,8 +24,7 @@ Check `demo.py` for usage example.
|
|||||||
Edit `demo.py` and configure FairHopper Game host settings:
|
Edit `demo.py` and configure FairHopper Game host settings:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
FAIRHOPPER_HOST = "http://127.0.0.1"
|
FAIRHOPPER_HOST = "http://127.0.0.1:8010"
|
||||||
FAIRHOPPER_PORT = 8010
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Activate environment and run example:
|
Activate environment and run example:
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
from fh_sdk import Direction, FairHopper, PositionError
|
from fh_sdk import Direction, FairHopper, PositionError
|
||||||
|
|
||||||
FAIRHOPPER_HOST = "http://127.0.0.1"
|
FAIRHOPPER_HOST = "https://api.fairhopper.mjerenja.com"
|
||||||
FAIRHOPPER_PORT = 8010
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
fh = FairHopper(FAIRHOPPER_HOST, FAIRHOPPER_PORT)
|
fh = FairHopper(FAIRHOPPER_HOST)
|
||||||
|
|
||||||
print(f"Pinging FairHopper server on {FAIRHOPPER_HOST}:{FAIRHOPPER_PORT}")
|
print(f"Pinging FairHopper server on {FAIRHOPPER_HOST}")
|
||||||
res = fh.ping()
|
res = fh.ping()
|
||||||
print("Ping result:", res)
|
print("Ping result:", res)
|
||||||
print()
|
print()
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from typing import List, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
@ -20,6 +21,10 @@ class PlayerNotFoundError(BaseError):
|
|||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationError(BaseError):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class Direction(str, Enum):
|
class Direction(str, Enum):
|
||||||
LEFT = "left"
|
LEFT = "left"
|
||||||
RIGHT = "right"
|
RIGHT = "right"
|
||||||
@ -27,6 +32,13 @@ class Direction(str, Enum):
|
|||||||
DOWN = "down"
|
DOWN = "down"
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerState(str, Enum):
|
||||||
|
CREATED = "CREATED"
|
||||||
|
MOVING = "MOVING"
|
||||||
|
ON_DESTINATION = "ON_DESTINATION"
|
||||||
|
INACTIVE = "INACTIVE"
|
||||||
|
|
||||||
|
|
||||||
class Board(BaseModel):
|
class Board(BaseModel):
|
||||||
width: int
|
width: int
|
||||||
height: int
|
height: int
|
||||||
@ -48,6 +60,13 @@ class Player(BaseModel):
|
|||||||
position: Position
|
position: Position
|
||||||
move_count: int
|
move_count: int
|
||||||
move_attempt_count: int
|
move_attempt_count: int
|
||||||
|
state: PlayerState
|
||||||
|
|
||||||
|
|
||||||
|
class Product(BaseModel):
|
||||||
|
name: str
|
||||||
|
id: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class PingResponse(BaseModel):
|
class PingResponse(BaseModel):
|
||||||
@ -69,13 +88,16 @@ class GameInfoResponse(BaseModel):
|
|||||||
destination: Destination
|
destination: Destination
|
||||||
|
|
||||||
|
|
||||||
|
class ProductListResponse(BaseModel):
|
||||||
|
products: List[Product]
|
||||||
|
|
||||||
|
|
||||||
class FairHopper:
|
class FairHopper:
|
||||||
def __init__(self, host, port) -> None:
|
def __init__(self, host: str) -> None:
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
|
||||||
|
|
||||||
def format_url(self, path: str) -> str:
|
def format_url(self, path: str) -> str:
|
||||||
return f"{self.host}:{self.port}{path}"
|
return f"{self.host}{path}"
|
||||||
|
|
||||||
def ping(self) -> PingResponse:
|
def ping(self) -> PingResponse:
|
||||||
r = requests.get(self.format_url("/ping"))
|
r = requests.get(self.format_url("/ping"))
|
||||||
@ -87,7 +109,12 @@ class FairHopper:
|
|||||||
"player_name": player_name,
|
"player_name": player_name,
|
||||||
}
|
}
|
||||||
r = requests.post(self.format_url("/game"), json=payload)
|
r = requests.post(self.format_url("/game"), json=payload)
|
||||||
|
|
||||||
|
if r.status_code == 422:
|
||||||
|
raise ValidationError()
|
||||||
|
else:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
return StartGameResponse(**r.json())
|
return StartGameResponse(**r.json())
|
||||||
|
|
||||||
def get_game_info(self) -> GameInfoResponse:
|
def get_game_info(self) -> GameInfoResponse:
|
||||||
@ -95,32 +122,34 @@ class FairHopper:
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return GameInfoResponse(**r.json())
|
return GameInfoResponse(**r.json())
|
||||||
|
|
||||||
def get_player_info(self, id: str) -> PlayerInfoResponse:
|
def get_player_info(self, player_id: str) -> PlayerInfoResponse:
|
||||||
r = requests.get(self.format_url(f"/player/{id}"))
|
r = requests.get(self.format_url(f"/player/{player_id}"))
|
||||||
|
|
||||||
if r.status_code == 403:
|
if r.status_code == 403:
|
||||||
raise PlayerInactiveError()
|
raise PlayerInactiveError()
|
||||||
elif r.status_code == 404:
|
elif r.status_code == 404:
|
||||||
raise PlayerNotFoundError()
|
raise PlayerNotFoundError()
|
||||||
|
elif r.status_code == 422:
|
||||||
|
raise ValidationError()
|
||||||
else:
|
else:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
return PlayerInfoResponse(**r.json())
|
return PlayerInfoResponse(**r.json())
|
||||||
|
|
||||||
def move_left(self, id: str) -> PlayerInfoResponse:
|
def move_left(self, player_id: str) -> PlayerInfoResponse:
|
||||||
return self.move(id, Direction.LEFT)
|
return self.move(player_id, Direction.LEFT)
|
||||||
|
|
||||||
def move_right(self, id: str) -> PlayerInfoResponse:
|
def move_right(self, player_id: str) -> PlayerInfoResponse:
|
||||||
return self.move(id, Direction.RIGHT)
|
return self.move(player_id, Direction.RIGHT)
|
||||||
|
|
||||||
def move_up(self, id: str) -> PlayerInfoResponse:
|
def move_up(self, player_id: str) -> PlayerInfoResponse:
|
||||||
return self.move(id, Direction.UP)
|
return self.move(player_id, Direction.UP)
|
||||||
|
|
||||||
def move_down(self, id: str) -> PlayerInfoResponse:
|
def move_down(self, player_id: str) -> PlayerInfoResponse:
|
||||||
return self.move(id, Direction.DOWN)
|
return self.move(player_id, Direction.DOWN)
|
||||||
|
|
||||||
def move(self, id: str, direction: Direction) -> PlayerInfoResponse:
|
def move(self, player_id: str, direction: Direction) -> PlayerInfoResponse:
|
||||||
path = f"/player/{id}/move/{direction}"
|
path = f"/player/{player_id}/move/{direction}"
|
||||||
r = requests.post(self.format_url(path))
|
r = requests.post(self.format_url(path))
|
||||||
|
|
||||||
if r.status_code == 403:
|
if r.status_code == 403:
|
||||||
@ -129,7 +158,44 @@ class FairHopper:
|
|||||||
raise PlayerNotFoundError()
|
raise PlayerNotFoundError()
|
||||||
elif r.status_code == 409:
|
elif r.status_code == 409:
|
||||||
raise PositionError()
|
raise PositionError()
|
||||||
|
elif r.status_code == 422:
|
||||||
|
raise ValidationError()
|
||||||
else:
|
else:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
return PlayerInfoResponse(**r.json())
|
return PlayerInfoResponse(**r.json())
|
||||||
|
|
||||||
|
def get_products(self) -> List[Product]:
|
||||||
|
r = requests.get(self.format_url("/products"))
|
||||||
|
response_data = ProductListResponse(**r.json())
|
||||||
|
return response_data.products
|
||||||
|
|
||||||
|
def get_product(self, product_id: str) -> Product:
|
||||||
|
r = requests.get(self.format_url(f"/products/{product_id}"))
|
||||||
|
|
||||||
|
if r.status_code == 422:
|
||||||
|
raise ValidationError()
|
||||||
|
else:
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
return Product(**r.json())
|
||||||
|
|
||||||
|
def purchase_product(self, player_id: str, product_id: str) -> Product:
|
||||||
|
url = self.format_url(f"/player/{player_id}/product/purchase")
|
||||||
|
payload = {
|
||||||
|
"product_id": product_id,
|
||||||
|
}
|
||||||
|
r = requests.post(url, json=payload)
|
||||||
|
|
||||||
|
if r.status_code == 403:
|
||||||
|
raise PlayerInactiveError()
|
||||||
|
elif r.status_code == 404:
|
||||||
|
raise PlayerNotFoundError()
|
||||||
|
elif r.status_code == 409:
|
||||||
|
raise PositionError()
|
||||||
|
elif r.status_code == 422:
|
||||||
|
raise ValidationError()
|
||||||
|
else:
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
return Product(**r.json())
|
||||||
|
|||||||
4
python/poetry.lock
generated
4
python/poetry.lock
generated
@ -215,5 +215,5 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.7"
|
||||||
content-hash = "a8d95adf4819c22b47d4cac44f18a3bc5040d1c0487ead04cb2f09692de0d411"
|
content-hash = "6d7fc4c2ae40fa75f772d15817348dd97078b9aa25ecd3ad52e71bec25732be8"
|
||||||
|
|||||||
@ -7,7 +7,7 @@ readme = "README.md"
|
|||||||
packages = [{include = "fh_sdk"}]
|
packages = [{include = "fh_sdk"}]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.7"
|
||||||
requests = "^2.28.2"
|
requests = "^2.28.2"
|
||||||
pydantic = "^1.10.7"
|
pydantic = "^1.10.7"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user