fix conflict

This commit is contained in:
Maoake Teriierooiterai
2026-03-27 14:39:06 +01:00
8 changed files with 189 additions and 15 deletions
+1
View File
@@ -214,4 +214,5 @@ __marimo__/
# Streamlit # Streamlit
.streamlit/secrets.toml .streamlit/secrets.toml
test.txt
+3
View File
@@ -5,6 +5,9 @@ install:
run: install run: install
uv run python3 a_maze_ing.py config.txt uv run python3 a_maze_ing.py config.txt
run_windows:
.venv\Scripts\python -m a_maze_ing config.txt
debug: debug:
uv pdb python3 a_maze_ing.py config.txt uv pdb python3 a_maze_ing.py config.txt
+2 -2
View File
@@ -8,8 +8,8 @@ from src.amaz_lib import Maze, MazeGenerator, MazeSolver
class AMazeIng(BaseModel): class AMazeIng(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True) model_config = ConfigDict(arbitrary_types_allowed=True)
width: int = Field(ge=3) width: int = Field(ge=4)
height: int = Field(ge=3) height: int = Field(ge=4)
entry: tuple[int, int] entry: tuple[int, int]
exit: tuple[int, int] exit: tuple[int, int]
output_file: str = Field(min_length=3) output_file: str = Field(min_length=3)
+88 -4
View File
@@ -6,6 +6,11 @@ import math
class MazeGenerator(ABC): class MazeGenerator(ABC):
def __init__(self, start: tuple, end: tuple, perfect: bool) -> None:
self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1)
self.perfect = perfect
@abstractmethod @abstractmethod
def generator( def generator(
self, height: int, width: int, seed: int | None = None self, height: int, width: int, seed: int | None = None
@@ -35,8 +40,60 @@ class MazeGenerator(ABC):
forty_two.add((y + 2, x + 3)) forty_two.add((y + 2, x + 3))
return forty_two return forty_two
@staticmethod
def unperfect_maze(width: int, height: int,
maze: np.ndarray, forty_two: set | None,
prob: float = 0.1
) -> Generator[np.ndarray, None, np.ndarray]:
directions = {
"N": (0, -1),
"S": (0, 1),
"W": (-1, 0),
"E": (1, 0)
}
reverse = {
"N": "S",
"S": "N",
"W": "E",
"E": "W"
}
min_break = 2
while True:
count = 0
for y in range(height):
for x in range(width):
if forty_two and (x, y) in forty_two:
continue
for direc, (dx, dy) in directions.items():
nx, ny = x + dx, y + dy
if forty_two and (
(y, x) in forty_two
or (ny, nx) in forty_two
):
continue
if not (0 <= nx < width and 0 < ny < height):
continue
if direc in ["S", "E"]:
continue
if np.random.random() < prob:
count += 1
cell = maze[y][x]
cell_n = maze[ny][nx]
cell = DepthFirstSearch.broken_wall(cell, direc)
cell_n = DepthFirstSearch.broken_wall(cell_n,
reverse[
direc])
maze[y][x] = cell
maze[ny][nx] = cell_n
yield maze
if count > min_break:
break
return maze
class Kruskal(MazeGenerator): class Kruskal(MazeGenerator):
class Set: class Set:
def __init__(self, cells: list[int]) -> None: def __init__(self, cells: list[int]) -> None:
self.cells: list[int] = cells self.cells: list[int] = cells
@@ -118,6 +175,8 @@ class Kruskal(MazeGenerator):
cells_ft = None cells_ft = None
if height > 10 and width > 10: if height > 10 and width > 10:
cells_ft = self.get_cell_ft(width, height) cells_ft = self.get_cell_ft(width, height)
if cells_ft and (self.start in cells_ft or self.end in cells_ft):
cells_ft = None
if seed is not None: if seed is not None:
np.random.seed(seed) np.random.seed(seed)
@@ -146,10 +205,23 @@ class Kruskal(MazeGenerator):
len(sets.sets) == 19 and cells_ft is not None len(sets.sets) == 19 and cells_ft is not None
): ):
break break
return self.walls_to_maze(walls, height, width) print(f"nb sets: {len(sets.sets)}")
maze = self.walls_to_maze(walls, height, width)
if self.perfect is False:
gen = Kruskal.unperfect_maze(width, height, maze,
cells_ft)
for res in gen:
maze = res
yield maze
return maze
class DepthFirstSearch(MazeGenerator): class DepthFirstSearch(MazeGenerator):
def __init__(self, start: bool, end: bool, perfect: bool) -> None:
self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1)
self.perfect = perfect
self.forty_two: set | None = None
def generator( def generator(
self, height: int, width: int, seed: int = None self, height: int, width: int, seed: int = None
@@ -157,9 +229,15 @@ class DepthFirstSearch(MazeGenerator):
if seed is not None: if seed is not None:
np.random.seed(seed) np.random.seed(seed)
maze = self.init_maze(width, height) maze = self.init_maze(width, height)
forty_two = self.get_cell_ft(width, height) if width > 9 and height > 9:
self.forty_two = self.get_cell_ft(width, height)
visited = np.zeros((height, width), dtype=bool) visited = np.zeros((height, width), dtype=bool)
visited = self.lock_cell_ft(visited, forty_two) if (
self.forty_two
and self.start not in self.forty_two
and self.end not in self.forty_two
):
visited = self.lock_cell_ft(visited, self.forty_two)
path = list() path = list()
w_h = (width, height) w_h = (width, height)
coord = (0, 0) coord = (0, 0)
@@ -190,6 +268,12 @@ class DepthFirstSearch(MazeGenerator):
x, y = coord x, y = coord
maze[y][x] = self.broken_wall(maze[y][x], wall_r) maze[y][x] = self.broken_wall(maze[y][x], wall_r)
yield maze yield maze
if self.perfect is False:
gen = DepthFirstSearch.unperfect_maze(width, height, maze,
self.forty_two)
for res in gen:
maze = res
yield maze
return maze return maze
@staticmethod @staticmethod
@@ -250,7 +334,7 @@ class DepthFirstSearch(MazeGenerator):
return {"N": "S", "S": "N", "W": "E", "E": "W"}[direction] return {"N": "S", "S": "N", "W": "E", "E": "W"}[direction]
@staticmethod @staticmethod
def back_on_step(path: list, w_h: tuple, visited: np.array) -> list: def back_on_step(path: list, w_h: tuple, visited: np.ndarray) -> list:
while path: while path:
last = path[-1] last = path[-1]
if DepthFirstSearch.random_cells(visited, last, w_h): if DepthFirstSearch.random_cells(visited, last, w_h):
+80 -1
View File
@@ -9,7 +9,8 @@ class MazeSolver(ABC):
self.end = (end[1] - 1, end[0] - 1) self.end = (end[1] - 1, end[0] - 1)
@abstractmethod @abstractmethod
def solve(self, maze: Maze) -> str: ... def solve(self, maze: Maze, height: int = None,
width: int = None) -> str: ...
class AStar(MazeSolver): class AStar(MazeSolver):
@@ -156,3 +157,81 @@ class AStar(MazeSolver):
if res is None: if res is None:
raise Exception("Path not found") raise Exception("Path not found")
return res return res
class DepthFirstSearchSolver(MazeSolver):
def __init__(self, start, end):
self.start = (start[1] - 1, start[0] - 1)
self.end = (end[1] - 1, end[0] - 1)
def solve(self, maze: Maze, height: int = None,
width: int = None) -> str:
path_str = ""
visited = np.zeros((height, width), dtype=bool)
path = list()
move = list()
maze_s = maze.get_maze()
coord = self.start
h_w = (height, width)
while coord != self.end:
visited[coord] = True
path.append(coord)
rand_p = self.random_path(visited, coord, maze_s, h_w)
if not rand_p:
path, move = self.back_on_step(path, visited, maze_s, h_w,
move)
if not path:
break
coord = path[-1]
rand_p = self.random_path(visited, coord, maze_s, h_w)
next = self.next_path(rand_p)
move.append(next)
coord = self.next_cell(coord, next)
for m in move:
path_str += m
if not path:
raise Exception("Path not found")
return path_str
@staticmethod
def random_path(visited: np.ndarray, coord: tuple,
maze: np.ndarray, h_w: tuple) -> list:
random_p = []
h, w = h_w
y, x = coord
if y - 1 >= 0 and not maze[y][x].get_north() and not visited[y - 1][x]:
random_p.append("N")
if y + 1 < h and not maze[y][x].get_south() and not visited[y + 1][x]:
random_p.append("S")
if x - 1 >= 0 and not maze[y][x].get_west() and not visited[y][x - 1]:
random_p.append("W")
if x + 1 < w and not maze[y][x].get_est() and not visited[y][x + 1]:
random_p.append("E")
return random_p
@staticmethod
def next_path(rand_path: list) -> str:
return np.random.choice(rand_path)
@staticmethod
def back_on_step(path: list, visited: np.ndarray,
maze: np.ndarray, h_w: tuple, move: list) -> list:
while path:
last = path[-1]
if DepthFirstSearchSolver.random_path(visited, last, maze, h_w):
break
path.pop()
move.pop()
return path, move
@staticmethod
def next_cell(coord: tuple, next: str) -> tuple:
y, x = coord
next_step = {"N": (-1, 0), "S": (1, 0), "W": (0, -1), "E": (0, 1)}
add_y, add_x = next_step[next]
return (y + add_y, x + add_x)
+2 -2
View File
@@ -2,9 +2,9 @@ from .Cell import Cell
from .Maze import Maze from .Maze import Maze
from .MazeGenerator import MazeGenerator, DepthFirstSearch from .MazeGenerator import MazeGenerator, DepthFirstSearch
from .MazeGenerator import Kruskal from .MazeGenerator import Kruskal
from .MazeSolver import MazeSolver, AStar from .MazeSolver import MazeSolver, AStar, DepthFirstSearchSolver
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "us" __author__ = "us"
__all__ = ["Cell", "Maze", "MazeGenerator", __all__ = ["Cell", "Maze", "MazeGenerator", "DepthFirstSearchSolver",
"MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"] "MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"]
+6 -3
View File
@@ -54,12 +54,14 @@ class DataMaze:
res.update({key: DataMaze.convert_bool(data[key])}) res.update({key: DataMaze.convert_bool(data[key])})
res.update({"OUTPUT_FILE": data["OUTPUT_FILE"]}) res.update({"OUTPUT_FILE": data["OUTPUT_FILE"]})
res.update( res.update(
DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"]) DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"],
res["PERFECT"])
) )
return res return res
@staticmethod @staticmethod
def get_solver_generator(data: dict, entry: int, exit: int) -> dict: def get_solver_generator(data: dict, entry: tuple, exit: tuple,
perfect: bool) -> dict:
available_generator = { available_generator = {
"Kruskal": Kruskal, "Kruskal": Kruskal,
"DFS": DepthFirstSearch, "DFS": DepthFirstSearch,
@@ -68,7 +70,8 @@ class DataMaze:
"AStar": AStar, "AStar": AStar,
} }
res = {} res = {}
res["GENERATOR"] = available_generator[data["GENERATOR"]]() res["GENERATOR"] = available_generator[data["GENERATOR"]](entry, exit,
perfect)
res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit) res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit)
return res return res
+7 -3
View File
@@ -1,14 +1,18 @@
import numpy import numpy
from amaz_lib.MazeGenerator import DepthFirstSearch from amaz_lib.MazeGenerator import DepthFirstSearch, MazeGenerator
class TestMazeGenerator: class TestMazeGenerator:
def test_generator(self) -> None: def test_generator(self) -> None:
w_h = (300, 300) w_h = (10, 10)
maze = numpy.array([]) maze = numpy.array([])
generator = DepthFirstSearch().generator(*w_h) generator = DepthFirstSearch((1, 1), (2, 2), True).generator(*w_h)
for output in generator: for output in generator:
maze = output maze = output
assert maze.shape == w_h assert maze.shape == w_h
def test_gen_broken(self) -> None:
test = MazeGenerator.gen_broken_set(50, 50)
assert len(test) > 0