49 Commits

Author SHA1 Message Date
da7e be997c5d17 fix lint + black formating + SITULITUPU 2026-04-01 18:02:38 +02:00
da7e a4d8f3fbfe Merge branch 'whl' 2026-04-01 17:44:20 +02:00
da7e 3fe46026ec Merge branch 'docstring' 2026-04-01 17:42:48 +02:00
Maoake Teriierooiterai d2f38468a4 need to be merge to the main and add some line for the makefile 2026-04-01 17:17:20 +02:00
Maoake Teriierooiterai b659871902 finish the mypy 2026-04-01 16:09:42 +02:00
Maoake Teriierooiterai c9e0cf0610 fix some mypy need to fix for the others 2026-04-01 15:25:38 +02:00
Maoake Teriierooiterai aadccfba53 finish the mypy strict 2026-04-01 15:19:46 +02:00
Maoake Teriierooiterai c7c7213fb9 fix some mypy strict on file a_maze_ing.py 2026-04-01 15:03:22 +02:00
da7e 843fe5f80c uv config for build wheel package
add clean and fclean method to Makefile
2026-04-01 14:31:11 +02:00
Maoake Teriierooiterai 03b5f9e6fd fix mypy strict on MazeSolver and Maze Generator 2026-04-01 14:12:39 +02:00
da7e 68c40be144 add(docstring): doc string on every class and functions 2026-04-01 12:34:19 +02:00
maoake ed16566677 finish to fix parsing mypy 2026-03-31 22:43:03 +02:00
maoake 40e25757c7 starting mypy with maze 2026-03-31 22:31:48 +02:00
maoake b1eda06fa5 fixing flake8 2026-03-31 22:01:45 +02:00
maoake 769198c06b adding the blink on the 42 2026-03-31 21:03:10 +02:00
maoake 2c7b565137 give a checkpoint to the project blink the 42 2026-03-31 20:29:01 +02:00
maoake d23959ce74 fix conflict 2026-03-31 20:17:08 +02:00
maoake 4cb678b5be something is up 2026-03-31 19:59:09 +02:00
da7e b520210d58 fix(MazeMLX): margin calculation, big maze are now display fully 2026-03-30 16:36:52 +02:00
da7e bdb1056d69 fix(AmazMLX): draw_ft margin 2026-03-30 15:57:16 +02:00
da7e b2aa93e04d add color to put block 2026-03-30 15:47:39 +02:00
da7e 56ebb2823a code refactor(AmazMLX) 2026-03-30 15:45:15 +02:00
da7e 150eaedc94 Merge branch 'main' of github.com:maoakeEnterprise/amazing 2026-03-30 15:41:35 +02:00
da7e 6f4699c29f wip(entry exit) 2026-03-30 15:37:45 +02:00
Maoake Teriierooiterai 5913f5267d trying to get the blink on the 42 2026-03-30 15:36:52 +02:00
Maoake Teriierooiterai d4251dc8b7 fixing the conflict 2026-03-30 14:47:16 +02:00
Maoake Teriierooiterai 282fbd6867 poop the conflict 2026-03-30 14:39:05 +02:00
da7e 0f77e0c6e4 fix buffer overflow in put pixel + margin calculation 2026-03-30 14:37:33 +02:00
Maoake Teriierooiterai cfac4bed25 need to add the color 2026-03-30 13:53:14 +02:00
Maoake Teriierooiterai cd3c75fb1e set up the path print with the button 2026-03-30 12:01:23 +02:00
Maoake Teriierooiterai 628bb8a94b put the functions color and need to refactor the code 2026-03-30 08:26:53 +02:00
mteriier dc19b526fa testing colors on the project cause we need to test it out 2026-03-29 23:35:42 +02:00
Maoake Teriierooiterai 68d710e313 color 42 2026-03-29 18:47:29 +02:00
da7e 92c6237f06 fix(astar): the actual astar wasn't the real astar algoritm 2026-03-29 15:38:40 +02:00
Maoake Teriierooiterai b682274102 opti path 2026-03-29 14:31:04 +02:00
mteriier d534993f4c starting my branch need to rush this 2026-03-28 23:01:42 +01:00
da7e fa38f7a311 Merge branch 'mlx' 2026-03-27 21:53:06 +01:00
da7e 16d97e9912 fix(astar): function f() miscalculate the best path 2026-03-27 21:51:49 +01:00
da7e b317f7a3a0 FIX(path render): path render was called twice 2026-03-27 21:42:14 +01:00
da7e 2fc67683d8 add key handling without color management (not implemented) 2026-03-27 20:58:28 +01:00
da7e cb19cf1413 ADD(mlx path animation) 2026-03-27 19:47:21 +01:00
da7e 6ec617848f Merge branch 'main' of github.com:maoakeEnterprise/amazing into mlx 2026-03-27 18:29:39 +01:00
da7e 349e58ce41 ifpjefp 2026-03-27 18:29:09 +01:00
Maoake Teriierooiterai b078241359 fix something on the solver 2026-03-27 18:05:05 +01:00
Maoake Teriierooiterai 3c072de0f4 finish the animation generator maze 2026-03-27 16:49:13 +01:00
Maoake Teriierooiterai a3bbce861d fix conflict 2026-03-27 14:39:06 +01:00
Maoake Teriierooiterai ca9444778e need to test the mlx 2026-03-27 14:35:04 +01:00
da7e 135e13deff WIP(iterative display): display change for every generation doesn't work 2026-03-27 13:33:45 +01:00
da7e 2828e37853 maze is display once 2026-03-26 18:22:45 +01:00
39 changed files with 1446 additions and 2113 deletions
+1
View File
@@ -216,3 +216,4 @@ __marimo__/
.streamlit/secrets.toml .streamlit/secrets.toml
test.txt test.txt
mazegen-1.0.0-py3-none-any.whl
+20 -4
View File
@@ -1,5 +1,10 @@
build:
uv build --clear --wheel
cp dist/*.whl mazegen-1.0.0-py3-none-any.whl
install: install:
uv sync uv sync
uv pip install mlx-2.2-py3-none-any.whl
run: install run: install
uv run python3 a_maze_ing.py config.txt uv run python3 a_maze_ing.py config.txt
@@ -11,15 +16,22 @@ debug:
uv pdb python3 a_maze_ing.py config.txt uv pdb python3 a_maze_ing.py config.txt
clean: clean:
rm -rf __pycache__ .mypy_cache .venv rm -rf */**/__pycache__ __pycache__ .mypy_cache .venv dist build */**/*.egg-info *.egg-info test.txt
fclean: clean
rm mazegen-1.0.0-py3-none-any.whl
lint: lint:
uv run flake8 . --exclude=.venv uv run flake8 . --exclude=.venv
uv run mypy . --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs uv run env PYTHONPATH=src python3 -m mypy --warn-return-any --warn-unused-ignores --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs src
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
lint-strict: lint-strict:
uv run flake8 . uv run flake8 . --exclude=.venv
uv run mypy . --strict uv run env PYTHONPATH=src python3 -m mypy --strict src
uv run env PYTHONPATH=src python3 -m mypy --strict tests
uv run env PYTHONPATH=src python3 -m mypy --strict a_maze_ing.py
run_test_parsing: run_test_parsing:
PYTHONPATH=src uv run pytest tests/test_parsing.py PYTHONPATH=src uv run pytest tests/test_parsing.py
@@ -31,3 +43,7 @@ run_test_maze_gen:
PYTHONPATH=src uv run pytest tests/test_MazeGenerator.py PYTHONPATH=src uv run pytest tests/test_MazeGenerator.py
run_test: run_test:
uv run pytest uv run pytest
mlx:
uv run python3 test.py
.PHONY: build install run debug clean fclean lint lint-strict run_test
+486 -7
View File
@@ -1,20 +1,499 @@
import os from typing import Any
from numpy.typing import NDArray
from src.AMazeIng import AMazeIng from src.AMazeIng import AMazeIng
from src.parsing import Parsing from src.parsing.Parsing import DataMaze as Parsing
from mlx import Mlx
import time
class MazeMLX:
"""Render, animate, and interact with a maze using an MLX window."""
def __init__(self, height: int, width: int) -> None:
"""Initialize the MLX renderer and create the window and image buffer.
Args:
height: Height of the rendering area in pixels.
width: Width of the rendering area in pixels.
"""
self.mlx = Mlx()
self.height = height
self.width = width
self.print_path = False
self.color = [0x00, 0x00, 0xFF, 0xFF]
self.mlx_ptr = self.mlx.mlx_init()
self.win_ptr = self.mlx.mlx_new_window(
self.mlx_ptr, width, height + 200, "A-Maze-Ing"
)
self.img_ptr = self.mlx.mlx_new_image(self.mlx_ptr, width, height)
self.buf, self.bpp, self.size_line, self.format = (
self.mlx.mlx_get_data_addr(self.img_ptr)
)
def close(self) -> None:
"""Destroy the image used by the renderer."""
self.mlx.mlx_destroy_image(self.mlx_ptr, self.img_ptr)
def close_loop(self, _: Any) -> None:
"""Stop the MLX event loop.
Args:
_: Unused callback argument.
"""
self.mlx.mlx_loop_exit(self.mlx_ptr)
def clear_image(self) -> None:
"""Clear the image buffer."""
self.buf[:] = b"\x00" * len(self.buf)
def redraw_image(self) -> None:
"""Redraw the window contents and display the control help text."""
self.mlx.mlx_clear_window(self.mlx_ptr, self.win_ptr)
self.mlx.mlx_put_image_to_window(
self.mlx_ptr, self.win_ptr, self.img_ptr, 0, 0
)
self.mlx.mlx_string_put(
self.mlx_ptr,
self.win_ptr,
self.width // 3,
self.height + 100,
0xFFFFFF,
"1: regen; 2: path; 3: color; 4: quit;",
)
def put_pixel(
self, x: int, y: int, color: list[Any] | None = None
) -> None:
"""Draw a single pixel into the image buffer.
Args:
x: Horizontal pixel position.
y: Vertical pixel position.
color: Optional RGBA color list. If omitted, the current renderer
color is used.
"""
if x < 0 or y < 0 or x >= self.width or y >= self.height:
return
offset = y * self.size_line + x * (self.bpp // 8)
if color:
self.buf[offset + 0] = color[0]
self.buf[offset + 1] = color[1]
self.buf[offset + 2] = color[2]
if self.bpp >= 32:
self.buf[offset + 3] = color[3]
else:
self.buf[offset + 0] = self.color[0]
self.buf[offset + 1] = self.color[1]
self.buf[offset + 2] = self.color[2]
if self.bpp >= 32:
self.buf[offset + 3] = self.color[3]
def put_line(
self,
start: tuple[int, int],
end: tuple[int, int],
color: list[Any] | None = None,
) -> None:
"""Draw a horizontal or vertical line.
Args:
start: Starting pixel coordinates.
end: Ending pixel coordinates.
color: Optional RGBA color list.
"""
sx, sy = start
ex, ey = end
if sy == ey:
for x in range(min(sx, ex), max(sx, ex) + 1):
self.put_pixel(x, sy, color)
if sx == ex:
for y in range(min(sy, ey), max(sy, ey) + 1):
self.put_pixel(sx, y, color)
def put_block(
self,
ul: tuple[int, int],
dr: tuple[int, int],
color: list[Any] | None = None,
) -> None:
"""Draw a filled rectangular block.
Args:
ul: Upper-left corner coordinates.
dr: Lower-right corner coordinates.
color: Optional RGBA color list.
"""
for y in range(min(ul[1], dr[1]), max(dr[1], ul[1])):
self.put_line(
(min(ul[0], dr[0]), y), (max(ul[0], dr[0]), y), color
)
@staticmethod
def random_color_ft() -> Any:
"""Yield colors in a repeating sequence for the reserved pattern.
Yields:
RGBA color lists.
"""
colors = [
[0xFF, 0xBF, 0x00, 0xFF], # blue
[0x00, 0xFF, 0x40, 0xFF], # green
[0xFF, 0x00, 0xFF, 0xFF], # pink
[0x00, 0xFF, 0xFF, 0xFF], # yellow
]
while True:
for color in colors:
yield color
@staticmethod
def random_color() -> Any:
"""Yield colors in a repeating sequence for maze rendering.
Yields:
RGBA color lists.
"""
colors = [
[0xFF, 0x00, 0xFF, 0xFF], # pink
[0x00, 0xFF, 0xFF, 0xFF], # yellow
[0x00, 0xFF, 0x40, 0xFF], # green
[0xFF, 0xBF, 0x00, 0xFF], # blue
[0xFF, 0x00, 0x80, 0xFF], # purple
[0x00, 0x00, 0xFF, 0xFF], # red
]
while True:
for color in colors:
yield color
def get_margin_line_len(self, maze: NDArray[Any]) -> tuple[int, int, int]:
"""Compute the cell size and margins for centering the maze.
Args:
maze: Maze grid to render.
Returns:
A tuple containing the cell side length, horizontal margin, and
vertical margin.
"""
rows = len(maze)
cols = len(maze[0])
line_len = min(self.width // cols, self.height // rows) - 1
maze_width = cols * line_len
maze_height = rows * line_len
margin_x = ((self.width - maze_width) // 2) + 1
margin_y = ((self.height - maze_height) // 2) + 1
return (line_len, margin_x, margin_y)
def update_maze(self, maze: NDArray[Any]) -> None:
"""Render the maze walls into the image buffer.
Args:
maze: Maze grid to render.
"""
self.clear_image()
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
for y in range(len(maze)):
for x in range(len(maze[0])):
x0 = x * line_len + margin_x
y0 = y * line_len + margin_y
x1 = x * line_len + line_len + margin_x
y1 = y * line_len + line_len + margin_y
if maze[y][x].get_north():
self.put_line((x0, y0), (x1, y0))
if maze[y][x].get_est():
self.put_line((x1, y0), (x1, y1))
if maze[y][x].get_south():
self.put_line((x0, y1), (x1, y1))
if maze[y][x].get_west():
self.put_line((x0, y0), (x0, y1))
def put_path(self, amazing: AMazeIng) -> Any:
"""Animate the solution path inside the maze.
Args:
amazing: Maze container with generation and solving logic.
Yields:
Control after each path segment so the animation can be rendered
progressively.
"""
path = amazing.solve_path()
print(path)
actual = amazing.entry
actual = (actual[0] - 1, actual[1] - 1)
maze = amazing.maze.get_maze()
if maze is None:
return
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
for i in range(len(path)):
ul = (
(actual[0]) * line_len + margin_x + 12,
(actual[1]) * line_len + 12 + margin_y,
)
dr = (
(actual[0]) * line_len + line_len + margin_x - 12,
(actual[1]) * line_len + line_len - 12 + margin_y,
)
self.put_block(ul, dr)
x0 = actual[0] * line_len + margin_x + 12
y0 = actual[1] * line_len + margin_y + 12
x1 = actual[0] * line_len + line_len + margin_x - 12
y1 = actual[1] * line_len + line_len + margin_y - 12
yield
match path[i]:
case "N":
self.put_block((x0, y0), (x1, y0 - 24))
actual = (actual[0], actual[1] - 1)
case "E":
self.put_block((x1, y0), (x1 + 24, y1))
actual = (actual[0] + 1, actual[1])
case "S":
self.put_block((x0, y1), (x1, y1 + 24))
actual = (actual[0], actual[1] + 1)
case "W":
self.put_block((x0, y0), (x0 - 24, y1))
actual = (actual[0] - 1, actual[1])
ul = (
(actual[0]) * line_len + margin_x + 12,
(actual[1]) * line_len + 12 + margin_y,
)
dr = (
(actual[0]) * line_len + line_len + margin_x - 12,
(actual[1]) * line_len + line_len - 12 + margin_y,
)
self.put_block(ul, dr)
return
def put_start_end(self, amazing: AMazeIng) -> None:
"""Draw highlighted blocks for the maze entry and exit.
Args:
amazing: Maze container with current maze data.
"""
entry = amazing.entry
exit = amazing.exit
maze = amazing.maze.get_maze()
if maze is None:
return
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
ul = (
(entry[0] - 1) * line_len + margin_x + 3,
(entry[1] - 1) * line_len + 3 + margin_y,
)
dr = (
(entry[0] - 1) * line_len + line_len + margin_x - 3,
(entry[1] - 1) * line_len + line_len - 3 + margin_y,
)
self.put_block(ul, dr, [0xFF, 0xBF, 0x00, 0x9F])
ul = (
(exit[0] - 1) * line_len + margin_x + 3,
(exit[1] - 1) * line_len + 3 + margin_y,
)
dr = (
(exit[0] - 1) * line_len + line_len + margin_x - 3,
(exit[1] - 1) * line_len + line_len - 3 + margin_y,
)
self.put_block(ul, dr, [0x00, 0xFF, 0x40, 0x9F])
def draw_ft(
self, maze: NDArray[Any], color: list[Any] | None = None
) -> None:
"""Draw filled cells corresponding to the reserved fully
walled pattern.
Args:
maze: Maze grid to inspect.
color: Optional RGBA color list.
"""
line_len, margin_x, margin_y = self.get_margin_line_len(maze)
for y in range(len(maze)):
for x in range(len(maze[0])):
if maze[y][x].value == 15:
x0 = x * line_len + margin_x
y0 = y * line_len + margin_y
x1 = x * line_len + line_len + margin_x
y1 = y * line_len + line_len + margin_y
self.put_block((x0, y0), (x1, y1), color)
def draw_image(self, amazing: AMazeIng) -> None:
maze = amazing.maze.get_maze()
"""Main rendering callback used by the MLX loop.
Args:
amazing: Maze container to render.
"""
if self.render_maze(amazing):
if self.print_path:
if self.render_path():
color = next(self.color_gen_ft)
if maze is not None:
self.draw_ft(maze, color)
next(self.timer_gen)
else:
self.time_gen()
if maze is not None:
self.update_maze(maze)
self.draw_ft(maze)
self.put_start_end(amazing)
self.redraw_image()
def shift_color(self) -> None:
"""Reset the maze color generator."""
self.color_gen = self.random_color()
def shift_color_ft(self) -> None:
"""Reset the reserved-pattern color generator."""
self.color_gen_ft = self.random_color_ft()
def time_gen(self) -> None:
"""Reset the timing generator used for animation pacing."""
self.timer_gen = self.time_generator()
def restart_maze(self, amazing: AMazeIng) -> None:
"""Restart maze generation.
Args:
amazing: Maze container providing the generation generator.
"""
self.generator = amazing.generate()
def time_generator(self) -> Any:
"""Yield regularly with a fixed delay for animation timing.
Yields:
``None`` at each step after sleeping.
"""
yield
while True:
time.sleep(0.3)
yield
def restart_path(self, amazing: AMazeIng) -> None:
"""Restart solution path animation.
Args:
amazing: Maze container providing the solution path.
"""
self.path_printer = self.put_path(amazing)
def render_path(self) -> bool:
"""Advance the path animation by one step.
Returns:
``True`` if the path animation is complete, otherwise ``False``.
"""
try:
next(self.path_printer)
time.sleep(0.03)
return False
except StopIteration:
pass
return True
def render_maze(self, amazing: AMazeIng) -> bool:
"""Advance maze generation by one step and redraw it.
Args:
amazing: Maze container being generated.
Returns:
``True`` if maze generation is complete, otherwise ``False``.
"""
try:
maze = amazing.maze.get_maze()
next(self.generator)
if maze is not None:
self.update_maze(maze)
return False
except StopIteration:
pass
return True
def handle_key_press(self, keycode: int, amazing: AMazeIng) -> None:
"""Handle keyboard input for one keycode mapping.
Args:
keycode: Key code received from MLX.
amazing: Maze container to update or render.
"""
if keycode == 49:
self.restart_maze(amazing)
self.print_path = False
if keycode == 50:
self.restart_path(amazing)
self.print_path = True if self.print_path is False else False
if keycode == 51:
self.print_path = False
self.color = next(self.color_gen)
if keycode == 52:
self.close_loop(None)
def handle_key_press_mteriier(
self, keycode: int, amazing: AMazeIng
) -> None:
"""Handle keyboard input for an alternative keycode mapping.
Args:
keycode: Key code received from MLX.
amazing: Maze container to update or render.
"""
if keycode == 38:
self.restart_maze(amazing)
self.print_path = False
if keycode == 233:
self.restart_path(amazing)
self.print_path = True if self.print_path is False else False
if keycode == 34:
self.print_path = False
self.color = next(self.color_gen)
if keycode == 39:
self.close_loop(None)
def start(self, amazing: AMazeIng) -> None:
"""Start the MLX rendering loop.
Args:
amazing: Maze container to generate, solve, and display.
"""
self.restart_maze(amazing)
self.shift_color()
self.shift_color_ft()
self.time_gen()
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, 2, 1 << 0, self.handle_key_press, amazing
)
self.mlx.mlx_loop(self.mlx_ptr)
def main() -> None: def main() -> None:
"""Run the maze application."""
mlx = None
try: try:
config = Parsing.DataMaze.get_data_maze("config.txt") mlx = MazeMLX(1000, 1000)
print(config) config = Parsing.get_data_maze("config.txt")
amazing = AMazeIng(**config) amazing = AMazeIng(**config)
for _ in amazing.generate(): mlx.start(amazing)
os.system("clear")
amazing.maze.ascii_print()
with open("test.txt", "w") as output: with open("test.txt", "w") as output:
output.write(amazing.__str__()) output.write(amazing.__str__())
except Exception as err: except Exception as err:
print(err) print(err)
finally:
if mlx is not None:
mlx.close()
if __name__ == "__main__": if __name__ == "__main__":
+6 -6
View File
@@ -1,8 +1,8 @@
WIDTH=11 WIDTH=10
HEIGHT=11 HEIGHT=10
ENTRY=4,3 ENTRY=1,1
EXIT=2,1 EXIT=10,10
OUTPUT_FILE=maze.txt OUTPUT_FILE=maze.txt
PERFECT=False PERFECT=True
GENERATOR=DFS GENERATOR=Kruskal
SOLVER=AStar SOLVER=AStar
-2
View File
@@ -1,2 +0,0 @@
# src/mlx/__init__.py
from .mlx import *
-136
View File
@@ -1,136 +0,0 @@
.TH MiniLibX 3 "September 19, 2002"
.SH NAME
MiniLibX - Simple Window Interface Library for students
.SH SYNOPSYS
#include <mlx.h>
.nf
.I void *
.fi
.B mlx_init
();
.nf
.I int
.fi
.B mlx_release
(void *mlx_ptr);
.SH DESCRIPTION
MiniLibX is an easy way to create graphical software,
without any X-Window/Wayland/Vulkan programming knowledge under Unix/Linux,
nor any AppKit programming knowledge under MacOS. It provides
simple window creation, a drawing tool, image and basic events
management.
.SH Unix/Linux: HISTORICAL X-WINDOW CONCEPT
X-Window is a network-oriented graphical system for Unix.
It is based on two main parts:
.br
On one side, your software wants to draw something on the screen and/or
get keyboard & mouse entries.
.br
On the other side, the X-Server manages the screen, keyboard and mouse
(It is often referred to as a "display").
.br
A network connection must be established between these two entities to send
drawing orders (from the software to the X-Server), and keyboard/mouse
events (from the X-Server to the software).
.br
Nowadays, most of the time, both run on the same computer.
.SH Unix/Linux: MODERN GRAPHICAL APPROACH
Modern computers come with a powerful GPU that is directly accessed by applications.
Along GPU libraries like Vulkan or OpenGL, the Wayland protocol ensure communication
with the compositor program that manages the various windows on screen and the user
input events.
For your own application:
.br
The Vulkan or OpenGL library allow you to directly draw any content into your window.
.br
The Wayland compositor handles the place of your window on screen and send you back
the keyboard and mouse inputs from the user.
.br
Unfortunately, this gain of graphical power through GPU access removes the networking aspects
that exist with X-Window. It is not possible for a program to access a remote GPU and show its
window on a remote display. But current software architectures are more likely based on a local
display application that gets data in JSON through a web API.
.SH MacOS: WINDOW SERVER AND GPU
Your software interacts directly with the Window server who handles the
cohabitation on the screen with other software and the event system,
and interacts with the GPU to handle all drawing commands.
.SH INCLUDE FILE
.B mlx.h
should be included for a correct use of the MiniLibX API.
It only contains function prototypes, no structure is needed.
.SH LIBRARY FUNCTIONS
.P
First of all, you need to initialize the connection
between your software and the graphic and user sub-systems.
Once this completed, you'll be able to use other MiniLibX
functions to send and receive the messages from
the display, like "I want to draw a yellow pixel in this window" or
"did the user hit a key?".
.P
The
.B mlx_init
function will create this connection. No parameters are needed, ant it will
return a
.I "void *"
identifier, used for further calls to the library routines. The
.B mlx_release
function can be used at the end of the program to disconnect from the graphic
system and release resources.
.P
All other MiniLibX functions are described in the following man pages:
.TP 20
.B mlx_new_window
: manage windows
.TP 20
.B mlx_pixel_put
: draw inside a window
.TP 20
.B mlx_new_image
: manipulate images
.TP 20
.B mlx_loop
: handle keyboard or mouse events
.TP 20
.B mlx_extra
: extra functions available in the MinilibX
.SH LINKING MiniLibX
To use MiniLibX functions, you may -or not- need to link
your software with several libraries, including the MiniLibX library itself.
On Unix/Linux, depending on the specific operating system, either just using
.B -lmlx
works, or you need to add
.B -lxcb -lxcb-keysyms -lvulkan -lz -lbsd
\&.
On MacOS, the dynamic Metal library will find on its own the missing components:
.B -lmlx
\&.
You may also need to specify the path to these libraries, using the
.B -L
flag.
.SH RETURN VALUES
If
.B mlx_init()
fails to set up the connection to the display, it will return NULL, otherwise
a non-null pointer is returned as a connection identifier.
.SH SEE ALSO
mlx_new_window(3), mlx_pixel_put(3), mlx_new_image(3), mlx_loop(3), mlx_extra(3)
.SH AUTHOR
Copyright ol@ - 2002-2025 - Olivier Crouzet
-209
View File
@@ -1,209 +0,0 @@
/*
** mlx.h for MinilibX in
**
** Made by Charlie Root
** Login <ol@42.fr>
**
** Started on Mon Jul 31 16:37:50 2000 Olivier Crouzet
** Last update Tue Jun 25 16:23:28 2025 Olivier Crouzet
*/
/*
** MinilibX - Please report bugs
*/
/* mlx_CLXV version 2.2 */
/*
**
** This library is a simple framework to help 42 students
** create simple graphical apps.
** It only provides the minimum functions, it's students' job
** to create the missing pieces for their own project :)
**
** Current XCB-Vulkan requirements for Linux:
** libxcb, libxcb-keysyms, libvulkan,
** libz, libbsd
** You also need glslc to re-compile shaders if needed.
** At 42, on current Ubuntu 22.04 dump in cluster, you need to get
** libxcb-keysyms source for the include file and compile the .a library.
**
** The MinilibX can load XPM and PNG images.
** Please note that both image loaders are incomplete, some
** image may not load. Also, image loaders only work for little endian hosts.
**
** Historically, the alpha byte did represent transparency
** instead of opacity. It's not the case anymore. MLX matches GPUs standards.
**
** MLX_CLXV API changes:
** - mlx_get_data_addr now provides the image format instead of the
endian, and returns an 'unsigned char' pointer.
** - 'unsigned int' replace 'int' in many calls.
** - mlx_get_color_value() is now deprecated.
** - adding mlx_loop_exit().
**
** With recent X11 implementation and default configuration, the Expose event is only
** received once at the program launch. This is often due to X server saving the
** content of the window.
** With Wayland, there is no such thing like Expose event, and the compositor saves
** the window's content.
**
*/
#ifndef MLX_H
#define MLX_H
/*
** mlx_init() is needed before everything else.
** mlx_init() returns 'void *0' in case of failure.
** mlx_release() returns 0 on success.
*/
void *mlx_init();
int mlx_release(void *mlx_ptr);
/*
** Window actions
*/
void *mlx_new_window(void *mlx_ptr, unsigned int width,
unsigned int height, const char *title);
int mlx_clear_window(void *mlx_ptr, void *win_ptr);
int mlx_pixel_put(void *mlx_ptr, void *win_ptr,
unsigned int x, unsigned int y, unsigned int color);
int mlx_destroy_window(void *mlx_ptr, void *win_ptr);
/*
** mlx_new_window() returns 'void *0' if failed.
** Other functions return 0 on success.
** Origin for x & y is top left corner of the window, y down is positive.
** x and y must fit into the size of the window, values are not controled
** Color byte order is B8G8R8A8, which could be 0xAARRGGBB or 0xBBGGRRAA
** depending on local endianess.
*/
/*
** Images
*/
void *mlx_new_image(void *mlx_ptr, unsigned int width, unsigned int height);
unsigned char *mlx_get_data_addr(void *img_ptr, unsigned int *bits_per_pixel,
unsigned int *size_line,
unsigned int *format);
int mlx_put_image_to_window(void *mlx_ptr, void *win_ptr, void *img_ptr,
int x, int y);
int mlx_destroy_image(void *mlx_ptr, void *img_ptr);
/*
** mlx_new_image() returns 'void *0' in case of failure.
** mlx_get_data_addr() returns a pointer to a height * size_line bytes buffer
** that holds the pixel values.
** Other functions return 0 on success.
** 'format' can be: 0 = B8G8R8A8; 1 = A8R8G8B8; (byte order).
** Carefully consider the format, it can be reversed in some cases, like a remote graphic server
*/
/*
** deprecated function - format of image allows conversion on student's side
** unsigned int mlx_get_color_value(void *mlx_ptr, int color);
**
*/
/*
** main loop & dealing with events
*/
typedef int (*mlx_mouse_callback)(unsigned int, unsigned int, unsigned int, void*);
typedef int (*mlx_key_callback)(unsigned int, void *);
typedef int (*mlx_expose_callback)(void *);
typedef int (*mlx_loop_callback)(void *);
typedef int (*mlx_hook_callback)(void *);
int mlx_loop(void *mlx_ptr);
int mlx_loop_exit(void *mlx_ptr);
int mlx_mouse_hook(void *win_ptr, mlx_mouse_callback funct_ptr, void *param);
int mlx_key_hook(void *win_ptr, mlx_key_callback funct_ptr, void *param);
int mlx_expose_hook(void *win_ptr, mlx_expose_callback funct_ptr, void *param);
int mlx_loop_hook(void *mlx_ptr, mlx_loop_callback funct_ptr, void *param);
/*
** Functions return 0 on success.
** Key event is triggered on KeyRelease, not KeyPressed.
** Mouse event is triggered on clic.
**
** hook functions are called as follow:
** expose_hook(void *param);
** key_hook(unsigned int keycode, void *param);
** mouse_hook(unsigned int button, unsigned int x, unsigned int y,
** void *param);
** loop_hook(void *param);
*/
/*
** Generic hook system for all events, and minilibX functions that
** can be hooked. Some macro and defines from X11/X.h are needed here.
** Warning: you may need to cast your function pointer for key and mouse events
** as there will be extra parameters.
*/
int mlx_hook(void *win_ptr, unsigned int x_event, unsigned int x_mask,
mlx_hook_callback funct_ptr, void *param);
/*
** Convenience functions
** mlx_string_put() display may vary in size between OS and between
** mlx implementations
** mlx_string_put() returns 0 on success.
** Other functions return an image (like mlx_new_image()) or 'void *0'.
**
*/
int mlx_string_put(void *mlx_ptr, void *win_ptr,
unsigned int x, unsigned int y,
unsigned int color, char *string);
void *mlx_xpm_to_image(void *mlx_ptr, const char **xpm_data,
unsigned int *width, unsigned int *height);
void *mlx_xpm_file_to_image(void *mlx_ptr, const char *filename,
unsigned int *width, unsigned int *height);
void *mlx_png_file_to_image(void *mlx_ptr, const char *filename,
unsigned int *width, unsigned int *height);
/*
** Convenience functions
** All functions return 0 on success.
*/
int mlx_mouse_hide(void *mlx_ptr);
int mlx_mouse_show(void *mlx_ptr);
int mlx_mouse_move(void *win_ptr, int x, int y);
int mlx_mouse_get_pos(void *win_ptr, int *x, int *y);
int mlx_do_key_autorepeatoff(void *mlx_ptr);
int mlx_do_key_autorepeaton(void *mlx_ptr);
int mlx_get_screen_size(void *mlx_ptr,
unsigned int *width, unsigned int *height);
/*
** Flush & Sync
*/
int mlx_do_sync(void *mlx_ptr);
#define MLX_SYNC_IMAGE_WRITABLE 1
#define MLX_SYNC_WIN_FLUSH 2
#define MLX_SYNC_WIN_COMPLETED 3
int mlx_sync(void *mlx_ptr, int cmd, void *param);
/*
** Functions return 0 on success.
** mlx_do_sync() will *flush* (not sync) all requests and wait for completion.
** Note: mlx_loop() always flush requests.
** mlx_sync() 'cmd' commands are:
** - 'image_writable' returns when image data can be written again.
** - 'win_flush' returns when all pending requests are sent to server.
** - 'win_completed' returns after flush and completion.
** 'param' is image pointer or window pointer, according to the command.
** mlx_do_sync() equals 'win_flush' for all windows.
**
*/
#endif /* MLX_H */
-121
View File
@@ -1,121 +0,0 @@
.TH MiniLibX 3 "September 19, 2002"
.SH NAME
MiniLibX - Extra functions
.SH SYNOPSYS
.nf
.I int
.fi
.B mlx_mouse_hide
(
.I void *mlx_ptr
);
.nf
.I int
.fi
.B mlx_mouse_show
(
.I void *mlx_ptr
);
.nf
.I int
.fi
.B mlx_mouse_move
(
.I void *mlx_ptr, int x, int y
);
.nf
.I int
.fi
.B mlx_mouse_get_pos
(
.I void *win_ptr, int *x, int *y
);
.nf
.I int
.fi
.B mlx_do_key_autorepeatoff
(
.I void *mlx_ptr
);
.nf
.I int
.fi
.B mlx_do_key_autorepeaton
(
.I void *mlx_ptr
);
.nf
.I int
.fi
.B mlx_get_screen_size
(
.I void *mlx_ptr, unsigned int *width, unsigned int *height
);
.nf
.I int
.fi
.B mlx_do_sync
(
.I void *mlx_ptr
);
.nf
.I int
.fi
.B mlx_sync
(
.I void *mlx_ptr, int cmd, void *param
);
.SH MOUSE EXTRA FUNCTIONS
It is possible to show / hide the mouse, and get its current position without user click or
force its position inside a window.
.SH KEYBOARD EXTRA FUNCTIONS
The auto-repeat mode of the keyboard can be controlled. By default, auto-repeat is on:
multiple "key pressed" events are generated every second until the key is released.
.SH SCREEN EXTRA FUNCTION
It is possible to retrieve the size of the current screen, even before the first
window is created.
.SH FLUSH AND SYNC FUNCTIONS
The
.B mlx_do_sync
function will flush the pending commands to the graphic subsystems, ensuring nothing
is cached on your software's side. On return, there is no guarantee that your
commands have been processed.
.br
With
.B mlx_sync
you have more detailed control over the synchronisation mechanisms. Three different commands
are available:
.br
#define MLX_SYNC_IMAGE_WRITABLE 1
.br
#define MLX_SYNC_WIN_FLUSH 2
.br
#define MLX_SYNC_WIN_COMPLETED 3
.br
The third parameter
.I param
can be either the image identifier (command #1) or the window identifier (commands #2 and #3).
.SH SEE ALSO
mlx(3), mlx_new_window(3), mlx_pixel_put(3), mlx_new_image(3), mlx_loop(3)
.SH AUTHOR
Copyright ol@ - 2002-2025 - Olivier Crouzet
-154
View File
@@ -1,154 +0,0 @@
.TH MiniLibX 3 "September 19, 2002"
.SH NAME
MiniLibX - Handle events
.SH SYNOPSYS
.nf
.I int
.fi
.B mlx_loop
(
.I void *mlx_ptr
);
.nf
.I int
.fi
.B mlx_key_hook
(
.I void *win_ptr, int (*funct_ptr)(), void *param
);
.nf
.I int
.fi
.B mlx_mouse_hook
(
.I void *win_ptr, int (*funct_ptr)(), void *param
);
.nf
.I int
.fi
.B mlx_expose_hook
(
.I void *win_ptr, int (*funct_ptr)(), void *param
);
.nf
.I int
.fi
.B mlx_loop_hook
(
.I void *mlx_ptr, int (*funct_ptr)(), void *param
);
.nf
.I int
.fi
.B mlx_loop_exit
(
.I void *mlx_ptr
);
.SH EVENTS
The graphical system is bi-directional. On one hand, the program sends orders to
the screen to display pixels, images, and so on. On the other hand,
it can get information from the keyboard and mouse associated to
the screen. To do so, the program receives "events" from the keyboard or the
mouse.
.SH DESCRIPTION
To receive events, you must use
.B mlx_loop
(). This function never returns, unless
.B mlx_loop_exit
is called. It is an
infinite loop that waits for an event, and then calls a user-defined
function associated with this event. A single parameter is needed,
the connection identifier
.I mlx_ptr
(see the
.B mlx manual).
You can assign different functions to the three following events:
.br
- A key is released
.br
- The mouse button is pressed
.br
- A part of the window should be re-drawn
(this is called an "expose" event, and it is your program's job to handle it in the
Unix/Linux X11 environment, but at the opposite it never happens on Unix/Linux Wayland-Vulkan nor on MacOS).
.br
Each window can define a different function for the same event.
The three functions
.B mlx_key_hook
(),
.B mlx_mouse_hook
() and
.B mlx_expose_hook
() work exactly the same way.
.I funct_ptr
is a pointer to the function you want to be called
when an event occurs. This assignment is specific to the window defined by the
.I win_ptr
identifier. The
.I param
address will be passed back to your function every time it is called, and should be
used to store the parameters it might need.
The syntax for the
.B mlx_loop_hook
() function is similar to the previous ones, but the given function will be
called when no event occurs, and is not bound to a specific window.
When it catches an event, the MiniLibX calls the corresponding function
with fixed parameters:
.nf
expose_hook(void *param);
key_hook(unsigned int keycode, void *param);
mouse_hook(unsigned int button, unsigned int x, unsigned int y, void *param);
loop_hook(void *param);
.fi
These function names are arbitrary. They here are used to distinguish
parameters according to the event. These functions are NOT part of the
MiniLibX.
.I param
is the address specified in the mlx_*_hook calls. This address is never
used nor modified by the MiniLibX. On key and mouse events, additional
information is passed:
.I keycode
tells you which key is pressed (just try to find out :) ),
(
.I x
,
.I y
) are the coordinates of the mouse click in the window, and
.I button
tells you which mouse button was pressed.
.SH GOING FURTHER WITH EVENTS
The MiniLibX provides a much generic access to other available events. The
.I mlx.h
include define
.B mlx_hook()
in the same manner mlx_*_hook functions work. The event and mask values
will be taken from the historical X11 include file "X.h". Some Wayland and MacOS events are mapped
to these values when it makes sense, and the mask may not be used in some configurations.
See source code of the MiniLibX to find out how it will
call your own function for a specific event.
.SH SEE ALSO
mlx(3), mlx_new_window(3), mlx_pixel_put(3), mlx_new_image(3), mlx_extra(3)
.SH AUTHOR
Copyright ol@ - 2002-2025 - Olivier Crouzet
-180
View File
@@ -1,180 +0,0 @@
.TH MiniLibX 3 "September 19, 2002"
.SH NAME
MiniLibX - Manipulating images
.SH SYNOPSYS
.nf
.I void *
.fi
.B mlx_new_image
(
.I void *mlx_ptr, unsigned int width, unsigned int height
);
.nf
.I unsigned char *
.fi
.B mlx_get_data_addr
(
.I void *img_ptr, unsigned int *bits_per_pixel, unsigned int *size_line, unsigned int *format
);
.nf
.I int
.fi
.B mlx_put_image_to_window
(
.I void *mlx_ptr, void *win_ptr, void *img_ptr, int x, int y
);
.nf
.I void *
.fi
.B mlx_xpm_to_image
(
.I void *mlx_ptr, const char **xpm_data, unsigned int *width, unsigned int *height
);
.nf
.I void *
.fi
.B mlx_xpm_file_to_image
(
.I void *mlx_ptr, const char *filename, unsigned int *width, unsigned int *height
);
.nf
.I void *
.fi
.B mlx_png_file_to_image
(
.I void *mlx_ptr, const char *filename, unsigned int *width, unsigned int *height
);
.nf
.I int
.fi
.B mlx_destroy_image
(
.I void *mlx_ptr, void *img_ptr
);
.SH DESCRIPTION
.B mlx_new_image
() creates a new image in memory. It returns a
.I void *
identifier needed to manipulate this image later. It only needs
the size of the image to be created, using the
.I width
and
.I height
parameters, and the
.I mlx_ptr
connection identifier (see the
.B mlx
manual).
The user can draw inside the image (see below), and
can dump the image inside a specified window at any time to
display it on the screen. This is done using
.B mlx_put_image_to_window
(). Three identifiers are needed here, for the connection to the
display, the window to use, and the image (respectively
.I mlx_ptr
,
.I win_ptr
and
.I img_ptr
). The (
.I x
,
.I y
) coordinates define where the image should be placed in the window.
.B mlx_get_data_addr
() returns information about the created image, allowing a user
to modify it later. The
.I img_ptr
parameter specifies the image to use. The three next parameters should
be the addresses of three different valid unsigned integers.
.I bits_per_pixel
will be filled with the number of bits needed to represent a pixel colour
(also called the depth of the image).
.I size_line
is the number of bytes used to store one line of the image in memory.
This information is needed to move from one line to another in the image.
.I format
tells you how each pixel colour in the image is structured. Currently only 2 values are defined:
.P
0 means format B8G8R8A8
.P
1 means format A8R8G8B8
.B mlx_get_data_addr
returns an
.I unsigned char *
address that represents the beginning of the memory area where the image
is stored. From this address, the first
.I bits_per_pixel
bits represent the colour of the first pixel in the first line of
the image. The second group of
.I bits_per_pixel
bits represent the second pixel of the first line, and so on.
Add
.I size_line
to the address to get the beginning of the second line. You can reach any
pixels of the image that way.
.B mlx_destroy_image
destroys the given image (
.I img_ptr
).
.SH STORING COLOURS INSIDE IMAGES
Depending on the graphic system, the number of bits used to store a pixel colour
used to be different from one hardware to another. Today, the way the user usually
represents a colour, in the ARGB mode, almost always matches the hardware capabilities
on modern computers.
Keep in mind that packing the 4-byte ARGB into an unsigned int depends on the local
computer's endian. Adjust your code accordingly.
.SH XPM AND PNG IMAGES
The
.B mlx_xpm_to_image
() ,
.B mlx_xpm_file_to_image
() and
.B mlx_png_file_to_image
() functions will create a new image the same way.
They will fill it using the specified
.I xpm_data
or
.I filename
, depending on which function is used.
Note that MiniLibX does not use the standard
Xpm and png libraries to deal with xpm and png images. You may not be able to
read all types of xpm and png images. It however handles transparency.
.SH RETURN VALUES
The four functions that create images,
.B mlx_new_image()
,
.B mlx_xpm_to_image()
,
.B mlx_xpm_file_to_image()
and
.B mlx_png_file_to_image()
, will return NULL if an error occurs. Otherwise they return a non-null pointer
as an image identifier.
.SH SEE ALSO
mlx(3), mlx_new_window(3), mlx_pixel_put(3), mlx_loop(3), mlx_extra(3)
.SH AUTHOR
Copyright ol@ - 2002-2025 - Olivier Crouzet
-79
View File
@@ -1,79 +0,0 @@
.TH MiniLibX 3 "September 19, 2002"
.SH NAME
MiniLibX - Managing windows
.SH SYNOPSYS
.nf
.I void *
.fi
.B mlx_new_window
(
.I void *mlx_ptr, unsigned int width, unsigned int height, const char *title
);
.nf
.I int
.fi
.B mlx_clear_window
(
.I void *mlx_ptr, void *win_ptr
);
.nf
.I int
.fi
.B mlx_destroy_window
(
.I void *mlx_ptr, void *win_ptr
);
.SH DESCRIPTION
The
.B mlx_new_window
() function creates a new window on the screen, using the
.I width
and
.I height
parameters to determine its size, and
.I title
as the text that should be displayed in the window's title bar.
The
.I mlx_ptr
parameter is the connection identifier returned by
.B mlx_init
() (see the
.B mlx
man page).
.B mlx_new_window
() returns a
.I void *
window identifier that can be used by other MiniLibX calls.
Note that the MiniLibX
can handle an arbitrary number of separate windows.
.B mlx_clear_window
() and
.B mlx_destroy_window
() respectively clear (in black) and destroy the given window. They both have
the same parameters:
.I mlx_ptr
is the screen connection identifier, and
.I win_ptr
is a window identifier.
.SH RETURN VALUES
If
.B mlx_new_window()
fails to create a new window (whatever the reason), it will return NULL,
otherwise a non-null pointer is returned as a window identifier.
.B mlx_clear_window
and
.B mlx_destroy_window
return nothing.
.SH SEE ALSO
mlx(3), mlx_pixel_put(3), mlx_new_image(3), mlx_loop(3), mlx_extra(3)
.SH AUTHOR
Copyright ol@ - 2002-2025 - Olivier Crouzet
-83
View File
@@ -1,83 +0,0 @@
.TH MiniLibX 3 "September 19, 2002"
.SH NAME
MiniLibX - Drawing inside windows
.SH SYNOPSYS
.nf
.I int
.fi
.B mlx_pixel_put
(
.I void *mlx_ptr, void *win_ptr, unsigned int x, unsigned int y, unsigned int color
);
.nf
.I int
.fi
.B mlx_string_put
(
.I void *mlx_ptr, void *win_ptr, unsigned int x, unsigned int y, unsigned int color, char *string
);
.SH DESCRIPTION
The
.B mlx_pixel_put
() function draws a defined pixel in the window
.I win_ptr
using the (
.I x
,
.I y
) coordinates, and the specified
.I color
\&. The origin (0,0) is the upper left corner of the window, the x and y axis
respectively pointing right and down. The connection
identifier,
.I mlx_ptr
, is needed (see the
.B mlx
man page).
Parameters for
.B mlx_string_put
() have the same meaning. Instead of a simple pixel, the specified
.I string
will be displayed at (
.I x
,
.I y
).
Both functions will discard any display outside the window. This makes
.B mlx_pixel_put
slow. Consider using images instead.
.SH COLOUR MANAGEMENT
The
.I color
parameter has an unsigned integer type. The displayed colour needs to be encoded
in this integer, following a defined scheme. All displayable colours
can be split in 3 basic colours: red, green and blue. Three associated
values, in the 0-255 range, represent how much of each colour is mixed up
to create the original colour. The fourth byte represent transparency,
where 0 is fully transparent and 255 opaque. Theses four values must be set inside the
unsigned integer to display the right colour. The bytes of
this integer are filled as shown in the picture below:
.nf
| B | G | R | A | colour integer
+---+---+---+---+
.fi
While filling the integer, make sure you avoid endian problems. Example:
the "blue" byte will be the least significant byte inside the integer on a
little endian machine.
.SH SEE ALSO
mlx(3), mlx_new_window(3), mlx_new_image(3), mlx_loop(3), mlx_extra(3)
.SH AUTHOR
Copyright ol@ - 2002-2025 - Olivier Crouzet
-268
View File
@@ -1,268 +0,0 @@
# MLX python wrapper for Mlx C library
# See mlx manuals and mlx.h from C library for function usage
# C Functions that require addresses to pass back info are converted to
# Pythod methods that return a tuple
from ctypes import *
import os
class Mlx:
def __init__(self):
module_dir = os.path.dirname(os.path.abspath(__file__))
self.so_file = os.path.join(module_dir, "libmlx.so")
self.mlx_func = CDLL(self.so_file)
self._python_ref_std = {}
self._python_ref_gen = {}
self._img_height = {}
# Initialisation
def mlx_init(self):
self.mlx_func.mlx_init.restype = c_void_p
return self.mlx_func.mlx_init()
def mlx_release(self, mlx_ptr):
self.mlx_func.mlx_release.argtypes = [c_void_p]
self.mlx_func.mlx_release.restypes = [c_int]
return self.mlx_func.mlx_release(mlx_ptr)
# Windows
def mlx_new_window(self, mlx_ptr, width, height, title):
self.mlx_func.mlx_new_window.argtypes = [c_void_p, c_uint, c_uint, c_char_p]
self.mlx_func.mlx_new_window.restype = c_void_p
return self.mlx_func.mlx_new_window(mlx_ptr, width, height, title.encode('utf-8'))
def mlx_clear_window(self, mlx_ptr, win_ptr):
self.mlx_func.mlx_clear_window.argtypes = [c_void_p, c_void_p]
self.mlx_func.mlx_clear_window.restype = c_int
return self.mlx_func.mlx_clear_window(mlx_ptr, win_ptr)
def mlx_pixel_put(self, mlx_ptr, win_ptr, x, y, color):
self.mlx_func.mlx_pixel_put.argtypes = [c_void_p, c_void_p, c_uint, c_uint, c_uint]
self.mlx_func.mlx_pixel_put.restype = c_int
return self.mlx_func.mlx_pixel_put(mlx_ptr, win_ptr, x, y, color)
def mlx_destroy_window(self, mlx_ptr, win_ptr):
self.mlx_func.mlx_destroy_window.argtypes = [c_void_p, c_void_p]
self.mlx_func.mlx_destroy_window.restype = c_int
return self.mlx_func.mlx_destroy_window(mlx_ptr, win_ptr)
# Images
def mlx_new_image(self, mlx_ptr, width, height):
self.mlx_func.mlx_new_image.argtypes = [c_void_p, c_uint, c_uint]
self.mlx_func.mlx_new_image.restype = c_void_p
ret = self.mlx_func.mlx_new_image(mlx_ptr, width, height)
if ret is not None:
self._img_height[str(ret)] = height
return ret
# API break, returns tuple
def mlx_get_data_addr(self, img_ptr):
bits_per_pixel = c_uint()
size_line = c_uint()
theformat = c_uint()
data = POINTER(c_char)
self.mlx_func.mlx_get_data_addr.argtypes = [c_void_p, POINTER(c_uint), POINTER(c_uint), POINTER(c_uint)]
self.mlx_func.mlx_get_data_addr.restype = POINTER(c_char)
data = self.mlx_func.mlx_get_data_addr(img_ptr, byref(bits_per_pixel), byref(size_line), byref(theformat))
data_array = c_char * (self._img_height[str(img_ptr)] * size_line.value)
data_view = data_array.from_address(addressof(data.contents))
return (memoryview(data_view).cast('B'), bits_per_pixel.value, size_line.value, theformat.value)
def mlx_put_image_to_window(self, mlx_ptr, win_ptr, img_ptr, x, y):
self.mlx_func.mlx_put_image_to_window.argtypes = [c_void_p, c_void_p, c_void_p, c_int, c_int]
self.mlx_func.mlx_put_image_to_window.restype = c_int
return self.mlx_func.mlx_put_image_to_window(mlx_ptr, win_ptr, img_ptr, x, y)
def mlx_destroy_image(self, mlx_ptr, img_ptr):
self._img_height.pop(str(img_ptr))
self.mlx_func.mlx_destroy_image.argtypes = [c_void_p, c_void_p]
self.mlx_func.mlx_destroy_image.restype = c_int
return self.mlx_func.mlx_destroy_image(mlx_ptr, img_ptr)
# Events & main loop
# Note: Python can't catch C^-C from keyboard during mlx_loop execution.
# Use C^-\ to kill your program.
def mlx_loop(self, mlx_ptr):
self.mlx_func.mlx_loop.argtypes = [c_void_p]
self.mlx_func.mlx_loop.restype = c_int
return self.mlx_func.mlx_loop(mlx_ptr)
def mlx_loop_exit(self, mlx_ptr):
self.mlx_func.mlx_loop_exit.argtypes = [c_void_p]
self.mlx_func.mlx_loop_exit.restype = c_int
return self.mlx_func.mlx_loop_exit(mlx_ptr)
def mlx_mouse_hook(self, win_ptr, callback, param):
self.mlx_func.mlx_mouse_hook.restype = c_int
if not callback:
self._python_ref_std[str(win_ptr)+"_mouse_f"] = None
self._python_ref_std[str(win_ptr)+"_mouse_p"] = None
self.mlx_func.mlx_mouse_hook.argtypes = [c_void_p, c_void_p, c_void_p]
return self.mlx_func.mlx_mouse_hook(win_ptr, None, None)
callback_type = CFUNCTYPE(None, c_uint, c_uint, c_uint, py_object)
self.mlx_func.mlx_mouse_hook.argtypes = [c_void_p, callback_type, py_object]
callback_ref = callback_type(callback)
self._python_ref_std[str(win_ptr)+"_mouse_f"] = callback_ref
self._python_ref_std[str(win_ptr)+"_mouse_p"] = param
return self.mlx_func.mlx_mouse_hook(win_ptr, callback_ref, param)
def mlx_key_hook(self, win_ptr, callback, param):
self.mlx_func.mlx_key_hook.restype = c_int
if not callback:
self._python_ref_std[str(win_ptr)+"_key_f"] = None
self._python_ref_std[str(win_ptr)+"_key_p"] = None
self.mlx_func.mlx_key_hook.argtypes = [c_void_p, c_void_p, c_void_p]
return self.mlx_func.mlx_key_hook(win_ptr, None, None)
callback_type = CFUNCTYPE(None, c_uint, py_object)
self.mlx_func.mlx_key_hook.argtypes = [c_void_p, callback_type, py_object]
callback_ref = callback_type(callback)
self._python_ref_std[str(win_ptr)+"_key_f"] = callback_ref
self._python_ref_std[str(win_ptr)+"_key_p"] = param
return self.mlx_func.mlx_key_hook(win_ptr, callback_ref, param)
def mlx_expose_hook(self, win_ptr, callback, param):
self.mlx_func.mlx_expose_hook.restype = c_int
if not callback:
self._python_ref_std[str(win_ptr)+"_expose_f"] = None
self._python_ref_std[str(win_ptr)+"_expose_p"] = None
self.mlx_func.mlx_expose_hook.argtypes = [c_void_p, c_void_p, c_void_p]
return self.mlx_func.mlx_expose_hook(win_ptr, None, None)
callback_type = CFUNCTYPE(None, py_object)
self.mlx_func.mlx_expose_hook.argtypes = [c_void_p, callback_type, py_object]
callback_ref = callback_type(callback)
self._python_ref_std[str(win_ptr)+"_expose_f"] = callback_ref
self._python_ref_std[str(win_ptr)+"_expose_p"] = param
return self.mlx_func.mlx_expose_hook(win_ptr, callback_ref, param)
def mlx_loop_hook(self, mlx_ptr, callback, param):
self.mlx_func.mlx_loop_hook.restype = c_int
if not callback:
self._python_ref_std["loop_f"] = None
self._python_ref_std["loop_p"] = None
self.mlx_func.mlx_loop_hook.argtypes = [c_void_p, c_void_p, c_void_p]
return self.mlx_func.mlx_loop_hook(mlx_ptr, None, None)
callback_type = CFUNCTYPE(None, py_object)
self.mlx_func.mlx_loop_hook.argtypes = [c_void_p, callback_type, py_object]
callback_ref = callback_type(callback)
self._python_ref_std["loop_f"] = callback_ref
self._python_ref_std["loop_p"] = param
return self.mlx_func.mlx_loop_hook(mlx_ptr, callback_ref, param)
def mlx_hook(self, win_ptr, x_event, x_mask, callback, param):
x_event_key = [2, 3]
x_event_mouse = [4, 5]
x_event_motion = [6]
self.mlx_func.mlx_hook.restype = c_int
if not callback:
self._python_ref_gen[str(win_ptr)+"_f_"+str(x_event)] = None
self._python_ref_gen[str(win_ptr)+"_p_"+str(x_event)] = None
self.mlx_func.mlx_hook.argtypes = [c_void_p, c_uint, c_uint, c_void_p, c_void_p]
return self.mlx_func.mlx_hook(win_ptr, 0, 0, None, None)
if x_event in x_event_key:
callback_type = CFUNCTYPE(None, c_uint, py_object)
elif x_event in x_event_mouse:
callback_type = CFUNCTYPE(None, c_uint, c_uint, c_uint, py_object)
elif x_event in x_event_motion:
callback_type = CFUNCTYPE(None, c_uint, c_uint, py_object)
else:
callback_type = CFUNCTYPE(None, py_object)
self.mlx_func.mlx_hook.argtypes = [c_void_p, c_uint, c_uint, callback_type, py_object]
callback_ref = callback_type(callback)
self._python_ref_gen[str(win_ptr)+"_f_"+str(x_event)] = callback_ref
self._python_ref_gen[str(win_ptr)+"_p_"+str(x_event)] = param
return self.mlx_func.mlx_hook(win_ptr, x_event, x_mask, callback_ref, param)
# Misc.
def mlx_string_put(self, mlx_ptr, win_ptr, x, y, color, string):
self.mlx_func.mlx_string_put.argtypes = [c_void_p, c_void_p, c_uint, c_uint, c_uint, c_char_p]
self.mlx_func.mlx_string_put.restype = c_int
return self.mlx_func.mlx_string_put(mlx_ptr, win_ptr, x, y, color, string.encode('utf-8'))
# API break, returns tuple
def mlx_xpm_file_to_image(self, mlx_ptr, filename):
width = c_uint()
height = c_uint()
self.mlx_func.mlx_xpm_file_to_image.argtypes = [c_void_p, c_char_p, c_void_p, c_void_p]
self.mlx_func.mlx_xpm_file_to_image.restype = c_void_p
img = self.mlx_func.mlx_xpm_file_to_image(mlx_ptr, filename.encode('utf8'), byref(width), byref(height))
if img is not None:
self._img_height[str(img)] = height.value
return (img, width.value, height.value)
# API break, returns tuple
def mlx_png_file_to_image(self, mlx_ptr, filename):
width = c_uint()
height = c_uint()
self.mlx_func.mlx_png_file_to_image.argtypes = [c_void_p, c_char_p, c_void_p, c_void_p]
self.mlx_func.mlx_png_file_to_image.restype = c_void_p
img = self.mlx_func.mlx_png_file_to_image(mlx_ptr, filename.encode('utf8'), byref(width), byref(height))
if img is not None:
self._img_height[str(img)] = height.value
return (img, width.value, height.value)
# not really usefull in Python context
#void *mlx_xpm_to_image(void *mlx_ptr, const char **xpm_data,
# unsigned int *width, unsigned int *height);
def mlx_mouse_hide(self, mlx_ptr):
self.mlx_func.mlx_mouse_hide.argtypes = [c_void_p]
self.mlx_func.mlx_mouse_hide.restype = c_int
return self.mlx_func.mlx_mouse_hide(mlx_ptr)
def mlx_mouse_show(self, mlx_ptr):
self.mlx_func.mlx_mouse_show.argtypes = [c_void_p]
self.mlx_func.mlx_mouse_show.restype = c_int
return self.mlx_func.mlx_mouse_show(mlx_ptr)
def mlx_mouse_move(self, mlx_ptr, x, y):
self.mlx_func.mlx_mouse_move.argtypes = [c_void_p, c_int, c_int]
self.mlx_func.mlx_mouse_move.restype = c_int
return self.mlx_func.mlx_mouse_move(mlx_ptr, x, y)
# API break, returns tuple
def mlx_mouse_get_pos(self, mlx_ptr):
x = c_int()
y = c_int()
self.mlx_func.mlx_mouse_get_pos.argtypes = [c_void_p, c_void_p, c_void_p]
self.mlx_func.mlx_mouse_get_pos.restype = c_int
val = self.mlx_func.mlx_mouse_get_pos(mlx_ptr, byref(x), byref(y))
return (val, x.value, y.value)
def mlx_do_key_autorepeatoff(self, mlx_ptr):
self.mlx_func.mlx_do_key_autorepeatoff.argtypes = [c_void_p]
self.mlx_func.mlx_do_key_autorepeatoff.restype = c_int
return self.mlx_func.mlx_do_key_autorepeatoff(mlx_ptr)
def mlx_do_key_autorepeaton(self, mlx_ptr):
self.mlx_func.mlx_do_key_autorepeaton.argtypes = [c_void_p]
self.mlx_func.mlx_do_key_autorepeaton.restype = c_int
return self.mlx_func.mlx_do_key_autorepeaton(mlx_ptr)
# API break, returns tuple
def mlx_get_screen_size(self, mlx_ptr):
w = c_uint()
h = c_uint()
self.mlx_func.mlx_get_screen_size.argtypes = [c_void_p, POINTER(c_uint), POINTER(c_uint)]
self.mlx_func.mlx_get_screen_size.restype = c_int
val = self.mlx_func.mlx_get_screen_size(mlx_ptr, byref(w), byref(h))
return (val, w.value, h.value)
# Sync funct
def mlx_do_sync(self, mlx_ptr):
self.mlx_func.mlx_do_sync.argtypes = [c_void_p]
self.mlx_func.mlx_do_sync.restype = c_int
return self.mlx_func.mlx_do_sync(mlx_ptr)
def mlx_sync(self, mlx_ptr, cmd, img_or_win_ptr):
self.mlx_func.mlx_sync.argtypes = [c_void_p, c_int, c_void_p]
self.mlx_func.mlx_sync.restype = c_int
return self.mlx_func.mlx_sync(mlx_ptr, cmd, img_or_win_ptr)
SYNC_IMAGE_WRITABLE = 1
SYNC_WIN_FLUSH = 2
SYNC_WIN_COMPLETED = 3
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

-250
View File
@@ -1,250 +0,0 @@
/* XPM */
static char *Dont_panic[] = {
/* columns rows colors chars-per-pixel */
"64 64 180 2 ",
" c #000000",
". c #020D03",
"X c #0D0E0A",
"o c #090906",
"O c #130F02",
"+ c #031505",
"@ c #051B06",
"# c #12120E",
"$ c #181814",
"% c #370D03",
"& c #002304",
"* c #012D06",
"= c #07280B",
"- c #04360A",
"; c #0C3F11",
": c #113F16",
"> c #20201B",
", c #37372D",
"< c #3E3E33",
"1 c #4E1205",
"2 c #5E1706",
"3 c #691A07",
"4 c #73240F",
"5 c #034A0B",
"6 c #005C0B",
"7 c #01640D",
"8 c #006E0D",
"9 c #15670D",
"0 c #00710D",
"q c #096815",
"w c #047512",
"e c #097A16",
"r c #0D7D1A",
"t c #077414",
"y c #107D1C",
"u c #11681B",
"i c #1B4420",
"p c #057E25",
"a c #0A7B22",
"s c #05772A",
"d c #057A2B",
"f c #047723",
"g c #197B24",
"h c #276E2E",
"j c #49580C",
"k c #6A4D0D",
"l c #53601A",
"z c #44443C",
"x c #4B4C3D",
"c c #504F41",
"v c #535345",
"b c #58584D",
"n c #5C5C53",
"m c #5C6153",
"M c #666859",
"N c #7B7B6C",
"B c #767764",
"V c #AE3A0E",
"C c #B9370E",
"Z c #C52F0C",
"A c #C6300D",
"S c #C8320E",
"D c #CB3511",
"F c #CF3A14",
"G c #C93C18",
"H c #D33C16",
"J c #D23E18",
"K c #B65726",
"L c #98662B",
"P c #D3431C",
"I c #DC451C",
"U c #DC491E",
"Y c #D54C1E",
"T c #E14A1F",
"R c #DC4C24",
"E c #D94C26",
"W c #DC5A33",
"Q c #E14D22",
"! c #E05A32",
"~ c #DF6039",
"^ c #E3643D",
"/ c #E56B43",
"( c #E5724A",
") c #05861E",
"_ c #0F801B",
"` c #068A1C",
"' c #068F1A",
"] c #11821E",
"[ c #07921B",
"{ c #08941D",
"} c #058322",
"| c #068922",
" . c #098D24",
".. c #05842C",
"X. c #068B2C",
"o. c #088426",
"O. c #158421",
"+. c #198926",
"@. c #1D8D29",
"#. c #1A8626",
"$. c #0A9224",
"%. c #0D952A",
"&. c #0E9A2C",
"*. c #0A9528",
"=. c #109C2E",
"-. c #068D31",
";. c #068730",
":. c #069433",
">. c #069A35",
",. c #079E38",
"<. c #119D32",
"1. c #22882D",
"2. c #22922E",
"3. c #269532",
"4. c #299435",
"5. c #299936",
"6. c #2D9D39",
"7. c #2B9739",
"8. c #308A3A",
"9. c #07A63B",
"0. c #14A235",
"q. c #15A339",
"w. c #32A23E",
"e. c #36A541",
"r. c #3AA746",
"t. c #3AA946",
"y. c #3EAD49",
"u. c #63985E",
"i. c #40AE4B",
"p. c #43A64E",
"a. c #43B24E",
"s. c #46B451",
"d. c #49B654",
"f. c #4AB956",
"g. c #4EBD59",
"h. c #50BF5B",
"j. c #50B158",
"k. c #6F9266",
"l. c #61A862",
"z. c #63B667",
"x. c #66B76B",
"c. c #52C15E",
"v. c #56C461",
"b. c #58C763",
"n. c #5BC966",
"m. c #5ECD69",
"M. c #61CF6C",
"N. c #64C66C",
"B. c #63D16E",
"V. c #76C57B",
"C. c #66D471",
"Z. c #68D773",
"A. c #6BD976",
"S. c #6EDC79",
"D. c #70DE7B",
"F. c #72E07D",
"G. c #8D927F",
"H. c #7FC381",
"J. c #76E481",
"K. c #79E783",
"L. c #7CEA86",
"P. c #7FEC89",
"I. c #969986",
"U. c #8BBF8C",
"Y. c #9BAF93",
"T. c #A8AA9A",
"R. c #A3B59A",
"E. c #ADB1A2",
"W. c #B5B7A9",
"Q. c #83C286",
"!. c #96C998",
"~. c #81EE8B",
"^. c #84F28F",
"/. c #86F491",
"(. c #8CF996",
"). c #8BF895",
"_. c #ABC9A8",
"`. c #C5C6BB",
"'. c #CACAC0",
"]. c None",
/* pixels */
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].;.].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].,.>.].-.;.].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].]...].].-.,.].-.,.].]...].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].:.].]...X.].-.-.].:.:.].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].o.].].} p p o.p ].>.-.].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].p ].} ) ].} p ].p ..].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].) | ) ) ) ) } p p ].].].].].].].].].].@.3.3.4.7.].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].]. .&.<.%.` ) ) } p ].].].].].].].+.@.3.3.5.6.6.e.e.e.e.e.].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].| <.q.<.0.&.) ) | ].].].].].g +.@.2.3.5.6.6.e.t.e.t.t.t.y.y.t.].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].]. .<.<.[ $.0.$.) ].].].].].] +.@.@.5.6.6.e.e.y.y.y.a.a.s.s.s.a.a.i.].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].| <.<.' [ =.$.` | >.>.].O.+.@.2.5.5.w.e.t.y.y.y.s.d.f.f.f.f.f.f.s.s.].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].| %.q.<.0.0. .) | o.o.O.+.@.2.5.6.w.e.y.y.s.s.f.g.c.c.c.c.c.c.h.g.g.d.].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].| .&.&.` ` ].].e r O.+.2.3.5.w.e.y.y.s.f.g.c.v.v.b.n.n.n.b.b.v.c.h.f.].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].| | ) ` ].].].].r ] +.@.3.5.6.e.y.y.s.f.g.v.v.n.m.M.B.B.M.M.m.n.b.v.h.d.].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].o.} ].].].].].e _ O.@.2.5.6.e.y.y.s.g.g.v.b.m.B.B.C.Z.A.Z.Z.C.M.m.n.v.h.d.].].].].].].].].].].].].].].].].].].",
"].].].].].].].].X.-.].].].].].].e ] +.@.3.5.w.e.y.y.d.g.v.b.m.B.Z.A.S.D.D.D.S.A.Z.B.m.n.v.g.].].].].].].].].].].].].].].].].].].",
"].].].].].].].].:.].].].].].].t r O.+.2.3.6.w.t.y.s.g.c.b.m.C.Z.S.F.J.J.K.J.J.D.S.Z.B.m.b.c.d.].].].].].].].].].].].].].].].].].",
"].].].].].].].:.-.].].].].].].t _ O.@.2.7.6.e.t.s.d.g.v.b.B.C.A.F.J.L.P.~.P.K.J.F.A.C.M.n.v.g.].].].].].].].].].].].].].].].].].",
"].].].].].].:.:.].].].].].].w e ] O.@.2.7.w.e.y.s.d.c.v.m.C.Z.F.J.L.~././.^.~.L.J.S.Z.B.n.v.g.].].].].].].].].].].].].].].].].].",
"].].].].].;.-.d ].].].].].].w e ] +.@.1.4.w.r.y.s.f.c.v.m.C.A.F.J.P./.).(.).^.P.J.D.A.B.m.v.h.d.].].].].].].].].].].].].].].].].",
"].].].]...-.d ].].].].].].].w e ] u = + @ h r.y.s.f.v.v.m.C.A.F.K.~./.).(./.^.L.J.D.Z.B.n.v.h.d.].].].].].].].].].].].].].].].].",
"].].].].d d d d ;.:.9.9.,.e w e q + o M U.y.s.d.c.v.m.B.Z.F.J.P.^.^./.^.P.K.F.S.Z.M.n.c.g.d.].].].].].].].].].].].].].].].].",
"].].].].d d s d d d ;.-.:.d w e - X B E.l.y.d.c.v.m.B.Z.S.F.J.K.P.~.P.K.J.D.A.C.m.b.c.g.s.].].].].].].].].].].].].].].].].",
"].].].].].].].].].d s d d f w t + $ v I.x.s.g.c.b.m.C.Z.S.F.J.J.J.J.F.D.A.C.M.n.v.h.f.a.].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].8 w 7 . c `._.f.f.c.v.b.B.B.A.A.S.D.D.D.A.A.C.M.m.v.c.g.s.i.].].].].].].].:.-.].].].].].].].",
"].].].].].].].].].].].].].].w 7 . $ B N u.d.g.c.c.n.B.B.C.C.C.A.C.C.B.m.n.v.c.g.f.s.y.].].].].].].;.9.].].-.].].].].].",
"].].].].].].].].].].].].].].8 8 + o o , E.Q.j.c.c.c.c.n.m.M.M.M.N.V.H.z.h.c.g.f.a.y.e.].].].].].].X.;.].:.,.].].].].].",
"].].].].].].].].].].].].].].0 0 * z `._.u.j.f.c.c.c.b.n.M.U.k.W.`.m i p.f.a.y.t.e.].].].].].p p ]...:.:.].].].].].",
"].].].].].].].].].].].].].].0 0 5 # n b v R.!.l.z.V.x.u.R.T.c M m $ i a.y.t.w.5.o.:.].].p } ].p ....].>.:.].].].",
"].].].].].].].].].].].].].].8 0 8 @ < T.E.M G.`.I., v x X . 8.t.e.6.].].X.X.].) } ) p p ]...:.].].].].",
"].].].].].].].].].].].].].].].0 0 5 X < < # x N x o h w.6.7.].].} } ) ) ) ) ) ].p p p ].].].].",
"].].].].].].].].].].].].].].].8 0 8 * o X o 1.5.5.].].].| | %.%.` ` ) ) } o.].].].].].",
"].].].].].].].].].].].].].].].].0 0 8 & : 3.3.2.].].].| <.0.0.<. .` ) } ].].].].].].",
"].].].].].].].].].].].].].].].].8 8 0 7 @ = g 3.@.].].].]. .0.&.{ =.<.| ) ].| :.-.].].].",
"].].].].].].].].].].].].].].].].].8 0 0 7 & = g 2.#.].].].].].$.0.*.' [ 0.&.) } X.>.].].].].",
"].].].].].].].].].].].].].].].].].].8 0 0 8 - + o 5 g +.O.O.].].].].]. .&.$.' ' =.<.| } ].].].].].].",
"].].].].].].].].].].].].].].].].].].].0 0 0 0 6 * O 1 3 3 3 3 % . = u O.] ] y ].].].].].].| ) ) ' ' ` .].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].8 0 0 0 0 9 k C P E J G 4 O * 5 q y _ a a a ].].].].].].].| | ) ` ` ].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].8 0 0 0 8 j G / ^ R ~ l w e e e t f d ;.].].].].].].X.o.].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].8 0 0 9 V W / R ^ L 0 w 0 0 ].].d -.:.].].].].:...].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].0 9 V J Q U E K 9 ].].].].].d ..,.].].].>.;.].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].S H I T Y I ].].].].].].].d :.,.].].;.d ].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].A S I T Y I ].].].].].].].s d -.]...d ].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].Z S J Q E U ].].].].].].].].s d d d d ].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].Z Z H I U U I ].].].].].].].].d d d ].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].Z Z S J I T R P ].].].].].].].d s s ].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].Z Z Z H R R ! ! G ].].].].].].].d ].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].Z Z D E ^ ( W D ].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].Z Z A F J D Z ].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].Z Z S ].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].",
"].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].]."
};
-276
View File
@@ -1,276 +0,0 @@
# Mlx large test
import sys
from mlx import Mlx # Import Mlx class
class ImgData:
"""Structure for image data"""
def __init__(self):
self.img = None
self.width = 0
self.height = 0
self.data = None
self.sl = 0 # size line
self.bpp = 0 # bits per pixel
self.iformat = 0
class XVar:
"""Structure for main vars"""
def __init__(self):
self.mlx = None
self.mlx_ptr = None
self.screen_w = 0
self.screen_h = 0
self.win_1 = None
self.win_2 = None
self.img_1 = ImgData()
self.img_2 = ImgData()
self.img_png = ImgData()
self.img_xpm = ImgData()
self.imgidx = 0
def draw_colormap(xvar):
"""Draw the colormap"""
print("Drawing colormap...")
for i in range(400):
for j in range(400):
r = int((0xFF * i) / 400)
g = int((0xFF * j) / 400)
b = int((0xFF * (400 - (i + j) // 2)) / 400)
col = 0xFF000000 | (r << 16) | (g << 8) | b
xvar.mlx.mlx_pixel_put(xvar.mlx_ptr, xvar.win_1, i, j, col)
def gere_key_press(key, xvar):
print(f"Pressed key {key}")
def gere_key(key, xvar):
print(f"Got key {key}: ", end="")
if key == 113: # 'q'
xvar.mlx.mlx_do_key_autorepeatoff(xvar.mlx_ptr)
print("key repeat off")
return 0
elif key == 119: # 'w'
xvar.mlx.mlx_do_key_autorepeaton(xvar.mlx_ptr)
print("key repeat on")
return 0
elif key == 101: # 'e'
draw_colormap(xvar)
print("colormap")
return 0
elif key == 114: # 'r'
xvar.mlx.mlx_mouse_hide(xvar.mlx_ptr)
print("mouse hide")
return 0
elif key == 116: # 't'
xvar.mlx.mlx_mouse_show(xvar.mlx_ptr)
print("mouse show")
return 0
elif key == 121: # 'y'
xvar.mlx.mlx_mouse_move(xvar.win_1, 200, 200)
print("mouse move")
return 0
elif key == 117: # 'u'
ret, x, y = xvar.mlx.mlx_mouse_get_pos(xvar.win_1)
print(f"current mouse pos is {x} x {y}")
return 0
elif key == 105: # 'i'
xvar.mlx.mlx_sync(xvar.mlx_ptr, Mlx.SYNC_IMAGE_WRITABLE, xvar.img_2.img)
# fill image in white
for offset in range(0, xvar.img_2.sl * 100, 4):
xvar.img_2.data[offset:offset+4] = (0xFFFFFFFF).to_bytes(4, 'little')
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, xvar.win_1, xvar.img_2.img, 50, 50)
# update in red
for offset in range(0, xvar.img_2.sl * 100, 4):
xvar.img_2.data[offset:offset+4] = (0xFFFF0000).to_bytes(4, 'little')
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, xvar.win_1, xvar.img_2.img, 250, 250)
print("update image without sync - most likely 2 red squares")
return 0
elif key == 111: # 'o'
xvar.mlx.mlx_sync(xvar.mlx_ptr, Mlx.SYNC_IMAGE_WRITABLE, xvar.img_2.img)
# fill image in white
for offset in range(0, xvar.img_2.sl * 100, 4):
xvar.img_2.data[offset:offset+4] = (0xFFFFFFFF).to_bytes(4, 'little')
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, xvar.win_1, xvar.img_2.img, 50, 50)
xvar.mlx.mlx_sync(xvar.mlx_ptr, Mlx.SYNC_IMAGE_WRITABLE, xvar.img_2.img)
# update in red
for offset in range(0, xvar.img_2.sl * 100, 4):
xvar.img_2.data[offset:offset+4] = (0xFFFF0000).to_bytes(4, 'little')
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, xvar.win_1, xvar.img_2.img, 250, 250)
print("update image with sync - white and red squares")
return 0
# Default
print("clear and string put")
xvar.mlx.mlx_clear_window(xvar.mlx_ptr, xvar.win_1)
xvar.mlx.mlx_string_put(xvar.mlx_ptr, xvar.win_1, 20, 20, 0xFFFF00FF, "Hello MLX!")
def gere_expose(xvar):
print("Expose !")
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, xvar.win_1, xvar.img_1.img, 0, 0)
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, xvar.win_1, xvar.img_1.img, 201, 201)
def gere_mouse(button, x, y, xvar, win):
print(f"Got mouse : {button} at {x}x{y}")
if button == 1:
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, win, xvar.img_1.img, 100, 100)
return 0
if button == 3: # right click
if xvar.imgidx % 2:
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, win, xvar.img_png.img, x, y)
else:
xvar.mlx.mlx_put_image_to_window(xvar.mlx_ptr, win, xvar.img_xpm.img, x, y)
xvar.imgidx += 1
def gere_mouse_1(button, x, y, xvar):
gere_mouse(button, x, y, xvar, xvar.win_1)
def gere_mouse_2(button, x, y, xvar):
gere_mouse(button, x, y, xvar, xvar.win_2)
def gere_close_1(xvar):
xvar.mlx.mlx_loop_exit(xvar.mlx_ptr)
def gere_close_2(xvar):
xvar.mlx.mlx_destroy_window(xvar.mlx_ptr, xvar.win_2)
xvar.win_2 = None
def main():
xvar = XVar()
# Mlx Initialisation
try:
xvar.mlx = Mlx()
except Exception as e:
print(f"Error: Can't initialize MLX: {e}", file=sys.stderr)
sys.exit(1)
xvar.mlx_ptr = xvar.mlx.mlx_init()
ret, xvar.screen_w, xvar.screen_h = xvar.mlx.mlx_get_screen_size(xvar.mlx_ptr)
print(f"Screen size: {xvar.screen_w} x {xvar.screen_h}")
# Windows creation
try:
xvar.win_1 = xvar.mlx.mlx_new_window(xvar.mlx_ptr, 400, 400, "MLX main win")
if not xvar.win_1:
raise Exception("Can't create main window")
xvar.win_2 = xvar.mlx.mlx_new_window(xvar.mlx_ptr, 150, 150, "Secondary window")
if not xvar.win_2:
raise Exception("Can't create secondary window")
except Exception as e:
print(f"Error Win create: {e}", file=sys.stderr)
sys.exit(1)
# Image #1
xvar.img_1.img = xvar.mlx.mlx_new_image(xvar.mlx_ptr, 200, 200)
if not xvar.img_1.img:
raise Exception("Can't create image 1")
xvar.img_1.width = 200
xvar.img_1.height = 200
xvar.img_1.data, xvar.img_1.bpp, xvar.img_1.sl, xvar.img_1.iformat = \
xvar.mlx.mlx_get_data_addr(xvar.img_1.img)
# Fill image #1
for i in range(xvar.img_1.sl * 200):
xvar.img_1.data[i] = 0x80
for i in range(xvar.img_1.sl * 100):
xvar.img_1.data[i] = 0xFF
try:
# Add some red pixels
pixel_positions = [
0 * 200 * 4, # top left
(1 * 200 + 1) * 4, # top left + 1
(199 * 200 + 199) * 4, # bottom right
(198 * 200 + 198) * 4 # bottom right - 1
]
for pos in pixel_positions:
if pos < len(xvar.img_1.data) - 3:
xvar.img_1.data[pos:pos+4] = (0xFFFF0000).to_bytes(4, 'little')
except Exception as e:
print(f"Error img1: {e}", file=sys.stderr)
sys.exit(1)
# Image #2
try:
xvar.img_2.img = xvar.mlx.mlx_new_image(xvar.mlx_ptr, 100, 100)
if not xvar.img_2.img:
raise Exception("Can't create image 2")
xvar.img_2.width = 100
xvar.img_2.height = 100
xvar.img_2.data, xvar.img_2.bpp, xvar.img_2.sl, xvar.img_2.iformat = \
xvar.mlx.mlx_get_data_addr(xvar.img_2.img)
except Exception as e:
print(f"Error img2: {e}", file=sys.stderr)
sys.exit(1)
# Load PNG & XPM
result = xvar.mlx.mlx_png_file_to_image(xvar.mlx_ptr, "puffy_small.png")
if not result:
raise Exception("Can't load PNG")
xvar.img_png.img, xvar.img_png.width, xvar.img_png.height = result
if not xvar.img_png.img:
raise Exception("Can't create png")
xvar.img_png.data, xvar.img_png.bpp, xvar.img_png.sl, xvar.img_png.iformat = \
xvar.mlx.mlx_get_data_addr(xvar.img_png.img)
result = xvar.mlx.mlx_xpm_file_to_image(xvar.mlx_ptr, "Dont_panic.xpm")
if not result:
raise Exception("Can't load XPM")
xvar.img_xpm.img, xvar.img_xpm.width, xvar.img_xpm.height = result
xvar.img_xpm.data, xvar.img_xpm.bpp, xvar.img_xpm.sl, xvar.img_xpm.iformat = \
xvar.mlx.mlx_get_data_addr(xvar.img_xpm.img)
# event hooks
xvar.mlx.mlx_key_hook(xvar.win_1, gere_key, xvar)
xvar.mlx.mlx_hook(xvar.win_2, 2, 1, gere_key_press, xvar) # KeyPress event
xvar.mlx.mlx_expose_hook(xvar.win_1, gere_expose, xvar)
xvar.mlx.mlx_mouse_hook(xvar.win_1, gere_mouse_1, xvar)
xvar.mlx.mlx_mouse_hook(xvar.win_2, gere_mouse_2, xvar)
xvar.mlx.mlx_hook(xvar.win_1, 33, 0, gere_close_1, xvar) # WM_DELETE_WINDOW
xvar.mlx.mlx_hook(xvar.win_2, 33, 0, gere_close_2, xvar) # WM_DELETE_WINDOW
# User Instructions
print("On main window:")
print(" mouse button 1: place white/gray image in 0x0 and 200x200")
print(" mouse button 2: place png image and xpm image, alternatively")
print(" try keys QWERTYUIO and others")
print(" click window's X button to end the program")
print("On secondary window (smaller):")
print(" show key pressed for auto repeat")
print(" click window's X button to close it")
# Main loop
xvar.mlx.mlx_loop(xvar.mlx_ptr)
# Cleaning resources
print("destroy xpm")
xvar.mlx.mlx_destroy_image(xvar.mlx_ptr, xvar.img_xpm.img)
print("destroy png")
xvar.mlx.mlx_destroy_image(xvar.mlx_ptr, xvar.img_png.img)
print("destroy imgs")
xvar.mlx.mlx_destroy_image(xvar.mlx_ptr, xvar.img_1.img)
xvar.mlx.mlx_destroy_image(xvar.mlx_ptr, xvar.img_2.img)
print("destroy win(s)")
xvar.mlx.mlx_destroy_window(xvar.mlx_ptr, xvar.win_1)
if xvar.win_2:
xvar.mlx.mlx_destroy_window(xvar.mlx_ptr, xvar.win_2)
print("destroy mlx")
xvar.mlx.mlx_release(xvar.mlx_ptr)
if __name__ == "__main__":
main()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

-29
View File
@@ -1,29 +0,0 @@
from mlx import Mlx
def mymouse(button, x, y, mystuff):
print(f"Got mouse event! button {button} at {x},{y}.")
def mykey(keynum, mystuff):
print(f"Got key {keynum}, and got my stuff back:")
print(mystuff)
if keynum == 32:
m.mlx_mouse_hook(win_ptr, None, None)
def gere_close(dummy):
m.mlx_loop_exit(mlx_ptr)
m = Mlx()
mlx_ptr = m.mlx_init()
win_ptr = m.mlx_new_window(mlx_ptr, 200, 200, "win title")
m.mlx_clear_window(mlx_ptr, win_ptr)
m.mlx_string_put(mlx_ptr, win_ptr, 20, 20, 255, "Hello PyMlx!")
(ret, w, h) = m.mlx_get_screen_size(mlx_ptr)
print(f"Got screen size: {w} x {h} .")
stuff = [1, 2]
m.mlx_mouse_hook(win_ptr, mymouse, None)
m.mlx_key_hook(win_ptr, mykey, stuff)
m.mlx_hook(win_ptr, 33, 0, gere_close, None)
m.mlx_loop(mlx_ptr)
-25
View File
@@ -1,25 +0,0 @@
# This script does not check for errors or malformed files.
# It only validates that neighbooring cells sharing a wall have
# both the correct encoding.
# Usage: python3 output_validator.py output_maze.txt
import sys
if len(sys.argv) != 2:
print(f"Usage: python3 {sys.argv[0]} <output_file>")
sys.exit(1)
g = []
for line in open(sys.argv[1]):
if line.strip() == '':
break
g.append([int(c, 16) for c in line.strip(' \t\n\r')])
for r in range(len(g)):
for c in range(len(g[0])):
v = g[r][c]
if not all([(r < 1 or v & 1 == (g[r-1][c] >> 2) & 1),
(c >= len(g[0])-1 or (v >> 1) & 1 == (g[r][c+1] >> 3) & 1),
(r >= len(g)-1 or (v >> 2) & 1 == g[r+1][c] & 1),
(c < 1 or (v >> 3) & 1 == (g[r][c-1] >> 1) & 1)]):
print(f'Wrong encoding for ({c},{r})')
+9
View File
@@ -20,6 +20,15 @@ dev = [
[tool.mypy] [tool.mypy]
python_version = "3.10" python_version = "3.10"
explicit_package_bases = true
[tool.pytest.ini_options] [tool.pytest.ini_options]
pythonpath = ["src"] pythonpath = ["src"]
[build-system]
requires = ["setuptools>=78.1.0", "wheel>=0.45.1"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
package-dir = {"" = "src/amaz_lib"}
+37 -4
View File
@@ -2,10 +2,14 @@ from typing import Generator
from typing_extensions import Self from typing_extensions import Self
from pydantic import BaseModel, Field, model_validator, ConfigDict from pydantic import BaseModel, Field, model_validator, ConfigDict
from src.amaz_lib import Maze, MazeGenerator, MazeSolver from .amaz_lib import Maze, MazeGenerator, MazeSolver
class AMazeIng(BaseModel): class AMazeIng(BaseModel):
"""Represent a complete maze configuration, generation,
and solving setup.
"""
model_config = ConfigDict(arbitrary_types_allowed=True) model_config = ConfigDict(arbitrary_types_allowed=True)
width: int = Field(ge=4) width: int = Field(ge=4)
@@ -20,21 +24,50 @@ class AMazeIng(BaseModel):
@model_validator(mode="after") @model_validator(mode="after")
def check_entry_exit(self) -> Self: def check_entry_exit(self) -> Self:
if self.entry[0] >= self.width or self.entry[1] >= self.height: """Validate that entry and exit coordinates fit within maze bounds.
Returns:
The validated model instance.
Raises:
ValueError: If entry or exit coordinates exceed maze dimensions.
"""
if self.entry[0] > self.width or self.entry[1] > self.height:
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")
return self return self
def generate(self) -> Generator[Maze, None, None]: def generate(self) -> Generator[Maze, None, None]:
"""Generate the maze step by step.
The internal maze state is updated at each generation step.
Yields:
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.maze.set_maze(array) self.maze.set_maze(array)
yield self.maze yield self.maze
return
def solve_path(self) -> str: def solve_path(self) -> str:
return self.solver.solve(self.maze) """Solve the current maze and return the path string.
Returns:
A string of direction letters representing the solution path.
"""
return self.solver.solve(self.maze, self.height, self.width)
def __str__(self) -> str: def __str__(self) -> str:
"""Return a string representation of the maze and its solution.
The output includes the maze, entry coordinates, exit coordinates, and
the computed solution path.
Returns:
A formatted string representation of the maze data.
"""
res = self.maze.__str__() res = self.maze.__str__()
res += "\n" res += "\n"
res += f"{self.entry[0]},{self.entry[1]}\n" res += f"{self.entry[0]},{self.entry[1]}\n"
+72
View File
@@ -3,50 +3,122 @@ from dataclasses import dataclass
@dataclass @dataclass
class Cell: class Cell:
"""Represent a maze cell encoded as a bitmask of surrounding walls.
The cell value is stored as an integer where each bit represents the
presence of a wall in one cardinal direction:
- bit 0 (1): north wall
- bit 1 (2): east wall
- bit 2 (4): south wall
- bit 3 (8): west wall
"""
def __init__(self, value: int) -> None: def __init__(self, value: int) -> None:
"""Initialize a cell with its encoded wall value.
Args:
value: Integer bitmask representing the cell walls.
"""
self.value = value self.value = value
def __str__(self) -> str: def __str__(self) -> str:
"""Return the hexadecimal representation of the cell value.
Returns:
The uppercase hexadecimal form of the cell value without the
``0x`` prefix.
"""
return hex(self.value).removeprefix("0x").upper() return hex(self.value).removeprefix("0x").upper()
def set_value(self, value: int) -> None: def set_value(self, value: int) -> None:
"""Set the encoded value of the cell.
Args:
value: Integer bitmask representing the cell walls.
"""
self.value = value self.value = value
def get_value(self) -> int: def get_value(self) -> int:
"""Return the encoded value of the cell.
Returns:
The integer bitmask representing the cell walls.
"""
return self.value return self.value
def set_north(self, is_wall: bool) -> None: def set_north(self, is_wall: bool) -> None:
"""Set or clear the north wall.
Args:
is_wall: ``True`` to add the north wall, ``False`` to remove it.
"""
if (not is_wall and self.value | 14 == 15) or ( if (not is_wall and self.value | 14 == 15) or (
is_wall and self.value | 14 != 15 is_wall and self.value | 14 != 15
): ):
self.value = self.value ^ (1) self.value = self.value ^ (1)
def get_north(self) -> bool: def get_north(self) -> bool:
"""Return whether the north wall is present.
Returns:
``True`` if the north wall is set, otherwise ``False``.
"""
return self.value & 1 == 1 return self.value & 1 == 1
def set_est(self, is_wall: bool) -> None: def set_est(self, is_wall: bool) -> None:
"""Set or clear the east wall.
Args:
is_wall: ``True`` to add the east wall, ``False`` to remove it.
"""
if (not is_wall and self.value | 13 == 15) or ( if (not is_wall and self.value | 13 == 15) or (
is_wall and self.value | 13 != 15 is_wall and self.value | 13 != 15
): ):
self.value = self.value ^ (2) self.value = self.value ^ (2)
def get_est(self) -> bool: def get_est(self) -> bool:
"""Return whether the east wall is present.
Returns:
``True`` if the east wall is set, otherwise ``False``.
"""
return self.value & 2 == 2 return self.value & 2 == 2
def set_south(self, is_wall: bool) -> None: def set_south(self, is_wall: bool) -> None:
"""Set or clear the south wall.
Args:
is_wall: ``True`` to add the south wall, ``False`` to remove it.
"""
if (not is_wall and self.value | 11 == 15) or ( if (not is_wall and self.value | 11 == 15) or (
is_wall and self.value | 11 != 15 is_wall and self.value | 11 != 15
): ):
self.value = self.value ^ (4) self.value = self.value ^ (4)
def get_south(self) -> bool: def get_south(self) -> bool:
"""Return whether the south wall is present.
Returns:
``True`` if the south wall is set, otherwise ``False``.
"""
return self.value & 4 == 4 return self.value & 4 == 4
def set_west(self, is_wall: bool) -> None: def set_west(self, is_wall: bool) -> None:
"""Set or clear the west wall.
Args:
is_wall: ``True`` to add the west wall, ``False`` to remove it.
"""
if (not is_wall and self.value | 7 == 15) or ( if (not is_wall and self.value | 7 == 15) or (
is_wall and self.value | 7 != 15 is_wall and self.value | 7 != 15
): ):
self.value = self.value ^ (8) self.value = self.value ^ (8)
def get_west(self) -> bool: def get_west(self) -> bool:
"""Return whether the west wall is present.
Returns:
``True`` if the west wall is set, otherwise ``False``.
"""
return self.value & 8 == 8 return self.value & 8 == 8
+35 -7
View File
@@ -1,21 +1,41 @@
from dataclasses import dataclass from dataclasses import dataclass
from numpy.typing import NDArray
import numpy from typing import Optional, Any
from .Cell import Cell
from .MazeGenerator import MazeGenerator
@dataclass @dataclass
class Maze: class Maze:
maze: numpy.ndarray """Represent a maze as a two-dimensional array of cells."""
def get_maze(self) -> numpy.ndarray | None: maze: Optional[NDArray[Any]] = None
def get_maze(self) -> Optional[NDArray[Any]]:
"""Return the underlying maze array.
Returns:
The two-dimensional array representing the maze, or ``None`` if no
maze has been set.
"""
return self.maze return self.maze
def set_maze(self, new_maze: numpy.ndarray) -> None: def set_maze(self, new_maze: NDArray[Any]) -> None:
"""Set the maze array.
Args:
new_maze: A two-dimensional array containing the maze cells.
"""
self.maze = new_maze self.maze = new_maze
def __str__(self) -> str: def __str__(self) -> str:
"""Return a string representation of the maze.
Each cell is converted to its string representation and concatenated
line by line.
Returns:
A multiline string representation of the maze, or ``"None"`` if the
maze is not set.
"""
if self.maze is None: if self.maze is None:
return "None" return "None"
res = "" res = ""
@@ -26,6 +46,14 @@ class Maze:
return res return res
def ascii_print(self) -> None: def ascii_print(self) -> None:
"""Print an ASCII representation of the maze.
The maze is rendered using underscores and vertical bars to show the
walls of each cell. If no maze is set, ``"None"`` is printed.
"""
if self.maze is None:
print("None")
return
for cell in self.maze[0]: for cell in self.maze[0]:
print("_", end="") print("_", end="")
if cell.get_north(): if cell.get_north():
+291 -53
View File
@@ -1,12 +1,25 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Generator, Set from typing import Generator, Any
import numpy as np import numpy as np
from numpy.typing import NDArray
from .Cell import Cell from .Cell import Cell
import math import math
import random
class MazeGenerator(ABC): class MazeGenerator(ABC):
def __init__(self, start: tuple, end: tuple, perfect: bool) -> None: """Define the common interface and helpers for maze generators."""
def __init__(
self, start: tuple[int, int], end: tuple[int, int], perfect: bool
) -> None:
"""Initialize the maze generator.
Args:
start: Starting cell coordinates, using 1-based indexing.
end: Ending cell coordinates, using 1-based indexing.
perfect: Whether to generate a perfect maze with no loops.
"""
self.start = (start[0] - 1, start[1] - 1) self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1) self.end = (end[0] - 1, end[1] - 1)
self.perfect = perfect self.perfect = perfect
@@ -14,10 +27,33 @@ class MazeGenerator(ABC):
@abstractmethod @abstractmethod
def generator( def generator(
self, height: int, width: int, seed: int | None = None self, height: int, width: int, seed: int | None = None
) -> Generator[np.ndarray, None, np.ndarray]: ... ) -> Generator[NDArray[Any], None, NDArray[Any]]:
"""Generate a maze step by step.
Args:
height: Number of rows in the maze.
width: Number of columns in the maze.
seed: Optional random seed for reproducibility.
Yields:
Intermediate maze states during generation.
Returns:
The final generated maze.
"""
...
@staticmethod @staticmethod
def get_cell_ft(width: int, height: int) -> set: def get_cell_ft(width: int, height: int) -> set[tuple[int, int]]:
"""Return the coordinates used to reserve the '42' pattern.
Args:
width: Number of columns in the maze.
height: Number of rows in the maze.
Returns:
A set of cell coordinates belonging to the reserved pattern.
"""
forty_two = set() forty_two = set()
y, x = (int(height / 2), int(width / 2)) y, x = (int(height / 2), int(width / 2))
forty_two.add((y, x - 1)) forty_two.add((y, x - 1))
@@ -41,23 +77,37 @@ class MazeGenerator(ABC):
return forty_two return forty_two
@staticmethod @staticmethod
def unperfect_maze(width: int, height: int, def unperfect_maze(
maze: np.ndarray, forty_two: set | None, width: int,
prob: float = 0.1 height: int,
) -> Generator[np.ndarray, None, np.ndarray]: maze: NDArray[Any],
directions = { forty_two: set[tuple[int, int]] | None,
"N": (0, -1), prob: float = 0.1,
"S": (0, 1), ) -> Generator[NDArray[Any], None, NDArray[Any]]:
"W": (-1, 0), """Add extra openings to transform a perfect maze into an imperfect
"E": (1, 0) one.
}
reverse = { Random walls are removed while optionally preserving the reserved
"N": "S", ``forty_two`` area.
"S": "N",
"W": "E", Args:
"E": "W" width: Number of columns in the maze.
} height: Number of rows in the maze.
maze: The maze to modify.
forty_two: Optional set of reserved coordinates that must not be
altered.
prob: Probability of breaking an eligible wall.
Yields:
Intermediate maze states after each wall removal.
Returns:
The modified maze.
"""
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 min_break = 2
while True: while True:
count = 0 count = 0
@@ -68,8 +118,7 @@ class MazeGenerator(ABC):
for direc, (dx, dy) in directions.items(): for direc, (dx, dy) in directions.items():
nx, ny = x + dx, y + dy nx, ny = x + dx, y + dy
if forty_two and ( if forty_two and (
(y, x) in forty_two (y, x) in forty_two or (ny, nx) in forty_two
or (ny, nx) in forty_two
): ):
continue continue
if not (0 <= nx < width and 0 < ny < height): if not (0 <= nx < width and 0 < ny < height):
@@ -81,9 +130,10 @@ class MazeGenerator(ABC):
cell = maze[y][x] cell = maze[y][x]
cell_n = maze[ny][nx] cell_n = maze[ny][nx]
cell = DepthFirstSearch.broken_wall(cell, direc) cell = DepthFirstSearch.broken_wall(cell, direc)
cell_n = DepthFirstSearch.broken_wall(cell_n, cell_n = DepthFirstSearch.broken_wall(
reverse[ cell_n,
direc]) reverse[direc],
)
maze[y][x] = cell maze[y][x] = cell
maze[ny][nx] = cell_n maze[ny][nx] = cell_n
yield maze yield maze
@@ -93,20 +143,47 @@ class MazeGenerator(ABC):
class Kruskal(MazeGenerator): class Kruskal(MazeGenerator):
"""Generate a maze using a Kruskal-based algorithm."""
class KruskalSet:
"""Represent a connected component of maze cells."""
class Set:
def __init__(self, cells: list[int]) -> None: def __init__(self, cells: list[int]) -> None:
"""Initialize a set of connected cells.
Args:
cells: List of cell indices belonging to the set.
"""
self.cells: list[int] = cells self.cells: list[int] = cells
class Sets: class Sets:
def __init__(self, sets: list[Set]) -> None: """Store all connected components used during generation."""
def __init__(self, sets: list["Kruskal.KruskalSet"]) -> None:
"""Initialize the collection of connected components.
Args:
sets: List of disjoint cell sets.
"""
self.sets = sets self.sets = sets
@staticmethod @staticmethod
def walls_to_maze( def walls_to_maze(
walls: np.ndarray, height: int, width: int walls: list[tuple[int, int]], height: int, width: int
) -> np.ndarray: ) -> NDArray[Any]:
maze: np.ndarray = np.array( """Convert a list of remaining walls into a maze grid.
Args:
walls: Collection of wall pairs between adjacent cells.
height: Number of rows in the maze.
width: Number of columns in the maze.
Returns:
A two-dimensional array of :class:`Cell` instances representing the
maze.
"""
maze: NDArray[Any] = np.array(
[[Cell(value=0) for _ in range(width)] for _ in range(height)] [[Cell(value=0) for _ in range(width)] for _ in range(height)]
) )
for wall in walls: for wall in walls:
@@ -132,6 +209,15 @@ class Kruskal(MazeGenerator):
@staticmethod @staticmethod
def is_in_same_set(sets: Sets, wall: tuple[int, int]) -> bool: def is_in_same_set(sets: Sets, wall: tuple[int, int]) -> bool:
"""Check whether both cells connected by a wall are in the same set.
Args:
sets: Current collection of connected components.
wall: Pair of adjacent cell indices.
Returns:
``True`` if both cells belong to the same set, otherwise ``False``.
"""
a, b = wall a, b = wall
for set in sets.sets: for set in sets.sets:
if a in set.cells and b in set.cells: if a in set.cells and b in set.cells:
@@ -142,6 +228,15 @@ class Kruskal(MazeGenerator):
@staticmethod @staticmethod
def merge_sets(sets: Sets, wall: tuple[int, int]) -> None: def merge_sets(sets: Sets, wall: tuple[int, int]) -> None:
"""Merge the two sets connected by the given wall.
Args:
sets: Current collection of connected components.
wall: Pair of adjacent cell indices.
Raises:
Exception: If the two corresponding sets cannot be found.
"""
a, b = wall a, b = wall
base_set = None base_set = None
for i in range(len(sets.sets)): for i in range(len(sets.sets)):
@@ -163,6 +258,17 @@ class Kruskal(MazeGenerator):
wall: tuple[int, int], wall: tuple[int, int],
cells_ft: None | set[tuple[int, int]], cells_ft: None | set[tuple[int, int]],
) -> bool: ) -> bool:
"""Check whether a wall touches the reserved '42' pattern.
Args:
width: Number of columns in the maze.
wall: Pair of adjacent cell indices.
cells_ft: Reserved coordinates, or ``None``.
Returns:
``True`` if either endpoint of the wall belongs to the reserved
pattern, otherwise ``False``.
"""
if cells_ft is None: if cells_ft is None:
return False return False
s1 = (math.trunc(wall[0] / width), wall[0] % width) s1 = (math.trunc(wall[0] / width), wall[0] % width)
@@ -171,7 +277,20 @@ class Kruskal(MazeGenerator):
def generator( def generator(
self, height: int, width: int, seed: int | None = None self, height: int, width: int, seed: int | None = None
) -> Generator[np.ndarray, None, np.ndarray]: ) -> Generator[NDArray[Any], None, NDArray[Any]]:
"""Generate a maze using a Kruskal-based approach.
Args:
height: Number of rows in the maze.
width: Number of columns in the maze.
seed: Optional random seed for reproducibility.
Yields:
Intermediate maze states during generation.
Returns:
The final generated maze.
"""
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)
@@ -180,7 +299,7 @@ class Kruskal(MazeGenerator):
if seed is not None: if seed is not None:
np.random.seed(seed) np.random.seed(seed)
sets = self.Sets([self.Set([i]) for i in range(height * width)]) sets = self.Sets([self.KruskalSet([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):
@@ -188,7 +307,6 @@ class Kruskal(MazeGenerator):
for h in range(height - 1): for h in range(height - 1):
for w in range(width): for w in range(width):
walls += [(w + (width * h), w + (width * (h + 1)))] 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)
@@ -209,8 +327,7 @@ class Kruskal(MazeGenerator):
print(f"nb sets: {len(sets.sets)}") 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, gen = Kruskal.unperfect_maze(width, height, maze, cells_ft)
cells_ft)
for res in gen: for res in gen:
maze = res maze = res
yield maze yield maze
@@ -218,28 +335,52 @@ class Kruskal(MazeGenerator):
class DepthFirstSearch(MazeGenerator): class DepthFirstSearch(MazeGenerator):
def __init__(self, start: bool, end: bool, perfect: bool) -> None: """Generate a maze using a depth-first search backtracking algorithm."""
def __init__(
self, start: tuple[int, int], end: tuple[int, int], perfect: bool
) -> None:
"""Initialize the depth-first search generator.
Args:
start: Starting cell coordinates, using 1-based indexing.
end: Ending cell coordinates, using 1-based indexing.
perfect: Whether to generate a perfect maze with no loops.
"""
self.start = (start[0] - 1, start[1] - 1) self.start = (start[0] - 1, start[1] - 1)
self.end = (end[0] - 1, end[1] - 1) self.end = (end[0] - 1, end[1] - 1)
self.perfect = perfect self.perfect = perfect
self.forty_two: set | None = None self.forty_two: set[tuple[int, int]] | None = None
def generator( 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]: ) -> Generator[NDArray[Any], None, NDArray[Any]]:
"""Generate a maze using depth-first search.
Args:
height: Number of rows in the maze.
width: Number of columns in the maze.
seed: Optional random seed for reproducibility.
Yields:
Intermediate maze states during generation.
Returns:
The final generated maze.
"""
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)
if width > 9 and height > 9: if width > 9 and height > 9:
self.forty_two = self.get_cell_ft(width, height) self.forty_two = self.get_cell_ft(width, height)
visited = np.zeros((height, width), dtype=bool) visited: NDArray[np.object_] = np.zeros((height, width), dtype=bool)
if ( if (
self.forty_two self.forty_two
and self.start not in self.forty_two and self.start not in self.forty_two
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)
path = list() path: list[tuple[int, int]] = list()
w_h = (width, height) w_h = (width, height)
coord = (0, 0) coord = (0, 0)
x, y = coord x, y = coord
@@ -270,28 +411,66 @@ class DepthFirstSearch(MazeGenerator):
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: if self.perfect is False:
gen = DepthFirstSearch.unperfect_maze(width, height, maze, gen = DepthFirstSearch.unperfect_maze(
self.forty_two) width,
height,
maze,
self.forty_two,
)
for res in gen: for res in gen:
maze = res maze = res
yield maze yield maze
return maze return maze
@staticmethod @staticmethod
def init_maze(width: int, height: int) -> np.ndarray: def init_maze(width: int, height: int) -> NDArray[Any]:
"""Create a fully walled maze grid.
Args:
width: Number of columns in the maze.
height: Number of rows in the maze.
Returns:
A two-dimensional array of cells initialized with all
walls present.
"""
maze = np.array( maze = np.array(
[[Cell(value=15) for _ in range(width)] 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, path: set) -> list: def add_cell_visited(
coord: tuple[int, int], path: list[tuple[int, int]]
) -> list[tuple[int, int]]:
"""Append a visited coordinate to the current traversal path.
Args:
coord: Coordinate of the visited cell.
path: Current traversal path.
Returns:
The updated path.
"""
path.append(coord) path.append(coord)
return path return path
@staticmethod @staticmethod
def random_cells(visited: np.array, coord: tuple, w_h: tuple) -> list: def random_cells(
rand_cell = [] visited: NDArray[Any], coord: tuple[int, int], w_h: tuple[int, int]
) -> list[str]:
"""Return the list of unvisited neighboring directions.
Args:
visited: Boolean array marking visited cells.
coord: Current cell coordinate.
w_h: Tuple containing maze width and height.
Returns:
A list of direction strings among ``"N"``, ``"S"``, ``"W"``, and
``"E"``.
"""
rand_cell: list[str] = []
x, y = coord x, y = coord
width, height = w_h width, height = w_h
@@ -309,11 +488,28 @@ class DepthFirstSearch(MazeGenerator):
return rand_cell return rand_cell
@staticmethod @staticmethod
def next_step(rand_cell: list) -> str: def next_step(rand_cell: list[str]) -> str:
return np.random.choice(rand_cell) """Select the next direction at random.
Args:
rand_cell: List of candidate directions.
Returns:
A randomly selected direction.
"""
return random.choice(rand_cell)
@staticmethod @staticmethod
def broken_wall(cell: Cell, wall: str) -> Cell: def broken_wall(cell: Cell, wall: str) -> Cell:
"""Remove the specified wall from a cell.
Args:
cell: The cell to modify.
wall: Direction of the wall to remove.
Returns:
The modified cell.
"""
if wall == "N": if wall == "N":
cell.set_north(False) cell.set_north(False)
elif wall == "S": elif wall == "S":
@@ -325,17 +521,50 @@ class DepthFirstSearch(MazeGenerator):
return cell return cell
@staticmethod @staticmethod
def next_cell(x: int, y: int, next: str) -> tuple: def next_cell(x: int, y: int, next: str) -> tuple[int, int]:
"""Return the coordinates of the adjacent cell in the given direction.
Args:
x: Current column index.
y: Current row index.
next: Direction to move.
Returns:
The coordinates of the next cell.
"""
next_step = {"N": (0, -1), "S": (0, 1), "W": (-1, 0), "E": (1, 0)} next_step = {"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)
@staticmethod @staticmethod
def reverse_path(direction: str) -> str: def reverse_path(direction: str) -> str:
"""Return the opposite cardinal direction.
Args:
direction: Input direction.
Returns:
The opposite direction.
"""
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.ndarray) -> list: def back_on_step(
path: list[tuple[int, int]],
w_h: tuple[int, int],
visited: NDArray[Any],
) -> list[tuple[int, int]]:
"""Backtrack through the path until a cell with unvisited neighbors
is found.
Args:
path: Current traversal path.
w_h: Tuple containing maze width and height.
visited: Boolean array marking visited cells.
Returns:
The truncated path after backtracking.
"""
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):
@@ -345,8 +574,17 @@ class DepthFirstSearch(MazeGenerator):
@staticmethod @staticmethod
def lock_cell_ft( def lock_cell_ft(
visited: np.ndarray, forty_two: set[tuple[int]] visited: NDArray[Any], forty_two: set[tuple[int, int]]
) -> np.ndarray: ) -> NDArray[Any]:
"""Mark the reserved '42' pattern cells as already visited.
Args:
visited: Boolean array marking visited cells.
forty_two: Set of reserved cell coordinates.
Returns:
The updated visited array.
"""
tab = [cell for cell in forty_two] tab = [cell for cell in forty_two]
for cell in tab: for cell in tab:
visited[cell] = True visited[cell] = True
+329 -139
View File
@@ -1,186 +1,325 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .Maze import Maze from .Maze import Maze
from typing import Any
import numpy as np import numpy as np
from numpy.typing import NDArray
import random
class MazeSolver(ABC): class MazeSolver(ABC):
"""Define the common interface for maze-solving algorithms."""
def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None: def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None:
"""Initialize the maze solver.
Args:
start: Start coordinates using 1-based indexing.
end: End coordinates using 1-based indexing.
"""
self.start = (start[1] - 1, start[0] - 1) self.start = (start[1] - 1, start[0] - 1)
self.end = (end[1] - 1, end[0] - 1) self.end = (end[1] - 1, end[0] - 1)
@abstractmethod @abstractmethod
def solve(self, maze: Maze, height: int = None, def solve(
width: int = None) -> str: ... self, maze: Maze, height: int | None = None, width: int | None = None
) -> str:
"""Solve the maze and return the path as direction letters.
Args:
maze: The maze to solve.
height: Optional maze height.
width: Optional maze width.
Returns:
A string representing the path using cardinal directions.
"""
...
class AStar(MazeSolver): class AStar(MazeSolver):
"""Solve a maze using the A* pathfinding algorithm."""
class Node:
"""Represent a node used during A* exploration."""
def __init__(
self,
coordinate: tuple[int, int],
g: int,
h: int,
f: int,
parent: Any,
) -> None:
"""Initialize a search node.
Args:
coordinate: Coordinates of the node.
g: Cost from the start node.
h: Heuristic cost to the goal.
f: Total estimated cost.
parent: Parent node in the reconstructed path.
"""
self.coordinate = coordinate
self.g = g
self.h = h
self.f = f
self.parent = parent
def __eq__(self, value: object, /) -> bool:
"""Compare a node to a coordinate.
Args:
value: Object to compare with.
Returns:
``True`` if the value equals the node coordinate, otherwise
``False``.
"""
return value == self.coordinate
def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None: def __init__(self, start: tuple[int, int], end: tuple[int, int]) -> None:
"""Initialize the A* solver.
Args:
start: Start coordinates using 1-based indexing.
end: End coordinates using 1-based indexing.
"""
super().__init__(start, end) super().__init__(start, end)
def f(self, n): def h(self, n: tuple[int, int]) -> int:
def g(n: tuple[int, int]) -> int: """Compute the Manhattan distance heuristic to the goal.
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: Args:
res = 0 n: Coordinates of the current node.
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: Returns:
return g(n) + h(n) The heuristic distance to the end coordinate.
except Exception: """
return 1000 return (
max(n[0], self.end[0])
- min(n[0], self.end[0])
+ max(n[1], self.end[1])
- min(n[1], self.end[1])
)
def best_path( def get_paths(
self, self,
maze: np.ndarray, maze: NDArray[Any],
actual: tuple[int, int], actual: tuple[int, int],
last: str | None, close: list["Node"],
) -> dict[str, int]: ) -> list[tuple[int, int]]:
path = { """Return all reachable neighboring coordinates.
"N": (
self.f((actual[0], actual[1] - 1)) Args:
if not maze[actual[1]][actual[0]].get_north() and actual[1] > 0 maze: Maze grid to inspect.
actual: Current coordinate.
close: List of already explored nodes.
Returns:
A list of reachable adjacent coordinates not yet closed.
"""
path = [
(
(actual[0], actual[1] - 1)
if not maze[actual[1]][actual[0]].get_north()
and actual[1] > 0
and (actual[0], actual[1] - 1)
not in [n.coordinate for n in close]
else None else None
), ),
"E": ( (
self.f((actual[0] + 1, actual[1])) (actual[0] + 1, actual[1])
if not maze[actual[1]][actual[0]].get_est() if not maze[actual[1]][actual[0]].get_est()
and actual[0] < len(maze[0]) - 1 and actual[0] < len(maze[0]) - 1
and (actual[0] + 1, actual[1])
not in [n.coordinate for n in close]
else None else None
), ),
"S": ( (
self.f((actual[0], actual[1] + 1)) (actual[0], actual[1] + 1)
if not maze[actual[1]][actual[0]].get_south() if not maze[actual[1]][actual[0]].get_south()
and actual[1] < len(maze) - 1 and actual[1] < len(maze) - 1
and (actual[0], actual[1] + 1)
not in [n.coordinate for n in close]
else None else None
), ),
"W": (
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])
if v is not None and k != last
}
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:
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
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, (actual[0] - 1, actual[1])
self.best_path(maze, next_pos, pre), if not maze[actual[1]][actual[0]].get_west()
and actual[0] > 0
and (actual[0] - 1, actual[1])
not in [n.coordinate for n in close]
else None
),
]
return [p for p in path if p is not None]
def get_path(self, maze: NDArray[Any]) -> list["Node"]:
"""Perform A* exploration until the destination is reached.
Args:
maze: Maze grid to solve.
Returns:
The closed list ending with the goal node.
Raises:
Exception: If no path can be found.
"""
open: list[AStar.Node] = []
close: list[AStar.Node] = []
open.append(
AStar.Node(
self.start,
0,
self.h(self.start),
self.h(self.start),
None,
) )
) )
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: while len(open) > 0:
res = self.get_path(maze.get_maze()) to_check = sorted(open, key=lambda x: x.f)[0]
if res is None: open.remove(to_check)
close.append(to_check)
if to_check.coordinate == self.end:
return close
paths = self.get_paths(maze, to_check.coordinate, close)
for path in paths:
open.append(
self.Node(
path,
to_check.g + 1,
self.h(path),
self.h(path) + to_check.g + 1,
to_check,
)
)
raise Exception("Path not found") raise Exception("Path not found")
def get_rev_dir(self, current: Node) -> str:
"""Determine the direction taken from the parent to the current node.
Args:
current: Current node in the reconstructed path.
Returns:
A cardinal direction letter.
Raises:
Exception: If the parent-child relationship cannot be translated.
"""
if current.parent.coordinate == (
current.coordinate[0],
current.coordinate[1] - 1,
):
return "S"
elif current.parent.coordinate == (
current.coordinate[0] + 1,
current.coordinate[1],
):
return "W"
elif current.parent.coordinate == (
current.coordinate[0],
current.coordinate[1] + 1,
):
return "N"
elif current.parent.coordinate == (
current.coordinate[0] - 1,
current.coordinate[1],
):
return "E"
else:
raise Exception("Translate error: AStar path not found")
def translate(self, close: list["Node"]) -> str:
"""Translate a node chain into a path string.
Args:
close: Closed list ending with the goal node.
Returns:
A string of direction letters from start to end.
"""
current = close[-1]
res = ""
while True:
res = self.get_rev_dir(current) + res
current = current.parent
if current.coordinate == self.start:
break
return res return res
def solve(
self, maze: Maze, height: int | None = None, width: int | None = None
) -> str:
"""Solve the maze using A*.
Args:
maze: The maze to solve.
height: Unused optional maze height.
width: Unused optional maze width.
Returns:
A string representing the path using cardinal directions.
"""
maze_arr = maze.get_maze()
if maze_arr is None:
raise Exception("Maze is not initialized")
path: list[AStar.Node] = self.get_path(maze_arr)
return self.translate(path)
class DepthFirstSearchSolver(MazeSolver): class DepthFirstSearchSolver(MazeSolver):
def __init__(self, start, end): """Solve a maze using depth-first search with backtracking."""
self.start = (start[1] - 1, start[0] - 1)
self.end = (end[1] - 1, end[0] - 1)
def solve(self, maze: Maze, height: int = None, def __init__(self, start: tuple[int, int], end: tuple[int, int]):
width: int = None) -> str: """Initialize the depth-first search solver.
Args:
start: Start coordinates using 1-based indexing.
end: End coordinates using 1-based indexing.
"""
super().__init__(start, end)
def solve(
self, maze: Maze, height: int | None = None, width: int | None = None
) -> str:
"""Solve the maze using depth-first search.
Args:
maze: The maze to solve.
height: Maze height.
width: Maze width.
Returns:
A string representing the path using cardinal directions.
Raises:
Exception: If no path can be found.
"""
path_str = "" path_str = ""
visited = np.zeros((height, width), dtype=bool) if height is None or width is None:
path = list() raise Exception("We need Height and Width in the arg")
move = list() visited: NDArray[Any] = np.zeros((height, width), dtype=bool)
path: list[tuple[int, int]] = list()
move: list[str] = list()
maze_s = maze.get_maze() maze_s = maze.get_maze()
if maze_s is None:
raise Exception("Maze is not initializef")
coord = self.start coord = self.start
h_w = (height, width) h_w: tuple[int, int] = (height, width)
while coord != self.end: while coord != self.end:
visited[coord] = True visited[coord] = True
path.append(coord) path.append(coord)
rand_p = self.random_path(visited, coord, maze_s, h_w) rand_p: list[str] = self.random_path(visited, coord, maze_s, h_w)
if not rand_p: if not rand_p:
path, move = self.back_on_step(path, visited, maze_s, h_w, path, move = self.back_on_step(
move) path, visited, maze_s, h_w, move
)
if not path: if not path:
break break
coord = path[-1] coord = path[-1]
@@ -195,8 +334,23 @@ class DepthFirstSearchSolver(MazeSolver):
return path_str return path_str
@staticmethod @staticmethod
def random_path(visited: np.ndarray, coord: tuple, def random_path(
maze: np.ndarray, h_w: tuple) -> list: visited: NDArray[Any],
coord: tuple[int, int],
maze: NDArray[Any],
h_w: tuple[int, int],
) -> list[str]:
"""Return all valid unvisited directions from the current cell.
Args:
visited: Boolean array marking visited cells.
coord: Current coordinate.
maze: Maze grid to inspect.
h_w: Tuple containing maze height and width.
Returns:
A list of valid direction letters.
"""
random_p = [] random_p = []
h, w = h_w h, w = h_w
y, x = coord y, x = coord
@@ -215,12 +369,39 @@ class DepthFirstSearchSolver(MazeSolver):
return random_p return random_p
@staticmethod @staticmethod
def next_path(rand_path: list) -> str: def next_path(rand_path: list[str]) -> str:
return np.random.choice(rand_path) """Select the next move at random.
Args:
rand_path: List of available directions.
Returns:
A randomly selected direction.
"""
return random.choice(rand_path)
@staticmethod @staticmethod
def back_on_step(path: list, visited: np.ndarray, def back_on_step(
maze: np.ndarray, h_w: tuple, move: list) -> list: path: list[tuple[int, int]],
visited: NDArray[Any],
maze: NDArray[Any],
h_w: tuple[int, int],
move: list[str],
) -> tuple[list[Any], list[Any]]:
"""Backtrack until a cell with an unexplored path is found.
Args:
path: Current path of visited coordinates.
visited: Boolean array marking visited cells.
maze: Maze grid to inspect.
h_w: Tuple containing maze height and width.
move: List of moves made so far.
Returns:
A tuple containing the updated path and move list.
"""
while path: while path:
last = path[-1] last = path[-1]
if DepthFirstSearchSolver.random_path(visited, last, maze, h_w): if DepthFirstSearchSolver.random_path(visited, last, maze, h_w):
@@ -230,7 +411,16 @@ class DepthFirstSearchSolver(MazeSolver):
return path, move return path, move
@staticmethod @staticmethod
def next_cell(coord: tuple, next: str) -> tuple: def next_cell(coord: tuple[int, int], next: str) -> tuple[int, int]:
"""Return the coordinates of the next cell in the given direction.
Args:
coord: Current coordinate.
next: Direction to move.
Returns:
The coordinates of the next cell.
"""
y, x = coord y, x = coord
next_step = {"N": (-1, 0), "S": (1, 0), "W": (0, -1), "E": (0, 1)} next_step = {"N": (-1, 0), "S": (1, 0), "W": (0, -1), "E": (0, 1)}
add_y, add_x = next_step[next] add_y, add_x = next_step[next]
+10 -2
View File
@@ -6,5 +6,13 @@ from .MazeSolver import MazeSolver, AStar, DepthFirstSearchSolver
__version__ = "1.0.0" __version__ = "1.0.0"
__author__ = "us" __author__ = "us"
__all__ = ["Cell", "Maze", "MazeGenerator", "DepthFirstSearchSolver", __all__ = [
"MazeSolver", "AStar", "Kruskal", "DepthFirstSearch"] "Cell",
"Maze",
"MazeGenerator",
"DepthFirstSearchSolver",
"MazeSolver",
"AStar",
"Kruskal",
"DepthFirstSearch",
]
+111 -16
View File
@@ -1,11 +1,24 @@
from src.amaz_lib.MazeGenerator import DepthFirstSearch, Kruskal from ..amaz_lib import DepthFirstSearch, Kruskal
from src.amaz_lib.MazeSolver import AStar from ..amaz_lib import AStar, DepthFirstSearchSolver
from typing import Any
class DataMaze: class DataMaze:
"""Provide helper methods to load and validate maze configuration data."""
@staticmethod @staticmethod
def get_file_data(name_file: str) -> str: def get_file_data(name_file: str) -> str:
"""Read and return the contents of a configuration file.
Args:
name_file: Path to the configuration file.
Returns:
The file contents as a string.
Raises:
ValueError: If the file is empty.
"""
with open(name_file, "r") as file: with open(name_file, "r") as file:
data = file.read() data = file.read()
if data == "": if data == "":
@@ -13,14 +26,32 @@ class DataMaze:
return data return data
@staticmethod @staticmethod
def transform_data(data: str) -> dict: def transform_data(data: str) -> dict[str, str]:
"""Transform raw configuration text into a dictionary.
Each non-empty line containing ``=`` is split into a key-value pair.
Args:
data: Raw configuration text.
Returns:
A dictionary mapping configuration keys to their string values.
"""
tmp = data.split("\n") tmp = data.split("\n")
tmp2 = [value.split("=", 1) for value in tmp if "=" in value] tmp2 = [value.split("=", 1) for value in tmp if "=" in value]
data_t = {value[0]: value[1] for value in tmp2} data_t = {value[0]: value[1] for value in tmp2}
return data_t return data_t
@staticmethod @staticmethod
def verif_key_data(data: dict) -> None: def verif_key_data(data: dict[str, str]) -> None:
"""Validate that the configuration contains the expected keys.
Args:
data: Configuration dictionary to validate.
Raises:
KeyError: If keys are missing or unexpected keys are present.
"""
key_test = { key_test = {
"WIDTH", "WIDTH",
"HEIGHT", "HEIGHT",
@@ -41,11 +72,20 @@ class DataMaze:
) )
@staticmethod @staticmethod
def convert_values(data: dict): def convert_values(data: dict[str, str]) -> dict[str, Any]:
"""Convert configuration values to their appropriate Python types.
Args:
data: Raw configuration dictionary with string values.
Returns:
A dictionary containing converted values and instantiated
solver and generator objects.
"""
key_int = {"WIDTH", "HEIGHT"} key_int = {"WIDTH", "HEIGHT"}
key_tuple = {"ENTRY", "EXIT"} key_tuple = {"ENTRY", "EXIT"}
key_bool = {"PERFECT"} key_bool = {"PERFECT"}
res: dict = {} 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])})
for key in key_tuple: for key in key_tuple:
@@ -54,29 +94,65 @@ 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(
res["PERFECT"]) data,
res["ENTRY"],
res["EXIT"],
res["PERFECT"],
)
) )
return res return res
@staticmethod @staticmethod
def get_solver_generator(data: dict, entry: tuple, exit: tuple, def get_solver_generator(
perfect: bool) -> dict: data: dict[str, str],
available_generator = { entry: tuple[int, int],
exit: tuple[int, int],
perfect: bool,
) -> dict[str, Any]:
"""Instantiate the configured maze generator and solver.
Args:
data: Raw configuration dictionary.
entry: Entry coordinates.
exit: Exit coordinates.
perfect: Whether the maze must be perfect.
Returns:
A dictionary containing initialized ``GENERATOR`` and ``SOLVER``
objects.
"""
available_generator: dict[str, Any] = {
"Kruskal": Kruskal, "Kruskal": Kruskal,
"DFS": DepthFirstSearch, "DFS": DepthFirstSearch,
} }
available_solver = { available_solver: dict[str, Any] = {
"AStar": AStar, "AStar": AStar,
"DFS": DepthFirstSearchSolver,
} }
res = {} res = {}
res["GENERATOR"] = available_generator[data["GENERATOR"]](entry, exit, res["GENERATOR"] = available_generator[data["GENERATOR"]](
perfect) entry,
exit,
perfect,
)
res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit) res["SOLVER"] = available_solver[data["SOLVER"]](entry, exit)
return res return res
@staticmethod @staticmethod
def convert_tuple(data: str) -> tuple: def convert_tuple(data: str) -> tuple[int, int]:
"""Convert a comma-separated coordinate string into a tuple.
Args:
data: Coordinate string in the form ``"x,y"``.
Returns:
A tuple of two integers.
Raises:
ValueError: If the coordinate string does not contain exactly two
values.
"""
data_t = data.split(",") data_t = data.split(",")
if len(data_t) != 2: if len(data_t) != 2:
raise ValueError( raise ValueError(
@@ -88,6 +164,17 @@ class DataMaze:
@staticmethod @staticmethod
def convert_bool(data: str) -> bool: def convert_bool(data: str) -> bool:
"""Convert a string to a boolean value.
Args:
data: String representation of a boolean.
Returns:
``True`` if the string is ``"True"``, otherwise ``False``.
Raises:
ValueError: If the string is neither ``"True"`` nor ``"False"``.
"""
if data != "True" and data != "False": if data != "True" and data != "False":
raise ValueError("This is not True or False") raise ValueError("This is not True or False")
if data == "True": if data == "True":
@@ -95,7 +182,15 @@ class DataMaze:
return False return False
@staticmethod @staticmethod
def get_data_maze(name_file: str) -> dict: def get_data_maze(name_file: str) -> dict[str, Any]:
"""Load, validate, and convert maze configuration data from a file.
Args:
name_file: Path to the configuration file.
Returns:
A dictionary of validated configuration values with lowercase keys.
"""
try: try:
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)
-6
View File
@@ -1,6 +0,0 @@
__version__ = "1.0.0"
__author__ = "mteriier, dgaillet"
from .Parsing import DataMaze
__all__ = ["DataMaze"]
View File
-15
View File
@@ -1,15 +0,0 @@
BD1553D3913
C3AD54386AA
BAC5396C7AA
82956C5396E
A86D553AC53
C295552C512
9283B9693AA
AAAAC456AAA
AC2C553D2C2
83C3D3C3E96
C454547C547
4,3
2,1
EENWWNNEEESESWSEESSEEESSSSWSWWNWNWWWNNWSSSESWWNWNNNENNNNNW
-1
View File
@@ -1,4 +1,3 @@
import pytest
from amaz_lib.Cell import Cell from amaz_lib.Cell import Cell
+3 -1
View File
@@ -15,7 +15,9 @@ def test_maze_setter_getter() -> None:
) )
maze.set_maze(test) maze.set_maze(test)
assert numpy.array_equal(maze.get_maze(), test) is True m = maze.get_maze()
assert m is not None
assert numpy.array_equal(m, test) is True
def test_maze_str() -> None: def test_maze_str() -> None:
+1 -5
View File
@@ -1,5 +1,5 @@
import numpy import numpy
from amaz_lib.MazeGenerator import DepthFirstSearch, MazeGenerator from amaz_lib.MazeGenerator import DepthFirstSearch
class TestMazeGenerator: class TestMazeGenerator:
@@ -12,7 +12,3 @@ class TestMazeGenerator:
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
+1 -1
View File
@@ -1,6 +1,6 @@
from amaz_lib.Cell import Cell from amaz_lib.Cell import Cell
import numpy as np import numpy as np
from amaz_lib import AStar, Maze, MazeSolver from amaz_lib import AStar, Maze
def test_solver() -> None: def test_solver() -> None:
+17 -17
View File
@@ -4,71 +4,71 @@ import pytest
class TestParsing: class TestParsing:
def test_get_data_valid(self): def test_get_data_valid(self) -> None:
data = DataMaze.get_file_data("tests/test_txt/config_1.txt") data = DataMaze.get_file_data("tests/test_txt/config_1.txt")
assert isinstance(data, str) is True assert isinstance(data, str) is True
def test_file_error(self): def test_file_error(self) -> None:
with pytest.raises(FileNotFoundError): with pytest.raises(FileNotFoundError):
DataMaze.get_file_data("tete") DataMaze.get_file_data("tete")
# def test_permission_error(self): # def test_permission_error(self) -> None:
# with pytest.raises(PermissionError): # with pytest.raises(PermissionError):
# DataMaze.get_file_data("tests/test_txt/error_1.txt") # DataMaze.get_file_data("tests/test_txt/error_1.txt")
def test_empty_file_error(self): def test_empty_file_error(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
DataMaze.get_file_data("tests/test_txt/error_6.txt") DataMaze.get_file_data("tests/test_txt/error_6.txt")
def test_transform_data_valid(self): def test_transform_data_valid(self) -> None:
data = DataMaze.get_file_data("tests/test_txt/config_1.txt") data = DataMaze.get_file_data("tests/test_txt/config_1.txt")
data_2 = DataMaze.transform_data(data) data_2 = DataMaze.transform_data(data)
assert isinstance(data_2, dict) assert isinstance(data_2, dict)
def test_transform__index_error(self): def test_transform__index_error(self) -> None:
with pytest.raises(IndexError): with pytest.raises(IndexError):
DataMaze.transform_data("asdasdasdasdasdasda\nasdasdas=asdasd") DataMaze.transform_data("asdasdasdasdasdasda\nasdasdas=asdasd")
def test_key_data_error(self): def test_key_data_error(self) -> None:
with pytest.raises(KeyError): with pytest.raises(KeyError):
data = DataMaze.get_file_data("tests/test_txt/error_8.txt") data = DataMaze.get_file_data("tests/test_txt/error_8.txt")
data2 = DataMaze.transform_data(data) data2 = DataMaze.transform_data(data)
DataMaze.verif_key_data(data2) DataMaze.verif_key_data(data2)
def test_key_data_error_2(self): def test_key_data_error_2(self) -> None:
with pytest.raises(KeyError): with pytest.raises(KeyError):
data = DataMaze.get_file_data("tests/test_txt/error_9.txt") data = DataMaze.get_file_data("tests/test_txt/error_9.txt")
data2 = DataMaze.transform_data(data) data2 = DataMaze.transform_data(data)
DataMaze.verif_key_data(data2) DataMaze.verif_key_data(data2)
def test_convert_int(self): def test_convert_int(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
data = DataMaze.get_file_data("tests/test_txt/error_2.txt") data = DataMaze.get_file_data("tests/test_txt/error_2.txt")
data2 = DataMaze.transform_data(data) data2 = DataMaze.transform_data(data)
DataMaze.convert_values(data2) DataMaze.convert_values(data2)
def test_tuple_error(self): def test_tuple_error(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
DataMaze.convert_tuple("0,3,5,5") DataMaze.convert_tuple("0,3,5,5")
def test_tuple_error1(self): def test_tuple_error1(self) -> None:
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
DataMaze.convert_tuple(None) DataMaze.convert_tuple("None")
def test_bool_error(self): def test_bool_error(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
DataMaze.convert_bool("Trueeee") DataMaze.convert_bool("Trueeee")
def test_valid_tuple(self): def test_valid_tuple(self) -> None:
assert DataMaze.convert_tuple("7534564654, 78") == (7534564654, 78) assert DataMaze.convert_tuple("7534564654, 78") == (7534564654, 78)
def test_valid_bool(self): def test_valid_bool(self) -> None:
assert DataMaze.convert_bool("False") is False assert DataMaze.convert_bool("False") is False
def test_valid_bool1(self): def test_valid_bool1(self) -> None:
assert DataMaze.convert_bool("True") is True assert DataMaze.convert_bool("True") is True
def test_data_maze(self): def test_data_maze(self) -> None:
data = DataMaze.get_data_maze("tests/test_txt/config_1.txt") data = DataMaze.get_data_maze("tests/test_txt/config_1.txt")
assert data["WIDTH"] == 200 assert data["WIDTH"] == 200
assert data["HEIGHT"] == 100 assert data["HEIGHT"] == 100
Generated
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]] [[package]]
name = "a-maze-ing" name = "a-maze-ing"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },