improving haulers

This commit is contained in:
Richard 2024-01-06 07:17:53 +01:00
parent 524ba45639
commit 2181583843
9 changed files with 175 additions and 48 deletions

View File

@ -3,7 +3,16 @@ 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:
resource: str
@ -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))

View File

@ -235,8 +235,9 @@ class Api:
return result
######## Commerce #########
def sell(self, ship, typ):
units = ship.get_cargo(typ)
def sell(self, ship, typ,units=None):
if units is None:
units = ship.get_cargo(typ)
data = {
'symbol': typ,
'units': units

View File

@ -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

View File

@ -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):

View File

@ -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,11 +215,21 @@ 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):
if type(dest) == str:
dest = self.store.get(Waypoint, 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:
try:
self.api.refuel(self.ship)
except Exception as e:
pass
#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}',
'more': f'go-{nm}'
}),
steps = {
f'travel-{nm}': (calc, f'dock-{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

View File

@ -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'),
}

View File

@ -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:

View File

@ -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')
@ -40,6 +43,11 @@ class Marketplace(Base):
if resource not in self.prices:
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]

View File

@ -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)