diff --git a/nullptr/analyzer.py b/nullptr/analyzer.py index 4963456..51aa258 100644 --- a/nullptr/analyzer.py +++ b/nullptr/analyzer.py @@ -27,6 +27,7 @@ class TradeOption: resource: str source: Waypoint dest: Waypoint + buy: int margin: int dist: int score: float @@ -159,31 +160,49 @@ class Analyzer: prices[r].append({ 'wp': m.waypoint, 'buy': p.buy, - 'sell': p.sell + 'sell': p.sell, + 'volume': p.volume }) return prices def find_trade(self, system): prices = self.prices(system) - occupied_resources = set() + occupied_routes = set() for s in self.store.all('Ship'): if s.mission != 'trade': continue - occupied_resources.add(s.mission_state['resource']) + occupied_routes.add((s.mission_state['site'], s.mission_state['dest'])) best = None for resource, markets in prices.items(): - if resource in occupied_resources: - continue 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'] - - dist = source['wp'].distance(dest['wp']) + if (swp.symbol,dwp.symbol) in occupied_routes: + continue + dist = swp.distance(dwp) dist = max(dist, 0.0001) score = margin / dist if margin < 0: continue - o = TradeOption(resource, source['wp'], dest['wp'], margin, dist, score) + 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 + print(f'finding deal from {smkt} to {dmkt}') + for r, sp in smkt.prices.items(): + if not r in dmkt.prices: + print(r, 'not in dmkt') + continue + dp = dmkt.prices[r] + margin = dp.sell - sp.buy + print(r, margin) + if margin > best_margin: + best_margin = margin + best_resource = r + return best_resource diff --git a/nullptr/central_command.py b/nullptr/central_command.py index b211312..78a4cde 100644 --- a/nullptr/central_command.py +++ b/nullptr/central_command.py @@ -131,10 +131,8 @@ class CentralCommand: return print(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, 'resource', t.resource) self.smipa(s, 'site', t.source) self.smipa(s, 'dest', t.dest) - self.smipa(s, 'delivery', 'sell') def init_mission(self, s, mtyp): if mtyp == 'none': diff --git a/nullptr/commander.py b/nullptr/commander.py index 3ae6718..dcdb093 100644 --- a/nullptr/commander.py +++ b/nullptr/commander.py @@ -85,7 +85,7 @@ class Commander(CommandLine): elif len(matches) > 1: raise CommandError('multiple matches') else: - raise CommandError('not found') + raise CommandError(f'{arg} not found') def resolve_system(self, system_str): if type(system_str) == System: @@ -97,6 +97,20 @@ class Commander(CommandLine): system = self.store.get(System, system_str) return system + def resolve_waypoint(self, w): + if type(w) == Waypoint: + return w + if w == '': + if not self.has_ship(): return + return self.ship.location + p = w.split('-') + if len(p) == 1: + if not self.has_ship(): return + s = self.ship.location.system + w = f'{s}-{w}' + return self.store.get(Waypoint, w) + + ######## First run ######### def agent_setup(self): symbol = input('agent name: ') @@ -232,12 +246,17 @@ class Commander(CommandLine): r = self.api.jumps(waypoint) pprint(r) - def do_shipyard(self): - if not self.has_ship(): return - location = self.ship.location + def do_shipyard(self, w=''): + location = self.resolve_waypoint(w) + if location is None: + raise CommandError(f'waypoint {w} not found') data = self.api.shipyard(location) - for s in must_get(data, 'ships'): - print(s['type'], s['purchasePrice']) + if 'ships' in data: + for s in must_get(data, 'ships'): + print(s['type'], s['purchasePrice']) + else: + for s in must_get(data, 'shipTypes'): + print(s['type']) ######## Commerce ######### def do_refuel(self, source='market'): @@ -532,9 +551,12 @@ class Commander(CommandLine): system = self.ship.location.system prices = self.analyzer.prices(system) if resource is not None: - pprint(prices[resource.upper()]) - else: - pprint(prices) + prices = {resource: prices[resource.upper()]} + + for res, p in prices.items(): + print('==' + res) + for m in p: + print(f"{m['wp'].symbol:12s} {m['volume']:5d} {m['buy']:5d} {m['sell']:5d}") def do_path(self, waypoint_str): if not self.has_ship(): return diff --git a/nullptr/missions/base.py b/nullptr/missions/base.py index e8073f4..c988bd9 100644 --- a/nullptr/missions/base.py +++ b/nullptr/missions/base.py @@ -61,6 +61,8 @@ class Mission: def rst(self, typ, nm): symbol = self.st(nm) + if symbol is None: + return None return self.store.get(typ, symbol) def st(self, nm): @@ -186,21 +188,6 @@ class BaseMission(Mission): else: return 'more' - def step_load(self): - credits = self.api.agent.credits - cargo_space = self.ship.cargo_capacity - self.ship.cargo_units - resource = self.st('resource') - 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) - 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 == []: diff --git a/nullptr/missions/trade.py b/nullptr/missions/trade.py index 812406b..4fcbc9d 100644 --- a/nullptr/missions/trade.py +++ b/nullptr/missions/trade.py @@ -6,14 +6,29 @@ class TradeMission(BaseMission): def start_state(self): return 'travel-to' + def step_load(self): + credits = self.api.agent.credits + 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) + if resource is None: + return 'done' + price = smkt.buy_price(resource) + volume = smkt.volume(resource) + affordable = credits // price + print(cargo_space, affordable, volume) + amount = min(cargo_space, affordable, volume) + if amount == 0: + return 'done' + self.api.buy(self.ship, resource, amount) + return 'done' if amount == cargo_space else 'more' + @classmethod def params(cls): return { 'site': MissionParam(Waypoint, True), - 'resource': MissionParam(str, True), 'dest': MissionParam(Waypoint, True), - 'delivery': MissionParam(str, True, 'deliver'), - 'contract': MissionParam(Contract, False) } def steps(self): @@ -21,7 +36,10 @@ class TradeMission(BaseMission): **self.travel_steps('to', 'site', 'dock'), 'dock': (self.step_dock, 'market-pre'), 'market-pre': (self.step_market, 'load'), - 'load': (self.step_load, 'market-post'), + 'load': (self.step_load, { + 'more': 'market-pre', + 'done': 'market-post' + }), 'market-post': (self.step_market, 'travel-back'), **self.travel_steps('back', 'dest', 'dock-dest'), 'dock-dest': (self.step_dock, 'unload'), diff --git a/nullptr/models/marketplace.py b/nullptr/models/marketplace.py index ee83a76..8303334 100644 --- a/nullptr/models/marketplace.py +++ b/nullptr/models/marketplace.py @@ -100,7 +100,7 @@ class Marketplace(Base): r += 'X: ' + ', '.join(self.exchange) + '\n' r += '\n' - for p in self.prices.values(): - t = self.rtype(p['symbol']) - r += f'{t} {p["symbol"]:25s} {p["sell"]:5d} {p["buy"]:5d}\n' + for res, p in self.prices.items(): + t = self.rtype(res) + r += f'{t} {res:25s} {p.sell:5d} {p.buy:5d}\n' return r diff --git a/nullptr/models/ship.py b/nullptr/models/ship.py index a4e7a22..67bdd70 100644 --- a/nullptr/models/ship.py +++ b/nullptr/models/ship.py @@ -99,6 +99,8 @@ class Ship(Base): def deliverable_cargo(self, contract): result = [] + if contract is None: + return result for d in contract.deliveries: if self.get_cargo(d['trade_symbol']) > 0: result.append(d['trade_symbol']) @@ -126,6 +128,8 @@ class Ship(Base): mstatus = self.mission_status if mstatus == 'error': mstatus = mstatus.upper() + if mstatus is None: + mstatus = 'none' status = self.status.lower() if status.startswith('in_'): status = status[3:]