///|
priv struct StringGraphData {
nodes : Array[String]
arcs : Array[Arc[String]]
} derive(ToJson, FromJson)
///|
priv struct GridData {
width : Int
height : Int
blocked : Array[Point]
terrain : Array[CellCost]
} derive(ToJson, FromJson)
///|
pub fn Graph::to_json(self : Graph[String]) -> Json {
StringGraphData::{
nodes: sorted_strings(self.nodes()),
arcs: sorted_string_arcs(self.arcs()),
}.to_json()
}
///|
pub fn Graph::to_json_string(self : Graph[String]) -> String {
self.to_json().stringify(indent=2)
}
///|
pub fn Graph::from_json_string(text : String) -> Graph[String]? {
try {
let json = @json.parse(text)
let data : StringGraphData = @json.from_json(json)
graph_from_data(data)
} catch {
_ => None
}
}
///|
pub fn Graph::to_text(self : Graph[String]) -> String {
let out = StringBuilder()
out.write_string("# moonpath graph v1\n")
for node in sorted_strings(self.nodes()) {
out.write_string("node\t")
out.write_string(node.to_json().stringify())
out.write_char('\n')
}
for arc in sorted_string_arcs(self.arcs()) {
out.write_string("arc\t")
out.write_string(arc.from.to_json().stringify())
out.write_char('\t')
out.write_string(arc.to.to_json().stringify())
out.write_char('\t')
out.write_object(arc.cost)
out.write_char('\n')
}
out.to_string()
}
///|
pub fn Graph::from_text(text : String) -> Graph[String]? {
let nodes : Array[String] = []
let arcs : Array[Arc[String]] = []
for raw in text.split("\n") {
let line = raw.trim()
if line.is_empty() || line.has_prefix("#") {
continue
}
let parts = line.split("\t").to_array()
if parts.length() == 2 && parts[0].equal_to_string("node") {
match parse_json_string(parts[1]) {
Some(node) => nodes.push(node)
None => return None
}
} else if parts.length() == 4 && parts[0].equal_to_string("arc") {
match
(
parse_json_string(parts[1]),
parse_json_string(parts[2]),
parse_int(parts[3]),
) {
(Some(from), Some(to), Some(cost)) => arcs.push(Arc::{ from, to, cost })
_ => return None
}
} else {
return None
}
}
graph_from_data(StringGraphData::{ nodes, arcs })
}
///|
pub fn Grid::to_json(self : Grid) -> Json {
GridData::{
width: self.width,
height: self.height,
blocked: sorted_points(self.blocked_points()),
terrain: sorted_cell_costs(self.terrain_cells()),
}.to_json()
}
///|
pub fn Grid::to_json_string(self : Grid) -> String {
self.to_json().stringify(indent=2)
}
///|
pub fn Grid::from_json_string(text : String) -> Grid? {
try {
let json = @json.parse(text)
let data : GridData = @json.from_json(json)
grid_from_data(data)
} catch {
_ => None
}
}
///|
pub fn Grid::to_text(self : Grid) -> String {
let out = StringBuilder()
out.write_string("# moonpath grid v1\n")
out.write_string("grid\t")
out.write_object(self.width)
out.write_char('\t')
out.write_object(self.height)
out.write_char('\n')
for point in sorted_points(self.blocked_points()) {
out.write_string("blocked\t")
out.write_object(point.x)
out.write_char('\t')
out.write_object(point.y)
out.write_char('\n')
}
for cell in sorted_cell_costs(self.terrain_cells()) {
out.write_string("terrain\t")
out.write_object(cell.point.x)
out.write_char('\t')
out.write_object(cell.point.y)
out.write_char('\t')
out.write_object(cell.cost)
out.write_char('\n')
}
out.to_string()
}
///|
pub fn Grid::from_text(text : String) -> Grid? {
let mut width : Int? = None
let mut height : Int? = None
let blocked : Array[Point] = []
let terrain : Array[CellCost] = []
for raw in text.split("\n") {
let line = raw.trim()
if line.is_empty() || line.has_prefix("#") {
continue
}
let parts = line.split("\t").to_array()
if parts.length() == 3 && parts[0].equal_to_string("grid") {
match (parse_int(parts[1]), parse_int(parts[2])) {
(Some(w), Some(h)) => {
width = Some(w)
height = Some(h)
}
_ => return None
}
} else if parts.length() == 3 && parts[0].equal_to_string("blocked") {
match (parse_int(parts[1]), parse_int(parts[2])) {
(Some(x), Some(y)) => blocked.push(Point::new(x, y))
_ => return None
}
} else if parts.length() == 4 && parts[0].equal_to_string("terrain") {
match (parse_int(parts[1]), parse_int(parts[2]), parse_int(parts[3])) {
(Some(x), Some(y), Some(cost)) =>
terrain.push(CellCost::{ point: Point::new(x, y), cost })
_ => return None
}
} else {
return None
}
}
match (width, height) {
(Some(w), Some(h)) =>
grid_from_data(GridData::{ width: w, height: h, blocked, terrain })
_ => None
}
}
///|
fn graph_from_data(data : StringGraphData) -> Graph[String]? {
match Graph::try_from_arcs(data.arcs) {
Some(graph) => {
for node in data.nodes {
graph.add_node(node)
}
Some(graph)
}
None => None
}
}
///|
fn grid_from_data(data : GridData) -> Grid? {
guard data.width >= 0 && data.height >= 0 else { return None }
for cell in data.terrain {
guard cell.cost >= 1 else { return None }
}
Some(Grid::from_parts(data.width, data.height, data.blocked, data.terrain))
}
///|
fn sorted_strings(items : Array[String]) -> Array[String] {
let out = items.copy()
out.sort()
out
}
///|
fn sorted_string_arcs(arcs : Array[Arc[String]]) -> Array[Arc[String]] {
let out = arcs.copy()
out.sort_by(fn(a, b) {
let from_cmp = a.from.lexical_compare(b.from)
if from_cmp != 0 {
from_cmp
} else {
let to_cmp = a.to.lexical_compare(b.to)
if to_cmp != 0 {
to_cmp
} else {
a.cost - b.cost
}
}
})
out
}
///|
fn sorted_points(points : Array[Point]) -> Array[Point] {
let out = points.copy()
out.sort_by(compare_points)
out
}
///|
fn sorted_cell_costs(cells : Array[CellCost]) -> Array[CellCost] {
let out = cells.copy()
out.sort_by(fn(a, b) {
let point_cmp = compare_points(a.point, b.point)
if point_cmp != 0 {
point_cmp
} else {
a.cost - b.cost
}
})
out
}
///|
fn compare_points(a : Point, b : Point) -> Int {
if a.y != b.y {
a.y - b.y
} else {
a.x - b.x
}
}
///|
fn parse_json_string(text : StringView) -> String? {
try {
let json = @json.parse(text)
let value : String = @json.from_json(json)
Some(value)
} catch {
_ => None
}
}
///|
fn parse_int(text : StringView) -> Int? {
Some(@string.parse_int(text)) catch {
_ => None
}
}