2023-09-13 17:49:53 +00:00
|
|
|
--[[
|
|
|
|
A central server to coordinate a rail network. This central server keeps a
|
|
|
|
graph of the entire network, and handles requests to find paths to route
|
|
|
|
traffic through.
|
|
|
|
]]--
|
|
|
|
|
2023-09-14 15:09:26 +00:00
|
|
|
local RECEIVE_CHANNEL = 45452
|
2023-09-13 17:49:53 +00:00
|
|
|
|
2023-09-17 11:44:47 +00:00
|
|
|
local g = require("simple-graphics")
|
|
|
|
local W, H = term.getSize()
|
|
|
|
local console = g.createConsole(W, H-2, colors.white, colors.black, "UP")
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
-- ONLY FOR DEBUGGING
|
|
|
|
-- inspect = require("inspect")
|
2023-09-14 23:06:16 +00:00
|
|
|
local modem = peripheral.wrap("top") or error("Missing top modem")
|
|
|
|
modem.open(RECEIVE_CHANNEL)
|
2023-09-13 17:49:53 +00:00
|
|
|
|
2023-09-17 11:44:47 +00:00
|
|
|
local function logToConsole(text)
|
|
|
|
local timestamp = os.date("%F %T")
|
|
|
|
g.appendAndDrawConsole(term, console, timestamp.." "..text, 1, 3)
|
|
|
|
end
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
local function generateStandardNode(id, edgeIds)
|
|
|
|
local node = {id = id, connections = {}, type = "JUNCTION"}
|
|
|
|
for _, edgeId in pairs(edgeIds) do
|
|
|
|
for _, edgeId2 in pairs(edgeIds) do
|
|
|
|
if edgeId2 ~= edgeId then
|
|
|
|
table.insert(node.connections, {from = edgeId, to = edgeId2})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return node
|
|
|
|
end
|
|
|
|
|
2023-09-14 23:06:16 +00:00
|
|
|
local function generateStationNode(id, displayName, edgeId)
|
2023-09-14 14:48:55 +00:00
|
|
|
return {
|
|
|
|
id = id,
|
2023-09-14 23:06:16 +00:00
|
|
|
displayName = displayName,
|
2023-09-14 14:48:55 +00:00
|
|
|
connections = {
|
|
|
|
{from = nil, to = edgeId},
|
|
|
|
{from = edgeId, to = nil}
|
|
|
|
},
|
|
|
|
type = "STATION"
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local function loadGraph()
|
|
|
|
-- local g = nil
|
|
|
|
-- local f = io.open("network_graph.tbl", "r")
|
|
|
|
-- g = textutils.unserialize(f:read("*a"))
|
|
|
|
-- f:close()
|
2023-09-13 17:49:53 +00:00
|
|
|
--return g
|
2023-09-14 23:06:16 +00:00
|
|
|
local tempGraph = {
|
2023-09-13 17:49:53 +00:00
|
|
|
nodes = {
|
2023-09-14 14:48:55 +00:00
|
|
|
generateStandardNode("Junction-HandieVale", {"handievale", "N1", "W1"}),
|
|
|
|
generateStandardNode("Junction-Middlecross", {"W1", "N2", "W2", "S1"}),
|
|
|
|
generateStandardNode("Junction-Foundry", {"E1", "N3", "W3", "N2"}),
|
|
|
|
generateStandardNode("Junction-End", {"E1", "E2", "end"}),
|
|
|
|
generateStandardNode("Junction-Klausville", {"N3", "N4", "klausville"}),
|
|
|
|
generateStandardNode("Junction-Foundry-West", {"W3", "foundry", "W4"}),
|
2023-09-17 11:44:47 +00:00
|
|
|
generateStandardNode("Junction-Cam", {"S1", "S2", "cam"}),
|
2023-09-14 23:06:16 +00:00
|
|
|
generateStationNode("station-klausville", "Klausville", "klausville"),
|
|
|
|
generateStationNode("station-handievale", "HandieVale", "handievale"),
|
|
|
|
generateStationNode("station-end", "End & Biofuel Refinery", "end"),
|
2023-09-17 11:44:47 +00:00
|
|
|
generateStationNode("station-foundry", "Jack's Foundry", "foundry"),
|
|
|
|
generateStationNode("station-cam", "Camville", "cam")
|
2023-09-13 17:49:53 +00:00
|
|
|
},
|
|
|
|
edges = {
|
2023-09-14 14:48:55 +00:00
|
|
|
{id = "handievale", length = 16},
|
|
|
|
{id = "end", length = 48},
|
|
|
|
{id = "foundry", length = 45},
|
|
|
|
{id = "klausville", length = 12},
|
2023-09-17 11:44:47 +00:00
|
|
|
{id = "cam", length = 16},
|
2023-09-14 14:48:55 +00:00
|
|
|
{id = "N1", length = nil},
|
|
|
|
{id = "W1", length = 300},
|
|
|
|
{id = "N2", length = 600},
|
|
|
|
{id = "E1", length = 75},
|
|
|
|
{id = "W2", length = nil},
|
2023-09-17 11:44:47 +00:00
|
|
|
{id = "S1", length = 420},
|
|
|
|
{id = "S2", length = nil},
|
2023-09-14 14:48:55 +00:00
|
|
|
{id = "W3", length = 50},
|
|
|
|
{id = "W4", length = nil},
|
|
|
|
{id = "N3", length = 350},
|
|
|
|
{id = "N4", length = nil}
|
2023-09-13 17:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-14 23:06:16 +00:00
|
|
|
return tempGraph
|
2023-09-13 17:49:53 +00:00
|
|
|
end
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
local function filterTable(arr, func)
|
|
|
|
local new_index = 1
|
|
|
|
local size_orig = #arr
|
|
|
|
for old_index, v in ipairs(arr) do
|
|
|
|
if func(v, old_index) then
|
|
|
|
arr[new_index] = v
|
|
|
|
new_index = new_index + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for i = new_index, size_orig do arr[i] = nil end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function findNodeById(graph, nodeId)
|
2023-09-13 17:49:53 +00:00
|
|
|
for _, node in pairs(graph.nodes) do
|
|
|
|
if node.id == nodeId then return node end
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
local function findEdgeById(graph, edgeId)
|
2023-09-13 17:49:53 +00:00
|
|
|
for _, edge in pairs(graph.edges) do
|
|
|
|
if edge.id == edgeId then return edge end
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
local function findEdgeBetweenNodes(graph, fromNode, toNode)
|
|
|
|
local edgeIdsFrom = {}
|
|
|
|
for _, conn in pairs(fromNode.connections) do
|
|
|
|
if conn.to and not tableContains(edgeIdsFrom, conn.to) then
|
|
|
|
table.insert(edgeIdsFrom, conn.to)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local edgeIds = {}
|
|
|
|
for _, conn in pairs(toNode.connections) do
|
|
|
|
if conn.from and tableContains(edgeIdsFrom, conn.from) and not tableContains(edgeIds, conn.from) then
|
|
|
|
table.insert(edgeIds, conn.from)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
local function tableContains(table, value)
|
|
|
|
for _, item in pairs(table) do
|
|
|
|
if item == value then return true end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local function removeElement(table, value)
|
|
|
|
local idx = nil
|
|
|
|
for i, item in pairs(table) do
|
|
|
|
if item == value then idx = i break end
|
|
|
|
end
|
|
|
|
if idx then table.remove(table, idx) end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function findNextEdges(graph, edgeId)
|
2023-09-13 17:49:53 +00:00
|
|
|
local edges = {}
|
|
|
|
for _, node in pairs(graph.nodes) do
|
|
|
|
for _, connection in pairs(node.connections) do
|
|
|
|
if connection.from == edgeId then
|
|
|
|
table.insert(edges, findEdgeById(connection.to))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return edges
|
|
|
|
end
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
-- Find the set of nodes directly connected to this one via some edges
|
|
|
|
local function findConnectedNodes(graph, startNode)
|
|
|
|
local edges = {}
|
|
|
|
local edgeIds = {}
|
|
|
|
for _, conn in pairs(startNode.connections) do
|
|
|
|
if conn.to ~= nil then
|
|
|
|
local edge = findEdgeById(graph, conn.to)
|
|
|
|
if edge ~= nil and edge.length ~= nil and not tableContains(edgeIds, edge.id) then
|
|
|
|
table.insert(edges, edge)
|
|
|
|
table.insert(edgeIds, edge.id)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local connections = {}
|
|
|
|
for _, edge in pairs(edges) do
|
|
|
|
for _, node in pairs(graph.nodes) do
|
|
|
|
if node.id ~= startNode.id then
|
|
|
|
for _, conn in pairs(node.connections) do
|
|
|
|
if conn.from == edge.id then
|
|
|
|
table.insert(connections, {node = node, distance = edge.length, via = edge.id})
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return connections
|
2023-09-13 17:49:53 +00:00
|
|
|
end
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
local function findPath(graph, startNode, endNode)
|
|
|
|
local INFINITY = 1000000000
|
|
|
|
local dist = {}
|
|
|
|
local prev = {}
|
|
|
|
local queue = {}
|
|
|
|
for _, node in pairs(graph.nodes) do
|
|
|
|
dist[node.id] = INFINITY
|
|
|
|
prev[node.id] = nil
|
|
|
|
table.insert(queue, node)
|
|
|
|
end
|
|
|
|
dist[startNode.id] = 0
|
|
|
|
|
|
|
|
while #queue > 0 do
|
|
|
|
local minIdx = nil
|
|
|
|
local minDist = INFINITY + 1
|
|
|
|
for i, node in pairs(queue) do
|
|
|
|
if dist[node.id] < minDist then
|
|
|
|
minIdx = i
|
|
|
|
minDist = dist[node.id]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if minIdx == nil then return nil end
|
|
|
|
local u = table.remove(queue, minIdx)
|
|
|
|
if u.id == endNode.id and (prev[u.id] or u.id == startNode.id) then
|
|
|
|
local s = {}
|
|
|
|
while u ~= nil do
|
|
|
|
local via = nil
|
|
|
|
local distance = nil
|
|
|
|
local node = u
|
|
|
|
u = nil
|
|
|
|
if prev[node.id] then
|
|
|
|
via = prev[node.id].via
|
|
|
|
distance = prev[node.id].distance
|
|
|
|
u = prev[node.id].node
|
|
|
|
end
|
|
|
|
table.insert(s, 1, {node = node, via = via, distance = distance})
|
|
|
|
end
|
|
|
|
return s
|
|
|
|
end
|
|
|
|
for _, neighbor in pairs(findConnectedNodes(graph, u)) do
|
|
|
|
local unvisited = false
|
|
|
|
for _, node in pairs(queue) do
|
|
|
|
if node.id == neighbor.node.id then
|
|
|
|
unvisited = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if unvisited then
|
|
|
|
local alt = dist[u.id] + neighbor.distance
|
|
|
|
if alt < dist[neighbor.node.id] then
|
|
|
|
dist[neighbor.node.id] = alt
|
|
|
|
prev[neighbor.node.id] = {node = u, via = neighbor.via, distance = neighbor.distance}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
2023-09-13 17:49:53 +00:00
|
|
|
|
2023-09-14 15:09:26 +00:00
|
|
|
local function getReachableStations(graph, startNode)
|
|
|
|
local queue = findConnectedNodes(graph, startNode)
|
|
|
|
local stations = {}
|
|
|
|
local visitedNodeIds = {startNode.id}
|
|
|
|
while #queue > 0 do
|
|
|
|
local node = table.remove(queue, 1).node
|
|
|
|
if node.type == "STATION" and not tableContains(visitedNodeIds, node.id) then
|
|
|
|
table.insert(stations, node)
|
|
|
|
end
|
|
|
|
table.insert(visitedNodeIds, node.id)
|
|
|
|
for _, conn in pairs(findConnectedNodes(graph, node)) do
|
|
|
|
if not tableContains(visitedNodeIds, conn.node.id) then
|
|
|
|
table.insert(queue, conn)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return stations
|
|
|
|
end
|
|
|
|
|
2023-09-14 14:48:55 +00:00
|
|
|
local function handleRouteRequest(graph, replyChannel, msg)
|
|
|
|
if not msg.startNode or not msg.endNode then
|
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Invalid request"})
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local startNode = findNodeById(graph, msg.startNode)
|
|
|
|
local endNode = findNodeById(graph, msg.endNode)
|
|
|
|
if not startNode or not endNode then
|
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Unknown node(s)"})
|
|
|
|
return
|
2023-09-13 17:49:53 +00:00
|
|
|
end
|
2023-09-17 11:44:47 +00:00
|
|
|
logToConsole("Finding path from "..startNode.id.." to "..endNode.id.."...")
|
2023-09-14 14:48:55 +00:00
|
|
|
local path = findPath(graph, startNode, endNode)
|
|
|
|
if not path then
|
2023-09-17 11:44:47 +00:00
|
|
|
logToConsole("Couldn't find a path!")
|
2023-09-14 14:48:55 +00:00
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "No valid path"})
|
|
|
|
return
|
|
|
|
end
|
2023-09-17 11:44:47 +00:00
|
|
|
|
|
|
|
local pathStr = ""
|
|
|
|
for i, segment in pairs(path) do
|
|
|
|
pathStr = pathStr .. segment.node.id
|
|
|
|
if i < #path - 1 then pathStr = pathStr .. ", " end
|
|
|
|
end
|
|
|
|
logToConsole("Found path: "..pathStr)
|
2023-09-14 14:48:55 +00:00
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = true, route = path})
|
2023-09-13 17:49:53 +00:00
|
|
|
end
|
|
|
|
|
2023-09-14 15:09:26 +00:00
|
|
|
local function handleGetRoutesRequest(graph, replyChannel, msg)
|
|
|
|
if not msg.startNode then
|
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Invalid request"})
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local startNode = findNodeById(graph, msg.startNode)
|
|
|
|
if not startNode then
|
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Unknown node"})
|
|
|
|
return
|
|
|
|
end
|
2023-09-17 11:44:47 +00:00
|
|
|
logToConsole("Finding reachable stations from "..startNode.id.."...")
|
2023-09-14 15:09:26 +00:00
|
|
|
local stations = getReachableStations(graph, startNode)
|
2023-09-17 11:44:47 +00:00
|
|
|
logToConsole("Found "..#stations.." results.")
|
2023-09-14 15:09:26 +00:00
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = true, stations = stations})
|
|
|
|
end
|
|
|
|
|
2023-09-17 11:44:47 +00:00
|
|
|
local function handleRequests(graph, console)
|
2023-09-13 17:49:53 +00:00
|
|
|
while true do
|
|
|
|
local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message")
|
2023-09-14 14:48:55 +00:00
|
|
|
if channel == RECEIVE_CHANNEL and msg and msg.command and type(msg.command) == "string" then
|
2023-09-17 11:44:47 +00:00
|
|
|
logToConsole("Got request on CH: "..channel..", RCH: "..replyChannel..", CMD: "..msg.command)
|
2023-09-14 14:48:55 +00:00
|
|
|
if msg.command == "ROUTE" then
|
|
|
|
handleRouteRequest(graph, replyChannel, msg)
|
2023-09-14 15:09:26 +00:00
|
|
|
elseif msg.command == "GET_ROUTES" then
|
|
|
|
handleGetRoutesRequest(graph, replyChannel, msg)
|
2023-09-14 14:48:55 +00:00
|
|
|
else
|
|
|
|
modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Invalid command"})
|
|
|
|
end
|
2023-09-13 17:49:53 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-17 11:44:47 +00:00
|
|
|
g.clear(term, colors.black)
|
|
|
|
g.drawTextCenter(term, W/2, 1, "CC-Rail Central Server", colors.yellow, colors.black)
|
|
|
|
g.drawXLine(term, 1, W, 2, colors.gray)
|
|
|
|
logToConsole("Now taking requests.")
|
|
|
|
handleRequests(loadGraph(), console)
|