From 447f30551abb5b21740d22d453bc20837c5f7983 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Tue, 21 May 2024 01:26:37 +0200 Subject: layout processor --- tools/layout.svg | 2906 +++++++++++++++++++++++++++++++++++++++++++++++++ tools/parse-layout.js | 466 ++++++++ 2 files changed, 3372 insertions(+) create mode 100644 tools/layout.svg create mode 100644 tools/parse-layout.js diff --git a/tools/layout.svg b/tools/layout.svg new file mode 100644 index 0000000..fd2a090 --- /dev/null +++ b/tools/layout.svg @@ -0,0 +1,2906 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/parse-layout.js b/tools/parse-layout.js new file mode 100644 index 0000000..b382849 --- /dev/null +++ b/tools/parse-layout.js @@ -0,0 +1,466 @@ +"use strict" + +/* COMMON PARSING */ + +const fs = require("fs") + +let points = {} +let circles = {} +let rects = {} +let edges = {} +let labels = [] + +let mode, name, x, y, w, h, cx, cy, rx, ry, x2, y2 + +function array_insert(array, index, item) { + for (let i = array.length; i > index; --i) + array[i] = array[i - 1] + array[index] = item +} + +function set_add(set, item) { + let a = 0 + let b = set.length - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = set[m] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else + return + } + array_insert(set, a, item) +} + +function add_point(x, y) { + if (!(name in points)) + points[name] = [] + points[name].push({x,y}) +} + +function add_circle(cx, cy, rx, ry) { + if (!(name in circles)) + circles[name] = [] + circles[name].push({x:cx-rx,y:cy-ry,w:rx*2,h:ry*2}) +} + +function add_rect(x, y, w, h) { + if (!(name in rects)) + rects[name] = [] + rects[name].push({x,y,w,h}) +} + +function add_edge(x1, y1, x2, y2) { + if (!(name in edges)) + edges[name] = [] + edges[name].push({x1,y1,x2,y2}) +} + +function flush() { + if (mode === 'path') { + add_edge(x, y, x2, y2) + } + if (mode === 'rect') { + add_rect(x, y, w, h) + add_point(x + w/2, y + h/2) + } + if (mode === 'circle') { + add_circle(cx, cy, rx, ry) + add_point(cx, cy) + } + x = y = x2 = y2 = w = h = cx = cy = rx = ry = 0 +} + +function parse_path_data(path) { + let cx = 0 + let cy = 0 + let abs = 0 + for (let i = 0; i < path.length;) { + switch (path[i]) { + case 'M': + x2 = x = cx = Number(path[i+1]) + y2 = y = cy = Number(path[i+2]) + i += 3 + abs = true + break + case 'm': + x2 = x = cx = cx + Number(path[i+1]) + y2 = y = cy = cy + Number(path[i+2]) + i += 3 + abs = false + break + case 'C': + x2 = cx = Number(path[i+5]) + y2 = cy = Number(path[i+6]) + i += 7 + abs = true + break + case 'L': + i += 1 + abs = true + break + case 'H': + x2 = cx = Number(path[i+1]) + i += 2 + abs = true + break + case 'V': + y2 = cy = Number(path[i+1]) + i += 2 + abs = true + break + case 'c': + x2 = cx = cx + Number(path[i+5]) + y2 = cy = cy + Number(path[i+6]) + i += 7 + abs = false + break + case 'l': + i += 1 + abs = false + break + case 'h': + x2 = cx = cx + Number(path[i+1]) + i += 2 + abs = false + break + case 'v': + y2 = cy = cy + Number(path[i+1]) + i += 2 + abs = false + break + default: + if (abs) { + x2 = cx = Number(path[i+0]) + y2 = cy = Number(path[i+1]) + } else { + x2 = cx = cx + Number(path[i+0]) + y2 = cy = cy + Number(path[i+1]) + } + i += 2 + break + } + } +} + +for (let line of fs.readFileSync("tools/layout.svg", "utf-8").split("\n")) { + line = line.trim() + if (line.startsWith("")) { + let name = line.replace(/^[^>]*>/, "").replace(/<\/tspan.*/, "") + labels.push({x, y, name}) + } +} + +flush() + +function find_closest_label(x, y) { + let nd = Infinity, nn = null + + for (let n of labels) { + let d = Math.hypot(n.x - x, n.y - y) + if (d < nd) { + nd = d + nn = n + } + } + + if (!nn) { + console.log("NOT FOUND", x, y) + return null + } + + if (nd > 50) { + console.log("NO LABEL", x, y, nd) + return null + } + + return nn.name +} + +function find_closest_node(nodes, x, y) { + let nd = Infinity, nn = -1 + + for (let i = 0; i < nodes.length; ++i) { + let n = nodes[i] + let d = Math.hypot(n.x - x, n.y - y) + if (d < nd) { + nd = d + nn = i + } + } + + if (nd > 100) + return -1 + + return nn +} + +function label_boxes(top) { + for (let key in top) { + console.log("BOX", key) + for (let item of top[key]) + item.name = find_closest_label(item.x + item.w/2, item.y + item.h/2) + } +} + +function label_points(top) { + for (let key in top) { + console.log("POINT", key) + for (let item of top[key]) + item.name = find_closest_label(item.x, item.y) + } +} + +label_boxes(rects) +label_boxes(circles) +label_points(points) + +function connect_edge_2way(node_list, edge_name) { + console.log("EDGE", edge_name) + for (let e of edges[edge_name]) { + let a = find_closest_node(node_list, e.x1, e.y1) + let b = find_closest_node(node_list, e.x2, e.y2) + if (a < 0 || b < 0) + console.log("CANNOT FIND EDGE", e, node_list[a], node_list[b]) + if (!node_list[a][edge_name]) + node_list[a][edge_name] = [] + if (!node_list[b][edge_name]) + node_list[b][edge_name] = [] + set_add(node_list[a][edge_name], b) + set_add(node_list[b][edge_name], a) + } +} + +function connect_edge_1way(a_list, b_list, edge_name, prop_name) { + console.log("EDGE", edge_name) + for (let e of edges[edge_name]) { + let a1 = find_closest_node(a_list, e.x1, e.y1) + let a2 = find_closest_node(a_list, e.x2, e.y2) + let b1 = find_closest_node(b_list, e.x1, e.y1) + let b2 = find_closest_node(b_list, e.x2, e.y2) + let a = a1 >= 0 ? a1 : a2 + let b = b1 >= 0 ? b1 : b2 + if (a < 0 || b < 0) + console.log("CANNOT FIND EDGE", e, a_list[a], b_list[b]) + if (!a_list[a][prop_name]) + a_list[a][prop_name] = [] + set_add(a_list[a][prop_name], b) + } +} + +/* WASHINGTON'S WAR */ + +function sort_alpha(list) { + list.sort((a,b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0) +} + +sort_alpha(points.fortress) +sort_alpha(points.winter_quarters) +sort_alpha(points.space) +points.colony.reverse() + +const data = {} + +data.spaces = [ ...points.fortress, ...points.winter_quarters, ...points.space ] +data.colonies = [ ...points.colony ] +data.seas = [ ...points.sea ] + +connect_edge_2way(data.spaces, "path") +connect_edge_2way(data.spaces, "wilderness") +connect_edge_1way(data.colonies, data.spaces, "colony", "spaces") +connect_edge_1way(data.seas, data.spaces, "sea", "spaces") + +console.log(JSON.stringify(data,0,4)) + +/* +function find_closest_node(list, x, y) { + let nd = Infinity, nn = null + + for (let n of list) { + let d = Math.hypot(n.x - x, n.y - y) + if (d < nd) { + nd = d + nn = n + } + } + + if (!nn) { + console.log("NOT FOUND", x, y) + return [ null, 0 ] + } + + return [ nn, nd ] +} + +function find_enclosing_rect(list, x, y) { + for (let [x1, y1, x2, y2] of list) { + if (x >= x1 && x <= x2) + if (y >= y1 && y <= y2) + return true + } + return false +} + + +function make_spaces(points) { + let spaces = [] + for (let [x,y] of points) { + let [ name, dist ] = find_closest_node(labels, x, y) + if (dist > 50) console.log("DISTANCE TOO FAR", x, y, dist) + + spaces.push({ name: name.name, x, y }) + } + return spaces +} + +let data = {} + +data.spaces = make_spaces([ ...points.fortress, ...points.winter_quarters, ...points.space ]) + +console.log(data) + +function find_closest_point(x, y) { + let nd = Infinity, nn = -1 + + for (let i = 0; i < points.length; ++i) { + let n = points[i] + let d = Math.hypot(n.x - x, n.y - y) + if (d < nd) { + nd = d + nn = i + } + } + + return nn +} + +// FIND and label all points! +let all_labels = labels.slice() +let cities = [] +for (let key in points) { + for (let [x, y] of points[key]) { + let [ node, dist ] = find_closest_node(labels, x, y) + if (dist > 15) { + console.log("DISTANCE TOO FAR", key,x,y, "dist=" + dist, "name=" + node.name) + } + if (node) { + labels = labels.filter(x => x !== node) + let suit = "UNKNOWN" + + if (find_enclosing_rect(rects.$CLUBS, x, y)) + suit = CLUBS + else if (find_enclosing_rect(rects.$HEARTS, x, y)) + suit = HEARTS + else if (find_enclosing_rect(rects.$DIAMONDS, x, y)) + suit = DIAMONDS + else if (find_enclosing_rect(rects.$SPADES, x, y)) + suit = SPADES + else + console.log("NOT ASSIGNED SUIT", x, y) + + let country = "UNKNOWN" + if (find_enclosing_rect(rects.$Empire, x, y)) { + country = EMPIRE + } + else if (find_enclosing_rect(rects.$Austria, x, y)) { + country = AUSTRIA + } + else if (find_enclosing_rect(rects.$Hanover, x, y)) { + country = HANOVER + } + else if (find_enclosing_rect(rects.$Saxony, x, y)) { + country = SAXONY + } + else if (find_enclosing_rect(rects.$Sweden, x, y)) { + country = SWEDEN + } + else if (find_enclosing_rect(rects.$Poland, x, y)) { + country = POLAND + } + else if (find_enclosing_rect(rects.$Prussia, x, y)) { + country = PRUSSIA + } + + if (country === "UNKNOWN") + console.log("no country:", node) + + cities.push({ + name: node.name, + country, + suit, + type: key, + x: Math.round(x), + y: Math.round(y), + adjacent: [], + major_roads: [], + roads: [], + }) + } else { + let [ dupname, dupdist ] = find_closest_node(all_labels, x, y) + console.log("ALREADY USED", dupname, dupdist, x, y) + } + } +} + +for (let e of edges.major_road) { + let a = find_closest_point(e.x1, e.y1) + let b = find_closest_point(e.x2, e.y2) + set_add(cities[a].major_roads, b) + set_add(cities[b].major_roads, a) + set_add(cities[a].adjacent, b) + set_add(cities[b].adjacent, a) +} + +for (let e of edges.road) { + let a = find_closest_point(e.x1, e.y1) + let b = find_closest_point(e.x2, e.y2) + set_add(cities[a].roads, b) + set_add(cities[b].roads, a) + set_add(cities[a].adjacent, b) + set_add(cities[b].adjacent, a) +} + +console.log("if (typeof module === 'object') module.exports = data") +*/ -- cgit v1.2.3