16 Commits

Author SHA1 Message Date
da7e 21e9aba95f docstring to output file function 2026-04-03 18:34:18 +02:00
da7e 9fa121cdce add export file 2026-04-03 18:30:52 +02:00
da7e 04c28a851f fix Astar solver 2026-04-03 17:59:37 +02:00
da7e 6189d7f321 lint fix 2026-04-03 16:48:51 +02:00
da7e e63c2679a6 Remove test prints 2026-04-03 16:44:49 +02:00
da7e f04381568d wheel file + config 2026-04-03 16:12:36 +02:00
da7e ee4f48a5c0 add limit for height and width 2026-04-03 15:42:20 +02:00
da7e 2532a35e30 add mazegen wheel package 2026-04-03 15:10:17 +02:00
da7e 6f503bdd36 add file format checker for parsing 2026-04-03 15:00:08 +02:00
da7e 5022cfe020 add message when ft logo not display 2026-04-03 14:47:34 +02:00
Maoake Teriierooiterai 11947db62f fix the print on 42 in the maze 2026-04-03 14:20:01 +02:00
da7e 0045def73b SEED implementation 2026-04-03 13:58:41 +02:00
Maoake Teriierooiterai b6067b2045 finish the thing 2026-04-03 12:30:42 +02:00
Maoake Teriierooiterai 9c76914366 makefile update 2026-04-03 11:45:38 +02:00
Maoake Teriierooiterai b38da3fc31 need to verify everything on each files scan every line every pixel cause i like pixel its pretty beautiful 2026-04-03 11:36:40 +02:00
da7e b54e49122c README 2026-04-02 14:15:40 +02:00
10 changed files with 686 additions and 36 deletions
-2
View File
@@ -215,5 +215,3 @@ __marimo__/
# Streamlit # Streamlit
.streamlit/secrets.toml .streamlit/secrets.toml
test.txt test.txt
mazegen-1.0.0-py3-none-any.whl
+4 -4
View File
@@ -13,15 +13,15 @@ run_windows:
.venv\Scripts\python -m a_maze_ing config.txt .venv\Scripts\python -m a_maze_ing config.txt
debug: debug:
uv pdb python3 a_maze_ing.py config.txt uv run python3 -m pdb a_maze_ing.py config.txt
clean: clean:
rm -rf */**/__pycache__ */__pycache__ __pycache__ .mypy_cache .venv dist build */**/*.egg-info */*.egg-info *.egg-info test.txt rm -rf */**/__pycache__ */__pycache__ __pycache__ */.mypy_cache .mypy_cache .venv dist build */**/*.egg-info */*.egg-info *.egg-info test.txt
fclean: clean fclean: clean
rm mazegen-1.0.0-py3-none-any.whl rm mazegen-1.0.0-py3-none-any.whl
lint: lint: install
uv run flake8 . --exclude=.venv uv run flake8 . --exclude=.venv
uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs -p mazegen uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs -p mazegen
uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs -p parsing uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs -p parsing
@@ -29,7 +29,7 @@ lint:
uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs tests uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs tests
uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs a_maze_ing.py uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs a_maze_ing.py
lint-strict: lint-strict: install
uv run flake8 . --exclude=.venv uv run flake8 . --exclude=.venv
uv run env PYTHONPATH=src python3 -m mypy --strict -p mazegen uv run env PYTHONPATH=src python3 -m mypy --strict -p mazegen
uv run env PYTHONPATH=src python3 -m mypy --strict src/AMazeIng.py uv run env PYTHONPATH=src python3 -m mypy --strict src/AMazeIng.py
+615 -1
View File
@@ -1 +1,615 @@
SITULITUPU This project has been created as part of the 42 curriculum by *mteriier*, *dgaillet*
# A-Maze-ing
## Description
A-Maze-ing is a Python project that generates, solves, exports, and displays mazes.
The program:
- reads a configuration file,
- generates a maze according to the requested parameters,
- optionally enforces a **perfect maze** property,
- solves the maze from entry to exit,
- writes the maze to an output file using the required hexadecimal wall encoding,
- and displays the maze visually through an **MLX graphical window**.
This project was designed with **code reusability** in mind.
The maze generation and solving logic is exposed through a reusable Python package named **`mazegen`**, which can be built and installed independently for use in future projects.
---
## Features
- Maze generation from a config file
- Multiple generation algorithms:
- `DFS` (depth-first search / recursive backtracking style)
- `Kruskal`
- Multiple solving algorithms:
- `AStar`
- `DFS`
- Perfect and imperfect maze support
- Maze export using hexadecimal wall encoding
- Graphical rendering with MLX
- Animated generation
- Animated solution path display
- Wall color switching
- Reserved visual **“42” pattern** using fully closed cells when the maze is large enough
- Reusable `mazegen` package
---
## Project Structure
```text
.
├── a_maze_ing.py # Main executable script and MLX display
├── config.txt # Default configuration file
├── Makefile
├── README.md
├── src/
│ ├── AMazeIng.py
│ ├── mazegen/
│ │ ├── __init__.py
│ │ ├── Cell.py
│ │ ├── Maze.py
│ │ ├── MazeGenerator.py
│ │ └── MazeSolver.py
│ └── parsing/
│ └── Parsing.py
└── tests/
```
---
## Instructions
### Requirements
- Python **3.10+**
- `uv`, `pip`
- MLX Python binding used by the project
### Installation
Using the provided `Makefile`:
```bash
make install
```
This installs project dependencies and the MLX wheel used by the graphical display.
---
## Run
```bash
make run
```
---
## Debug
```bash
make debug
```
---
## Lint
Mandatory lint target:
```bash
make lint
```
Strict lint target:
```bash
make lint-strict
```
---
## Clean
```bash
make clean
```
Full cleanup:
```bash
make fclean
```
---
## Configuration File Format
The configuration file contains one `KEY=VALUE` pair per line.
### Mandatory keys
| Key | Description | Example |
|---|---|---|
| `WIDTH` | Maze width in cells | `WIDTH=20` |
| `HEIGHT` | Maze height in cells | `HEIGHT=15` |
| `ENTRY` | Entry coordinates `(x,y)` | `ENTRY=1,1` |
| `EXIT` | Exit coordinates `(x,y)` | `EXIT=20,15` |
| `OUTPUT_FILE` | Output filename | `OUTPUT_FILE=maze.txt` |
| `PERFECT` | Perfect maze or not | `PERFECT=True` |
| `GENERATOR` | Generation algorithm | `GENERATOR=DFS` |
| `SOLVER` | Solving algorithm | `SOLVER=AStar` |
### Supported values
#### GENERATOR
- `DFS`
- `Kruskal`
#### SOLVER
- `AStar`
- `DFS`
#### PERFECT
- `True`
- `False`
### Example config
```ini
WIDTH=20
HEIGHT=15
ENTRY=1,1
EXIT=20,15
OUTPUT_FILE=maze.txt
PERFECT=True
GENERATOR=DFS
SOLVER=AStar
SEED=31766516
```
### Notes
- Coordinates are handled as tuples in the form `x,y`.
- In the current implementation, coordinates are expected to be **inside maze bounds**.
- Entry and exit must be valid cells.
- The parser validates required keys and converts values to the correct Python types.
- You can add a `SEED` value
---
## Output File Format
The generated maze is written row by row using **one hexadecimal digit per cell**.
Each cell stores wall information using this bitmask:
| Bit | Direction |
|---|---|
| `1` | North |
| `2` | East |
| `4` | South |
| `8` | West |
A bit set to `1` means the wall is **closed**.
### Example
- `3` = `0011` → north and east closed
- `A` = `1010` → east and west closed
### Output layout
```text
<maze row 1>
<maze row 2>
...
<maze row n>
<entry coordinates>
<exit coordinates>
<solution path>
```
Example:
```text
FFFF
9A63
8C47
FFFF
1,1
4,4
EESSEN
```
---
## Visual Representation
This project provides a graphical rendering through **MLX**.
The display shows:
- maze walls,
- entry cell,
- exit cell,
- optional shortest path,
- reserved “42” pattern when present.
### Controls
In the MLX window:
- `1` / mapped equivalent: regenerate maze
- `2` / mapped equivalent: show/hide path
- `3` / mapped equivalent: change wall color
- `4` / mapped equivalent: quit
The code includes two key mappings to handle platform/layout differences.
### Visual Features
- animated generation,
- animated path display,
- color cycling for walls,
- separate color cycling for the “42” cells.
---
## Maze Generation Algorithm
This project supports two generation algorithms.
### 1. Depth-First Search (DFS)
This algorithm starts from a cell and repeatedly visits an unvisited neighbour, removing walls as it advances. When it reaches a dead end, it backtracks until it finds a cell with an unvisited neighbour.
#### Why this algorithm was chosen
- simple to implement,
- naturally produces connected mazes,
- works well for animation,
- produces visually interesting long corridors,
- easy to adapt for perfect mazes.
### 2. Kruskal
This algorithm treats each cell as its own set and removes walls between cells only when it connects two different sets. This avoids cycles and guarantees connectivity.
#### Why this algorithm was included
- classic maze generation algorithm,
- good complement to DFS,
- demonstrates modularity and algorithm interchangeability,
- naturally fits the reusable package requirement.
---
## Why These Algorithms Were Chosen
We chose DFS and Kruskal because together they provide:
- two well-known and complementary approaches,
- good pedagogical value,
- simple integration into a reusable class-based architecture,
- deterministic structure when used with a seed,
- compatibility with perfect maze generation.
DFS is particularly suitable for progressive visual rendering.
Kruskal is useful to show a different construction logic based on set merging.
---
## Perfect and Imperfect Mazes
When `PERFECT=True`:
- the maze is generated as a **perfect maze**,
- there is exactly one path between any two reachable cells,
- in particular, entry and exit have a unique valid path.
When `PERFECT=False`:
- additional walls may be removed after initial generation,
- loops can appear,
- the maze remains connected,
- the solver still computes a valid path.
---
## The “42” Pattern
For sufficiently large mazes, the generator reserves a group of fully closed cells to draw a visible **“42”** pattern in the visual rendering.
### Behaviour
- the pattern is added only if the maze is large enough,
- if the maze is too small, the pattern may be omitted,
- this should be reported to the user with a console message.
### Current implementation note
The current code includes support for reserving and rendering the “42” pattern using cells with value `15` (all walls closed).
The pattern is drawn in the central area when dimensions are large enough.
---
## Error Handling
The project is designed to fail gracefully and provide clear messages for common problems such as:
- missing configuration file,
- empty file,
- missing or invalid keys,
- invalid boolean values,
- invalid coordinates,
- invalid maze dimensions,
- solving an uninitialized maze.
The parser catches several common exceptions and prints user-friendly messages before exiting.
---
## Reusable Code
The reusable part of the project is the **`mazegen`** package.
It contains:
- `Cell`: wall bitmask representation,
- `Maze`: maze container and textual/ascii rendering,
- `MazeGenerator`: abstract generator interface,
- `DepthFirstSearch`: DFS-based maze generator,
- `Kruskal`: Kruskal-based maze generator,
- `MazeSolver`: abstract solver interface,
- `AStar`: shortest-path solver,
- `DepthFirstSearchSolver`: DFS-based path solver.
This package can be built as a wheel and reused independently of the MLX application.
---
## How to Use the Reusable Module
### Basic example
```python
from mazegen import Maze
from mazegen import DepthFirstSearch, AStar
generator = DepthFirstSearch(start=(1, 1), end=(10, 10), perfect=True)
solver = AStar(start=(1, 1), end=(10, 10))
maze = Maze()
for grid in generator.generator(height=10, width=10, seed=42):
maze.set_maze(grid)
path = solver.solve(maze, height=10, width=10)
print(maze)
print(path)
```
### With Kruskal
```python
from mazegen import Maze, Kruskal, AStar
generator = Kruskal(start=(1, 1), end=(20, 15), perfect=True)
solver = AStar(start=(1, 1), end=(20, 15))
maze = Maze()
for grid in generator.generator(height=15, width=20, seed=123):
maze.set_maze(grid)
print(solver.solve(maze, height=15, width=20))
```
### Accessing the generated structure
```python
maze_array = maze.get_maze()
```
Each element of `maze_array` is a `Cell` object exposing:
- `get_north()`
- `get_est()`
- `get_south()`
- `get_west()`
- `get_value()`
### Accessing a solution
```python
solution = solver.solve(maze, height=15, width=20)
print(solution) # Example: "EESSWN..."
```
---
## Packaging
The reusable package is distributed as **`mazegen-*`**.
Example expected artifact:
```text
mazegen-1.0.0-py3-none-any.whl
```
Build with:
```bash
make build
```
This produces a wheel suitable for later installation with `pip`/`uv`.
---
## Tests
Unit tests are recommended and partially integrated through `pytest` targets in the Makefile.
Start test with:
```bash
make run_test
```
These tests are useful to validate:
- parsing,
- generation,
- solver behavior,
- edge cases.
---
## Technical Choices
### Language
- Python 3.10+
### Libraries
- `numpy` for grid storage
- `pydantic` for model validation
- `mlx` for graphical rendering
- `pytest` for tests
- `mypy` for static typing
- `flake8` for style checking
### Architecture
The project is separated into three main parts:
1. **Main application**
- parsing,
- orchestration,
- MLX rendering,
- user interaction.
2. **Domain model**
- `AMazeIng`,
- maze configuration and lifecycle.
3. **Reusable package**
- generation,
- solving,
- maze structure.
This separation makes the generation logic portable to other projects.
---
## Team and Project Management
### Team roles
- **mteriier**
- Parsing
- DFS generator / solver
- Makefile
- some pytest
- Fix of mazegen package generation
- MLX
- **dgaillet**
- AMazeIng config class
- AStar solver
- Kruskal generator
- some pytest
- mazegen package generation
- MLX
- Cell / Maze class
### Initial planning
Our initial plan was:
1. define the maze data model,
2. implement one working generation algorithm,
3. export the maze to the required format,
4. implement solving,
5. add graphical rendering,
6. package reusable code,
7. write tests and documentation.
### How planning evolved
In practice:
- the reusable package structure had to be stabilized earlier than expected,
- coordinate handling between parser, generator, solver, and renderer required extra work,
- rendering and animation took longer than planned,
- algorithm modularity made later integration easier.
### What worked well
- clean separation between generation and display,
- abstract base classes for generator and solver,
- Makefile automation,
- packaging the reusable module.
### What could be improved
- stricter normalization of coordinate conventions,
- seed support should be exposed directly from configuration,
- more tests for edge cases and invalid inputs,
### Tools used
- Git
- `uv`
- `flake8`
- `mypy`
- `pytest`
- MLX
- optionally AI assistance for docstrings, README
---
## Resources
### Documentation and references
- [NumPy Documentation](https://numpy.org/doc/)
- [Pydantic Documentation](https://docs.pydantic.dev/)
- [A* Pathfinding explanation](https://matteo-tosato7.medium.com/exploring-the-depths-solving-mazes-with-a-search-algorithm-c15253104899)
- [Kruskal generation](https://medium.com/@anushidesilva28/understanding-kruskals-algorithm-44886bf8ba8b)
### How AI was used
AI was used as an assistant for:
- improving docstrings,
- helping structure the README,
---
## Reusable Module Summary
If you only want the reusable maze engine:
1. build/install `mazegen`,
2. import a generator and a solver,
3. generate a maze,
4. solve it,
5. access the grid through `Maze.get_maze()`.
This part is intended for reuse in future Python projects.
+3 -5
View File
@@ -224,7 +224,6 @@ class MazeMLX:
progressively. progressively.
""" """
path = amazing.solve_path() path = amazing.solve_path()
print(path)
actual = amazing.entry actual = amazing.entry
actual = (actual[0] - 1, actual[1] - 1) actual = (actual[0] - 1, actual[1] - 1)
maze = amazing.maze.get_maze() maze = amazing.maze.get_maze()
@@ -474,7 +473,7 @@ class MazeMLX:
self.mlx.mlx_loop_hook(self.mlx_ptr, self.draw_image, amazing) self.mlx.mlx_loop_hook(self.mlx_ptr, self.draw_image, amazing)
self.mlx.mlx_hook(self.win_ptr, 33, 0, self.close_loop, None) self.mlx.mlx_hook(self.win_ptr, 33, 0, self.close_loop, None)
self.mlx.mlx_hook( self.mlx.mlx_hook(
self.win_ptr, 2, 1 << 0, self.handle_key_press_mteriier, amazing self.win_ptr, 2, 1 << 0, self.handle_key_press, amazing
) )
self.mlx.mlx_loop(self.mlx_ptr) self.mlx.mlx_loop(self.mlx_ptr)
@@ -483,12 +482,11 @@ def main() -> None:
"""Run the maze application.""" """Run the maze application."""
mlx = None mlx = None
try: try:
mlx = MazeMLX(1000, 1000) mlx = MazeMLX(1600, 2000)
config = Parsing.get_data_maze("config.txt") config = Parsing.get_data_maze("config.txt")
amazing = AMazeIng(**config) amazing = AMazeIng(**config)
mlx.start(amazing) mlx.start(amazing)
with open("test.txt", "w") as output: amazing.export_maze()
output.write(amazing.__str__())
except Exception as err: except Exception as err:
print(err) print(err)
finally: finally:
+5 -5
View File
@@ -1,8 +1,8 @@
WIDTH=10 WIDTH=4
HEIGHT=10 HEIGHT=4
ENTRY=1,1 ENTRY=1,1
EXIT=10,10 EXIT=1,2
OUTPUT_FILE=maze.txt OUTPUT_FILE=con.txt
PERFECT=True PERFECT=False
GENERATOR=Kruskal GENERATOR=Kruskal
SOLVER=AStar SOLVER=AStar
Binary file not shown.
+15 -3
View File
@@ -12,8 +12,8 @@ class AMazeIng(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True) model_config = ConfigDict(arbitrary_types_allowed=True)
width: int = Field(ge=4) width: int = Field(ge=4, le=100)
height: int = Field(ge=4) height: int = Field(ge=4, le=100)
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)
@@ -21,6 +21,7 @@ class AMazeIng(BaseModel):
maze: Maze = Field(default=Maze(None)) maze: Maze = Field(default=Maze(None))
generator: MazeGenerator generator: MazeGenerator
solver: MazeSolver solver: MazeSolver
seed: int | None = Field(default=None)
@model_validator(mode="after") @model_validator(mode="after")
def check_entry_exit(self) -> Self: def check_entry_exit(self) -> Self:
@@ -36,6 +37,10 @@ class AMazeIng(BaseModel):
raise ValueError("Entry coordinates exceed the maze size") raise ValueError("Entry coordinates exceed the maze size")
if self.exit[0] > self.width or self.exit[1] > self.height: if self.exit[0] > self.width or self.exit[1] > self.height:
raise ValueError("Exit coordinates exceed the maze size") raise ValueError("Exit coordinates exceed the maze size")
if self.entry == self.exit:
raise ValueError("Entry and Exit coordinates cant be the same")
if self.width <= 10 or self.height <= 10:
print("Height or width to low for disply forty two logo")
return self return self
def generate(self) -> Generator[Maze, None, None]: def generate(self) -> Generator[Maze, None, None]:
@@ -46,7 +51,9 @@ class AMazeIng(BaseModel):
Yields: Yields:
The current maze state after each generation step. The current maze state after each generation step.
""" """
for array in self.generator.generator(self.height, self.width): for array in self.generator.generator(
self.height, self.width, self.seed
):
self.maze.set_maze(array) self.maze.set_maze(array)
yield self.maze yield self.maze
return return
@@ -59,6 +66,11 @@ class AMazeIng(BaseModel):
""" """
return self.solver.solve(self.maze, self.height, self.width) return self.solver.solve(self.maze, self.height, self.width)
def export_maze(self) -> None:
"""Export maze, entry, exit and resolved path in output_file"""
with open(self.output_file, "w") as file:
file.write(self.__str__())
def __str__(self) -> str: def __str__(self) -> str:
"""Return a string representation of the maze and its solution. """Return a string representation of the maze and its solution.
+15 -7
View File
@@ -20,8 +20,8 @@ class MazeGenerator(ABC):
end: Ending cell coordinates, using 1-based indexing. end: Ending cell coordinates, using 1-based indexing.
perfect: Whether to generate a perfect maze with no loops. perfect: Whether to generate a perfect maze with no loops.
""" """
self.start = (start[0] - 1, start[1] - 1) self.start = (start[1] - 1, start[0] - 1)
self.end = (end[0] - 1, end[1] - 1) self.end = (end[1] - 1, end[0] - 1)
self.perfect = perfect self.perfect = perfect
@abstractmethod @abstractmethod
@@ -295,6 +295,10 @@ class Kruskal(MazeGenerator):
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): if cells_ft and (self.start in cells_ft or self.end in cells_ft):
print(
"Forty two will not be display. "
"Entry or exit set in the ft logo"
)
cells_ft = None cells_ft = None
if seed is not None: if seed is not None:
@@ -324,7 +328,6 @@ 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
print(f"nb sets: {len(sets.sets)}")
maze = self.walls_to_maze(walls, height, width) maze = self.walls_to_maze(walls, height, width)
if self.perfect is False: if self.perfect is False:
gen = Kruskal.unperfect_maze(width, height, maze, cells_ft) gen = Kruskal.unperfect_maze(width, height, maze, cells_ft)
@@ -347,8 +350,8 @@ class DepthFirstSearch(MazeGenerator):
end: Ending cell coordinates, using 1-based indexing. end: Ending cell coordinates, using 1-based indexing.
perfect: Whether to generate a perfect maze with no loops. perfect: Whether to generate a perfect maze with no loops.
""" """
self.start = (start[0] - 1, start[1] - 1) self.start = (start[1] - 1, start[0] - 1)
self.end = (end[0] - 1, end[1] - 1) self.end = (end[1] - 1, end[0] - 1)
self.perfect = perfect self.perfect = perfect
self.forty_two: set[tuple[int, int]] | None = None self.forty_two: set[tuple[int, int]] | None = None
@@ -369,9 +372,9 @@ class DepthFirstSearch(MazeGenerator):
The final generated maze. The final generated maze.
""" """
if seed is not None: if seed is not None:
np.random.seed(seed) random.seed(seed)
maze = self.init_maze(width, height) maze = self.init_maze(width, height)
if width > 9 and height > 9: if width > 10 and height > 10:
self.forty_two = self.get_cell_ft(width, height) self.forty_two = self.get_cell_ft(width, height)
visited: NDArray[np.object_] = np.zeros((height, width), dtype=bool) visited: NDArray[np.object_] = np.zeros((height, width), dtype=bool)
if ( if (
@@ -380,6 +383,11 @@ class DepthFirstSearch(MazeGenerator):
and self.end not in self.forty_two and self.end not in self.forty_two
): ):
visited = self.lock_cell_ft(visited, self.forty_two) visited = self.lock_cell_ft(visited, self.forty_two)
else:
print(
"Forty two will not be display. "
"Entry or exit set in the ft logo"
)
path: list[tuple[int, int]] = list() path: list[tuple[int, int]] = list()
w_h = (width, height) w_h = (width, height)
coord = (0, 0) coord = (0, 0)
+4 -1
View File
@@ -84,7 +84,8 @@ class AStar(MazeSolver):
start: Start coordinates using 1-based indexing. start: Start coordinates using 1-based indexing.
end: End coordinates using 1-based indexing. end: End coordinates using 1-based indexing.
""" """
super().__init__(start, end) self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1)
def h(self, n: tuple[int, int]) -> int: def h(self, n: tuple[int, int]) -> int:
"""Compute the Manhattan distance heuristic to the goal. """Compute the Manhattan distance heuristic to the goal.
@@ -196,6 +197,8 @@ class AStar(MazeSolver):
to_check, to_check,
) )
) )
if path == self.end:
break
raise Exception("Path not found") raise Exception("Path not found")
def get_rev_dir(self, current: Node) -> str: def get_rev_dir(self, current: Node) -> str:
+25 -8
View File
@@ -62,14 +62,12 @@ class DataMaze:
"GENERATOR", "GENERATOR",
"SOLVER", "SOLVER",
} }
set_key = {key for key in data.keys()} i = 0
if len(set_key) != len(key_test): for key in data:
raise KeyError("Missing some data the len do not correspond") if key in key_test:
res_key = {key for key in set_key if key not in key_test} i += 1
if len(res_key) != 0: if len(key_test) != i:
raise KeyError( raise Exception("Some mandatory key not provide")
"Some Key " f"do not correspond the keys: {res_key}"
)
@staticmethod @staticmethod
def convert_values(data: dict[str, str]) -> dict[str, Any]: def convert_values(data: dict[str, str]) -> dict[str, Any]:
@@ -88,6 +86,10 @@ class DataMaze:
res: dict[str, Any] = {} res: dict[str, Any] = {}
for key in key_int: for key in key_int:
res.update({key: int(data[key])}) res.update({key: int(data[key])})
try:
res.update({"SEED": int(data["SEED"])})
except KeyError:
pass
for key in key_tuple: for key in key_tuple:
res.update({key: DataMaze.convert_tuple(data[key])}) res.update({key: DataMaze.convert_tuple(data[key])})
for key in key_bool: for key in key_bool:
@@ -181,6 +183,20 @@ class DataMaze:
return True return True
return False return False
@staticmethod
def test_file_format(file: str) -> None:
with open(file) as data_str:
for line in data_str:
if len(line.split("=", 1)) != 2:
raise Exception(
"config file format not respected. excpected format : "
"KEY=VALUE"
)
if not line.split("=", 1)[1] or line.split("=", 1)[1] == "\n":
raise Exception(
f"VALUE not provide for {line.split('=')[0]} key"
)
@staticmethod @staticmethod
def get_data_maze(name_file: str) -> dict[str, Any]: def get_data_maze(name_file: str) -> dict[str, Any]:
"""Load, validate, and convert maze configuration data from a file. """Load, validate, and convert maze configuration data from a file.
@@ -192,6 +208,7 @@ class DataMaze:
A dictionary of validated configuration values with lowercase keys. A dictionary of validated configuration values with lowercase keys.
""" """
try: try:
DataMaze.test_file_format(name_file)
data_str = DataMaze.get_file_data(name_file) data_str = DataMaze.get_file_data(name_file)
data_dict = DataMaze.transform_data(data_str) data_dict = DataMaze.transform_data(data_str)
DataMaze.verif_key_data(data_dict) DataMaze.verif_key_data(data_dict)