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