major command staff restructure
Heads had to roll
This commit is contained in:
parent
fb3b6162fc
commit
74ce884b05
@ -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"]
|
@ -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
|
||||
prices = prices(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 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):
|
||||
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]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
@ -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,5 @@ 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)
|
||||
|
6
nullptr/context.py
Normal file
6
nullptr/context.py
Normal 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
91
nullptr/general.py
Normal 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]
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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(smkt, dmkt)
|
||||
if resource is None:
|
||||
return 'done'
|
||||
price = smkt.buy_price(resource)
|
||||
|
@ -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)
|
||||
|
20
nullptr/roles/__init__.py
Normal file
20
nullptr/roles/__init__.py
Normal 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
16
nullptr/roles/hauler.py
Normal 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
11
nullptr/roles/miner.py
Normal 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
15
nullptr/roles/probe.py
Normal 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
8
nullptr/roles/siphon.py
Normal 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
10
nullptr/roles/surveyor.py
Normal 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
14
nullptr/roles/trader.py
Normal 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)
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user