Skip to content

Commit

Permalink
Merged kfsone/tradedangerous into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Phil Davies committed Jan 16, 2015
2 parents cbce234 + 3201127 commit 8aadd6b
Show file tree
Hide file tree
Showing 12 changed files with 1,116 additions and 351 deletions.
13 changes: 12 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
TradeDangerous, Copyright (C) Oliver "kfsone" Smith, July 2014
==============================================================================

[work in progress]
. (tKE) "buy" sub-command now supports ship names (find a ship vendor)
. (kfsone) Partial code/documentation cleanup
. (kfsone) Added a "getRoute" function to TradeDB()
import tradedb
tdb = tradedb.TradeDB()
lave = tdb.lookupPlace("Lave")
barn = tdb.lookupPlace("Barnard's Star")
tdb.getRoute(lave, barn, maxJumpLy=12)

v6.6.1 Jan 10 2015
. (kfsone) Added "--blackmarket" option to "run" command, restrictions
to stations which have a black market.
Expand All @@ -11,7 +21,8 @@ v6.6.1 Jan 10 2015
trade.py run --from sol --to lave -e 3
will find runs from sol to somewhere within 3 jumps of lave.
. (kfsone) "rares" now has a friendly error when no rares are found
+ Data: kfsone, maddavo, Dave Ryalls, EDStarQuery/kfsone
+ Data: kfsone, maddavo, Dave Ryalls, NeoTron, Tyler Lund, Jared Buntain,
+ Stars: EDStarQuery/kfsone
[I'm adding several hundred new systems from EDStarQuery but I'm also
manually vetting all of them!]

Expand Down
9 changes: 5 additions & 4 deletions README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ For additional help on a specific command, such as 'update' use
Reads prices from a file and loads them into the cache

trade.py buy ...
Finds places to buy a given item
Finds places to buy a given item/ship

trade.py sell ...
Finds places to sell a given item
Expand Down Expand Up @@ -667,8 +667,9 @@ LOCAL sub-command:

BUY sub-command:

Looks for stations selling the specified item: that means they have a non-zero
asking price and a stock level other than "n/a".
Looks for stations selling the specified item or ship.

For items, that means they have a non-zero asking price and a stock level other than "n/a".

trade.py buy [-q | -v] [--quantity Q] [--near N] [--ly-per N] item [-P | -S] [--limit]

Expand All @@ -695,7 +696,7 @@ BUY sub-command:
Limit results to stations that match one of the pad sizes
specified.
--pad ML? (med, lrg or unknown only)
-o ML? "" "" "" ""
-p ML? "" "" "" ""
--pad ? (unknown only),
--pad L (large only, ignores unknown)

Expand Down
90 changes: 60 additions & 30 deletions commands/buy_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,49 @@

def run(results, cmdenv, tdb):
from commands.commandenv import ResultRow

item = tdb.lookupItem(cmdenv.item)
cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID)
try:
item = tdb.lookupItem(cmdenv.item)
cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID)
except LookupError:
item = tdb.lookupShip(cmdenv.item)
cmdenv.DEBUG0("Looking up ship {} (#{})", item.name(), item.ID)
cmdenv.ship = True

results.summary = ResultRow()
results.summary.item = item

if cmdenv.detail:
avgPrice = tdb.query("""
SELECT CAST(AVG(ss.price) AS INT)
FROM StationSelling AS ss
WHERE ss.item_id = ?
""", [item.ID]).fetchone()[0]
results.summary.avg = avgPrice
if cmdenv.ship:
results.summary.avg = item.cost
else:
avgPrice = tdb.query("""
SELECT CAST(AVG(ss.price) AS INT)
FROM StationSelling AS ss
WHERE ss.item_id = ?
""", [item.ID]).fetchone()[0]
results.summary.avg = avgPrice

# Constraints
tables = "StationSelling AS ss"
constraints = [ "(item_id = {})".format(item.ID) ]
columns = [
if cmdenv.ship:
tables = "ShipVendor AS ss"
constraints = [ "(ship_id = {})".format(item.ID) ]
columns = [
'ss.station_id',
'0',
'1',
"0",
]
bindValues = [ ]
else:
tables = "StationSelling AS ss"
constraints = [ "(item_id = {})".format(item.ID) ]
columns = [
'ss.station_id',
'ss.price',
'ss.units',
"JULIANDAY('NOW') - JULIANDAY(ss.modified)",
]
bindValues = [ ]
]
bindValues = [ ]

if cmdenv.quantity:
constraints.append("(units = -1 or units >= ?)")
Expand All @@ -108,14 +126,21 @@ def run(results, cmdenv, tdb):
includeSelf=True,
)
}
tables += (
" INNER JOIN Station AS stn"
" ON (stn.station_id = ss.station_id)"
)
constraints.append("(stn.system_id IN ({}))".format(
",".join(['?'] * len(systemRanges))
))
bindValues += list(system.ID for system in systemRanges.keys())
# We need to ensure we use less than 999 bind values in total.
# If we don't filter in the query, we'll filter by stations in
# systemRanges when building our result rows
if len(systemRanges) > 999-len(bindValues):
cmdenv.DEBUG0("Too many matching systems ({}) for SQL filter. "
"Hard way it is.".format(len(systemRanges)))
else:
tables += (
" INNER JOIN Station AS stn"
" ON (stn.station_id = ss.station_id)"
)
constraints.append("(stn.system_id IN ({}))".format(
",".join(['?'] * len(systemRanges))
))
bindValues += list(system.ID for system in systemRanges.keys())

whereClause = ' AND '.join(constraints)
stmt = """
Expand All @@ -140,7 +165,10 @@ def run(results, cmdenv, tdb):
row = ResultRow()
row.station = station
if nearSystem:
row.dist = systemRanges[row.station.system]
# check the system was in our range query
if row.station.system not in systemRanges:
continue
row.dist = systemRanges[row.station.system]
row.price = priceCr
row.stock = stock
row.age = age
Expand Down Expand Up @@ -179,17 +207,19 @@ def render(results, cmdenv, tdb):
stnRowFmt = RowFormat()
stnRowFmt.addColumn('Station', '<', longestNameLen,
key=lambda row: row.station.name())
stnRowFmt.addColumn('Cost', '>', 10, 'n',
key=lambda row: row.price)
stnRowFmt.addColumn('Stock', '>', 10,
key=lambda row: '{:n}'.format(row.stock) if row.stock >= 0 else '?')
if not cmdenv.ship:
stnRowFmt.addColumn('Cost', '>', 10, 'n',
key=lambda row: row.price)
stnRowFmt.addColumn('Stock', '>', 10,
key=lambda row: '{:n}'.format(row.stock) if row.stock >= 0 else '?')

if cmdenv.nearSystem:
stnRowFmt.addColumn('DistLy', '>', 6, '.2f',
key=lambda row: row.dist)

stnRowFmt.addColumn('Age/days', '>', 7, '.2f',
key=lambda row: row.age)
if not cmdenv.ship:
stnRowFmt.addColumn('Age/days', '>', 7, '.2f',
key=lambda row: row.age)
stnRowFmt.addColumn("StnLs", '>', 10,
key=lambda row: row.station.distFromStar())
stnRowFmt.addColumn('B/mkt', '>', 4,
Expand All @@ -206,7 +236,7 @@ def render(results, cmdenv, tdb):

if cmdenv.detail:
print("{:{lnl}} {:>10n}".format(
"-- Average",
"-- Average" if not cmdenv.ship else "-- Ship Cost",
results.summary.avg,
lnl=longestNameLen,
))
90 changes: 15 additions & 75 deletions commands/nav_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
help='Exclude a system from the route. If you specify a station, '
'the system that station is in will be avoided instead.',
action='append',
default=[],
),
ParseArgument('--via',
help='Require specified systems/stations to be en-route (in order).',
Expand All @@ -46,78 +47,6 @@ class NoRouteError(TradeException):
pass


class Distance(object):
def __init__(self, start, end, dist):
self.start, self.end, self.dist = start, end, dist

def __repr__(self):
return "Distance({}, {}, {})".format(
self.start, self.end, self.dist
)


def getRoute(cmdenv, tdb, srcSystem, dstSystem, maxLyPer):
openList = { srcSystem: 0 }
distances = { srcSystem: Distance(srcSystem, srcSystem, 0.0) }

avoiding = frozenset([
place.system if isinstance(place, Station) else place
for place in cmdenv.avoidPlaces
])
if avoiding:
cmdenv.DEBUG0("Avoiding: {}", list(avoiding))

leastHops = False

# Once we find a path, we can curb any remaining paths that
# are longer. This allows us to search aggresively until we
# find the shortest route.
shortestDist = float("inf")
while openList:

# Expand the search domain by one jump; grab the list of
# nodes that are this many hops out and then clear the list.
openNodes, openList = openList, {}

gsir = tdb.genSystemsInRange
for node, startDist in openNodes.items():
for (destSys, destDist) in gsir(node, maxLyPer):
if destSys in avoiding:
continue
dist = startDist + destDist
if dist >= shortestDist:
continue
# If we aready have a shorter path, do nothing
try:
distNode = distances[destSys]
if distances[destSys].dist <= dist:
continue
except KeyError:
pass
distances[destSys] = Distance(node, destSys, dist)
openList[destSys] = dist
if destSys == dstSystem:
shortestDist = dist

# Unravel the route by tracing back the vias.
route = [ dstSystem ]
jumpEnd = dstSystem
while jumpEnd != srcSystem:
try:
distEnt = distances[jumpEnd]
except KeyError:
raise NoRouteError(
"No route found between {} and {} "
"with {}ly jump limit.".format(
srcSystem.name(), dstSystem.name(), maxLyPer
))

route.append(distEnt.start)
jumpEnd = distEnt.start

return route


######################################################################
# Perform query and populate result set

Expand All @@ -143,9 +72,21 @@ def run(results, cmdenv, tdb):
hops.insert(0, [hop, None])
hops[0][1] = dstSystem

avoiding = [
avoid for avoid in cmdenv.avoidPlaces
if isinstance(avoid, System)
]

route = [ ]
for hop in hops:
hopRoute = getRoute(cmdenv, tdb, hop[0], hop[1], maxLyPer)
hopRoute = tdb.getRoute(hop[0], hop[1], maxLyPer, avoiding)
if not hopRoute:
raise NoRouteError(
"No route found between {} and {} "
"with a max {}ly/jump limit.".format(
hop[0].name(), hop[1].name(),
maxLyPer,
))
route = route[:-1] + hopRoute

results.summary = ResultRow(
Expand All @@ -155,7 +96,6 @@ def run(results, cmdenv, tdb):
)

lastSys, totalLy, dirLy = srcSystem, 0.00, 0.00
route.reverse()

if cmdenv.stations:
stationIDs = ",".join([
Expand All @@ -175,7 +115,7 @@ def run(results, cmdenv, tdb):
for ID, age in tdb.query(stmt):
ages[ID] = age

for jumpSys in route:
for (jumpSys, dist) in route:
jumpLy = math.sqrt(lastSys.distToSq(jumpSys))
totalLy += jumpLy
if cmdenv.detail:
Expand Down
10 changes: 5 additions & 5 deletions data/ShipVendor.csv
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,11 @@ unq:[email protected]_id,unq:[email protected]_id,unq:[email protected]_id
'HR 5451','Fraser Station','Lakon Type 7'
'HR 5451','Fraser Station','Lakon Type 9'
'HR 5451','Fraser Station','Sidewinder'
'HR 5451','Macdonald Hub','Adder'
'HR 5451','Macdonald Hub','Cobra'
'HR 5451','Macdonald Hub','Eagle'
'HR 5451','Macdonald Hub','Lakon Type 6'
'HR 5451','Macdonald Hub','Sidewinder'
'HR 5451','MacDonald Hub','Adder'
'HR 5451','MacDonald Hub','Cobra'
'HR 5451','MacDonald Hub','Eagle'
'HR 5451','MacDonald Hub','Lakon Type 6'
'HR 5451','MacDonald Hub','Sidewinder'
'HR 8170','Fiennes Vision','Adder'
'HR 8170','Fiennes Vision','Asp Explorer'
'HR 8170','Fiennes Vision','Cobra'
Expand Down
Loading

0 comments on commit 8aadd6b

Please sign in to comment.