merge main to the branch solver_dfs and fixing some conflict

This commit is contained in:
Maoake TERIIEROOITERAI
2026-03-26 00:14:39 +01:00
23 changed files with 1993 additions and 110 deletions
+5 -7
View File
@@ -1,22 +1,20 @@
from dataclasses import field
from os import eventfd_read
from typing import Generator
import numpy
from typing_extensions import Self
from pydantic import AfterValidator, BaseModel, Field, model_validator
from pydantic import BaseModel, Field, model_validator, ConfigDict
from amaz_lib import Maze, MazeGenerator, MazeSolver
from amaz_lib.Cell import Cell
from src.amaz_lib import Maze, MazeGenerator, MazeSolver
class AMazeIng(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
width: int = Field(ge=3)
height: int = Field(ge=3)
entry: tuple[int, int]
exit: tuple[int, int]
output_file: str = Field(min_length=3)
perfect: bool = Field(default=True)
maze: Maze = Field(default=Maze(maze=numpy.array([])))
maze: Maze = Field(default=Maze(None))
generator: MazeGenerator
solver: MazeSolver
+30 -7
View File
@@ -13,7 +13,7 @@ class MazeGenerator(ABC):
@abstractmethod
def generator(
self, height: int, width: int, seed: int = None
self, height: int, width: int, seed: int | None = None
) -> Generator[np.ndarray, None, np.ndarray]: ...
@staticmethod
@@ -156,9 +156,25 @@ class Kruskal(MazeGenerator):
return
raise Exception("two sets not found")
@staticmethod
def touch_ft(
width: int,
wall: tuple[int, int],
cells_ft: None | set[tuple[int, int]],
) -> bool:
if cells_ft is None:
return False
s1 = (math.trunc(wall[0] / width), wall[0] % width)
s2 = (math.trunc(wall[1] / width), wall[1] % width)
return s1 in cells_ft or s2 in cells_ft
def generator(
self, height: int, width: int, seed: int = None
self, height: int, width: int, seed: int | None = None
) -> Generator[np.ndarray, None, np.ndarray]:
cells_ft = None
if height > 10 and width > 10:
cells_ft = self.get_cell_ft(width, height)
if seed is not None:
np.random.seed(seed)
sets = self.Sets([self.Set([i]) for i in range(height * width)])
@@ -173,13 +189,19 @@ class Kruskal(MazeGenerator):
np.random.shuffle(walls)
yield self.walls_to_maze(walls, height, width)
while len(sets.sets) > 1:
while (len(sets.sets) != 1 and cells_ft is None) or (
len(sets.sets) != 19 and cells_ft is not None
):
for wall in walls:
if not self.is_in_same_set(sets, wall):
if not self.is_in_same_set(sets, wall) and not self.touch_ft(
width, wall, cells_ft
):
self.merge_sets(sets, wall)
walls.remove(wall)
yield self.walls_to_maze(walls, height, width)
if len(sets.sets) == 1:
if (len(sets.sets) == 1 and cells_ft is None) or (
len(sets.sets) == 19 and cells_ft is not None
):
break
print(f"nb sets: {len(sets.sets)}")
return self.walls_to_maze(walls, height, width)
@@ -312,8 +334,9 @@ class DepthFirstSearch(MazeGenerator):
return path
@staticmethod
def lock_cell_ft(visited: np.ndarray, forty_two: set[tuple[int]]
) -> np.ndarray:
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
+57 -20
View File
@@ -5,8 +5,8 @@ import numpy as np
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)
self.start = (start[1] - 1, start[0] - 1)
self.end = (end[1] - 1, end[0] - 1)
@abstractmethod
def solve(self, maze: Maze, height: int = None,
@@ -49,35 +49,39 @@ class AStar(MazeSolver):
return 1000
def best_path(
self, maze: np.ndarray, actual: tuple[int, int]
) -> dict[str, int | None]:
print(actual)
self,
maze: np.ndarray,
actual: tuple[int, int],
last: str | None,
) -> dict[str, int]:
path = {
"N": (
self.f((actual[1] - 1, actual[0]))
if not maze[actual[1]][actual[0]].get_north() and actual[0] > 0
self.f((actual[0], actual[1] - 1))
if not maze[actual[1]][actual[0]].get_north() and actual[1] > 0
else None
),
"E": (
self.f((actual[1], actual[0] + 1))
self.f((actual[0] + 1, actual[1]))
if not maze[actual[1]][actual[0]].get_est()
and actual[1] < len(maze) - 1
and actual[0] < len(maze[0]) - 1
else None
),
"S": (
self.f((actual[1] + 1, actual[0]))
self.f((actual[0], actual[1] + 1))
if not maze[actual[1]][actual[0]].get_south()
and actual[0] < len(maze) - 1
and actual[1] < 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
self.f((actual[0] - 1, actual[1]))
if not maze[actual[1]][actual[0]].get_west() and actual[0] > 0
else None
),
}
return {
k: v for k, v in sorted(path.items(), key=lambda item: item[0])
k: v
for k, v in sorted(path.items(), key=lambda item: item[0])
if v is not None and k != last
}
def get_opposit(self, dir: str) -> str:
@@ -108,15 +112,48 @@ class AStar(MazeSolver):
case _:
return actual
# def get_path(self, maze: np.ndarray) -> str | None:
# actual = self.start
# path = ""
def get_path(self, maze: np.ndarray) -> str | None:
path = [(self.start, self.best_path(maze, self.start, None))]
visited = [self.start]
while len(path) > 0 and path[-1][0] != self.end:
if len(path[-1][1]) == 0:
path.pop(-1)
if len(path) == 0:
break
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
continue
# return None
while len(path[-1][1]) > 0:
next_pos = self.get_next_pos(
list(path[-1][1].keys())[0], path[-1][0]
)
if next_pos in visited:
k = next(iter(path[-1][1]))
path[-1][1].pop(k)
else:
break
if len(path[-1][1]) == 0:
path.pop(-1)
continue
pre = self.get_opposit(list(path[-1][1].keys())[0])
path.append(
(
next_pos,
self.best_path(maze, next_pos, pre),
)
)
visited += [next_pos]
if len(path) == 0:
return None
path[-1] = (self.end, {})
return "".join(
str(list(c[1].keys())[0]) for c in path if len(c[1]) > 0
)
def solve(self, maze: Maze) -> str:
print(maze)
res = self.get_path(self.start, maze.get_maze(), None)
res = self.get_path(maze.get_maze())
if res is None:
raise Exception("Path not found")
return res
+60 -35
View File
@@ -1,3 +1,7 @@
from src.amaz_lib.MazeGenerator import DepthFirstSearch, Kruskal
from src.amaz_lib.MazeSolver import AStar
class DataMaze:
@staticmethod
@@ -11,28 +15,30 @@ class DataMaze:
@staticmethod
def transform_data(data: str) -> dict:
tmp = data.split("\n")
tmp2 = [
value.split("=", 1) for value in tmp
]
data_t = {
value[0]: value[1] for value in tmp2
}
tmp2 = [value.split("=", 1) for value in tmp if "=" in value]
data_t = {value[0]: value[1] for value in tmp2}
return data_t
@staticmethod
def verif_key_data(data: dict) -> None:
key_test = {
"WIDTH", "HEIGHT", "ENTRY", "EXIT", "OUTPUT_FILE", "PERFECT"
}
set_key = {
key for key in data.keys()
"WIDTH",
"HEIGHT",
"ENTRY",
"EXIT",
"OUTPUT_FILE",
"PERFECT",
"GENERATOR",
"SOLVER",
}
set_key = {key for key in data.keys()}
if len(set_key) != len(key_test):
raise KeyError("Missing some data the len do not correspond")
res_key = {key for key in set_key if key not in key_test}
if len(res_key) != 0:
raise KeyError("Some Key "
f"do not correspond the keys: {res_key}")
raise KeyError(
"Some Key " f"do not correspond the keys: {res_key}"
)
@staticmethod
def convert_values(data: dict):
@@ -47,8 +53,44 @@ class DataMaze:
for key in key_bool:
res.update({key: DataMaze.convert_bool(data[key])})
res.update({"OUTPUT_FILE": data["OUTPUT_FILE"]})
res.update(
DataMaze.get_solver_generator(data, res["ENTRY"], res["EXIT"])
)
return res
@staticmethod
def get_solver_generator(data: dict, entry: int, exit: int) -> dict:
available_generator = {
"Kruskal": Kruskal,
"DFS": DepthFirstSearch,
}
available_solver = {
"AStar": AStar,
}
res = {}
res["GENERATOR"] = available_generator[data["GENERATOR"]]()
res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit)
return res
@staticmethod
def convert_tuple(data: str) -> tuple:
data_t = data.split(",")
if len(data_t) != 2:
raise ValueError(
"There is too much " "argument in the coordinate given"
)
x, y = data_t
tup = (int(x), int(y))
return tup
@staticmethod
def convert_bool(data: str) -> bool:
if data != "True" and data != "False":
raise ValueError("This is not True or False")
if data == "True":
return True
return False
@staticmethod
def get_data_maze(name_file: str) -> dict:
try:
@@ -56,7 +98,7 @@ class DataMaze:
data_dict = DataMaze.transform_data(data_str)
DataMaze.verif_key_data(data_dict)
data_maze = DataMaze.convert_values(data_dict)
return data_maze
return {k.lower(): v for k, v in data_maze.items()}
except FileNotFoundError:
print("The file do not exist")
exit()
@@ -70,28 +112,11 @@ class DataMaze:
print(f"Error on the key in the file: {e}")
exit()
except IndexError as e:
print("In the function transform Data some data cannot "
f"be splited by '=' because '=' was not present: {e}")
print(
"In the function transform Data some data cannot "
f"be splited by '=' because '=' was not present: {e}"
)
exit()
except AttributeError as e:
print("Error on the "
f"funciton get_data_maze : {e}")
print("Error on the " f"funciton get_data_maze : {e}")
exit()
@staticmethod
def convert_tuple(data: str) -> tuple:
data_t = data.split(",")
if len(data_t) != 2:
raise ValueError("There is too much "
"argument in the coordinate given")
x, y = data_t
tup = (int(x), int(y))
return tup
@staticmethod
def convert_bool(data: str) -> bool:
if data != "True" and data != "False":
raise ValueError("This is not True or False")
if data == "True":
return True
return False