Skip to content

Commit

Permalink
#16: Allow route selection when departing if multiple routes with dif…
Browse files Browse the repository at this point in the history
…ferent tags exist.
  • Loading branch information
danports committed May 17, 2020
1 parent 3251eba commit 9801bac
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 10 deletions.
74 changes: 73 additions & 1 deletion src/railnetwork/apis/railnetwork
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ function locationsMatch(a, b)
return a.line == b.line and a.position == b.position
end

function distanceBetween(a, b)
if a.line == b.line then
return math.abs(a.position - b.position)
end
return 1 -- There's really no way to know.
end

local RailNetwork = {}
function RailNetwork:addNode(id, node)
self.nodes[id] = node
Expand Down Expand Up @@ -53,12 +60,19 @@ function RailNetwork:buildGraph()
return line[location.position]
end

local tags = {}
for id, node in pairs(self.nodes) do
node.edges = {}
if node.connections then
for _, connection in pairs(node.connections) do
establishNode(connection.location)
connection.destination = formatLocation(connection.location)
connection.distance = connection.distance or distanceBetween(node.location, connection.location)
if connection.tags then
for tag in pairs(connection.tags) do
tags[tag] = true
end
end
table.insert(node.edges, connection)
end
end
Expand Down Expand Up @@ -93,6 +107,7 @@ function RailNetwork:buildGraph()

self.graph = graph
self.lines = locationsByLine
self.tags = tags
end

function RailNetwork:findClosestNode(location)
Expand Down Expand Up @@ -160,7 +175,20 @@ function RailNetwork:findRoute(trip)
if not destinationNode then
return nil, string.format("Unable to find rail network location for destination %s: %s", formatLocation(trip.destination), destinationError)
end
local path = graph.shortestPath(self.graph, {origin = formatLocation(originNode), destination = formatLocation(destinationNode)})
local function edgeWeight(edge)
local distance = edge.distance or 1
if edge.tags and trip.tags then
for tag, weight in pairs(edge.tags) do
if trip.tags[tag] then
distance = distance * weight
else
return math.huge
end
end
end
return distance
end
local path = graph.shortestPath(self.graph, {origin = formatLocation(originNode), destination = formatLocation(destinationNode)}, edgeWeight)
if not path then
return nil, string.format("No path exists between origin %s and destination %s; did you misconfigure a switch or station?", formatLocation(originNode), formatLocation(destinationNode))
end
Expand All @@ -175,6 +203,50 @@ function RailNetwork:findRoute(trip)
return path
end

local function routeMatches(a, b)
if #a ~= #b then
return false
end
for key, edge in ipairs(a) do
if edge.destination ~= b[key].destination then
return false
end
end
return true
end

local function findMatchingRoute(routes, route)
for key, existing in pairs(routes) do
if routeMatches(existing, route) then
return key, existing
end
end
end

function RailNetwork:findRoutes(trip)
self:buildGraph()
local routes = {}
local function addRoute(trip, tripTags)
trip.tags = tripTags
local route, routeError = self:findRoute(trip)
if route then
local key = findMatchingRoute(routes, route)
if key then
table.insert(key, tripTags)
else
routes[{tripTags}] = route
end
end
trip.tags = nil
end

addRoute(trip, {})
for tag in pairs(self.tags) do
addRoute({[tag] = true})
end
return routes
end

local metatable = {
__index = RailNetwork
}
Expand Down
19 changes: 12 additions & 7 deletions src/railrouter/startup
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,13 @@ function removeStation(station)
end

function validateTrip(trip)
if trip.origin then
return network:findRoute(trip)
if trip.origin and trip.destination then
local routes = network:findRoutes(trip)
if next(routes) then
trip.routes = routes
return true
end
return nil, string.format("No routes exist between origin %s and destination %s; did you misconfigure a switch or station?", railnetwork.formatLocation(trip.origin), railnetwork.formatLocation(trip.destination))
end
-- We can only check whether the destination is valid.
return network:findClosestNode(trip.destination)
Expand Down Expand Up @@ -127,19 +132,19 @@ function minecartDetected(detection)

local switch = switches[detection.switchId]
if switch == nil or switch.computerId == nil then
log.warn(string.format("Detection at switch %i, which was not found; ignoring detection", detection.switchId))
log.warn(string.format("Ignoring detection: Detection at switch %i, which was not found", detection.switchId))
return
end

local path = network:findRoute({origin = detection.location, destination = trip.destination})
if path == nil then
log.warn(string.format("No path exists from %s to %s; ignoring detection", railnetwork.formatLocation(detection.location), railnetwork.formatLocation(trip.destination)))
local path, pathError = network:findRoute({origin = detection.location, destination = trip.destination, tags = trip.tags})
if not path then
log.warn(string.format("Ignoring detection: %s", pathError))
return
end

local edge = path[1]
if edge == nil then
log.warn(string.format("Path to destination %s is empty; ignoring detection", railnetwork.formatLocation(trip.destination)))
log.warn(string.format("Ignoring detection: Path to destination %s is empty", railnetwork.formatLocation(trip.destination)))
return
end

Expand Down
54 changes: 52 additions & 2 deletions src/railstation/startup
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,56 @@ function rejectDeparture(trip)
delayedWriteHeader()
end

function formatTags(tags)
local parts = {}
for tag, enabled in pairs(tags) do
local part = tag
if not enabled then
part = "NOT " .. part
end
table.insert(parts, part)
end
if next(parts) then
return table.concat(parts, ", ")
end
return "normal"
end

function formatRouteTags(tags)
local parts = {}
for _, tags in ipairs(tags) do
table.insert(parts, formatTags(tags))
end
return table.concat(parts, "; ")
end

function selectDepartureRoute(trip)
if not trip.routes then
handleDeparture(trip)
return
end
local routeOptions = {}
for tags, route in pairs(trip.routes) do
table.insert(routeOptions, tags)
end
if #routeOptions == 1 then
trip.routes = nil
handleDeparture(trip)
return
end
local selected = input.menu({
prompt = "Select your route:",
items = routeOptions,
formatter = formatRouteTags
})
if not selected then
return
end
trip.routes = nil
trip.tags = selected[1]
handleDeparture(trip)
end

function handleDeparture(trip)
if trip.type ~= "passenger" then
print(string.format("ERROR: Unrecognized departure type %s; ignoring", trip.type))
Expand Down Expand Up @@ -183,9 +233,9 @@ function delayedWriteHeader()
end

function handleStationUpdate(data)
print("Updating station list...")
stations = data
serializer.writeToFile("stations", stations)
print("Station list updated.")
end

function minecartDetected(eventName, detector, minecartType, minecartName)
Expand Down Expand Up @@ -218,7 +268,7 @@ end

function onStartup()
autoupdater.initialize()
net.registerMessageHandler("allowDeparture", handleDeparture)
net.registerMessageHandler("allowDeparture", selectDepartureRoute)
net.registerMessageHandler("rejectDeparture", rejectDeparture)
net.registerMessageHandler("stationUpdate", handleStationUpdate)
minecartevents.registerMinecartHandler(minecartDetected)
Expand Down

0 comments on commit 9801bac

Please sign in to comment.