improving haulers
This commit is contained in:
parent
524ba45639
commit
2181583843
@ -3,6 +3,15 @@ from nullptr.models.jumpgate import Jumpgate
|
|||||||
from nullptr.models.system import System
|
from nullptr.models.system import System
|
||||||
from nullptr.models.waypoint import Waypoint
|
from nullptr.models.waypoint import Waypoint
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from copy import copy
|
||||||
|
|
||||||
|
class AnalyzerException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Point:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TradeOption:
|
class TradeOption:
|
||||||
@ -70,27 +79,60 @@ class Analyzer:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def solve_tsp(self, waypoints):
|
def solve_tsp(self, waypoints):
|
||||||
# todo actually try to solve it
|
wps = copy(waypoints)
|
||||||
return 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):
|
def get_jumpgate(self, system):
|
||||||
gates = self.store.all_members(system, Jumpgate)
|
gates = self.store.all_members(system, Jumpgate)
|
||||||
return next(gates, None)
|
return next(gates, None)
|
||||||
|
|
||||||
def find_path(self, orig, to, depth=100, seen=None):
|
# 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
|
||||||
|
|
||||||
|
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 depth < 1: return None
|
||||||
if seen is None:
|
if seen is None:
|
||||||
seen = set()
|
seen = set()
|
||||||
if type(orig) == System:
|
if type(orig) == System:
|
||||||
orig = set([SearchNode(orig,None)])
|
orig = set([SearchNode(orig,None)])
|
||||||
result = [n for n in orig if n.system==to]
|
result = [n for n in orig if n==to]
|
||||||
if len(result) > 0:
|
if len(result) > 0:
|
||||||
return result[0].path()
|
return result[0].path()
|
||||||
dest = set()
|
dest = set()
|
||||||
for o in orig:
|
for o in orig:
|
||||||
jg = self.get_jumpgate(o.system)
|
jg = self.get_jumpgate(o)
|
||||||
if jg is None: continue
|
if jg is None: continue
|
||||||
for s in jg.systems:
|
for s in jg.connections:
|
||||||
if s in seen: continue
|
if s in seen: continue
|
||||||
seen.add(s)
|
seen.add(s)
|
||||||
dest.add(SearchNode(s, o))
|
dest.add(SearchNode(s, o))
|
||||||
@ -128,3 +170,16 @@ class Analyzer:
|
|||||||
if best is None or best.score < o.score:
|
if best is None or best.score < o.score:
|
||||||
best = o
|
best = o
|
||||||
return best
|
return best
|
||||||
|
|
||||||
|
def market_scan(self, system):
|
||||||
|
m = [w.waypoint for w in self.store.all_members(system, Marketplace) if not w.is_fuel()]
|
||||||
|
ms = len(m)
|
||||||
|
path = self.solve_tsp(m)
|
||||||
|
cur = Point(0,0)
|
||||||
|
for w in path:
|
||||||
|
print(w, w.distance(cur))
|
||||||
|
cur = w
|
||||||
|
far = self.store.get(Waypoint, 'X1-NN7-B7')
|
||||||
|
for w in path:
|
||||||
|
print(w, w.distance(far))
|
||||||
|
|
@ -235,8 +235,9 @@ class Api:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
######## Commerce #########
|
######## Commerce #########
|
||||||
def sell(self, ship, typ):
|
def sell(self, ship, typ,units=None):
|
||||||
units = ship.get_cargo(typ)
|
if units is None:
|
||||||
|
units = ship.get_cargo(typ)
|
||||||
data = {
|
data = {
|
||||||
'symbol': typ,
|
'symbol': typ,
|
||||||
'units': units
|
'units': units
|
||||||
|
@ -138,10 +138,10 @@ class CentralCommand:
|
|||||||
s.mission_state = {k: v.default for k,v in mclass.params().items()}
|
s.mission_state = {k: v.default for k,v in mclass.params().items()}
|
||||||
self.start_mission(s)
|
self.start_mission(s)
|
||||||
|
|
||||||
def restart_mission(self, s):
|
def restart_mission(self, s, status='init'):
|
||||||
if s not in self.missions:
|
if s not in self.missions:
|
||||||
raise CentralCommandError("no mission assigned")
|
raise CentralCommandError("no mission assigned")
|
||||||
s.mission_status = 'init'
|
s.mission_status = status
|
||||||
|
|
||||||
def start_mission(self, s):
|
def start_mission(self, s):
|
||||||
mtype = s.mission
|
mtype = s.mission
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
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
|
from nullptr.analyzer import Analyzer, Point
|
||||||
import argparse
|
import argparse
|
||||||
from nullptr.models import *
|
from nullptr.models import *
|
||||||
from nullptr.api import Api
|
from nullptr.api import Api
|
||||||
@ -127,8 +127,10 @@ class Commander(CommandLine):
|
|||||||
pprint(self.ship.mission_state)
|
pprint(self.ship.mission_state)
|
||||||
|
|
||||||
def do_role(self, role):
|
def do_role(self, role):
|
||||||
roles = ['hauler']
|
roles = [None, 'hauler']
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
|
if role == 'none':
|
||||||
|
role = None
|
||||||
if role not in roles:
|
if role not in roles:
|
||||||
print(f'role {role} not found. Choose from {roles}')
|
print(f'role {role} not found. Choose from {roles}')
|
||||||
return
|
return
|
||||||
@ -140,9 +142,9 @@ class Commander(CommandLine):
|
|||||||
self.centcom.init_mission(self.ship, arg)
|
self.centcom.init_mission(self.ship, arg)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
def do_mrestart(self):
|
def do_mrestart(self, status='init'):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
self.centcom.restart_mission(self.ship)
|
self.centcom.restart_mission(self.ship, status)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
def do_mreset(self):
|
def do_mreset(self):
|
||||||
@ -217,11 +219,21 @@ class Commander(CommandLine):
|
|||||||
self.centcom.set_mission_param(self.ship, 'hops', markets)
|
self.centcom.set_mission_param(self.ship, 'hops', markets)
|
||||||
self.print_mission()
|
self.print_mission()
|
||||||
|
|
||||||
|
def totaldist(self, m):
|
||||||
|
t = 0
|
||||||
|
o = Point(0,0)
|
||||||
|
for w in m:
|
||||||
|
t +=w.distance(o)
|
||||||
|
o = w
|
||||||
|
return t
|
||||||
|
|
||||||
def do_sprobe(self):
|
def do_sprobe(self):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
system = self.ship.location.system
|
system = self.ship.location.system
|
||||||
m = self.store.all_members(system, 'Marketplace')
|
m = [m.waypoint for m in self.store.all_members(system, 'Marketplace')]
|
||||||
|
print("pre", self.totaldist(m))
|
||||||
m = self.analyzer.solve_tsp(m)
|
m = self.analyzer.solve_tsp(m)
|
||||||
|
print("post", self.totaldist(m))
|
||||||
hops = [w.symbol for w in m]
|
hops = [w.symbol for w in m]
|
||||||
self.centcom.init_mission(self.ship, 'probe')
|
self.centcom.init_mission(self.ship, 'probe')
|
||||||
self.centcom.set_mission_param(self.ship, 'hops', hops)
|
self.centcom.set_mission_param(self.ship, 'hops', hops)
|
||||||
@ -262,6 +274,10 @@ class Commander(CommandLine):
|
|||||||
if w.type == 'JUMP_GATE':
|
if w.type == 'JUMP_GATE':
|
||||||
self.api.jumps(w)
|
self.api.jumps(w)
|
||||||
|
|
||||||
|
def do_market_scan(self):
|
||||||
|
if not self.has_ship(): return
|
||||||
|
loc = self.ship.location.system
|
||||||
|
pprint(self.analyzer.market_scan(loc))
|
||||||
|
|
||||||
def do_system(self, system_str):
|
def do_system(self, system_str):
|
||||||
system = self.store.get(System, system_str)
|
system = self.store.get(System, system_str)
|
||||||
@ -282,6 +298,8 @@ class Commander(CommandLine):
|
|||||||
traits = []
|
traits = []
|
||||||
if w.type == 'JUMP_GATE':
|
if w.type == 'JUMP_GATE':
|
||||||
traits.append('JUMP')
|
traits.append('JUMP')
|
||||||
|
if w.type == 'GAS_GIANT':
|
||||||
|
traits.append('GAS')
|
||||||
if 'SHIPYARD' in w.traits:
|
if 'SHIPYARD' in w.traits:
|
||||||
traits.append('SHIPYARD')
|
traits.append('SHIPYARD')
|
||||||
if 'MARKETPLACE' in w.traits:
|
if 'MARKETPLACE' in w.traits:
|
||||||
@ -293,8 +311,6 @@ class Commander(CommandLine):
|
|||||||
traits.append('METAL')
|
traits.append('METAL')
|
||||||
if 'PRECIOUS_METAL_DEPOSITS' in w.traits:
|
if 'PRECIOUS_METAL_DEPOSITS' in w.traits:
|
||||||
traits.append('GOLD')
|
traits.append('GOLD')
|
||||||
if 'EXPLOSIVE_GASES' in w.traits:
|
|
||||||
traits.append('GAS')
|
|
||||||
if 'MINERAL_DEPOSITS' in w.traits:
|
if 'MINERAL_DEPOSITS' in w.traits:
|
||||||
traits.append('MINS')
|
traits.append('MINS')
|
||||||
if 'STRIPPED' in w.traits:
|
if 'STRIPPED' in w.traits:
|
||||||
@ -362,7 +378,7 @@ class Commander(CommandLine):
|
|||||||
if arg.startswith('r'):
|
if arg.startswith('r'):
|
||||||
r = self.api.list_ships()
|
r = self.api.list_ships()
|
||||||
else:
|
else:
|
||||||
r = list(self.store.all('Ship'))
|
r = sorted(list(self.store.all('Ship')))
|
||||||
pprint(r)
|
pprint(r)
|
||||||
|
|
||||||
def do_contracts(self, arg=''):
|
def do_contracts(self, arg=''):
|
||||||
@ -482,9 +498,9 @@ class Commander(CommandLine):
|
|||||||
self.api.buy(self.ship, resource.upper(), amt)
|
self.api.buy(self.ship, resource.upper(), amt)
|
||||||
self.do_cargo()
|
self.do_cargo()
|
||||||
|
|
||||||
def do_sell(self, resource):
|
def do_sell(self, resource, amt=None):
|
||||||
if not self.has_ship(): return
|
if not self.has_ship(): return
|
||||||
self.api.sell(self.ship, resource.upper())
|
self.api.sell(self.ship, resource.upper(), amt)
|
||||||
self.do_cargo()
|
self.do_cargo()
|
||||||
|
|
||||||
def do_dump(self, resource):
|
def do_dump(self, resource):
|
||||||
|
@ -173,7 +173,14 @@ class BaseMission(Mission):
|
|||||||
sellables.remove(target)
|
sellables.remove(target)
|
||||||
if len(sellables) == 0:
|
if len(sellables) == 0:
|
||||||
return 'done'
|
return 'done'
|
||||||
self.api.sell(self.ship, sellables[0])
|
resource = sellables[0]
|
||||||
|
volume = market.volume(resource)
|
||||||
|
|
||||||
|
amount = self.ship.get_cargo(resource)
|
||||||
|
while amount > 0:
|
||||||
|
amt = min(amount, volume)
|
||||||
|
self.api.sell(self.ship, resource, amt)
|
||||||
|
amount -= amt
|
||||||
if len(sellables) == 1:
|
if len(sellables) == 1:
|
||||||
return 'done'
|
return 'done'
|
||||||
else:
|
else:
|
||||||
@ -186,19 +193,21 @@ class BaseMission(Mission):
|
|||||||
loc = self.ship.location
|
loc = self.ship.location
|
||||||
market = self.store.get('Marketplace', loc.symbol)
|
market = self.store.get('Marketplace', loc.symbol)
|
||||||
price = market.buy_price(resource)
|
price = market.buy_price(resource)
|
||||||
|
volume = market.volume(resource)
|
||||||
affordable = credits // price
|
affordable = credits // price
|
||||||
amount = min(cargo_space, affordable)
|
amount = min(cargo_space, affordable)
|
||||||
self.api.buy(self.ship, resource, amount)
|
while amount > 0:
|
||||||
|
amt = min(amount, volume)
|
||||||
|
self.api.buy(self.ship, resource, amt)
|
||||||
|
amount -= amt
|
||||||
|
|
||||||
def step_travel(self):
|
def step_travel(self):
|
||||||
traject = self.st('traject')
|
traject = self.st('traject')
|
||||||
if traject is None or traject == []:
|
if traject is None or traject == []:
|
||||||
return 'done'
|
return
|
||||||
dest = traject[-1]
|
dest = traject[-1]
|
||||||
loc = self.ship.location
|
loc = self.ship.location
|
||||||
if dest == loc:
|
|
||||||
self.sts('traject', None)
|
|
||||||
return 'done'
|
|
||||||
hop = traject.pop(0)
|
hop = traject.pop(0)
|
||||||
if type(hop) == Waypoint:
|
if type(hop) == Waypoint:
|
||||||
self.api.navigate(self.ship, hop)
|
self.api.navigate(self.ship, hop)
|
||||||
@ -206,9 +215,19 @@ class BaseMission(Mission):
|
|||||||
else:
|
else:
|
||||||
self.api.jump(self.ship, hop)
|
self.api.jump(self.ship, hop)
|
||||||
self.next_step = self.ship.cooldown
|
self.next_step = self.ship.cooldown
|
||||||
if traject == []:
|
|
||||||
traject= None
|
|
||||||
self.sts('traject', traject)
|
self.sts('traject', traject)
|
||||||
|
|
||||||
|
def step_navigate_traject(self):
|
||||||
|
traject = self.st('traject')
|
||||||
|
|
||||||
|
|
||||||
|
loc = self.ship.location
|
||||||
|
|
||||||
|
if traject is None or traject == []:
|
||||||
|
return 'done'
|
||||||
|
dest =traject[-1]
|
||||||
|
if dest == loc:
|
||||||
|
return 'done'
|
||||||
return 'more'
|
return 'more'
|
||||||
|
|
||||||
def step_calculate_traject(self, dest):
|
def step_calculate_traject(self, dest):
|
||||||
@ -222,7 +241,7 @@ class BaseMission(Mission):
|
|||||||
dest_sys = dest.system
|
dest_sys = dest.system
|
||||||
dest_jg = self.analyzer.get_jumpgate(dest_sys)
|
dest_jg = self.analyzer.get_jumpgate(dest_sys)
|
||||||
if dest_sys == loc_sys:
|
if dest_sys == loc_sys:
|
||||||
result = [dest]
|
result = self.analyzer.find_nav_path(loc, dest, self.ship.range())
|
||||||
self.sts('traject', result)
|
self.sts('traject', result)
|
||||||
return
|
return
|
||||||
path = self.analyzer.find_path(loc_sys, dest_sys)
|
path = self.analyzer.find_path(loc_sys, dest_sys)
|
||||||
@ -237,30 +256,47 @@ class BaseMission(Mission):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def step_dock(self):
|
def step_dock(self):
|
||||||
|
if self.ship.status == 'DOCKED':
|
||||||
|
return
|
||||||
self.api.dock(self.ship)
|
self.api.dock(self.ship)
|
||||||
|
|
||||||
def step_refuel(self):
|
def step_refuel(self):
|
||||||
if self.ship.fuel_capacity == 0:
|
if self.ship.fuel_capacity == 0:
|
||||||
return
|
return
|
||||||
if self.ship.fuel_current / self.ship.fuel_capacity < 0.5:
|
#if self.ship.fuel_capacity - self.ship.fuel_current > 100:
|
||||||
try:
|
try:
|
||||||
self.api.refuel(self.ship)
|
self.api.refuel(self.ship)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def step_orbit(self):
|
def step_orbit(self):
|
||||||
|
if self.ship.status != 'DOCKED':
|
||||||
|
return
|
||||||
self.api.orbit(self.ship)
|
self.api.orbit(self.ship)
|
||||||
|
|
||||||
def travel_steps(self, nm, destination, next_step):
|
def travel_steps(self, nm, destination, next_step):
|
||||||
destination = self.st(destination)
|
destination = self.st(destination)
|
||||||
calc = partial(self.step_calculate_traject, destination)
|
calc = partial(self.step_calculate_traject, destination)
|
||||||
return {
|
steps = {
|
||||||
f'travel-{nm}': (self.step_orbit, f'calc-trav-{nm}'),
|
|
||||||
f'calc-trav-{nm}': (calc, f'go-{nm}'),
|
f'travel-{nm}': (calc, f'dock-{nm}'),
|
||||||
f'go-{nm}': (self.step_travel, {
|
|
||||||
'done': f'dock-{nm}',
|
|
||||||
'more': f'go-{nm}'
|
|
||||||
}),
|
|
||||||
f'dock-{nm}': (self.step_dock, f'refuel-{nm}'),
|
f'dock-{nm}': (self.step_dock, f'refuel-{nm}'),
|
||||||
f'refuel-{nm}': (self.step_refuel, next_step)
|
f'refuel-{nm}': (self.step_refuel, 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,
|
||||||
|
'more': f'dock-{nm}'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
if self.ship.fuel_capacity == 0:
|
||||||
|
steps = {
|
||||||
|
|
||||||
|
f'travel-{nm}': (calc, f'go-{nm}'),
|
||||||
|
f'go-{nm}': (self.step_travel, f'nav-{nm}'),
|
||||||
|
f'nav-{nm}': (self.step_navigate_traject, {
|
||||||
|
'done': next_step,
|
||||||
|
'more': f'go-{nm}'
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
return steps
|
||||||
|
@ -18,10 +18,13 @@ class HaulMission(BaseMission):
|
|||||||
|
|
||||||
def steps(self):
|
def steps(self):
|
||||||
return {
|
return {
|
||||||
**self.travel_steps('to', 'site', 'market'),
|
**self.travel_steps('to', 'site', 'dock'),
|
||||||
'market': (self.step_market, 'load'),
|
'dock': (self.step_dock, 'market-pre'),
|
||||||
'load': (self.step_load, 'travel-back'),
|
'market-pre': (self.step_market, 'load'),
|
||||||
**self.travel_steps('back', 'dest', 'unload'),
|
'load': (self.step_load, 'market-post'),
|
||||||
|
'market-post': (self.step_market, 'travel-back'),
|
||||||
|
**self.travel_steps('back', 'dest', 'dock-dest'),
|
||||||
|
'dock-dest': (self.step_dock, 'unload'),
|
||||||
'unload': (self.step_unload, 'market-dest'),
|
'unload': (self.step_unload, 'market-dest'),
|
||||||
'market-dest': (self.step_market, 'done'),
|
'market-dest': (self.step_market, 'done'),
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,9 @@ class Base:
|
|||||||
val = interp(val)
|
val = interp(val)
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
|
|
||||||
|
def __lt__(self, o):
|
||||||
|
return self.symbol < o.symbol
|
||||||
|
|
||||||
def setlst(self, attr, d, name, member=None, interp=None):
|
def setlst(self, attr, d, name, member=None, interp=None):
|
||||||
val = sg(d, name)
|
val = sg(d, name)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
|
@ -19,6 +19,9 @@ class Marketplace(Base):
|
|||||||
waypoint = self.store.get(Waypoint, self.symbol, create=True)
|
waypoint = self.store.get(Waypoint, self.symbol, create=True)
|
||||||
self.waypoint = waypoint
|
self.waypoint = waypoint
|
||||||
|
|
||||||
|
def is_fuel(self):
|
||||||
|
return self.imports + self.exports + self.exchange == ['FUEL']
|
||||||
|
|
||||||
def update(self, d):
|
def update(self, d):
|
||||||
self.setlst('imports', d, 'imports', 'symbol')
|
self.setlst('imports', d, 'imports', 'symbol')
|
||||||
self.setlst('exports', d, 'exports', 'symbol')
|
self.setlst('exports', d, 'exports', 'symbol')
|
||||||
@ -41,6 +44,11 @@ class Marketplace(Base):
|
|||||||
return None
|
return None
|
||||||
return self.prices[resource]['buy']
|
return self.prices[resource]['buy']
|
||||||
|
|
||||||
|
def volume(self, resource):
|
||||||
|
if resource not in self.prices:
|
||||||
|
return None
|
||||||
|
return self.prices[resource]['volume']
|
||||||
|
|
||||||
def sellable_items(self, resources):
|
def sellable_items(self, resources):
|
||||||
return [r for r in resources if r in self.prices]
|
return [r for r in resources if r in self.prices]
|
||||||
|
|
||||||
|
@ -23,6 +23,11 @@ class Ship(Base):
|
|||||||
def ext(self):
|
def ext(self):
|
||||||
return 'shp'
|
return 'shp'
|
||||||
|
|
||||||
|
def range(self):
|
||||||
|
if self.fuel_capacity == 0:
|
||||||
|
return 100000
|
||||||
|
return self.fuel_capacity
|
||||||
|
|
||||||
def update(self, d):
|
def update(self, d):
|
||||||
self.seta('status', d, 'nav.status')
|
self.seta('status', d, 'nav.status')
|
||||||
getter = self.store.getter(Waypoint, create=True)
|
getter = self.store.getter(Waypoint, create=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user