Compare commits

..

2 Commits

Author SHA1 Message Date
Richard
cf930fe24b working on startup 2024-02-10 19:29:11 +01:00
Richard
74ce884b05 major command staff restructure
Heads had to roll
2024-02-09 15:52:30 +01:00
23 changed files with 488 additions and 322 deletions

View File

@ -9,4 +9,5 @@ ADD --chown=user . /app
RUN chmod +x /app/main.py
VOLUME /data
#ENTRYPOINT bash
RUN echo "python3 /app/main.py -d /data" > ~/.bash_history
CMD ["/bin/sh", "-c", "python3 /app/main.py -d /data ; bash -i"]

View File

@ -52,172 +52,184 @@ class SearchNode:
def __repr__(self):
return self.system.symbol
class Analyzer:
def __init__(self, store):
self.store = store
def find_markets(c, resource, sellbuy):
for m in c.store.all(Marketplace):
if 'sell' in sellbuy and resource in m.imports:
yield ('sell', m)
elif 'buy' in sellbuy and resource in m.exports:
yield ('buy', m)
elif 'exchange' in sellbuy and resource in m.exchange:
yield ('exchange', m)
def find_closest_markets(c, resource, sellbuy, location):
if type(location) == str:
location = c.store.get(Waypoint, location)
mkts = find_markets(resource, sellbuy)
candidates = []
origin = location.system
for typ, m in mkts:
system = m.waypoint.system
d = origin.distance(system)
candidates.append((typ, m, d))
possibles = sorted(candidates, key=lambda m: m[2])
possibles = possibles[:10]
results = []
for typ,m,d in possibles:
system = m.waypoint.system
p = find_jump_path(origin, system)
if p is None: continue
results.append((typ,m,d,len(p)))
return results
def solve_tsp(c, waypoints):
wps = copy(waypoints)
path = []
cur = Point(0,0)
while len(wps) > 0:
closest = wps[0]
for w in wps:
if w.distance(cur) < closest.distance(cur):
closest = w
cur = closest
path.append(closest)
wps.remove(closest)
return path
def find_markets(self, resource, sellbuy):
for m in self.store.all(Marketplace):
if 'sell' in sellbuy and resource in m.imports:
yield ('sell', m)
elif 'buy' in sellbuy and resource in m.exports:
yield ('buy', m)
elif 'exchange' in sellbuy and resource in m.exchange:
yield ('exchange', m)
def find_closest_markets(self, resource, sellbuy, location):
if type(location) == str:
location = self.store.get(Waypoint, location)
mkts = self.find_markets(resource, sellbuy)
candidates = []
origin = location.system
for typ, m in mkts:
system = m.waypoint.system
d = origin.distance(system)
candidates.append((typ, m, d))
possibles = sorted(candidates, key=lambda m: m[2])
possibles = possibles[:10]
results = []
for typ,m,d in possibles:
system = m.waypoint.system
p = self.find_path(origin, system)
if p is None: continue
results.append((typ,m,d,len(p)))
return results
def get_jumpgate(c, system):
gates = c.store.all_members(system, Jumpgate)
return next(gates, None)
# dijkstra shmijkstra
def find_nav_path(c, orig, to, ran):
path = []
mkts = [m.waypoint for m in c.store.all_members(orig.system, Marketplace)]
cur = orig
if orig == to:
def solve_tsp(self, waypoints):
wps = copy(waypoints)
path = []
cur = Point(0,0)
while len(wps) > 0:
closest = wps[0]
for w in wps:
if w.distance(cur) < closest.distance(cur):
closest = w
cur = closest
path.append(closest)
wps.remove(closest)
return path
def get_jumpgate(self, system):
gates = self.store.all_members(system, Jumpgate)
return next(gates, None)
return []
while cur != to:
best = cur
bestdist = cur.distance(to)
if bestdist < ran:
path.append(to)
break
for m in mkts:
dist = m.distance(to)
if dist < bestdist and cur.distance(m) < ran:
best = m
bestdist = dist
if best == cur:
raise AnalyzerException(f'no path to {to}')
cur = best
path.append(cur)
return path
# dijkstra shmijkstra
def find_nav_path(self, orig, to, ran):
path = []
mkts = [m.waypoint for m in self.store.all_members(orig.system, Marketplace)]
cur = orig
if orig == to:
return []
while cur != to:
best = cur
bestdist = cur.distance(to)
if bestdist < ran:
path.append(to)
break
for m in mkts:
dist = m.distance(to)
if dist < bestdist and cur.distance(m) < ran:
best = m
bestdist = dist
if best == cur:
raise AnalyzerException(f'no path to {to}')
cur = best
path.append(cur)
return path
def find_jump_path(self, orig, to, depth=100, seen=None):
if depth < 1: return None
if seen is None:
seen = set()
if type(orig) == System:
orig = set([SearchNode(orig,None)])
result = [n for n in orig if n==to]
if len(result) > 0:
return result[0].path()
dest = set()
for o in orig:
jg = self.get_jumpgate(o)
if jg is None: continue
for s in jg.connections:
if s in seen: continue
seen.add(s)
dest.add(SearchNode(s, o))
if len(dest) == 0:
return None
return self.find_path(dest, to, depth-1, seen)
def find_jump_path(c, orig, to, depth=100, seen=None):
if depth < 1: return None
if seen is None:
seen = set()
if type(orig) == System:
orig = set([SearchNode(orig,None)])
result = [n for n in orig if n==to]
if len(result) > 0:
return result[0].path()
dest = set()
for o in orig:
jg = get_jumpgate(o)
if jg is None: continue
for s in jg.connections:
if s in seen: continue
seen.add(s)
dest.add(SearchNode(s, o))
if len(dest) == 0:
return None
return find_jump_path(dest, to, depth-1, seen)
def prices(c, system):
prices = {}
for m in c.store.all_members(system, Marketplace):
for r, p in m.prices.items():
if not r in prices:
prices[r] = []
prices[r].append({
'wp': m.waypoint,
'buy': p.buy,
'sell': p.sell,
'volume': p.volume,
'category': m.rtype(r)
})
return prices
def find_trade(c, system):
max_traders = 3
pcs= prices(c, system)
occupied_routes = dict()
for s in c.store.all('Ship'):
if s.mission != 'trade':
continue
k = (s.mission_state['site'], s.mission_state['dest'])
if k in occupied_routes:
occupied_routes[k] += 1
else:
occupied_routes[k] = 1
best = None
for resource, markets in pcs.items():
source = sorted(markets, key=lambda x: x['buy'])[0]
dest = sorted(markets, key=lambda x: x['sell'])[-1]
swp = source['wp']
dwp = dest['wp']
margin = dest['sell'] -source['buy']
k = (swp.symbol,dwp.symbol)
if k in occupied_routes and occupied_routes[k] > max_traders:
continue
dist = swp.distance(dwp)
dist = max(dist, 0.0001)
score = margin / dist
if margin < 2:
continue
o = TradeOption(resource, swp, dwp, source['buy'], margin, dist, score)
if best is None or best.score < o.score:
best = o
return best
def find_deal(c, smkt, dmkt):
best_margin = 0
best_resource = None
for r, sp in smkt.prices.items():
if not r in dmkt.prices:
continue
dp = dmkt.prices[r]
margin = dp.sell - sp.buy
if margin > best_margin:
best_margin = margin
best_resource = r
return best_resource
def best_sell_market(c, system, r):
best_price = 0
best_market = None
for m in c.store.all_members(system, Marketplace):
if r not in m.prices: continue
price = m.prices[r].sell
if price > best_price:
best_price = price
best_market = m
return best_market
def prices(self, system):
prices = {}
for m in self.store.all_members(system, Marketplace):
for r, p in m.prices.items():
if not r in prices:
prices[r] = []
prices[r].append({
'wp': m.waypoint,
'buy': p.buy,
'sell': p.sell,
'volume': p.volume,
'category': m.rtype(r)
})
return prices
def find_gas(c, system):
m = [w for w in c.store.all_members(system, 'Waypoint') if w.type == 'GAS_GIANT']
if len(m)==0:
raise AnalyzerException('no gas giant found')
return m[0]
def find_trade(self, system):
max_traders = 3
prices = self.prices(system)
occupied_routes = dict()
for s in self.store.all('Ship'):
if s.mission != 'trade':
continue
k = (s.mission_state['site'], s.mission_state['dest'])
if k in occupied_routes:
occupied_routes[k] += 1
else:
occupied_routes[k] = 1
best = None
for resource, markets in prices.items():
source = sorted(markets, key=lambda x: x['buy'])[0]
dest = sorted(markets, key=lambda x: x['sell'])[-1]
swp = source['wp']
dwp = dest['wp']
margin = dest['sell'] -source['buy']
k = (swp.symbol,dwp.symbol)
if k in occupied_routes and occupied_routes[k] > max_traders:
continue
dist = swp.distance(dwp)
dist = max(dist, 0.0001)
score = margin / dist
if margin < 2:
continue
o = TradeOption(resource, swp, dwp, source['buy'], margin, dist, score)
if best is None or best.score < o.score:
best = o
return best
def find_deal(self, smkt, dmkt):
best_margin = 0
best_resource = None
for r, sp in smkt.prices.items():
if not r in dmkt.prices:
continue
dp = dmkt.prices[r]
margin = dp.sell - sp.buy
if margin > best_margin:
best_margin = margin
best_resource = r
return best_resource
def best_sell_market(self, system, r):
best_price = 0
best_market = None
for m in self.store.all_members(system, Marketplace):
if r not in m.prices: continue
price = m.prices[r].sell
if price > best_price:
best_price = price
best_market = m
return best_market
def find_metal(c, system):
m = [w for w in c.store.all_members(system, Waypoint) if 'COMMON_METAL_DEPOSITS' in w.traits]
if len(m) == 0:
return None
origin = Point(0,0)
m = sorted(m, key=lambda w: w.distance(origin))
return m[0]

View File

@ -7,7 +7,8 @@ from nullptr.models.ship import Ship
from nullptr.models.shipyard import Shipyard
from .util import *
from time import sleep, time
class ApiError(Exception):
class ApiError(AppError):
def __init__(self, msg, code):
super().__init__(msg)
self.code = code
@ -16,9 +17,9 @@ class ApiLimitError(Exception):
pass
class Api:
def __init__(self, store, agent):
def __init__(self, c, agent):
self.agent = agent
self.store = store
self.store = c.store
self.requests_sent = 0
self.last_meta = None
self.last_result = None

View File

@ -6,19 +6,25 @@ from random import choice, randrange
from time import sleep, time
from threading import Thread
from nullptr.atlas_builder import AtlasBuilder
from nullptr.analyzer import Analyzer, Point
from nullptr.general import General
from nullptr.util import *
from nullptr.roles import assign_mission
class CentralCommandError(Exception):
class CentralCommandError(AppError):
pass
class CentralCommand:
def __init__(self, store, api):
class Captain:
def __init__(self, context):
self.missions = {}
self.stopping = False
self.store = store
self.analyzer = Analyzer(store)
self.api = api
self.atlas_builder = AtlasBuilder(store, api)
self.store = context.store
self.c = context
self.general = context.general
self.api = context.api
self.general = context.general
self.atlas_builder = AtlasBuilder(self.store, self.api)
def setup(self):
self.update_missions()
def get_ready_missions(self):
@ -40,6 +46,7 @@ class CentralCommand:
mission.step()
def tick(self):
self.general.tick()
self.update_missions()
missions = self.get_ready_missions()
if len(missions) == 0: return False
@ -64,9 +71,10 @@ class CentralCommand:
self.stopping = True
print('stopping...')
def run(self):
while not self.stopping:
# any new orders?
self.c.general.tick()
did_step = True
request_counter = self.api.requests_sent
start = time()
@ -116,99 +124,11 @@ class CentralCommand:
if s in self.missions:
self.stop_mission(s)
if s.mission is None:
self.assign_mission(s)
assign_mission(self.c, s)
if s.mission is not None and s not in self.missions:
self.start_mission(s)
if s in self.missions:
m = self.missions[s]
def find_gas(self, system):
m = [w for w in self.store.all_members(system, 'Waypoint') if w.type == 'GAS_GIANT']
if len(m)==0:
raise CentralCommandError('no gas giant found')
return m[0]
def find_metal(self, system):
m = [w for w in self.store.all_members(system, Waypoint) if 'COMMON_METAL_DEPOSITS' in w.traits]
if len(m) == 0:
return None
origin = Point(0,0)
m = sorted(m, key=lambda w: w.distance(origin))
return m[0]
def assign_mission(self, s):
if s.role == 'trader':
self.assign_trade(s)
elif s.role == 'probe':
self.assign_probe(s)
elif s.role == 'siphon':
self.assign_siphon(s)
elif s.role == 'hauler':
self.assign_hauler(s)
elif s.role == 'surveyor':
self.assign_survey(s)
elif s.role == 'miner':
self.assign_mine(s)
def assign_mine(self, s):
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
resources = s.crew.resources
self.init_mission(s, 'mine')
self.smipa(s, 'site', w)
self.smipa(s, 'resources', resources)
def assign_survey(self, s):
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
self.init_mission(s, 'survey')
self.smipa(s, 'site', w)
def assign_hauler(self, s):
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
resources = s.crew.resources
resource = choice(resources)
m = self.analyzer.best_sell_market(s.location.system, resource)
s.log(f'assigning haul mission from {w} to {m}')
self.init_mission(s, 'haul')
self.smipa(s, 'site', w)
self.smipa(s, 'dest', m)
self.smipa(s, 'resources', resources)
def assign_siphon(self, s):
if s.crew is None:
raise CentralCommandError('ship has no crew')
w = s.crew.site
self.init_mission(s, 'siphon')
self.smipa(s, 'site', w)
def assign_probe(self, s):
system = s.location.system
m = [m.waypoint for m in self.store.all_members(system, 'Marketplace')]
m = self.analyzer.solve_tsp(m)
hops = [w.symbol for w in m]
start_hop = randrange(0, len(hops))
s.log(f'Assigning {s} to probe {len(hops)} starting at {hops[start_hop]}')
self.init_mission(s, 'probe')
self.smipa(s, 'hops', hops)
self.smipa(s, 'next-hop', start_hop)
def assign_trade(self, s):
t = self.analyzer.find_trade(s.location.system)
if t is None:
print(f"No trade for {s} found. Idling")
self.init_mission(s,'idle')
self.smipa(s, 'seconds', 600)
return
s.log(f'assigning {s} to deliver {t.resource} from {t.source} to {t.dest} at a margin of {t.margin}')
self.init_mission(s, 'trade')
self.smipa(s, 'site', t.source)
self.smipa(s, 'dest', t.dest)
def init_mission(self, s, mtyp):
if mtyp == 'none':
@ -232,7 +152,7 @@ class CentralCommand:
def start_mission(self, s):
mtype = s.mission
m = create_mission(mtype, s, self.store, self.api)
m = create_mission(mtype, s, self.c)
self.missions[s] = m
m.status(s.mission_status)
return m
@ -241,17 +161,4 @@ class CentralCommand:
if s in self.missions:
del self.missions[s]
def create_default_crews(self):
system = self.api.agent.headquarters.system
gas_w = self.find_gas(system)
metal_w = self.find_metal(system)
metal = self.store.get('Crew', 'METAL', create=True)
metal.site = metal_w
metal.resources = ['COPPER_ORE','IRON_ORE','ALUMINUM_ORE']
gas = self.store.get('Crew', 'GAS', create=True)
gas.site = gas_w
gas.resources = ['HYDROCARBON','LIQUID_HYDROGEN','LIQUID_NITROGEN']
return [gas, metal]

View File

@ -3,6 +3,7 @@ import inspect
import sys
import importlib
import logging
from nullptr.util import AppError
def func_supports_argcount(f, cnt):
argspec = inspect.getargspec(f)
@ -41,7 +42,7 @@ class CommandLine:
print(f'command not found; {c}')
def handle_error(self, cmd, args, e):
logging.error(e, exc_info=type(e).__name__ not in ['ApiError','CommandError', 'CentralCommandError'])
logging.error(e, exc_info=not issubclass(type(e), AppError))
def handle_empty(self):
pass

View File

@ -1,19 +1,21 @@
from nullptr.command_line import CommandLine
from nullptr.store import Store
from nullptr.analyzer import Analyzer, Point, path_dist
from nullptr.analyzer import *
from nullptr.context import Context
import argparse
from nullptr.models import *
from nullptr.api import Api
from .util import *
from time import sleep, time
from threading import Thread
from nullptr.central_command import CentralCommand
from nullptr.captain import Captain
from nullptr.general import General
import readline
import os
from copy import copy
class CommandError(Exception):
class CommandError(AppError):
pass
class Commander(CommandLine):
@ -25,15 +27,20 @@ class Commander(CommandLine):
if os.path.isfile(hist_file):
readline.read_history_file(hist_file)
self.store = Store(store_file, True)
self.c = Context(self.store)
self.agent = self.select_agent()
self.api = Api(self.store, self.agent)
self.centcom = CentralCommand(self.store, self.api)
self.c.api = self.api = Api(self.c, self.agent)
self.c.general = self.general = General(self.c)
self.c.captain = self.captain = Captain(self.c)
self.general.setup()
self.captain.setup()
self.api.info()
self.do_create_crews()
self.analyzer = Analyzer(self.store)
self.ship = None
self.stop_auto= False
self.stop_auto = False
super().__init__()
######## INFRA #########
@ -55,7 +62,7 @@ class Commander(CommandLine):
self.store.flush()
def do_auto(self):
self.centcom.run_interactive()
self.captain.run_interactive()
def do_log(self, level):
ship = self.has_ship()
@ -132,7 +139,7 @@ class Commander(CommandLine):
symbol = input('agent name: ')
agent = self.store.get(Agent, symbol, create=True)
self.agent = agent
api = Api(self.store, agent)
api = Api(self.c, agent)
self.api = api
faction = input('faction or token: ')
if len(faction) > 50:
@ -170,7 +177,7 @@ class Commander(CommandLine):
self.api.list_ships()
for s in self.store.all('Ship'):
self.dump(s, 'all')
self.centcom.init_mission(s, 'none')
self.captain.init_mission(s, 'none')
######## Fleet #########
def do_info(self, arg=''):
@ -373,17 +380,17 @@ class Commander(CommandLine):
def do_mission(self, arg=''):
ship = self.has_ship()
if arg:
self.centcom.init_mission(ship, arg)
self.captain.init_mission(ship, arg)
self.print_mission()
def do_mrestart(self, status='init'):
ship = self.has_ship()
self.centcom.restart_mission(ship, status)
self.captain.restart_mission(ship, status)
self.print_mission()
def do_mstep(self):
ship = self.has_ship()
self.centcom.single_step(ship)
self.captain.single_step(ship)
self.print_mission()
def do_mreset(self):
@ -392,7 +399,7 @@ class Commander(CommandLine):
def do_mset(self, nm, val):
ship = self.has_ship()
self.centcom.set_mission_param(ship, nm, val)
self.captain.set_mission_param(ship, nm, val)
def do_crew(self, arg):
ship = self.has_ship()
@ -402,7 +409,7 @@ class Commander(CommandLine):
######## Crews #########
def do_create_crews(self):
crews = self.centcom.create_default_crews()
crews = self.captain.create_default_crews()
for c in crews:
print(f'{c.symbol:15s} {c.site}')
@ -448,8 +455,8 @@ class Commander(CommandLine):
def do_travel(self, dest):
ship = self.has_ship()
dest = self.resolve('Waypoint', dest)
self.centcom.init_mission(ship, 'travel')
self.centcom.set_mission_param(ship, 'dest', dest)
self.captain.init_mission(ship, 'travel')
self.captain.set_mission_param(ship, 'dest', dest)
self.print_mission()
def do_go(self, arg):
@ -535,7 +542,7 @@ class Commander(CommandLine):
location = ship.location
resource = resource.upper()
print('Found markets:')
for typ, m, d, plen in self.analyzer.find_closest_markets(resource, 'buy,exchange',location):
for typ, m, d, plen in find_closest_markets(self.c, resource, 'buy,exchange',location):
price = '?'
if resource in m.prices:
price = m.prices[resource]['buy']
@ -544,13 +551,13 @@ class Commander(CommandLine):
def do_findtrade(self):
ship = self.has_ship()
system = ship.location.system
t = self.analyzer.find_trade(system)
t = find_trade(self.c, system)
pprint(t)
def do_prices(self, resource=None):
ship = self.has_ship()
system = ship.location.system
prices = self.analyzer.prices(system)
prices = prices(self.c, system)
if resource is not None:
prices = {resource: prices[resource.upper()]}
@ -562,5 +569,11 @@ class Commander(CommandLine):
def do_path(self, waypoint_str):
ship = self.has_ship()
w = self.resolve('Waypoint', waypoint_str)
p = self.analyzer.find_nav_path(ship.location, w, ship.fuel_capacity)
p = find_nav_path(self.c, ship.location, w, ship.fuel_capacity)
pprint(p)
def do_list(self, klass):
ship = self.has_ship()
for o in self.store.all_members(klass, ship.location.system):
print(o)

6
nullptr/context.py Normal file
View File

@ -0,0 +1,6 @@
class Context:
def __init__(self, store, api=None, captain=None, general=None):
self.store = store
self.api = api
self.captain = captain
self.general = general

94
nullptr/general.py Normal file
View File

@ -0,0 +1,94 @@
from nullptr.util import *
from nullptr.analyzer import find_gas, find_metal
class GeneralError(AppError):
pass
class General:
def __init__(self, context):
self.store = context.store
self.api = context.api
self.c = context
agents = self.store.all('Agent')
self.agent = next(agents, None)
self.phases = {
'init': self.phase_startup,
'probes': self.phase_probes,
'trade': self.phase_trade,
'mine': self.phase_mine,
'siphon': self.phase_siphon,
'rampup': self.phase_rampup,
'gate': self.phase_gate
}
def setup(self):
self.create_default_crews()
def find_shipyard(self, stype):
for shipyard in self.store.all('Shipyard'):
if stype in shipyard.types:
return stype
return None
def tick(self):
phase = self.agent.phase
if phase not in self.phases:
raise GeneralError('Invalid phase')
hdl = self.phases[phase]
new_phase = hdl()
if new_phase:
self.agent.phase = new_phase
def phase_startup(self):
# * first pricing info
# * probe at shipyard that sells probes
ag = self.agent.symbol
command = self.store.get('Ship', f'{ag}-1')
probe = self.store.get('Ship', f'{ag}-2')
if command.role is None:
command.role = 'probe'
if probe.role is None:
probe.role = 'sitter'
def phase_probes(self):
# * probes on all markets
pass
def phase_trade(self):
# 20? traders
pass
def phase_mine(self):
# metal mining crew
pass
def phase_siphon(self):
# siphon crew
pass
def phase_rampup(self):
# stimulate markets for gate building
pass
def phase_gate(self):
# build the gate
pass
def create_default_crews(self):
system = self.api.agent.headquarters.system
gas_w = find_gas(self.c, system)
metal_w = find_metal(self.c, system)
metal = self.store.get('Crew', 'METAL', create=True)
metal.site = metal_w
metal.resources = ['COPPER_ORE','IRON_ORE','ALUMINUM_ORE']
gas = self.store.get('Crew', 'GAS', create=True)
gas.site = gas_w
gas.resources = ['HYDROCARBON','LIQUID_HYDROGEN','LIQUID_NITROGEN']
return [gas, metal]

View File

@ -25,8 +25,8 @@ def get_mission_class( mtype):
raise ValueError(f'invalid mission type {mtype}')
return types[mtype]
def create_mission(mtype, ship, store, api):
def create_mission(mtype, ship, c):
typ = get_mission_class(mtype)
m = typ(ship, store, api)
m = typ(ship, c)
return m

View File

@ -5,7 +5,7 @@ from nullptr.models.contract import Contract
from nullptr.models.system import System
from nullptr.models.survey import Survey
from nullptr.models.ship import Ship
from nullptr.analyzer import Analyzer
from nullptr.analyzer import *
from time import time
from functools import partial
import logging
@ -48,13 +48,13 @@ class Mission:
}
def __init__(self, ship, store, api):
def __init__(self, ship, context):
self.ship = ship
self.store = store
self.api = api
self.c = context
self.store = context.store
self.api = context.api
self.wait_for = None
self.next_step = 0
self.analyzer = Analyzer(self.store)
self.setup()
def setup(self):
@ -263,15 +263,15 @@ class BaseMission(Mission):
loc = self.ship.location
loc_sys = loc.system
loc_jg = self.analyzer.get_jumpgate(loc_sys)
loc_jg = get_jumpgate(self.c, loc_sys)
loc_jg_wp = self.store.get(Waypoint, loc_jg.symbol)
dest_sys = dest.system
dest_jg = self.analyzer.get_jumpgate(dest_sys)
dest_jg = get_jumpgate(self.c, dest_sys)
if dest_sys == loc_sys:
result = self.analyzer.find_nav_path(loc, dest, self.ship.range())
result = find_nav_path(self.c, loc, dest, self.ship.range())
self.sts('traject', result)
return 'done' if len(result) == 0 else 'more'
path = self.analyzer.find_path(loc_sys, dest_sys)
path = find_jump_path(self.c, loc_sys, dest_sys)
result = []
if loc.symbol != loc_jg.symbol:
result.append(loc_jg_wp)
@ -322,7 +322,8 @@ class BaseMission(Mission):
if self.ship.fuel_capacity == 0:
steps = {
f'travel-{nm}': (calc, f'go-{nm}'),
f'travel-{nm}': (calc, f'orbit-{nm}'),
f'orbit-{nm}': (self.step_orbit, f'go-{nm}'),
f'go-{nm}': (self.step_travel, f'nav-{nm}'),
f'nav-{nm}': (self.step_navigate_traject, {
'done': next_step,

View File

@ -1,5 +1,6 @@
from nullptr.missions.base import BaseMission, MissionParam
from nullptr.models.waypoint import Waypoint
from time import time
class SitMission(BaseMission):
def start_state(self):
@ -14,10 +15,10 @@ class SitMission(BaseMission):
def steps(self):
return {
**self.travel_steps('to', 'dest', 'sit'),
'sit': (self.step_pass, 'done', self.wait_forever)
'sit': (self.step_sit, 'market'),
'market': (self.step_market, 'sit')
}
def step_sit(self):
self.next_step = time() + 15 * 60
def wait_forever(self):
return 0

View File

@ -2,6 +2,8 @@ from nullptr.missions.base import BaseMission, MissionParam
from nullptr.models.waypoint import Waypoint
from nullptr.models.survey import Survey
from nullptr.models.contract import Contract
from nullptr.analyzer import find_deal
class TradeMission(BaseMission):
def start_state(self):
return 'travel-to'
@ -11,7 +13,7 @@ class TradeMission(BaseMission):
cargo_space = self.ship.cargo_capacity - self.ship.cargo_units
smkt = self.store.get('Marketplace', self.st('site'))
dmkt = self.store.get('Marketplace', self.st('dest'))
resource = self.analyzer.find_deal(smkt, dmkt)
resource = find_deal(self.c, smkt, dmkt)
if resource is None:
return 'done'
price = smkt.buy_price(resource)

View File

@ -6,6 +6,7 @@ class Agent(Base):
self.token: str = None
self.credits: int = 0
self.headquarters: Waypoint = None
self.phase = 'init'
def update(self, d):
self.seta('credits', d)

23
nullptr/roles/__init__.py Normal file
View File

@ -0,0 +1,23 @@
from nullptr.roles.trader import assign_trader
from nullptr.roles.probe import assign_probe
from nullptr.roles.siphon import assign_siphon
from nullptr.roles.hauler import assign_hauler
from nullptr.roles.surveyor import assign_surveyor
from nullptr.roles.miner import assign_miner
from nullptr.roles.sitter import assign_sitter
def assign_mission(c, s):
if s.role == 'trader':
assign_trader(c, s)
elif s.role == 'probe':
assign_probe(c, s)
elif s.role == 'siphon':
assign_siphon(c, s)
elif s.role == 'hauler':
assign_hauler(c, s)
elif s.role == 'surveyor':
assign_surveyor(c, s)
elif s.role == 'miner':
assign_miner(c, s)
elif s.role == 'sitter':
assign_sitter(c, s)

16
nullptr/roles/hauler.py Normal file
View File

@ -0,0 +1,16 @@
from nullptr.util import AppError
from nullptr.analyzer import best_sell_market
from random import choice
def assign_hauler(c, s):
if s.crew is None:
raise AppError('ship has no crew')
w = s.crew.site
resources = s.crew.resources
resource = choice(resources)
m = best_sell_market(c,s.location.system, resource)
s.log(f'assigning haul mission from {w} to {m}')
c.captain.init_mission(s, 'haul')
c.captain.smipa(s, 'site', w)
c.captain.smipa(s, 'dest', m)
c.captain.smipa(s, 'resources', resources)

11
nullptr/roles/miner.py Normal file
View File

@ -0,0 +1,11 @@
from nullptr.util import AppError
def assign_miner(c, s):
if s.crew is None:
raise AppError('ship has no crew')
w = s.crew.site
resources = s.crew.resources
c.captain.init_mission(s, 'mine')
c.captain.smipa(s, 'site', w)
c.captain.smipa(s, 'resources', resources)

15
nullptr/roles/probe.py Normal file
View File

@ -0,0 +1,15 @@
from nullptr.analyzer import solve_tsp
from random import randrange
def assign_probe(c, s):
system = s.location.system
m = [m.waypoint for m in c.store.all_members(system, 'Marketplace')]
m = solve_tsp(c, m)
hops = [w.symbol for w in m]
start_hop = randrange(0, len(hops))
s.log(f'Assigning {s} to probe {len(hops)} starting at {hops[start_hop]}')
c.captain.init_mission(s, 'probe')
c.captain.smipa(s, 'hops', hops)
c.captain.smipa(s, 'next-hop', start_hop)

8
nullptr/roles/siphon.py Normal file
View File

@ -0,0 +1,8 @@
from nullptr.util import AppError
def assign_siphon(c, s):
if s.crew is None:
raise AppError('ship has no crew')
w = s.crew.site
c.captain.init_mission(s, 'siphon')
c.captain.smipa(s, 'site', w)

26
nullptr/roles/sitter.py Normal file
View File

@ -0,0 +1,26 @@
from nullptr.analyzer import Point
def assign_sitter_at(c, s, w):
c.captain.init_mission(s, 'sit')
c.captain.smipa(s, 'dest', w.symbol)
def assign_sitter(c, s):
system = s.location.system
ships = c.store.all('Ship')
markets = c.store.all_members(system, 'Marketplace')
origin = Point(0, 0)
markets = sorted(markets, key=lambda m: m.waypoint.distance(origin))
shipyards = c.store.all_members(system, 'Shipyard')
occupied = [s.mission_state['dest'] for s in ships if s.mission=='sit']
probe_shipyard = [y for y in shipyards if 'SHIP_PROBE' in y.types][0]
print('oc', occupied)
print('proya', probe_shipyard)
if probe_shipyard.symbol not in occupied:
return assign_sitter_at(c, s, probe_shipyard)
for y in shipyards:
if y.symbol not in occupied:
return assign_sitter_at(c, s, y)
for m in markets:
if m.symbol not in occupied:
return assign_sitter_at(c, s, m)

10
nullptr/roles/surveyor.py Normal file
View File

@ -0,0 +1,10 @@
from nullptr.util import AppError
def assign_surveyor(c, s):
if s.crew is None:
raise AppError('ship has no crew')
w = s.crew.site
c.init_mission(s, 'survey')
c.smipa(s, 'site', w)

14
nullptr/roles/trader.py Normal file
View File

@ -0,0 +1,14 @@
from nullptr.analyzer import find_trade
def assign_trader(c, s):
t = find_trade(c, s.location.system)
if t is None:
print(f"No trade for {s} found. Idling")
c.captain.init_mission(s,'idle')
c.captain.smipa(s, 'seconds', 600)
return
s.log(f'assigning {s} to deliver {t.resource} from {t.source} to {t.dest} at a margin of {t.margin}')
c.captain.init_mission(s, 'trade')
c.captain.smipa(s, 'site', t.source)
c.captain.smipa(s, 'dest', t.dest)

View File

@ -227,7 +227,7 @@ class Store:
symbol = obj.symbol
obj.store = self
self.data[typ][symbol] = obj
if type(obj).__name__ in ['Waypoint','Marketplace', 'Jumpgate', 'Survey']:
if type(obj).__name__ in ['Waypoint','Marketplace', 'Jumpgate', 'Survey', 'Shipyard']:
system_str = obj.system.symbol
if system_str not in self.system_members:
self.system_members[system_str] = set()

View File

@ -4,6 +4,9 @@ import os
from os.path import isfile, dirname
import traceback
class AppError(Exception):
pass
def open_file(fn):
d = dirname(fn)
os.makedirs(d, exist_ok=True)