feat/maze-representation #170
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
OragonEfreet/banjo!170
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/maze-representation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Introduce the core maze data structure, designed to encode any of the topologies from Jamis Buck's "Mazes for Programmers" (orthogonal, polar, hex, triangular, 3D, weave, wrapped surfaces) behind a single type. The representation splits into three layers: - Topology: a fixed-stride adjacency array, `degree` slots per cell, holding neighbour indices or BJ_MAZE_NO_CELL. Built once, then immutable. Slot index carries direction semantics per family. - State: one byte per cell, bit s marking a passage through slot s. The only mutable part of a maze. - Attributes: none. Cells are dense uint32_t indices, so per-cell data (distances, weights, marks) is a caller-owned parallel array. Fixed stride is preferred over CSR: degrees are small and bounded, fill is high, and O(1) slot access without an offset indirection matters for the random-access generation algorithms. Geometry lives in a private per-family tag plus union, so coordinate helpers can validate the family instead of trusting caller-supplied dimensions. Passages are always bidirectional; the reciprocal slot is resolved through a per-family callback table, since no arithmetic rule holds across all topologies (polar inward/outward is the counter-example). Opening a border slot is refused, keeping the link-implies-adjacency invariant that dead-end detection relies on. This commit covers the orthogonal 2D family only: constructor, destructor, coordinate lookup, open/close/query, and the accessors a caller needs to run its own traversals. Generation algorithms and rendering come later. Also drops the stray BANJO_EXPORT on bj_allocate_bitmap's definition, which was the only one in src/ and inconsistent with the rest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>Add the generation half of the maze API, plus the tests and the RNG groundwork it needed. An algorithm is a callback, so the built-in ones and a caller's own are the same kind of thing. Generation is driven a step at a time rather than run to completion behind the caller's back, which lets a program spread it over frames and watch the maze appear; bj_run_maze_builder is there for callers that only want the result. A step reports whether there is more to do, rather than whether it is finished. The two read alike but fail differently: a callback that returns nothing in particular ends generation early instead of spinning forever, and the same holds for the guards on a bad context. Algorithm state lives in the context as a single block the callback allocates and the builder frees, so an abandoned generation leaks nothing and no callback releases its own state. Binary tree is the first algorithm to use it, with a cursor and nothing else. Wire the error system through maze creation: dimensions that cannot work and allocation failures now say so. Build steps deliberately stay out of it, since they run once per cell and the error system allocates; a step that cannot continue logs and stops. Note that a failed call has to be recognised by its return value rather than by the error being set — when memory runs out, the error object cannot be allocated either. Also add to the RNG: - bj_uniform_uint32_distribution, for drawing cell indices, which can exceed the range the signed variant covers. - bj_rand_generator, exposing the global engine to the distribution API. It combines three draws because bj_rand yields 15 bits, and a single cast leaves the high bits clear — which collapses every rejection-based distribution onto its lower bound. - A fix for signed overflow in bj_uniform_int32_distribution when the span exceeds INT32_MAX: the high half was narrowed before being added to the lower bound. Only observable under UBSan. Tests cover the maze representation, the builder lifecycle, and the properties a generated maze must hold — every cell reachable, exactly one passage fewer than there are cells, and the same seed giving the same maze. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>An algorithm is now three functions and its settings, handed over together. Setup prepares whatever the algorithm remembers between steps, teardown releases it, and the builder never looks inside either. What used to be a documented convention — allocate one block, we will free it — is now something the shape of the API enforces, and an algorithm may keep its state in whatever form suits it. Setup also runs during creation rather than on the first step, which is where refusals belong: a family it cannot carve, or directions that describe no sweep, now make bj_create_maze_builder fail and say why. A step could only log and stop, since it runs too often to afford the error system. Carrying the settings in the description is what lets each algorithm take its own settings type. Passing a binary tree's pair of directions to sidewinder used to compile and quietly carve the wrong maze; the two structs have the same shape, so nothing caught it. It is a compile error now. The description travels by value, so one assembled at the call site needs no name and no lifetime: bj_create_maze_builder(maze, bj_maze_algorithm_sidewinder(&s), 0, 0, 0); Add restarting a builder in place, which is the same teardown and setup run again. It leaves the maze untouched: a builder does not own it, and carving twice over the same grid is a legitimate thing to want. Starting clean is bj_close_maze, added here along with bj_open_maze for the wall-adding algorithms that begin from an open field. Note that the outer boundary needs neither: edge cells have no neighbour to open towards, so it survives on its own. Add sidewinder, and with it the run-drawing that made the state fit in three integers: a run member is drawn as the run grows, so the run never has to be stored, however far apart its cells lie. Split the sources by subject, with a private header for what they share, and let the example show a maze being carved: each algorithm named under its own grid, SPACE to generate again, the engine seeded per run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>