major command staff restructure

Heads had to roll
This commit is contained in:
Richard 2024-02-09 15:52:30 +01:00
parent fb3b6162fc
commit 74ce884b05
21 changed files with 446 additions and 319 deletions

View File

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

View File

@ -52,172 +52,184 @@ class SearchNode:
def __repr__(self): def __repr__(self):
return self.system.symbol return self.system.symbol
class Analyzer:
def __init__(self, store): def find_markets(c, resource, sellbuy):
self.store = store 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): def get_jumpgate(c, system):
for m in self.store.all(Marketplace): gates = c.store.all_members(system, Jumpgate)
if 'sell' in sellbuy and resource in m.imports: return next(gates, None)
yield ('sell', m)
# dijkstra shmijkstra
elif 'buy' in sellbuy and resource in m.exports: def find_nav_path(c, orig, to, ran):
yield ('buy', m) path = []
mkts = [m.waypoint for m in c.store.all_members(orig.system, Marketplace)]
elif 'exchange' in sellbuy and resource in m.exchange: cur = orig
yield ('exchange', m) if orig == to:
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 solve_tsp(self, waypoints): return []
wps = copy(waypoints) while cur != to:
path = [] best = cur
cur = Point(0,0) bestdist = cur.distance(to)
while len(wps) > 0: if bestdist < ran:
closest = wps[0] path.append(to)
for w in wps: break
if w.distance(cur) < closest.distance(cur): for m in mkts:
closest = w dist = m.distance(to)
cur = closest if dist < bestdist and cur.distance(m) < ran:
path.append(closest) best = m
wps.remove(closest) bestdist = dist
return path if best == cur:
raise AnalyzerException(f'no path to {to}')
def get_jumpgate(self, system): cur = best
gates = self.store.all_members(system, Jumpgate) path.append(cur)
return next(gates, None) return path
# dijkstra shmijkstra def find_jump_path(c, orig, to, depth=100, seen=None):
def find_nav_path(self, orig, to, ran): if depth < 1: return None
path = [] if seen is None:
mkts = [m.waypoint for m in self.store.all_members(orig.system, Marketplace)] seen = set()
cur = orig if type(orig) == System:
if orig == to: orig = set([SearchNode(orig,None)])
result = [n for n in orig if n==to]
return [] if len(result) > 0:
while cur != to: return result[0].path()
best = cur dest = set()
bestdist = cur.distance(to) for o in orig:
if bestdist < ran: jg = get_jumpgate(o)
path.append(to) if jg is None: continue
break for s in jg.connections:
for m in mkts: if s in seen: continue
dist = m.distance(to) seen.add(s)
if dist < bestdist and cur.distance(m) < ran: dest.add(SearchNode(s, o))
best = m if len(dest) == 0:
bestdist = dist return None
if best == cur: return find_jump_path(dest, to, depth-1, seen)
raise AnalyzerException(f'no path to {to}')
cur = best def prices(c, system):
path.append(cur) prices = {}
return path for m in c.store.all_members(system, Marketplace):
for r, p in m.prices.items():
def find_jump_path(self, orig, to, depth=100, seen=None): if not r in prices:
if depth < 1: return None prices[r] = []
if seen is None: prices[r].append({
seen = set() 'wp': m.waypoint,
if type(orig) == System: 'buy': p.buy,
orig = set([SearchNode(orig,None)]) 'sell': p.sell,
result = [n for n in orig if n==to] 'volume': p.volume,
if len(result) > 0: 'category': m.rtype(r)
return result[0].path() })
dest = set() return prices
for o in orig:
jg = self.get_jumpgate(o) def find_trade(c, system):
if jg is None: continue max_traders = 3
for s in jg.connections: prices = prices(system)
if s in seen: continue occupied_routes = dict()
seen.add(s) for s in c.store.all('Ship'):
dest.add(SearchNode(s, o)) if s.mission != 'trade':
if len(dest) == 0: continue
return None k = (s.mission_state['site'], s.mission_state['dest'])
return self.find_path(dest, to, depth-1, seen) 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(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): def find_gas(c, system):
prices = {} m = [w for w in c.store.all_members(system, 'Waypoint') if w.type == 'GAS_GIANT']
for m in self.store.all_members(system, Marketplace): if len(m)==0:
for r, p in m.prices.items(): raise AnalyzerException('no gas giant found')
if not r in prices: return m[0]
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(self, system): def find_metal(c, system):
max_traders = 3 m = [w for w in c.store.all_members(system, Waypoint) if 'COMMON_METAL_DEPOSITS' in w.traits]
prices = self.prices(system) if len(m) == 0:
occupied_routes = dict() return None
for s in self.store.all('Ship'): origin = Point(0,0)
if s.mission != 'trade': m = sorted(m, key=lambda w: w.distance(origin))
continue return m[0]
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

View File

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

View File

@ -6,19 +6,25 @@ from random import choice, randrange
from time import sleep, time from time import sleep, time
from threading import Thread from threading import Thread
from nullptr.atlas_builder import AtlasBuilder 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 pass
class CentralCommand: class Captain:
def __init__(self, store, api): def __init__(self, context):
self.missions = {} self.missions = {}
self.stopping = False self.stopping = False
self.store = store self.store = context.store
self.analyzer = Analyzer(store) self.c = context
self.api = api self.general = context.general
self.atlas_builder = AtlasBuilder(store, api) self.api = context.api
self.general = context.general
self.atlas_builder = AtlasBuilder(self.store, self.api)
def setup(self):
self.update_missions() self.update_missions()
def get_ready_missions(self): def get_ready_missions(self):
@ -40,6 +46,7 @@ class CentralCommand:
mission.step() mission.step()
def tick(self): def tick(self):
self.general.tick()
self.update_missions() self.update_missions()
missions = self.get_ready_missions() missions = self.get_ready_missions()
if len(missions) == 0: return False if len(missions) == 0: return False
@ -64,9 +71,10 @@ class CentralCommand:
self.stopping = True self.stopping = True
print('stopping...') print('stopping...')
def run(self): def run(self):
while not self.stopping: while not self.stopping:
# any new orders?
self.c.general.tick()
did_step = True did_step = True
request_counter = self.api.requests_sent request_counter = self.api.requests_sent
start = time() start = time()
@ -116,99 +124,11 @@ class CentralCommand:
if s in self.missions: if s in self.missions:
self.stop_mission(s) self.stop_mission(s)
if s.mission is None: 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: if s.mission is not None and s not in self.missions:
self.start_mission(s) self.start_mission(s)
if s in self.missions: if s in self.missions:
m = self.missions[s] 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): def init_mission(self, s, mtyp):
if mtyp == 'none': if mtyp == 'none':
@ -232,7 +152,7 @@ class CentralCommand:
def start_mission(self, s): def start_mission(self, s):
mtype = s.mission mtype = s.mission
m = create_mission(mtype, s, self.store, self.api) m = create_mission(mtype, s, self.c)
self.missions[s] = m self.missions[s] = m
m.status(s.mission_status) m.status(s.mission_status)
return m return m
@ -241,17 +161,4 @@ class CentralCommand:
if s in self.missions: if s in self.missions:
del self.missions[s] 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 sys
import importlib import importlib
import logging import logging
from nullptr.util import AppError
def func_supports_argcount(f, cnt): def func_supports_argcount(f, cnt):
argspec = inspect.getargspec(f) argspec = inspect.getargspec(f)
@ -41,7 +42,7 @@ class CommandLine:
print(f'command not found; {c}') print(f'command not found; {c}')
def handle_error(self, cmd, args, e): 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): def handle_empty(self):
pass pass

View File

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

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

91
nullptr/general.py Normal file
View File

@ -0,0 +1,91 @@
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')
command.role = 'probe'
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}') raise ValueError(f'invalid mission type {mtype}')
return types[mtype] return types[mtype]
def create_mission(mtype, ship, store, api): def create_mission(mtype, ship, c):
typ = get_mission_class(mtype) typ = get_mission_class(mtype)
m = typ(ship, store, api) m = typ(ship, c)
return m return m

View File

@ -5,7 +5,7 @@ from nullptr.models.contract import Contract
from nullptr.models.system import System from nullptr.models.system import System
from nullptr.models.survey import Survey from nullptr.models.survey import Survey
from nullptr.models.ship import Ship from nullptr.models.ship import Ship
from nullptr.analyzer import Analyzer from nullptr.analyzer import *
from time import time from time import time
from functools import partial from functools import partial
import logging import logging
@ -48,13 +48,13 @@ class Mission:
} }
def __init__(self, ship, store, api): def __init__(self, ship, context):
self.ship = ship self.ship = ship
self.store = store self.c = context
self.api = api self.store = context.store
self.api = context.api
self.wait_for = None self.wait_for = None
self.next_step = 0 self.next_step = 0
self.analyzer = Analyzer(self.store)
self.setup() self.setup()
def setup(self): def setup(self):
@ -263,15 +263,15 @@ class BaseMission(Mission):
loc = self.ship.location loc = self.ship.location
loc_sys = loc.system 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) loc_jg_wp = self.store.get(Waypoint, loc_jg.symbol)
dest_sys = dest.system 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: 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) self.sts('traject', result)
return 'done' if len(result) == 0 else 'more' 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 = [] result = []
if loc.symbol != loc_jg.symbol: if loc.symbol != loc_jg.symbol:
result.append(loc_jg_wp) result.append(loc_jg_wp)

View File

@ -1,5 +1,6 @@
from nullptr.missions.base import BaseMission, MissionParam from nullptr.missions.base import BaseMission, MissionParam
from nullptr.models.waypoint import Waypoint from nullptr.models.waypoint import Waypoint
from time import time
class SitMission(BaseMission): class SitMission(BaseMission):
def start_state(self): def start_state(self):
@ -14,10 +15,10 @@ class SitMission(BaseMission):
def steps(self): def steps(self):
return { return {
**self.travel_steps('to', 'dest', 'sit'), **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.waypoint import Waypoint
from nullptr.models.survey import Survey from nullptr.models.survey import Survey
from nullptr.models.contract import Contract from nullptr.models.contract import Contract
from nullptr.analyzer import find_deal
class TradeMission(BaseMission): class TradeMission(BaseMission):
def start_state(self): def start_state(self):
return 'travel-to' return 'travel-to'
@ -11,7 +13,7 @@ class TradeMission(BaseMission):
cargo_space = self.ship.cargo_capacity - self.ship.cargo_units cargo_space = self.ship.cargo_capacity - self.ship.cargo_units
smkt = self.store.get('Marketplace', self.st('site')) smkt = self.store.get('Marketplace', self.st('site'))
dmkt = self.store.get('Marketplace', self.st('dest')) dmkt = self.store.get('Marketplace', self.st('dest'))
resource = self.analyzer.find_deal(smkt, dmkt) resource = find_deal(smkt, dmkt)
if resource is None: if resource is None:
return 'done' return 'done'
price = smkt.buy_price(resource) price = smkt.buy_price(resource)

View File

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

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

@ -0,0 +1,20 @@
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
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)

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)

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

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