PyRate is a top-down 2D naval battle simulation game to test and confront autonomous agents via API.
git clone https://github.com/jmaxrdgz/PyRate.git
cd pyrate
Using conda
conda env create -f environment.yml
conda activate pyrate
You can also use pip in an existing environment
pip install -r requirements.txt
You can test the game with the keyboard using the following constant in settings.py:
INPUT_MODE = "keyboard"
Then launch the game
python -m pyrate.main
Below is an updated README.md that reflects the current API endpoints, request/response schemas, and naming conventions from your latest api.py and game.py.
PyRate provides an HTTP API for programmatic control of player ships, retrieving game‐state information, and streaming a live MJPEG video.
-
Set input mode to API In
pyrate/settings.py, ensure:INPUT_MODE = "api"
-
Launch the server From the project root, run:
sh server_lancher.sh
This script starts a Uvicorn/FastAPI server, listening on port 8000 by default.
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Welcome message (basic health check). |
/players/{player_id}/status |
GET | Retrieve a specific player ship’s status. |
/players/{player_id}/sensor |
GET | Retrieve “nearby_ships” sensor readings for a given player ship (includes friendlies/enemies with noise). |
/players/{player_id}/command |
POST | Issue a control command (accelerate/decelerate/turn/fire) to a specific player ship. |
/video/stream |
GET | Live MJPEG video stream of the current game frame. |
/game/control |
POST | Switch the global control mode between "keyboard" and "api". |
GET http://localhost:8000/Response:
{
"message": "Welcome to the PyRate API!"
}Retrieve angle, rotation velocity, speed, health, living status, and last‐fire timestamp for one player ship.
GET http://localhost:8000/players/{player_id}/status-
Path parameter
player_id(integer): zero‐based index of the player ship (e.g.0,1, …).
-
Successful Response (200)
{ "angle": 45.0, "rotation_velocity": 2.5, "speed": 10.0, "health": 75.0, "is_living": true, "last_fire_time": { "seconds": 1624275600.0, "nanoseconds": 123000000 } }angle: Current orientation in degrees (0–360).rotation_velocity: Angular speed (degrees per frame).speed: Current forward speed.health: Remaining health (0–100).is_living:trueif the ship is still alive; otherwisefalse.last_fire_time: A timestamp object (seconds + nanoseconds) of when this ship last fired a cannon.
-
Error Responses
404 Not Foundifplayer_idis out of range.
Fetch a list of “nearby_ships” that the specified player can sense, each with distance, (noisy) angle, living status, health category, and identity. Friendlies are labeled "friendly", and distant unknown targets are "Unknown".
GET http://localhost:8000/players/{player_id}/sensor-
Path parameter
player_id(integer): index of the player ship making the sensor query.
-
Successful Response (200)
{ "nearby_ships": [ { "entity": "friendly", "distance": 150.234, "angle": 30.12, "is_living": true, "health": "high" }, { "entity": "EnemyShip42", "distance": 250.789, "angle": 120.45, "is_living": true, "health": "low" }, { "entity": "Unknown", "distance": 380.532, "angle": 278.91, "is_living": true, "health": "high" } // … more entries for every other ship (players + enemies) except self ] }-
Each object in
"nearby_ships"contains:-
entity:"friendly"if the other ship is on the same team as the querying player.- Otherwise, the ship’s
name(e.g."EnemyShip42"), or"Unknown"if distance ≥ 400.
-
distance: Euclidean distance (with noise applied if < 400). -
angle: Bearing in degrees (noisy) from the querying ship to that target. -
is_living:true/falsedepending on if that ship is still alive. -
health:"low"if health ≤ 40, else"high".
-
-
-
Error Responses
404 Not Foundifplayer_idis invalid.
Issue one of: "accelerate", "decelerate", "turn_left", "turn_right", or "fire". If firing, specify "side" as "left" or "right".
POST http://localhost:8000/players/{player_id}/command
Content-Type: application/json
{
"action": "fire",
"side": "left"
}-
Path parameter
player_id(integer): index of the player ship to control.
-
Request Body (JSON)
Field Type Description action"accelerate""decelerate""turn_left""turn_right""fire"Which action to perform. side"left"or"right"Only used if actionis"fire"; ignored otherwise. -
Successful Response (200)
{ "status": "ok" } -
Error Responses
400 Bad Requestifactionis not recognized, or ifsideis missing/invalid whenactionis"fire".404 Not Foundifplayer_idis invalid.
Streams the current game display as an MJPEG over an HTTP multipart response. Connect your client (e.g., a browser or video‐viewing tool) to:
GET http://localhost:8000/video/streamYou will receive a continuous stream of JPEG frames, boundary‐delimited by --frame.
Note: The server caps updates at approximately 30 FPS by default (
clock.tick(30)).
Toggle between keyboard input and API control for all player ships.
POST http://localhost:8000/game/control
Content-Type: application/json
{
"mode": "keyboard"
}-
Request Body (JSON)
Field Type Description mode"keyboard"or"api"Sets game.control_mode. -
Successful Response (200)
{ "status": "ok", "mode": "keyboard" } -
Error Responses
400 Bad Requestifmodeis not"keyboard"or"api".
-
Start the server
sh server_lancher.sh
-
Check that the server is running
curl http://localhost:8000/ # → {"message":"Welcome to the PyRate API!"} -
Switch to API mode (optional, since default is “api”)
curl -X POST http://localhost:8000/game/control \ -H "Content-Type: application/json" \ -d '{"mode":"api"}' # → {"status":"ok","mode":"api"}
-
Get the status of player 0
curl http://localhost:8000/players/0/status # → { "angle": 0.0, "rotation_velocity": 0.0, …, "last_fire_time": { "seconds": 0.0, "nanoseconds": 0 } } -
Issue an “accelerate” command to player 0
curl -X POST http://localhost:8000/players/0/command \ -H "Content-Type: application/json" \ -d '{"action": "accelerate"}' # → {"status":"ok"}
-
Get sensor readings for player 0
curl http://localhost:8000/players/0/sensor # → { "nearby_ships": [ … ] } -
Open a browser (or MJPEG‐compatible client) to view live video
http://localhost:8000/video/streamOn MacOS, the video stream should open automaticaly.
A simple agent script is included for testing or as a base to create your own.
tests/dummy_agent.py
On the client machine (where you want to control and view the game):
sh client_launcher.sh
This script will:
- Open the game stream in your browser
- Launch dummy_agent.py which sends commands to the player
- Randomly chooses one of: accelerate, decelerate, turn_left, turn_right
- If an enemy is within 200 units, it prioritizes firing
To write a custom agent:
- Copy dummy_agent.py and rename it:
cp tests/dummy_agent.py tests/my_agent.py
- Modify the logic inside run_agent():
def run_agent():
while True:
status = requests.get(f"{SERVER_URL}/player/status").json()
# Insert your logic here...
action = {"action": "turn_right"}
requests.post(f"{SERVER_URL}/player/command", json=action)
time.sleep(0.2)
- Update client_launcher.sh to call your custom script:
python tests/my_agent.py &
To stop everything:
- Press Ctrl+C in the terminal running the server
- Or use:
pkill -f "uvicorn pyrate.api:app"
- Close the browser tab
- Press Ctrl+C to stop the agent, or:
pkill -f dummy_agent.py