Most so_long builds stay flat. Mine doesn't.
Stone platforms adrift in a starless dark, a lamplighter chasing light between shrines — and the isometric engine built from scratch to render it all.
📜 Context •
🕯️ The World •
🎮 How to Play •
🧩 Modules
📺 Visuals •
🏗️ Architecture •
🧪 Testing •
💻 Usage
"A subject can name the pieces. It can't tell you what to build with them."
so_long is 42's first graphics project: a 2D game built on MiniLibX, reading a map from a .ber file, moving a character across it with the keyboard, and rendering everything through textures instead of raw pixels. The subject hands you a working example and then tells you, explicitly, to replace every part of it.
Unlike libft or get_next_line, this project doesn't restrict what you can use. open, close, read, write, the entire math library, every MLX42 function, and your own ft_printf are all fair game. The constraint here isn't the toolbox — it's whether the result is worth looking at.
This version takes that freedom further — not just a different perspective, but a different reference entirely. Monument Valley didn't just lend the isometric angle. It lent the instinct to tell a story without a single line of dialogue, to let floating architecture imply a world nobody has to explain, and to trust a muted palette over a busy one. so_long borrows the discipline, not just the aesthetic.
Every tile, every character facing, every flicker of animation in this build was drawn specifically for it — not pulled from a tileset, not adapted from someone else's asset pack. The visual language is as much a deliverable here as the code: floor and wall textures that read clearly at an isometric angle, a lamplighter and a set of shadows with four-directional sprites each, and a light that animates frame by frame instead of sitting as a static icon.
These platforms don't rest on any ground — they float, pale stone adrift in a sky that never sees daylight. Scattered across them are shrines, each one holding a single light.
You play the lamplighter: no name, no dialogue, just a lantern for company and a map full of platforms to cross. Every shrine you reach adds its light to yours. The shadows drifting between the stones want the same lights — and they don't share.
The exit doesn't open on its own. It opens once every shrine stands empty and every light is exactly where the lamplighter decided it should be.
Every map plays out the same simple loop: spawn at P, explore the grid, and track down every point of light (C) scattered across it. The exit stays sealed the whole time — it only switches from closed to open once the last light is collected, so there's no accidentally rushing past the objective in the dark. From there, it's a straight race to E in as few moves as possible.
Objective: collect every light, then get out — efficiently.
| Symbol | Element | Behavior & Rules |
|---|---|---|
0 |
Floor | Walkable open stone platform. |
1 |
Wall | Impassable floating architecture barrier. |
C |
Light | Collectibles scattered across shrines — all must be gathered. |
E |
Exit | Sealed portal; unlocks only once every light is collected. |
P |
Player | Lamplighter starting spawn point. |
| Key | Action |
|---|---|
W A S D |
Move character up / left / down / right |
↑ ↓ ← → |
Alternative directional movement |
ESC / Close |
Quit game cleanly without memory leaks |
Every move counts — literally. The mandatory build prints the running total to the terminal after each step; the bonus build puts that same counter directly on-screen, so "efficiently" stops being an abstract goal and becomes a live metric staring back at you.
The grid isn't limited to the maps already in assets/maps/ — any .ber file that passes the six checks in Map Validation is fair game, so building your own layout is just a text file away.
Two phases, two different disciplines:
- Map Validation & Gameplay (Mandatory): Parsing a
.berfile into a provably valid map, then rendering it and letting a player move through it cleanly. - Enemy Patrols, Animation & HUD (Bonus): Turning a static, silent map into something with movement, risk, and feedback on screen instead of in the terminal.
Click on each module below to view technical specifications and rendering pipelines.
🔹 Module I — Map Validation & Gameplay (Mandatory)
A map with a hole in one wall isn't "mostly valid." It's a program that hasn't crashed yet.
Six ways a .ber file can be wrong — and the six categories the entire map corpus is built around:
| Category | What it catches |
|---|---|
file |
Wrong extension, missing file, empty file |
format |
Not rectangular, dimensions too small, malformed lines |
grid |
Forbidden characters, hidden whitespace |
elements |
Duplicate or missing player, exit, or collectible |
wall |
Map not fully enclosed |
path |
Player can't reach the exit, or can't reach every collectible — checked with a flood fill before the window opens |
No MLX call happens until a map clears all six. A broken map never reaches the window.
While standard so_long implementations use orthogonal 2D top-down grids, this engine features a true 2.5D isometric projection pipeline, transforming 2D cartesian coordinates into 3D isometric screen space:
iso_x = (x - y) * cos(30°)
iso_y = (x + y) * sin(30°)
Isometric perspective introduces complex depth ordering challenges: diagonal screen overlaps require dynamic Z-sorting rather than simple sequential array loops. The engine solves this via an optimized Painter's Algorithm, ordering draw calls back-to-front by depth key (x + y) on every frame. This guarantees proper occlusion for terrain elevation, static obstacles, and animated dynamic entities.
- Grid-Aligned Locomotion: Discrete tile-based movement bounded by collision masks, responsive to both WASD and Arrow key vectors.
- Telemetry Logging: Real-time step counter emitted to standard output following every verified position update.
- Graceful Termination: Binds
ESCand window close events to a centralized destructor, guaranteeing clean teardown of MLX contexts, textures, and map buffers without memory leaks. - Hardware-Accelerated Image Rendering: All scene visuals are rendered using MLX42's texture and image pipeline, strictly avoiding software pixel-by-pixel bottlenecks.
🔸 Module II — Enemy Patrols, Animation & HUD (Bonus)
The subject calls these "extra points." They're also the difference between a program that passes and a game someone wants to keep playing.
- 🌑 AI Patrol & Tracking Subsystem: Shadows operate on a calibrated 0.5-second clock tick rather than frame rate. Within a 5-tile radius, enemies transition into a greedy chase state targeting the player along the dominant axis. Beyond 5 tiles, entities revert to bounded pseudo-random exploration while honoring map collision barriers.
- 💀 Narrative Failure Sequence: Collision with a shadow triggers an atmospheric terminal notification (
[ The path has faded... ]) followed by an immediate clean shutdown. - 🎨 Depth-Integrated Entity Occlusion: Enemy Z-indexes are dynamically interpolated within the isometric depth buffer, ensuring entities correctly pass behind foreground structures without visual artifacts.
- 🎞️ Frame-Based Sprite Animation: Animated multi-frame sprites for the player, collectible light shrines, and roaming shadows drive fluid visual feedback.
- 🔢 Direct Framebuffer HUD: Custom monospace bitmap font renderer directly draws real-time step counters and collectible progress onto the screen buffer without external font libraries.
The full sprite set, hand-drawn — floor, walls, the exit, the lamplighter, the shadows, and the lights they chase.
Enemy patrols (Bonus) |
Steps (Bonus)
Lights (Bonus) |
so_long keeps parsing, rendering, and game logic in separate folders on purpose — each layer only knows its own job, nothing about how the others work.
so_long/
├── inc/
│ ├── mandatory/ # so_long.h, types.h, settings.h, errors.h
│ └── bonus/ # Same headers, _bonus suffix
├── src/
│ ├── mandatory/
│ │ ├── core/ # main, game_loop, game_start
│ │ ├── parsing/ # map validation — zero MLX calls
│ │ ├── init/ # MLX + struct setup
│ │ ├── game/ # input, movement, collection
│ │ ├── render/ # window, map, player, UI
│ │ └── utils/ # cleanup, error handling
│ └── bonus/ # mirrors mandatory file-for-file, plus enemy AI
├── assets/
│ ├── maps/ # 50 .ber maps, categorized by what they test
│ └── textures/ # Custom sprite set, isometric style
├── lib/ # Unified core library (libft, ft_printf, get_next_line) & MLX42
├── config/ # Terminal banners & ANSI color palettes
└── Makefile # Multi-target build orchestrator (all, bonus, fclean)
- Strict File-for-File Bonus Parity: The bonus architecture mirrors the mandatory codebase file-for-file (e.g.
render_map.cvsrender_map_bonus.c), ensuring clean diffability and isolated extension of gameplay mechanics. - Unified Vendored Toolchain: Integrates
libft(primitives),ft_printf(terminal logging), andget_next_line(stream parsing) as pre-validated internal libraries without redundant code. - Autonomous In-Tree Graphics Pipeline: MLX42 is vendored directly within
lib/MLX42and compiled from source, eliminating external system package manager dependencies across host environments.
A game doesn't have a
libcto diff against. The only ground truth is: does it behave exactly like the spec says it should?
so_long has no deterministic reference to compare output against — there's no real printf here to check byte-for-byte. What it has instead is 50 hand-built .ber maps, split evenly across mandatory and bonus, organized into the same six failure categories the parser is designed to catch:
assets/maps/
├── mandatory/
│ ├── valid/ # 4 maps — verified playable, start to finish
│ └── invalid/ # 21 maps across the 6 failure categories
└── bonus/
├── valid/ # 6 maps — verified playable, start to finish
└── invalid/ # 19 maps across the same 6 categories
Each map gets run against its matching binary — ./so_long for mandatory, ./so_long_bonus for bonus — checking that the result, a clean load or a rejection with the right error, matches what its category promises. There's no automated runner yet; every one of these 50 maps has been verified manually, one at a time. The categorization itself is the test design — the runner is the next thing to build.
- Static Analysis: Validated by Norminette (42's internal linter).
- Compiler Flags: Compiled strictly with
-Wall -Wextra -Werror. - Memory: Verified leak-free across both the mandatory and bonus builds, MLX resources included.
Make sure you have a C compiler (gcc or clang), make, and MLX42's system dependencies installed, then clone the repository:
git clone https://github.com/joolibar/so_long.git
cd so_longCompile the game:
make # Compiles so_long — the mandatory build
make bonus # Compiles so_long_bonus — enemies, animation, on-screen HUDEach build has its own binary and its own map set — mandatory maps are built for so_long, bonus maps for so_long_bonus. Running a map against the wrong binary isn't the tested path.
./so_long assets/maps/mandatory/valid/01_cartesian_init.ber
./so_long_bonus assets/maps/bonus/valid/01_sentinel_incursion.berSee How to Play for controls and the map legend.
"A lamplighter doesn't build the path — he just makes sure you can see it." 🏮🗺️
Crafted by joolibar
Creative Developer building digital experiences at 42
© 2026 joolibar • Validated by Moulinette 🤖, crafted by Humans 🧠.




