From dfd8665920047e146f92f7d8ba0821faf3d4b909 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 14 Sep 2023 10:48:55 -0400 Subject: [PATCH] Added proper graph implementation in central_server.lua --- central_server.lua | 267 +++++++++++++++++++++++++++++++++++++-------- station.lua | 6 +- 2 files changed, 223 insertions(+), 50 deletions(-) diff --git a/central_server.lua b/central_server.lua index 9027ed9..baf0ffa 100644 --- a/central_server.lua +++ b/central_server.lua @@ -6,68 +6,130 @@ traffic through. local RECEIVE_CHANNEL = 45453 +-- ONLY FOR DEBUGGING +-- inspect = require("inspect") local modem = peripheral.wrap("top") or error("Missing top modem") modem.open(RECEIVE_CHANNEL) -local loadGraph() - local g = nil - local f = io.open("network_graph.tbl", "r") - g = textutils.unserialize(f:read("*a")) - f:close() +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 + +local function generateStationNode(id, edgeId) + return { + id = id, + 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() --return g return { nodes = { - { - id = "Junction-HandieVale", - connections = { - {from = "handievale", to = "N1"}, - {from = "N1", to = "handievale"}, - {from = "handievale", to = "W1"}, - {from = "W1", to = "handievale"}, - {from = "N1", to = "W1"}, - {from = "W1", to = "N1"} - } - }, - { - id = "Junction-Middlecross", - connections = { - {from = "W1", to = "W2"}, - {from = "W2", to = "W1"}, - {from = "N2", to = "S1"}, - {from = "S1", to = "N2"} - } - } + 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"}), + generateStationNode("station-klausville", "klausville"), + generateStationNode("station-handievale", "handievale"), + generateStationNode("station-end", "end"), + generateStationNode("station-foundry", "foundry") }, edges = { - { - {id = "handievale", length = 16}, - {id = "N1", length = -1}, - {id = "W1", length = 300}, - {id = "N2", length = 600}, - {id = "E1", length = 75}, - {id = "end", length = 60}, - {id = "W2", length = -1}, - {id = "S1", length = -1} - } + {id = "handievale", length = 16}, + {id = "end", length = 48}, + {id = "foundry", length = 45}, + {id = "klausville", length = 12}, + {id = "N1", length = nil}, + {id = "W1", length = 300}, + {id = "N2", length = 600}, + {id = "E1", length = 75}, + {id = "W2", length = nil}, + {id = "S1", length = nil}, + {id = "W3", length = 50}, + {id = "W4", length = nil}, + {id = "N3", length = 350}, + {id = "N4", length = nil} } } end -local findNodeById(graph, nodeId) +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) for _, node in pairs(graph.nodes) do if node.id == nodeId then return node end end return nil end -local findEdgeById(graph, edgeId) +local function findEdgeById(graph, edgeId) for _, edge in pairs(graph.edges) do if edge.id == edgeId then return edge end end return nil end -local findNextEdges(graph, edgeId) +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) local edges = {} for _, node in pairs(graph.nodes) do for _, connection in pairs(node.connections) do @@ -79,23 +141,138 @@ local findNextEdges(graph, edgeId) return edges end -local findPath(graph, nodeA, nodeB) - +-- 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 end -local handleRequest(graph, replyChannel, msg) - if msg.command == "ROUTE" then - +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 + +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 + end + local path = findPath(graph, startNode, endNode) + if not path then + modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "No valid path"}) + return + end + modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = true, route = path}) end local function handleRequests(graph) while true do local event, side, channel, replyChannel, msg, dist = os.pullEvent("modem_message") - if channel == RECEIVE_CHANNEL then - handleRequest(graph, replyChannel, msg) + if channel == RECEIVE_CHANNEL and msg and msg.command and type(msg.command) == "string" then + if msg.command == "ROUTE" then + handleRouteRequest(graph, replyChannel, msg) + else + modem.transmit(replyChannel, RECEIVE_CHANNEL, {success = false, error = "Invalid command"}) + end end end end handleRequests(loadGraph()) + +-- local graph = loadGraph() +-- print("GRAPH:") +-- print(inspect(graph)) +-- local startNode = findNodeById(graph, "station-handievale") +-- local endNode = findNodeById(graph, "station-foundry") +-- print("\n\nPATH:") +-- local path = findPath(graph, startNode, endNode) +-- if path then +-- print("Found path!") +-- for i, element in pairs(path) do +-- print(i..". "..element.node.id.." via edge "..inspect(element.via).." @ "..inspect(element.distance)) +-- end +-- end diff --git a/station.lua b/station.lua index 8b3cd36..460a992 100644 --- a/station.lua +++ b/station.lua @@ -6,11 +6,7 @@ You should add a "station_config.tbl" file containing: { name = "stationname", displayName = "Station Name", - range = 8, - routes = { - {name = "First", path = {"A", "B", "C"}}, - {name = "Second", path = {"D", "A", "C"}} - } + range = 8 } ]]--