15 Commits

Author SHA1 Message Date
Maoake Teriierooiterai 4055a8a7a2 finish to print the 42 2026-03-25 13:58:35 +01:00
Maoake TERIIEROOITERAI a39f348b1e set up the function draw ft 2026-03-24 22:11:54 +01:00
Maoake TERIIEROOITERAI 03c4d206d6 starting the branch parsing need to get a good start on this 2026-03-24 21:21:46 +01:00
Maoake TERIIEROOITERAI 8eb46f601f fixing the DFS and modify the main 2026-03-24 20:47:13 +01:00
da7e 991cdead51 Merge branch 'main' of github.com:maoakeEnterprise/amazing 2026-03-24 16:12:19 +01:00
da7e 6730ebcdb5 It's aliiiive! 2026-03-24 16:10:57 +01:00
Maoake Teriierooiterai c478400640 fix the cell pydantic cause the program was too long 2026-03-24 15:34:43 +01:00
da7e a79d4e5c3b algorithm edited but nothing better 2026-03-24 15:33:50 +01:00
Maoake Teriierooiterai 993bcce857 add generator to my maze generator DFS 2026-03-24 15:22:20 +01:00
Maoake Teriierooiterai a85e342a0a fix conflict 2026-03-24 14:31:49 +01:00
Maoake Teriierooiterai 4d151664ab finish the generator DFS 2026-03-24 14:28:10 +01:00
da7e 8dc00e238a Merge branch 'main' of github.com:maoakeEnterprise/amazing 2026-03-24 13:37:30 +01:00
da7e 0f19d24736 base but not working astar solver 2026-03-24 13:30:32 +01:00
Maoake Teriierooiterai 8b4ef7afce finish the maze generator 2026-03-24 11:10:16 +01:00
Maoake Teriierooiterai 030c6142ba need to fix my infinite while so i make a checkpoint if i need to restore it 2026-03-24 09:34:53 +01:00
10 changed files with 315 additions and 124 deletions
+5
View File
@@ -23,3 +23,8 @@ run_test_parsing:
run_test_dfs: run_test_dfs:
PYTHONPATH=src uv run pytest tests/test_Depth.py PYTHONPATH=src uv run pytest tests/test_Depth.py
run_test_maze_gen:
PYTHONPATH=src uv run pytest tests/test_MazeGenerator.py
run_test:
uv run pytest
+8 -7
View File
@@ -1,17 +1,18 @@
import os import os
from numpy import ma
from src.amaz_lib import MazeGenerator
from src.amaz_lib import Maze from src.amaz_lib import Maze
from src.amaz_lib import MazeGenerator
import src.amaz_lib as g
def main() -> None: def main(maze_gen: MazeGenerator) -> None:
# try: # try:
maze = Maze(maze=None, start=(1, 1), end=(16, 15)) maze = Maze(maze=None)
for alg in MazeGenerator.Kruskal.kruskal(20, 20): for alg in maze_gen.generator(10, 10):
maze.set_maze(alg) maze.set_maze(alg)
os.system("clear") os.system("clear")
maze.ascii_print() maze.ascii_print()
maze.export_maze("test.txt") # solver = AStar((1, 1), (14, 18))
# print(solver.solve(maze))
# except Exception as err: # except Exception as err:
@@ -19,4 +20,4 @@ def main() -> None:
if __name__ == "__main__": if __name__ == "__main__":
main() main(g.DepthFirstSearch())
+5 -3
View File
@@ -1,8 +1,10 @@
from pydantic import BaseModel, Field from dataclasses import dataclass
class Cell(BaseModel): @dataclass
value: int = Field(ge=0, le=15) class Cell:
def __init__(self, value: int) -> None:
self.value = value
def __str__(self) -> str: def __str__(self) -> str:
return hex(self.value).removeprefix("0x").upper() return hex(self.value).removeprefix("0x").upper()
+3 -4
View File
@@ -26,15 +26,14 @@ class Maze:
return res return res
def ascii_print(self) -> None: def ascii_print(self) -> None:
for line in self.maze: for cell in self.maze[0]:
if line is self.maze[0]:
for cell in line:
print("_", end="") print("_", end="")
if cell.get_north(): if cell.get_north():
print("__", end="") print("__", end="")
else: else:
print(" ", end="") print(" ", end="")
print() print("_")
for line in self.maze:
for cell in line: for cell in line:
if cell is line[0] and cell.get_west(): if cell is line[0] and cell.get_west():
print("|", end="") print("|", end="")
+131 -79
View File
@@ -1,22 +1,53 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Generator from typing import Generator, Set
import numpy as np import numpy as np
from .Cell import Cell from .Cell import Cell
import math import math
import random
class MazeGenerator(ABC): class MazeGenerator(ABC):
@abstractmethod @abstractmethod
def generator( def generator(
self, height: int, width: int self, height: int, width: int, seed: int = None
) -> Generator[np.ndarray, None, np.ndarray]: ... ) -> Generator[np.ndarray, None, np.ndarray]: ...
@staticmethod
def get_cell_ft(width: int, height: int) -> set:
forty_two = set()
y, x = (int(height / 2), int(width / 2))
forty_two.add((y, x - 1))
forty_two.add((y, x - 2))
forty_two.add((y, x - 3))
forty_two.add((y - 1, x - 3))
forty_two.add((y - 2, x - 3))
forty_two.add((y + 1, x - 1))
forty_two.add((y + 2, x - 1))
forty_two.add((y, x + 1))
forty_two.add((y, x + 2))
forty_two.add((y, x + 3))
forty_two.add((y - 1, x + 3))
forty_two.add((y - 2, x + 3))
forty_two.add((y - 2, x + 2))
forty_two.add((y - 2, x + 1))
forty_two.add((y + 1, x + 1))
forty_two.add((y + 2, x + 1))
forty_two.add((y + 2, x + 2))
forty_two.add((y + 2, x + 3))
return forty_two
class Kruskal(MazeGenerator): class Kruskal(MazeGenerator):
class Set:
def __init__(self, cells: list[int]) -> None:
self.cells: list[int] = cells
class Sets:
def __init__(self, sets: list[Set]) -> None:
self.sets = sets
@staticmethod @staticmethod
def walls_to_maze( def walls_to_maze(
walls: list[tuple[int, int]], height: int, width: int walls: np.ndarray, height: int, width: int
) -> np.ndarray: ) -> np.ndarray:
maze: np.ndarray = np.array( maze: np.ndarray = np.array(
[[Cell(value=0) for _ in range(width)] for _ in range(height)] [[Cell(value=0) for _ in range(width)] for _ in range(height)]
@@ -37,117 +68,144 @@ class Kruskal(MazeGenerator):
if x == height - 1: if x == height - 1:
maze[x][y].set_south(True) maze[x][y].set_south(True)
if y == 0: if y == 0:
maze[x][y].set_est(True)
if y == width - 1:
maze[x][y].set_west(True) maze[x][y].set_west(True)
if y == width - 1:
maze[x][y].set_est(True)
return maze return maze
@staticmethod @staticmethod
def is_in_same_set(sets: list[list[int]], wall: tuple[int, int]) -> bool: def is_in_same_set(sets: Sets, wall: tuple[int, int]) -> bool:
a, b = wall a, b = wall
for set in sets: for set in sets.sets:
if a in set and b in set: if a in set.cells and b in set.cells:
return True return True
if a in set or b in set: elif a in set.cells or b in set.cells:
return False return False
return False return False
@staticmethod @staticmethod
def merge_sets(sets: list[list[int]], wall: tuple[int, int]) -> None: def merge_sets(sets: Sets, wall: tuple[int, int]) -> None:
a, b = wall a, b = wall
base_set = None base_set = None
for set in sets: for i in range(len(sets.sets)):
if base_set is None and (a in set or b in set): if base_set is None and (
base_set = set a in sets.sets[i].cells or b in sets.sets[i].cells
elif base_set and (a in set or b in set): ):
base_set += set base_set = sets.sets[i]
sets.remove(set) elif base_set and (
a in sets.sets[i].cells or b in sets.sets[i].cells
):
base_set.cells += sets.sets[i].cells
sets.sets.pop(i)
return
raise Exception("two sets not found")
def generator( def generator(
self, height: int, width: int self, height: int, width: int, seed: int = None
) -> Generator[np.ndarray, None, np.ndarray]: ) -> Generator[np.ndarray, None, np.ndarray]:
sets = [[i] for i in range(height * width)] if seed is not None:
np.random.seed(seed)
sets = self.Sets([self.Set([i]) for i in range(height * width)])
walls = [] walls = []
for h in range(height): for h in range(height):
for w in range(width - 1): for w in range(width - 1):
walls += [(w + (width * h), w + (width * h) + 1)] walls += [(w + (width * h), w + (width * h) + 1)]
for w in range(width):
for h in range(height - 1): for h in range(height - 1):
walls += [(w + (width * h), w + (width * h) + width)] for w in range(width):
walls += [(w + (width * h), w + (width * (h + 1)))]
print(walls)
np.random.shuffle(walls) np.random.shuffle(walls)
yield self.walls_to_maze(walls, height, width) yield self.walls_to_maze(walls, height, width)
while len(sets.sets) > 1:
for wall in walls: for wall in walls:
if not self.is_in_same_set(sets, wall): if not self.is_in_same_set(sets, wall):
self.merge_sets(sets, wall) self.merge_sets(sets, wall)
walls.remove(wall) walls.remove(wall)
yield self.walls_to_maze(walls, height, width) yield self.walls_to_maze(walls, height, width)
if len(sets.sets) == 1:
break
print(f"nb sets: {len(sets.sets)}")
return self.walls_to_maze(walls, height, width) return self.walls_to_maze(walls, height, width)
class DepthFirstSearch: class DepthFirstSearch(MazeGenerator):
@staticmethod def generator(
def generator(width: int, height: int) -> np.ndarray: self, height: int, width: int, seed: int = None
maze = DepthFirstSearch.init_maze(width, height) ) -> Generator[np.ndarray, None, np.ndarray]:
visited = [] if seed is not None:
path = [] np.random.seed(seed)
maze = self.init_maze(width, height)
forty_two = self.get_cell_ft(width, height)
visited = np.zeros((height, width), dtype=bool)
visited = self.lock_cell_ft(visited, forty_two)
path = list()
w_h = (width, height) w_h = (width, height)
coord = (0, 0) coord = (0, 0)
while len(visited) < width * height:
x, y = coord x, y = coord
rand_steps = DepthFirstSearch.random_cells(visited, coord, w_h) first_iteration = True
if len(rand_steps) == 0:
path = DepthFirstSearch.back_on_step(path, w_h) while path or first_iteration:
coord = DepthFirstSearch.last(path) first_iteration = False
rand_steps = DepthFirstSearch.random_cells(path, coord, w_h)
visited[y, x] = True
path = self.add_cell_visited(coord, path)
random_c = self.random_cells(visited, coord, w_h)
if not random_c:
path = self.back_on_step(path, w_h, visited)
if not path:
break
coord = path[-1]
random_c = self.random_cells(visited, coord, w_h)
x, y = coord x, y = coord
wall = DepthFirstSearch.next_step(rand_steps)
wall_r = DepthFirstSearch.reverse_path(wall) wall = self.next_step(random_c)
maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall) maze[y][x] = self.broken_wall(maze[y][x], wall)
visited = DepthFirstSearch.add_cell_visited(coord, visited)
path = DepthFirstSearch.add_cell_visited(coord, path) coord = self.next_cell(x, y, wall)
coord = DepthFirstSearch.next_cell(x, y, wall) wall_r = self.reverse_path(wall)
x, y = coord x, y = coord
maze[y][x] = DepthFirstSearch.broken_wall(maze[y][x], wall_r) maze[y][x] = self.broken_wall(maze[y][x], wall_r)
yield maze
return maze return maze
@staticmethod @staticmethod
def init_maze(width: int, height: int) -> np.ndarray: def init_maze(width: int, height: int) -> np.ndarray:
maze = np.array([[Cell(value=15) for _ in range(width)] maze = np.array(
for _ in range(height)]) [[Cell(value=15) for _ in range(width)] for _ in range(height)]
)
return maze return maze
@staticmethod @staticmethod
def add_cell_visited(coord: tuple, visited: list = []) -> list: def add_cell_visited(coord: tuple, path: set) -> list:
visited.append(coord) path.append(coord)
return visited return path
@staticmethod @staticmethod
def random_cells(visited: list, coord: tuple, w_h: tuple) -> list: def random_cells(visited: np.array, coord: tuple, w_h: tuple) -> list:
rand_cell = [] rand_cell = []
x, y = coord x, y = coord
width, height = w_h width, height = w_h
# NORTH
if y - 1 >= 0 and (x, y - 1) not in visited: if y - 1 >= 0 and not visited[y - 1][x]:
rand_cell.append("N") rand_cell.append("N")
# SOUTH if y + 1 < height and not visited[y + 1][x]:
if y + 1 < height and (x, y + 1) not in visited:
rand_cell.append("S") rand_cell.append("S")
# WEST if x - 1 >= 0 and not visited[y][x - 1]:
if x - 1 >= 0 and (x - 1, y) not in visited:
rand_cell.append("W") rand_cell.append("W")
# EAST if x + 1 < width and not visited[y][x + 1]:
if x + 1 < width and (x + 1, y) not in visited:
rand_cell.append("E") rand_cell.append("E")
return rand_cell return rand_cell
@staticmethod @staticmethod
def next_step(rand_cell: list) -> str: def next_step(rand_cell: list) -> str:
return random.choice(rand_cell) return np.random.choice(rand_cell)
@staticmethod @staticmethod
def broken_wall(cell: Cell, wall: str) -> Cell: def broken_wall(cell: Cell, wall: str) -> Cell:
@@ -163,33 +221,27 @@ class DepthFirstSearch:
@staticmethod @staticmethod
def next_cell(x: int, y: int, next: str) -> tuple: def next_cell(x: int, y: int, next: str) -> tuple:
next_step = { next_step = {"N": (0, -1), "S": (0, 1), "W": (-1, 0), "E": (1, 0)}
"N": (0, -1),
"S": (0, 1),
"W": (-1, 0),
"E": (1, 0)
}
add_x, add_y = next_step[next] add_x, add_y = next_step[next]
return (x + add_x, y + add_y) return (x + add_x, y + add_y)
def reverse_path(next: str) -> str: @staticmethod
reverse = { def reverse_path(direction: str) -> str:
"N": "S", return {"N": "S", "S": "N", "W": "E", "E": "W"}[direction]
"S": "N",
"W": "E",
"E": "W"
}
return reverse[next]
@staticmethod @staticmethod
def last(path: list): def back_on_step(path: list, w_h: tuple, visited: np.array) -> list:
return path[len(path) - 1] while path:
last = path[-1]
def back_on_step(path: list, w_h: tuple) -> list: if DepthFirstSearch.random_cells(visited, last, w_h):
last = DepthFirstSearch.last(path) break
r_cells = DepthFirstSearch.random_cells(path, last, w_h) path.pop()
while len(r_cells == 0):
path.pop(len(path) - 1)
last = DepthFirstSearch.last(path)
r_cells = DepthFirstSearch.random_cells(path, last, w_h)
return path return path
@staticmethod
def lock_cell_ft(visited: np.ndarray, forty_two: set[tuple[int]]
) -> np.ndarray:
tab = [cell for cell in forty_two]
for cell in tab:
visited[cell] = True
return visited
+114
View File
@@ -1,7 +1,121 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .Maze import Maze from .Maze import Maze
import numpy as np
class MazeSolver(ABC): class MazeSolver(ABC):
def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None:
self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1)
@abstractmethod @abstractmethod
def solve(self, maze: Maze) -> str: ... def solve(self, maze: Maze) -> str: ...
class AStar(MazeSolver):
def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None:
super().__init__(start, end)
def f(self, n):
def g(n: tuple[int, int]) -> int:
res = 0
if n[0] < self.start[0]:
res += self.start[0] - n[0]
else:
res += n[0] - self.start[0]
if n[1] < self.start[1]:
res += self.start[1] - n[1]
else:
res += n[1] - self.start[1]
return res
def h(n: tuple[int, int]) -> int:
res = 0
if n[0] < self.end[0]:
res += self.end[0] - n[0]
else:
res += n[0] - self.end[0]
if n[1] < self.end[1]:
res += self.end[1] - n[1]
else:
res += n[1] - self.end[1]
return res
try:
return g(n) + h(n)
except Exception:
return 1000
def best_path(
self, maze: np.ndarray, actual: tuple[int, int]
) -> dict[str, int | None]:
print(actual)
path = {
"N": (
self.f((actual[1] - 1, actual[0]))
if not maze[actual[1]][actual[0]].get_north() and actual[0] > 0
else None
),
"E": (
self.f((actual[1], actual[0] + 1))
if not maze[actual[1]][actual[0]].get_est()
and actual[1] < len(maze) - 1
else None
),
"S": (
self.f((actual[1] + 1, actual[0]))
if not maze[actual[1]][actual[0]].get_south()
and actual[0] < len(maze) - 1
else None
),
"W": (
self.f((actual[1], actual[0] - 1))
if not maze[actual[1]][actual[0]].get_west() and actual[1] > 0
else None
),
}
return {
k: v for k, v in sorted(path.items(), key=lambda item: item[0])
}
def get_opposit(self, dir: str) -> str:
match dir:
case "N":
return "S"
case "E":
return "W"
case "S":
return "N"
case "W":
return "E"
case _:
return ""
def get_next_pos(
self, dir: str, actual: tuple[int, int]
) -> tuple[int, int]:
match dir:
case "N":
return (actual[0], actual[1] - 1)
case "E":
return (actual[0] + 1, actual[1])
case "S":
return (actual[0], actual[1] + 1)
case "W":
return (actual[0] - 1, actual[1])
case _:
return actual
def get_path(self, maze: np.ndarray) -> str | None:
actual = self.start
path = ""
return None
def solve(self, maze: Maze) -> str:
print(maze)
res = self.get_path(self.start, maze.get_maze(), None)
if res is None:
raise Exception("Path not found")
return res
+4 -2
View File
@@ -1,8 +1,10 @@
from .Cell import Cell from .Cell import Cell
from .Maze import Maze from .Maze import Maze
from .MazeGenerator import MazeGenerator, DepthFirstSearch from .MazeGenerator import MazeGenerator, DepthFirstSearch
from .MazeSolver import MazeSolver from .MazeGenerator import Kruskal
from .MazeSolver import MazeSolver, AStar
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "us" __author__ = "us"
__all__ = ["Cell", "Maze", "MazeGenerator", "MazeSolver", "DepthFirstSearch"] __all__ = ["Cell", "Maze", "MazeGenerator",
"MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"]
+4 -10
View File
@@ -1,5 +1,6 @@
from amaz_lib.MazeGenerator import DepthFirstSearch from amaz_lib.MazeGenerator import DepthFirstSearch
from amaz_lib.Cell import Cell from amaz_lib.Cell import Cell
import numpy as np
class TestDepth: class TestDepth:
@@ -12,22 +13,15 @@ class TestDepth:
def test_rand_cells(self) -> None: def test_rand_cells(self) -> None:
w_h = (10, 10) w_h = (10, 10)
lst = DepthFirstSearch.add_cell_visited((0, 0)) lst = np.zeros((10, 10), dtype=bool)
lst[0, 0] = True
rand_cells = DepthFirstSearch.random_cells(lst, (0, 1), w_h) rand_cells = DepthFirstSearch.random_cells(lst, (0, 1), w_h)
assert len(rand_cells) == 2 assert len(rand_cells) == 2
def test_next_cell(self) -> None: def test_next_cell(self) -> None:
coord = (5, 4) coord = (5, 4)
x, y = coord x, y = coord
assert DepthFirstSearch.next_cell(x, y, "N") == (5, 3) assert DepthFirstSearch.next_cell(x, y, "N") == (2, 3)
def test_reverse_path(self) -> None: def test_reverse_path(self) -> None:
assert DepthFirstSearch.reverse_path("N") == "S" assert DepthFirstSearch.reverse_path("N") == "S"
def test_last(self) -> None:
lst = [(0, 0), (1, 1)]
assert DepthFirstSearch.last(lst) == (1, 1)
def test_BOS(self) -> None:
path = [(0, 0), (0, 2), (1, 1)]
assert len(DepthFirstSearch.random_cells(path, (0, 1), (10, 10))) == 0
+8 -5
View File
@@ -1,11 +1,14 @@
import numpy import numpy
from amaz_lib.MazeGenerator import Kruskal from amaz_lib.MazeGenerator import DepthFirstSearch
def test_kruskal_output_shape() -> None: class TestMazeGenerator:
generator = Kruskal()
def test_generator(self) -> None:
w_h = (300, 300)
maze = numpy.array([]) maze = numpy.array([])
for output in generator.generator(10, 10): generator = DepthFirstSearch().generator(*w_h)
for output in generator:
maze = output maze = output
assert maze.shape == (10, 10) assert maze.shape == w_h
+19
View File
@@ -0,0 +1,19 @@
from amaz_lib.Cell import Cell
import numpy as np
from amaz_lib import AStar, Maze, MazeSolver
def test_solver() -> None:
maze = Maze(
np.array(
[
[Cell(value=13), Cell(value=3), Cell(value=11)],
[Cell(value=9), Cell(value=4), Cell(value=6)],
[Cell(value=12), Cell(value=5), Cell(value=7)],
]
)
)
print(maze)
solver = AStar((1, 1), (3, 3))
res = solver.solve(maze)
assert res == "ESWSEE"