generated from mwc/lab_retro
163 lines
4.9 KiB
Python
163 lines
4.9 KiB
Python
from retro.errors import GraphError
|
|
|
|
class Graph:
|
|
def __init__(self, vertices=None, edges=None):
|
|
self.vertices = vertices or []
|
|
self.edges = edges or []
|
|
|
|
def __str__(self):
|
|
return '\n'.join(str(e) for e in self.edges)
|
|
|
|
def get_or_create_vertex(self, x, y):
|
|
for v in self.vertices:
|
|
if x == v.x and y == v.y:
|
|
return v
|
|
for e in self.edges:
|
|
if e.crosses(x, y):
|
|
return self.split_edge(e, x, y)
|
|
v = Vertex(x, y)
|
|
self.vertices.append(v)
|
|
return v
|
|
|
|
def get_or_create_edge(self, x0, y0, x1, y1):
|
|
v0 = self.get_or_create_vertex(x0, y0)
|
|
v1 = self.get_or_create_vertex(x1, y1)
|
|
new_edge = Edge(v0, v1)
|
|
for e in self.edges:
|
|
if e == new_edge:
|
|
new_edge.remove()
|
|
return e
|
|
return new_edge
|
|
|
|
def split_edge(self, edge, x, y):
|
|
"""
|
|
Splits an edge by inserting a new vertex along the edge.
|
|
"""
|
|
if not edge.crosses(x, y):
|
|
raise GraphError(f"Can't split edge {edge} at ({x}, {y})")
|
|
self.remove_edge(edge)
|
|
v = Vertex(x, y)
|
|
self.vertices.append(v)
|
|
self.edges.append(Edge(edge.begin, v))
|
|
self.edges.append(Edge(v, edge.end))
|
|
|
|
def remove_edge(self, edge):
|
|
if edge not in self.edges:
|
|
raise GraphError(f"Edge {edge} is not in the graph")
|
|
self.edges.remove(edge)
|
|
edge.begin.edges.remove(edge)
|
|
edge.end.edges.remove(edge)
|
|
|
|
def render(self, terminal):
|
|
for v in self.vertices:
|
|
v.render(terminal)
|
|
for e in self.edges:
|
|
e.render(terminal)
|
|
|
|
class Vertex:
|
|
CHARACTERS = {
|
|
"0000": " ",
|
|
"0001": "═",
|
|
"0010": "║",
|
|
"0011": "╗",
|
|
"0100": "═",
|
|
"0101": "═",
|
|
"0110": "╔",
|
|
"0111": "╦",
|
|
"1000": "║",
|
|
"1001": "╝",
|
|
"1010": "║",
|
|
"1011": "╣",
|
|
"1100": "╚",
|
|
"1101": "╩",
|
|
"1110": "╠",
|
|
"1111": "╬",
|
|
}
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
self.edges = []
|
|
|
|
def __str__(self):
|
|
return f"({self.x}, {self.y})"
|
|
|
|
def __eq__(self, other):
|
|
return self.x == other.x and self.y == other.y
|
|
|
|
def neighbors(self):
|
|
vertices = []
|
|
for edge in self.edges:
|
|
if self == edge.begin:
|
|
vertices.append(edge.end)
|
|
else:
|
|
vertices.append(edge.begin)
|
|
return vertices
|
|
|
|
def render(self, terminal):
|
|
print(terminal.move_xy(self.x, self.y) + self.get_character())
|
|
|
|
def get_character(self):
|
|
u = self.has_up_edge()
|
|
r = self.has_right_edge()
|
|
d = self.has_down_edge()
|
|
l = self.has_left_edge()
|
|
code = ''.join([str(int(direction)) for direction in [u, r, d, l]])
|
|
return self.CHARACTERS[code]
|
|
|
|
def has_up_edge(self):
|
|
return any([v.x == self.x and v.y < self.y for v in self.neighbors()])
|
|
|
|
def has_right_edge(self):
|
|
return any([v.y == self.y and self.x < v.x for v in self.neighbors()])
|
|
|
|
def has_down_edge(self):
|
|
return any([v.x == self.x and self.y < v.y for v in self.neighbors()])
|
|
|
|
def has_left_edge(self):
|
|
return any([v.y == self.y and v.x < self.x for v in self.neighbors()])
|
|
|
|
class Edge:
|
|
def __init__(self, begin, end):
|
|
if not isinstance(begin, Vertex) or not isinstance(end, Vertex):
|
|
raise ValueError("Tried to initialize an Edge with a non-vertex")
|
|
if begin.x < end.x or begin.y < end.y:
|
|
self.begin = begin
|
|
self.end = end
|
|
else:
|
|
self.begin = end
|
|
self.end = begin
|
|
if not (self.is_horizontal() or self.is_vertical()):
|
|
raise ValueError("Edges must be horizontal or vertical.")
|
|
if self.is_horizontal() and self.is_vertical():
|
|
raise ValueError("Self-edges are not allowed.")
|
|
self.begin.edges.append(self)
|
|
self.end.edges.append(self)
|
|
|
|
def __str__(self):
|
|
return f"{self.begin} -> {self.end}"
|
|
|
|
def render(self, terminal):
|
|
if self.is_horizontal():
|
|
with terminal.location(self.begin.x + 1, self.begin.y):
|
|
line = "═" * (self.end.x - self.begin.x - 1)
|
|
print(line)
|
|
else:
|
|
for y in range(self.begin.y + 1, self.end.y):
|
|
print(terminal.move_xy(self.begin.x, y) + "║")
|
|
|
|
def is_horizontal(self):
|
|
return self.begin.y == self.end.y
|
|
|
|
def is_vertical(self):
|
|
return self.begin.x == self.end.x
|
|
|
|
def crosses(self, x, y):
|
|
if self.is_horizontal():
|
|
return self.begin.y == y and self.begin.x < x and x < self.end.x
|
|
else:
|
|
return self.begin.x == x and self.begin.y < y and y < self.end.y
|
|
|
|
def remove(self):
|
|
self.begin.edges.remove(self)
|
|
self.end.edges.remove(self)
|