from amazeing.config.config_parser import Config
-from amazeing.maze_class.maze_coords import Cardinal, CellCoord
+from amazeing.maze_class import maze_network_tracker
+from amazeing.maze_class.maze_coords import Cardinal, CellCoord, WallCoord
from amazeing.maze_display.TTYdisplay import TileCycle, TileMaps, extract_pairs
from amazeing.maze_display.backend import CloseRequested, IVec2
from amazeing.utils import AVLTree
-tree = AVLTree()
-
-keys = {i: tree.append(i) for i in range(25)}
-
-for i in range(1, 5):
- keys[i].remove()
-for i in range(5, 15, 2):
- keys[i].remove()
-
-tree2 = AVLTree()
-
-keys2 = {i: tree2.append(i) for i in range(25)}
-
-for i in range(1, 10, 3):
- keys2[i].remove()
-
-tree.rjoin(tree2)
-
-print(tree)
-
-lhs_tree, rhs_tree = keys2[18].split_up()
-
-print(lhs_tree)
-print(rhs_tree)
+forest = maze_network_tracker.DualForest()
+for wall in CellCoord(0, 0).walls():
+ forest.fill_wall(wall)
+for wall in CellCoord(1, 0).walls():
+ forest.fill_wall(wall)
+for wall in CellCoord(3, 0).walls():
+ forest.fill_wall(wall)
+print(forest)
exit(0)
)
return (IVec2(x, y) for x in x_iter for y in y_iter)
- def neighbour_cells(self) -> list["CellCoord"]:
+ def neighbour_cells(self) -> tuple["CellCoord", "CellCoord"]:
if self.orientation == Orientation.HORIZONTAL:
- return [
+ return (
CellCoord(self.b, self.a),
CellCoord(self.b, self.a - 1),
- ]
- return [
+ )
+ return (
CellCoord(self.a, self.b),
CellCoord(self.a - 1, self.b),
- ]
+ )
+
+ def to_split_wall(
+ self,
+ ) -> tuple["SplitWall", "SplitWall"]:
+ def find_cardinal(cell: CellCoord) -> Cardinal:
+ for cardinal in Cardinal.all():
+ if cell.get_wall(cardinal) == self:
+ return cardinal
+ raise Exception("Find cardinal on wall not on cell")
+
+ a, b = self.neighbour_cells()
+ return ((a, find_cardinal(a)), (b, find_cardinal(b)))
class CellCoord(IVec2):
def neighbours_unchecked(self) -> Iterable["CellCoord"]:
return map(self.get_neighbour, Cardinal.all())
+
+
+type SplitWall = tuple[CellCoord, Cardinal]
+
+
+def split_wall_cw(wall: SplitWall) -> SplitWall:
+ return (wall[0].get_neighbour(wall[1]), wall[1].right())
+
+
+def split_wall_ccw(wall: SplitWall) -> SplitWall:
+ return (wall[0].get_neighbour(wall[1]), wall[1].left())
+
+
+def split_wall_opposite(wall: SplitWall) -> SplitWall:
+ return (wall[0].get_neighbour(wall[1]), wall[1].opposite())
from amazeing.maze_class.maze import Maze
-from amazeing.maze_class.maze_coords import CellCoord, WallCoord
+from amazeing.maze_class.maze_coords import (
+ Cardinal,
+ CellCoord,
+ SplitWall,
+ WallCoord,
+ split_wall_ccw,
+ split_wall_opposite,
+)
from amazeing.utils import BiMap
from amazeing.utils import AVLTree, AVLLeaf
pass
-class DualForest[T]:
+class DualForest:
"""
A forest of trees that contour networks
AVL trees are used to represent the contours, such that split and
def __init__(
self,
) -> None:
- self.__trees: set[AVLTree[T]] = set()
- self.__revmap: dict[T, set[AVLLeaf[T]]] = {}
+ # Trees are left hand chiral
+ self.__trees: set[AVLTree[SplitWall]] = set()
+ self.__revmap: dict[SplitWall, AVLLeaf[SplitWall]] = {}
+
+ def __repr__(self) -> str:
+ return (
+ f"DualForest ({len(self.__trees)}):\n trees:\n"
+ + f"{self.__trees}\n revmap:\n{self.__revmap}\n"
+ )
+
+ def fill_wall(self, wall: WallCoord) -> None:
+ lhs, rhs = wall.to_split_wall()
+ if lhs in self.__revmap or rhs in self.__revmap:
+ return
+ a_tree = AVLTree()
+ b_tree = AVLTree()
+ self.__revmap[lhs] = a_tree.append(lhs)
+ self.__revmap[rhs] = b_tree.append(rhs)
+
+ def find_split(split_wall: SplitWall) -> SplitWall | None:
+ split_wall = split_wall_opposite(split_wall)
+ for _ in range(3):
+ split_wall = split_wall_ccw(split_wall)
+ if split_wall in self.__revmap:
+ return split_wall
+ return None
+
+ match (find_split(lhs), find_split(rhs)):
+ case (None, None):
+ a_tree.rjoin(b_tree)
+ self.__trees.add(a_tree)
+ case (None, b_split):
+ self.__trees.remove(self.__revmap[b_split].root())
+ lhs, rhs = self.__revmap[b_split].split_up()
+ lhs.rjoin(a_tree)
+ lhs.rjoin(b_tree)
+ self.__revmap[b_split] = lhs.append(b_split)
+ lhs.rjoin(rhs)
+ self.__trees.add(lhs)
+ case (a_split, None):
+ self.__trees.remove(self.__revmap[a_split].root())
+ lhs, rhs = self.__revmap[a_split].split_up()
+ lhs.rjoin(b_tree)
+ lhs.rjoin(a_tree)
+ self.__revmap[a_split] = lhs.append(a_split)
+ lhs.rjoin(rhs)
+ self.__trees.add(lhs)
+ case (a_split, b_split):
+ if (
+ self.__revmap[a_split].root()
+ is self.__revmap[b_split].root()
+ ):
+ self.__trees.remove(self.__revmap[a_split].root())
+ lhs, rhs = self.__revmap[a_split].split_up()
+ lhs.rjoin(b_tree)
+ self.__revmap[a_split] = rhs.prepend(a_split)
+ rhs.ljoin(a_tree)
+ rhs.rjoin(lhs)
+ lhs, rhs = self.__revmap[b_split].split_up()
+ self.__revmap[b_split] = rhs.prepend(b_split)
+ self.__trees.add(lhs)
+ self.__trees.add(rhs)
+ else:
+ self.__trees.remove(self.__revmap[a_split].root())
+ self.__trees.remove(self.__revmap[b_split].root())
+ a_lhs, a_rhs = self.__revmap[a_split].split_up()
+ b_lhs, b_rhs = self.__revmap[b_split].split_up()
+ self.__revmap[a_split] = a_rhs.prepend(a_split)
+ self.__revmap[b_split] = b_rhs.prepend(b_split)
+ res = a_lhs
+ res.rjoin(b_tree)
+ res.rjoin(b_rhs)
+ res.rjoin(b_lhs)
+ res.rjoin(a_tree)
+ res.rjoin(a_rhs)
+ self.__trees.add(res)
+
+ def empty_wall(self, wall: WallCoord) -> None:
+ pass
class MazeNetworkTracker:
)
return cast(Leaf, self.root.rhs)
+ def prepend(self, value: T) -> "Leaf[T]":
+ if self.root is None:
+ leaf = Leaf(self, value)
+ self.root = leaf
+ return leaf
+ if isinstance(self.root, Branch):
+ return self.root.prepend(value)
+ self.root = Branch(
+ self,
+ lambda parent: Leaf(parent, value),
+ self.root.with_parent,
+ )
+ return cast(Leaf, self.root.lhs)
+
def height(self) -> int:
return 0 if self.root is None else self.root.height
curr = self.root
insert = lhs.root
lhs.root = None
- while isinstance(curr, Branch) and curr.height > insert.height + 1:
+ while isinstance(curr, Branch) and curr.height > insert.height:
curr = curr.lhs
parent = curr.parent
- new = Branch(curr.parent, insert.with_parent, curr.with_parent)
+ new = Branch(parent, insert.with_parent, curr.with_parent)
parent.replace(curr, new)
new.update_height()
- new.parent.balance_one_propagate()
+ parent.balance_one_propagate()
def __rjoin(self, rhs: "Tree[T]") -> None:
if self.root is None:
curr = self.root
insert = rhs.root
rhs.root = None
- while isinstance(curr, Branch) and curr.height > insert.height + 1:
+ while isinstance(curr, Branch) and curr.height > insert.height:
curr = curr.rhs
parent = curr.parent
- new = Branch(curr.parent, curr.with_parent, insert.with_parent)
+ new = Branch(parent, curr.with_parent, insert.with_parent)
parent.replace(curr, new)
new.update_height()
- new.parent.balance_one_propagate()
+ parent.balance_one_propagate()
class Node[T]:
return self.parent
return self.parent.root()
- def split_up(self) -> tuple[Tree, Tree]:
+ def split_up(self) -> tuple[Tree[T], Tree[T]]:
"""
makes self.parent empty
"""
self.update_height()
def append(self, value: T) -> "Leaf[T]":
- if self.rhs is None:
- leaf = Leaf[T](self, value)
- self.rhs = leaf
- self.balance_one_propagate()
- return leaf
if isinstance(self.rhs, Branch):
return self.rhs.append(value)
new = Branch[T](
self.balance_one_propagate()
return new_leaf
+ def prepend(self, value: T) -> "Leaf[T]":
+ if isinstance(self.lhs, Branch):
+ return self.lhs.prepend(value)
+ new = Branch[T](
+ self,
+ lambda parent: Leaf[T](parent, value),
+ self.lhs.with_parent,
+ )
+ self.lhs = new
+ new_leaf = cast(Leaf[T], new.lhs)
+ self.balance_one_propagate()
+ return new_leaf
+
def balance_one(self):
if abs(self.get_balance()) <= 1:
return