Skip to content

MLX Library

This document explains how the MiniLibX (MLX) library is used in the A-Maze-ing project.

MiniLibX is a small graphical library commonly used in 42 School projects. It provides basic tools to: - open a window - load images - draw images to the screen - handle keyboard input - listen to window events - run a graphical loop

In this project, MLX is used to display the maze, menus, themes, player movement, BFS animations, and visual feedback.


๐Ÿ“ Main Files

render/
โ”œโ”€โ”€ mlx_renderer.py
โ”œโ”€โ”€ draw_maze.py
โ”œโ”€โ”€ menu.py
โ”œโ”€โ”€ Assets.py
โ”œโ”€โ”€ converter.py
โ””โ”€โ”€ GameState.py

๐Ÿง  What MLX Does

MLX works as the graphical layer of the project.

It does not generate the maze and it does not solve the maze.

Instead, it receives data from the project and displays it visually.

Maze generation
      โ”‚
      โ–ผ
Maze grid / path / state
      โ”‚
      โ–ผ
MLX renderer
      โ”‚
      โ–ผ
Window display

๐ŸชŸ Creating a Window

The first step in any MLX program is initializing MLX and creating a window.

In this project, this happens inside:

def mlx_window(game: GameState) -> None:

Example from the project:

mlx = Mlx()
mlx_ptr = mlx.mlx_init()

win_ptr = mlx.mlx_new_window(
    mlx_ptr,
    WINDOW_WIDTH,
    WINDOW_HEIGHT,
    "A-MAZE-ING"
)

This creates the main game window.

The project uses:

WINDOW_WIDTH = 1600
WINDOW_HEIGHT = 900

๐Ÿ–ผ๏ธ Images in MLX

MiniLibX works best with .xpm images.

Because of that, the project converts image assets into XPM before loading them.

Examples of loaded assets: - walls - floors - trails - ducks - exit doors - banners - menu backgrounds


๐Ÿ“ฆ Asset Loading

The Assets class loads and stores the images used by the renderer.

Main file:

render/Assets.py

Example:

self.wall, _, _ = mlx.mlx_xpm_file_to_image(
    mlx_ptr,
    f"assets/generated/{wall_file}_{tile_size}.xpm"
)

This loads an XPM image and stores it so it can be drawn later.


๐Ÿ”„ PNG / JPEG to XPM Conversion

The project stores original assets as PNG or JPEG files.

Before MLX can use them, they are: 1. resized 2. converted to XPM 3. saved in assets/generated/

Main file:

render/converter.py

Main functions:

generate_scaled_asset()
convert_to_xpm()
generate_all_assets()

Example:

os.system(f"magick {png_path} {xpm_path}")

The project uses ImageMagick through the magick command to perform the conversion.


๐Ÿ“ Dynamic Asset Scaling

The maze can have different sizes, so the tile size is calculated dynamically.

This prevents the maze from being too large for the screen.

Example:

def calculate_tile_size(game: GameState) -> int:

The tile size depends on: - maze width - maze height - available window width - available window height - banner space

Once the tile size is known, assets are generated for that exact size.


๐Ÿงฑ Drawing Images to the Window

MLX draws images using:

mlx.mlx_put_image_to_window()

Example from the project:

mlx.mlx_put_image_to_window(
    mlx_ptr,
    win_ptr,
    base_tile,
    screen_x,
    screen_y
)

This places an image at a specific pixel position.


๐Ÿงฉ Tile-Based Rendering

The maze is drawn tile by tile.

Each value in the maze grid represents a different visual element:

0 = floor
1 = wall
2 = 42 wall
3 = duck/player
4 = trail/path

The renderer reads the grid and chooses which image to draw.

Example logic:

if cell == 3:
    base_tile = assets.floor
elif cell == 0:
    base_tile = assets.floor
elif cell == 1:
    base_tile = assets.wall
elif cell == 2:
    base_tile = assets.wall_42
else:
    base_tile = assets.trail

๐Ÿฆ† Drawing the Player

The duck is drawn as a separate image on top of the floor.

This is important because the player should not permanently modify the maze grid.

Example:

if cell == 3:
    mlx.mlx_put_image_to_window(
        mlx_ptr,
        win_ptr,
        assets.duck,
        screen_x,
        screen_y
    )

This allows the player to move without destroying walls, floors, or trails.


๐Ÿšช Drawing Entry and Exit

The maze entry and exit are logical cells.

For rendering, they are converted to expanded grid positions.

The renderer uses:

logical_to_grid()
calculate_door_position()

These functions are used to draw the entry and exit doors in the correct position.


๐Ÿงญ Coordinate Systems

The project uses two coordinate systems:

Logical Coordinates

Used by the configuration file and maze structure.

Example:

ENTRY=0,0
EXIT=19,19

Grid Coordinates

Used by the expanded internal maze grid.

Conversion:

grid_x = logical_x * 2 + 1
grid_y = logical_y * 2 + 1

This allows the renderer to draw: - cells - walls - corridors - doors

correctly.


๐ŸŽฎ Keyboard Input

MLX can react to keyboard input using hooks.

In this project, keyboard input is handled with:

mlx.mlx_key_hook()

Example:

mlx.mlx_key_hook(
    win_ptr,
    lambda key, param: key_hook(
        key,
        game,
        frame,
        param,
        mlx,
        mlx_ptr,
        win_ptr
    ),
    None
)

The key_hook() function controls: - menu selection - maze regeneration - BFS animation - player mode - theme switching - exiting the game


โŒจ๏ธ Key Controls

The project uses numeric keys and arrow keys.

1        Generate new maze
2        Show shortest path
3        Show BFS exploration animation
4        Player mode
5        Change theme
6 / ESC  Exit
Arrows   Move player

๐ŸŽฎ Game State and MLX

MLX itself does not know what mode the game is in.

The project stores that information inside:

class GameState:

The renderer checks the game state every frame.

Example states:

game.mode
game.theme
game.show_path
game.animate_bfs
game.playing
game.game_won

This allows MLX to draw different things depending on the current mode.


๐Ÿ” The MLX Loop

MLX uses a loop to keep the window alive.

In this project:

mlx.mlx_loop_hook(
    mlx_ptr,
    on_loop,
    None
)

mlx.mlx_loop(mlx_ptr)

The loop repeatedly calls on_loop().

This function updates: - menu rendering - maze rendering - animations - player movement display - exit animation


๐ŸŽž๏ธ Animation With MLX

MLX does not provide a complex animation system by itself.

Instead, the project creates animation manually using: - frame counters - time checks - partial path drawing - repeated redraws

Example:

frame = [0]
last_time = [time.time()]

The renderer only advances the animation when enough time has passed.

Example:

if now - last_time[0] >= 0.05:

This prevents animations from running too fast.


๐Ÿฆ† Path Animation

Option 2 shows the shortest path.

The renderer progressively draws part of the path:

game.path[:frame[0]]

This creates the effect of the duck moving through the maze.


๐ŸŒŠ BFS Exploration Animation

Option 3 shows how BFS explores the maze.

Instead of displaying only the final path, it displays explored cells:

game.explored[:frame[0]]

This visualizes the search process step by step.


๐Ÿšช Exit Animation

The exit uses two images:

assets.exit
assets.exit2

The renderer alternates between them using time:

animation_frame = int(time.time() * 3.33) % 2

This creates a simple animated portal effect.


๐Ÿงน Clearing and Syncing the Window

Sometimes the renderer needs to clear the old screen before drawing a new state.

Example:

mlx.mlx_clear_window(mlx_ptr, win_ptr)

The project also uses:

mlx.mlx_do_sync(mlx_ptr)

to help synchronize visual updates.

This is useful when: - changing themes - regenerating the maze - switching modes - entering player mode


โŒ Closing the Window

The project handles the window close event with:

mlx.mlx_hook(
    win_ptr,
    33,
    0,
    close,
    None
)

The close() function exits the program:

def close(param: Any) -> None:
    os._exit(0)

The user can also quit with:

ESC
6

๐ŸŽจ Themes With MLX

The renderer supports different visual themes.

Current themes:

normal
gothic

The selected theme changes which assets are loaded.

Example:

f"assets/generated/{theme}_floor_{tile_size}.xpm"

This allows the same maze logic to be displayed with different visual styles.


๐Ÿงฑ MLX Limitations

MLX is intentionally simple.

It does not provide: - advanced UI widgets - built-in animation tools - sprite management - automatic scaling - layout systems - scene management

Because of that, the project manually handles: - asset conversion - image scaling - menu drawing - animation timing - player movement - screen updates


๐Ÿง  Why MLX Was Used

MLX was used because: - it is part of the 42 graphics ecosystem - it teaches low-level rendering concepts - it gives direct control over pixels and images - it integrates well with simple game loops - it is suitable for tile-based projects

For this project, MLX is enough to create: - a graphical maze - menus - animations - player movement - theme switching


๐Ÿ—๏ธ Role of MLX in A-Maze-ing

MLX acts as the visual interface between the user and the maze engine.

MazeGenerator
      โ”‚
      โ–ผ
Maze + Path + Explored Cells
      โ”‚
      โ–ผ
GameState
      โ”‚
      โ–ผ
MLX Renderer
      โ”‚
      โ–ผ
Window Output

The maze generation system stays independent from MLX.

This separation keeps the project modular: - mazegen/ handles maze logic - render/ handles graphics and interaction


๐ŸŽฏ Summary

In A-Maze-ing, MiniLibX is used to: - open the game window - load XPM images - draw the maze - render menus and banners - handle keyboard input - animate BFS and pathfinding - display player movement - support visual themes

The project builds a higher-level rendering system on top of MLX by combining: - GameState - dynamic assets - tile rendering - event hooks - animation logic

This allows a simple graphics library to support a complete interactive maze visualizer.