Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Ctri-The-Third/SpaceTraders
Browse files Browse the repository at this point in the history
  • Loading branch information
Ctri-The-Third committed Jan 28, 2024
2 parents 5813750 + 5203e2c commit d937019
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 95 deletions.
48 changes: 8 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@

# Readme outline
Executive Summary
# Executive Summary

An API wrapper for the [SpaceTraders API](https://spacetraders.io/) written in Python.
Implemented as an object-oriented wrapper around the [requests](https://docs.python-requests.org/en/master/) library.
SpaceTraders.io is a headless game that is played via executing commands via the Rest API.
This project contains a series `behaviours` - a set of instructions that a ship follows.

Reinventing the wheel for fun, and to express recent learnings.
The project also contains a `dispatcher` which based on values in a configured data

Eventually will be used to build an interactive gameplay UI built on top of the API.

[![PyTest](https://github.com/Ctri-The-Third/SpaceTraders/actions/workflows/main.yml/badge.svg)](https://github.com/Ctri-The-Third/SpaceTraders/actions/workflows/main.yml)

Expand All @@ -17,38 +15,8 @@ Eventually will be used to build an interactive gameplay UI built on top of the


## Overview
A series of helper classes for interacting with the API.

* `client_api` - directly interacts with the API
* `client_postgres` - updates info in / fetches info from a configured postgres database instead of bothering the API
* `client_pg_logger` - a client that just puts in logs to the postgres database whenever its methods are called (useful for tracking API usage & behaviours efficacy
* `client_stub` - a stub client that just does nothing.
*`client_mediator`✨ - a client that can connect all of the above, checking the DB before bothering the API, feeding API responses into the DB, and logging API calls to the DB.

All the clients implement the same abstract interface, so can be used interchangably (most useful when going from the basic API class to the mediator class / vice versa.)

## Setup

```bash
# build
py setup.py bdist_wheel

# install
py -m pip install dist/spacetraders-0.4.0-py3-none-any.whl
```

## Usage!

The package has a number of client classes that can be used to interact with the API. They are intended to be largely interchangable.

Quickstart - API only
```python
from straders_sdk.client_api import SpaceTradersApiClient as SpaceTraders
st = SpaceTraders("")
st.register("SAMPLE_AGENT")
ships = st.ships_view()
for ship in ships:
print (f"{ship.name} is at {ship.nav.waypoint_symbol}")


```
* `dispatcherWK25.py` - takes a user token from the user.json configuration file (or tries to register a user if one is not found)
dispatcher reads the database for behaviours and 1-off tasks and executes matching `behaviours`
* `conductorWK25.py` - assigns behaviours to ships based on the contents of a custom game plan file like or populates its own default game plan.
* `behaviours\` is a folder of python classes that inherit `generic_behaviour.py`. They all execute and initialise in the same way, and can be run from command line, or automatically the dispatcher.
7 changes: 4 additions & 3 deletions behaviours/buy_and_deliver_or_sell.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ def run(self):
self.end()

def end(self):
self.st.logging_client.log_ending(
BEHAVIOUR_NAME, self.ship.name, self.st.view_my_self().credits
)
if self.ship:
self.st.logging_client.log_ending(
BEHAVIOUR_NAME, self.ship_name, self.st.view_my_self().credits
)
super().end()

def default_params_obj(self):
Expand Down
9 changes: 5 additions & 4 deletions behaviours/buy_and_sell_dripfeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,14 +205,15 @@ def sell_half(self):
self.sell_cargo(self.target_tradegood, amount_to_sell, sell_market_mkt)

def end(self, error=None):
super().end()
self.st.logging_client.log_ending(
BEHAVIOUR_NAME, self.ship.name, self.agent.credits
)
if self.ship:
self.st.logging_client.log_ending(
BEHAVIOUR_NAME, self.ship.name, self.agent.credits
)
if error:
self.logger.error(error)
self.st.release_connection()
self.st.sleep(SAFETY_PADDING)
super().end()

def find_cheapest_markets_for_good(self, tradegood_sym: str) -> list[str]:
sql = """select market_symbol from market_tradegood_listings
Expand Down
32 changes: 13 additions & 19 deletions behaviours/explore_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import time

BEHAVIOUR_NAME = "EXPLORE_ONE_SYSTEM"
SAFETY_PADDING = 180


class ExploreSystem(Behaviour):
Expand Down Expand Up @@ -65,7 +66,7 @@ def _run(self):
st = self.st
agent = st.view_my_self()
# check all markets in the system
o_sys = st.systems_view_one(ship.nav.system_symbol)
self.o_sys = o_sys = st.systems_view_one(ship.nav.system_symbol)

path = None
if self.behaviour_params and "target_sys" in self.behaviour_params:
Expand All @@ -76,7 +77,7 @@ def _run(self):
st.system_jumpgate(jg, True)
path = self.pathfinder.astar(o_sys, d_sys, force_recalc=True)
else:
d_sys = self.find_unexplored_jumpgate()
d_sys = self.route_to_unexplored_jumpgate()
if d_sys:
d_sys = st.systems_view_one(d_sys)
if not d_sys:
Expand Down Expand Up @@ -120,23 +121,15 @@ def _run(self):
# travel to target system
# scan target system

def find_unexplored_jumpgate(self):
hq_sys_sym = waypoint_slicer(self.agent.headquarters)
sql = """select count(*) from jumpgate_connections"""
rows = try_execute_select(self.connection, sql, ())
if not rows or rows[0][0] == 0:
jump_gate = self.st.find_waypoints_by_type_one(hq_sys_sym, "JUMP_GATE")
if not jump_gate:
return None
self.st.system_jumpgate(jump_gate)

sql = """select system_symbol from systems_on_network_but_uncharted
order by random()
limit 1 """
rows = try_execute_select(self.connection, sql, ())
if not rows:
def route_to_unexplored_jumpgate(self):
source = self.st.find_waypoints_by_type(self.o_sys.symbol, "JUMP_GATE")
if not source:
self.st.sleep(SAFETY_PADDING)
return None
return rows[0][0]

gate = self.st.system_jumpgate(source[0], True)
for connected_system in gate.connected_waypoints:
print(connection)


if __name__ == "__main__":
Expand All @@ -147,7 +140,8 @@ def find_unexplored_jumpgate(self):
ship_number = sys.argv[2] if len(sys.argv) > 2 else "1"
ship = f"{agent}-{ship_number}"
behaviour_params = None
behaviour_params = {"priority": 3.5, "target_sys": "X1-DZ36"} # X1-TF72 X1-YF83
# behaviour_params = {"priority": 3.5, "target_sys": "X1-DZ36"} # X1-TF72 X1-YF83
behaviour_params = {"priority": 3.5}
bhvr = ExploreSystem(agent, ship, behaviour_params or {})

lock_ship(ship, "MANUAL", duration=120)
Expand Down
4 changes: 2 additions & 2 deletions behaviours/extract_and_sell.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,11 @@ def get_market_and_jettison_waste(
from dispatcherWK16 import lock_ship

agent = sys.argv[1] if len(sys.argv) > 2 else "CTRI-U-"
ship_number = sys.argv[2] if len(sys.argv) > 2 else "1"
ship_number = sys.argv[2] if len(sys.argv) > 2 else "23"
ship = f"{agent}-{ship_number}"
set_logging(logging.DEBUG)
behaviour_params = {
"asteroid_wp": "X1-PK16-B40",
"asteroid_wp": "X1-TN14-C40",
"script_name": "EXTRACT_AND_SELL",
"cargo_to_transfer": ["*"],
}
Expand Down
1 change: 1 addition & 0 deletions behaviours/generic_behaviour.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ def fulfil_any_relevant(self, excpetions: list = []):

if self.ship.nav.status != "DOCKED":
self.st.ship_dock(self.ship)
matching_items = None
for cargo in self.ship.cargo_inventory:
matching_items = [item for item in items if item.symbol == cargo.symbol]
if not matching_items:
Expand Down
7 changes: 4 additions & 3 deletions behaviours/manage_supply_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ def run(self):
self.end()

def end(self):
self.st.logging_client.log_ending(
BEHAVIOUR_NAME, self.ship.name, self.st.view_my_self().credits
)
if self.ship:
self.st.logging_client.log_ending(
BEHAVIOUR_NAME, self.ship.name, self.st.view_my_self().credits
)
super().end()

def _run(self):
Expand Down
39 changes: 15 additions & 24 deletions behaviours/scan_behaviour.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,40 +80,31 @@ def _run(self):
#
rows = [1]
wayps = [1]
self.pathfinder.load_jump_graph_from_db()
self.pathfinder.save_graph()

while len(wayps) > 0 or len(rows) > 0:
wayps = (
self.get_twenty_unscanned_waypoints("ORBITAL_STATION")
or self.get_twenty_unscanned_waypoints("ASTEROID_FIELD")
or self.get_twenty_unscanned_waypoints("JUMP_GATE")
or self.get_twenty_unscanned_waypoints("PLANET")
or self.get_twenty_unscanned_waypoints("MOON")
or []
self.get_unscanned_waypoints("JUMP_GATE")
+ self.get_unscanned_waypoints("ORBITAL_STATION")
+ self.get_unscanned_waypoints("ASTEROID_FIELD")
+ self.get_unscanned_waypoints("PLANET")
+ self.get_unscanned_waypoints("MOON")
)

for wayp in wayps:
resp = st.waypoints_view_one(wayp[0], True)
if resp.type == "JUMP_GATE":
st.system_jumpgate(resp, True)
if "MARKETPLACE" in [trait.symbol for trait in resp.traits]:
st.system_market(resp, True)
if "SHIPYARD" in [trait.symbol for trait in resp.traits]:
st.system_shipyard(resp, True)

#
# get 20 unscanned jump gates
#

rows = self.get_twenty_unscanned_jumpgates()

for row in rows:
jump_gate_sym = row[0]
sys = waypoint_slicer(jump_gate_sym)

wp = st.waypoints_view_one(jump_gate_sym)
if not wp.is_charted:
wp = st.waypoints_view_one(jump_gate_sym, True)
if not wp.is_charted:
continue
resp = st.system_jumpgate(wp, True)
if ship.seconds_until_cooldown > 0:
continue
if ship.nav.travel_time_remaining > 0:
continue

#
# MARKETS and SHIPYARDS
#
Expand Down Expand Up @@ -149,7 +140,7 @@ def _run(self):

st.logging_client.log_ending(BEHAVIOUR_NAME, ship.name, agent.credits)

def get_twenty_unscanned_waypoints(self, type: str = r"%s") -> list[str]:
def get_unscanned_waypoints(self, type: str = r"%s") -> list[str]:
sql = """
select * from waypoints_not_scanned
where type = %s
Expand Down
2 changes: 2 additions & 0 deletions dispatcherWK25.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,13 @@ def map_behaviour_to_class(
def upload_behaviour_definitions(self):
for behaviour_id, bhvr_class in behaviours_and_classes.items():
bhvr_obj = bhvr_class("", "", {})

bhvr_obj: Behaviour

params = json.dumps(bhvr_obj.default_params_obj())
sql = "insert into behaviour_definitions (behaviour_id, default_params) values (%s, %s) on conflict (behaviour_id) do update set default_params = EXCLUDED.default_params;"
try_execute_upsert(sql, (behaviour_id, params), self.connection)
bhvr_obj.end()
pass

def maybe_scan_all_systems(self):
Expand Down

0 comments on commit d937019

Please sign in to comment.