GitHunt
PS

psenger/random-path-generator

Procedural horizontal path generation that actually works written in GoLang.

Random Path Generator

Procedural horizontal path generation that actually works.

Go Version
Test Coverage
Tests
License

..................................................
......┌──────┐....................................
......│......│....................................
......└────┐.│....................................
...........│.│..┌●─┐.┌─●─┐.┌─●─┐.....┌─●┐.........
●───●──────┘.└─●┘..│.│...│.│...└┐...┌┘..└●─┐.┌─●──
...................│.│...└─┘....└┐.┌┘......│.│....
...................└─┘...........└─┘.......│.│....
...........................................└─┘....
..................................................

Left to right. Right to left. Every run different. Always valid.

Quick Start | How It Works | Tile Format | API Reference


The Problem

You need procedurally generated horizontal paths for your game, visualization, or creative project. Random walks hit dead ends. Maze algorithms feel too structured. You want something organic that guarantees a valid path from one side to the other - every single time.

The Solution

Random Path Generator builds horizontal paths using tile-based composition with backtracking. Define ASCII art tiles with entry and exit points. The algorithm chains them together randomly from edge to edge, and when it hits a dead end, it backtracks and tries another route.

Key constraint: Paths flow horizontally only - either left-to-right or right-to-left. Tiles connect at the left and right edges, never top or bottom.

Result: Infinite variety. Zero failures. Full control over visual style.

Why This Approach?

Alternative methods exist for procedural path generation:

Approach Trade-off
Wave Function Collapse Powerful but complex to implement, debug, and tune constraints
L-systems Great for organic growth, harder to guarantee endpoint connectivity
A* / Dijkstra variants Optimal paths but require pre-built graphs, less visual variety
Perlin noise + carving Natural-looking but unpredictable connectivity, requires post-validation

This tile-based approach prioritizes simplicity and reliability. The algorithm is straightforward to understand, test, and extend. Complexity grows linearly with the tile set, not exponentially with edge cases. The cost of more sophisticated methods is often hidden in debugging time, edge case handling, and test coverage requirements.


Quick Start

git clone https://github.com/psenger/random-path-generator.git
cd random-path-generator

# Generate path with defaults (20x10 grid, left-to-right)
go run main.go

# Generate path from RIGHT edge to LEFT edge
go run main.go -rtl

# Custom grid size and start position
go run main.go -width 30 -height 15 -start-y 7

# Use custom tiles directory
go run main.go -tiles ./my-tiles

Example Output (Left to Right)

Configuration:
  Tiles path:   ./tiles
  Tiles loaded: 18
  Min tile size: 3 x 3
  Grid size:    20 x 10
  Start Y:      5
  Direction:    left-to-right

Solution found!

Tiles used:
  1. medium-line.md at (0,4)
  2. small-jag-mirrored.md at (3,4)
  3. small-jag.md at (5,4)
  4. medium-wave.md at (7,1)
  5. medium-line.md at (13,4)
  6. small-s-curve.md at (16,0)

Path waypoints:
  (0,5) ENTRY [medium-line.md]
  (1,5) STRAIGHT_H [medium-line.md]
  (2,5) STRAIGHT_H [medium-line.md]
  (3,5) EXIT [medium-line.md]
  (3,5) ENTRY [small-jag-mirrored.md]
  (4,5) CORNER_SW [small-jag-mirrored.md]
  (4,6) CORNER_NE [small-jag-mirrored.md]
  (5,6) EXIT [small-jag-mirrored.md]
  (5,6) ENTRY [small-jag.md]
  (6,6) CORNER_NW [small-jag.md]
  (6,5) CORNER_SE [small-jag.md]
  (7,5) EXIT [small-jag.md]
  (7,5) ENTRY [medium-wave.md]
  (8,5) STRAIGHT_H [medium-wave.md]
  (9,5) CORNER_NW [medium-wave.md]
  (9,4) STRAIGHT_V [medium-wave.md]
  (9,3) STRAIGHT_V [medium-wave.md]
  (9,2) CORNER_SE [medium-wave.md]
  (10,2) STRAIGHT_H [medium-wave.md]
  (11,2) CORNER_SW [medium-wave.md]
  (11,3) STRAIGHT_V [medium-wave.md]
  (11,4) STRAIGHT_V [medium-wave.md]
  (11,5) CORNER_NE [medium-wave.md]
  (12,5) STRAIGHT_H [medium-wave.md]
  (13,5) EXIT [medium-wave.md]
  (13,5) ENTRY [medium-line.md]
  (14,5) STRAIGHT_H [medium-line.md]
  (15,5) STRAIGHT_H [medium-line.md]
  (16,5) EXIT [medium-line.md]
  (16,5) ENTRY [small-s-curve.md]
  (17,5) STRAIGHT_H [small-s-curve.md]
  (18,5) STRAIGHT_H [small-s-curve.md]
  (19,5) CORNER_NW [small-s-curve.md]
  (19,4) STRAIGHT_V [small-s-curve.md]
  (19,3) CORNER_SW [small-s-curve.md]
  (18,3) STRAIGHT_H [small-s-curve.md]
  (17,3) CORNER_NE [small-s-curve.md]
  (17,2) STRAIGHT_V [small-s-curve.md]
  (17,1) CORNER_SE [small-s-curve.md]
  (18,1) STRAIGHT_H [small-s-curve.md]
  (19,1) STRAIGHT_H [small-s-curve.md]
  (20,1) EXIT [small-s-curve.md]

Direction Modes

This generator supports two horizontal directions only:

Flag Direction Start End Use Case
(default) Left to Right Left edge (x=0) Right edge (x=width) Standard flow, reading direction
-rtl Right to Left Right edge (x=width) Left edge (x=0) Reverse flow, mirrored layouts

Note: Vertical path generation (top-to-bottom, bottom-to-top) is not supported. Tiles must have ENTRY on the left edge and EXIT on the right edge.


CLI Options

Flag Default Description
-width 20 Grid width (minimum: smallest tile width, default tiles: 3)
-height 10 Grid height (minimum: smallest tile height, default tiles: 3)
-start-y 5 Vertical start position (0 to height-1)
-tiles ./tiles Path to tiles directory
-rtl false Generate path from right to left

Note: Minimum dimensions are determined by the smallest tile in the loaded tile set. With the default tiles, the smallest tile is small-line.md at 3x3, so width and height must each be at least 3.

Examples

# Default: 20x10 grid, start at y=5, left-to-right
go run main.go

# Larger grid
go run main.go -width 40 -height 20 -start-y 10

# Right-to-left with custom dimensions
go run main.go -rtl -width 30 -height 12 -start-y 6

# Custom tiles directory
go run main.go -tiles /path/to/custom/tiles

Why This Exists

Feature Description
Horizontal paths Generates paths that flow left-to-right or right-to-left across a grid. Not mazes - connected paths.
Bidirectional Single flag (-rtl) reverses direction. Same tiles work both ways.
Guaranteed success Backtracking algorithm exhaustively tries all combinations. If a solution exists, it finds it.
Tile-based design Define your own visual style with ASCII art tiles. Swap tile sets without touching code.
Path metadata Every waypoint includes type info (CORNER_NW, STRAIGHT_H, etc.) for rendering engines.
Production ready 49 tests, 78% coverage, typed YAML parsing, proper error handling.

How It Works

Left to Right (Default)

+-------------------------------------------------------------+
|  1. Start at left edge (0, startY)                          |
|  2. Pick random tile from available set                     |
|  3. Align tile ENTRY (left side) with current position      |
|  4. Validate tile fits within grid bounds                   |
|  5. Place tile, snapshot grid region for backtracking       |
|  6. Tile EXIT reaches right edge? --> SUCCESS               |
|  7. Move current position to tile EXIT                      |
|  8. Repeat from step 2                                      |
|                                                             |
|  Dead end? (no tiles fit)                                   |
|  --> Pop last tile from stack                               |
|  --> Restore grid from snapshot                             |
|  --> Mark that tile as rejected at this position            |
|  --> Try next available tile                                |
+-------------------------------------------------------------+

Right to Left (-rtl flag)

+-------------------------------------------------------------+
|  1. Start at right edge (width, startY)                     |
|  2. Pick random tile from available set                     |
|  3. Align tile EXIT (right side) with current position      |
|  4. Validate tile fits within grid bounds                   |
|  5. Place tile, snapshot grid region for backtracking       |
|  6. Tile ENTRY reaches left edge? --> SUCCESS               |
|  7. Move current position to tile ENTRY                     |
|  8. Repeat from step 2                                      |
+-------------------------------------------------------------+

The algorithm is randomized tile placement with exhaustive backtracking. Randomness creates variety; backtracking guarantees success.


Tile Format

Tiles are Markdown files with YAML frontmatter defining the path geometry.

---
title: small-wave
path:
  - x: 0
    y: 3
    type: ENTRY
  - x: 1
    y: 3
    type: STRAIGHT_H
  - x: 2
    y: 3
    type: CORNER_NW
  - x: 2
    y: 2
    type: STRAIGHT_V
  - x: 2
    y: 1
    type: CORNER_SE
  - x: 3
    y: 1
    type: STRAIGHT_H
  - x: 4
    y: 1
    type: CORNER_SW
  - x: 4
    y: 2
    type: STRAIGHT_V
  - x: 4
    y: 3
    type: CORNER_NE
  - x: 5
    y: 3
    type: STRAIGHT_H
  - x: 6
    y: 3
    type: EXIT
size:
  - x: 7
    y: 5
---
.......
..+-+..
..|.|..
o-+.+-o
.......

Tile Requirements

Requirement Description
ENTRY at x=0 Entry point must be on the LEFT edge of the tile
EXIT at x=size.x-1 Exit point must be on the RIGHT edge of the tile
Horizontal flow Path flows from left to right within each tile
Size matches art The size field must match the ASCII art dimensions

Path Types

Use these types to tell your rendering engine what each cell represents:

Type Description
ENTRY Path entry point (left edge)
EXIT Path exit point (right edge)
STRAIGHT_H Horizontal line segment
STRAIGHT_V Vertical line segment
CORNER_NW Corner connecting south and east (└)
CORNER_NE Corner connecting south and west (┘)
CORNER_SW Corner connecting north and east (┌)
CORNER_SE Corner connecting north and west (┐)
custom Add your own types for special tiles

Tile Library

18 pre-built tiles included in ./tiles:

Category Tiles Description
Lines small-line, medium-line, large-line Straight horizontal paths
Waves small-wave, medium-wave, large-wave Undulating paths with vertical movement
Mirrored *-mirrored variants Vertically flipped versions
Jags small-jag, small-jag-mirrored Diagonal step patterns
Complex aztec, aztec-mirrored, crazy-ivan Multi-segment decorative paths

Mix and match. Remove tiles you don't want. Add your own.


API

Load and Build

// Load tiles from directory
tiles, err := loadAllTiles("./tiles")
if err != nil {
    log.Printf("Warning: %v", err)
}

// Build path left to right
solution, err := buildSolution(tiles, 20, 10, 5, LeftToRight)
if err != nil {
    log.Fatal(err)
}

// Build path right to left
solution, err = buildSolution(tiles, 20, 10, 5, RightToLeft)
if err != nil {
    log.Fatal(err)
}

// Render the grid
for _, row := range solution.Grid {
    fmt.Println(string(row))
}

Function Signature

func buildSolution(
    tiles map[string]*TileInfo,  // Loaded tile definitions
    width int,                    // Grid width (path spans this)
    height int,                   // Grid height
    startY int,                   // Vertical start position
    direction Direction,          // LeftToRight or RightToLeft
) (*Solution, error)

Access Path Data

// Tiles used in solution
for _, entry := range solution.Path {
    fmt.Printf("%s at (%d,%d)\n",
        entry.Tile.Name,
        entry.OffsetX,
        entry.OffsetY)
}

// Full path with cell types (for rendering engines)
for _, wp := range solution.Waypoints {
    fmt.Printf("(%d,%d) %s [%s]\n",
        wp.GridX, wp.GridY, wp.Type, wp.TileName)
}

Types

// Direction controls horizontal path flow
type Direction int

const (
    LeftToRight Direction = iota  // ENTRY at x=0, EXIT at x=width-1
    RightToLeft                   // EXIT at x=width-1, ENTRY at x=0
)

// Solution contains the generated path
type Solution struct {
    Grid      [][]rune       // Rendered ASCII grid
    Path      []StackEntry   // Tiles used and their positions
    Waypoints []PathSegment  // Full path with cell types
    Width     int
    Height    int
}

// PathSegment represents one cell in the path
type PathSegment struct {
    GridX    int     // X position on solution grid
    GridY    int     // Y position on solution grid
    Type     string  // ENTRY, EXIT, CORNER_NW, STRAIGHT_H, etc.
    TileName string  // Source tile filename
}

// TileInfo contains parsed tile data
type TileInfo struct {
    Name   string
    Entry  Point        // Entry point (must be at x=0)
    Exit   Point        // Exit point (must be at x=width-1)
    Width  int
    Height int
    Grid   [][]rune     // ASCII art
    Path   []PathPoint  // Full path metadata
}

Testing

# Run all tests
go test -v ./...

# Coverage report
go test -cover ./...
# coverage: 78% of statements

# Benchmarks
go test -bench=. ./...

Test Coverage

Component Tests
File parsing 5
Tile extraction 10
Directory loading 4
Grid operations 12
Path translation 6
Left-to-right generation 8
Right-to-left generation 3
Integration 1
Total 49

Project Structure

.
├── main.go            # All source code (~580 lines)
├── main_test.go       # Test suite (~1200 lines)
├── tiles/             # 18 pre-built tile definitions
│   ├── small-line.md
│   ├── small-wave.md
│   ├── aztec.md
│   ├── crazy-ivan.md
│   └── ...
├── go.mod
├── go.sum
├── CONTRIBUTING.md
└── README.md

Requirements

Dependency Version Purpose
Go 1.21+ Uses maps.Clone from stdlib
gopkg.in/yaml.v3 latest YAML frontmatter parsing

Use Cases

Domain Application
Game Development Procedural dungeon corridors, pipe puzzles, circuit boards, side-scrolling level generation
Data Visualization Flow diagrams, network paths, process flows, animated connection traces
Generative Art ASCII art, plotter drawings, textile patterns, decorative borders
Education Teaching backtracking algorithms, constraint satisfaction, procedural generation

Limitations

Limitation Reason
Horizontal only Paths flow left-to-right or right-to-left. No vertical (top-to-bottom) support.
Edge-to-edge Paths must span the full grid width. No partial paths.
Fixed tile orientation Tiles cannot be rotated or flipped at runtime. Create mirrored variants instead.

Contributing

See CONTRIBUTING.md for development setup, guidelines, and how to submit pull requests.


Future Work

Vertical Path Generation

Extend the generator to support vertical paths in addition to the current horizontal-only implementation.

Direction Start End Tile Requirements
Top to Bottom Top edge (y=0) Bottom edge (y=height) ENTRY on top edge, EXIT on bottom edge
Bottom to Top Bottom edge (y=height) Top edge (y=0) ENTRY on bottom edge, EXIT on top edge

Implementation requirements:

  • New tile set with entry/exit points on top and bottom edges
  • Modified algorithm to traverse vertically instead of horizontally
  • New CLI flags (-ttb, -btt) for direction selection

Path Crossing

Enable paths to intersect without requiring crossings to be pre-built into individual tiles.

Current behavior: Paths can only cross where a tile explicitly includes a crossing pattern baked into its ASCII art.

Proposed approaches:

Approach Description Complexity
Crossing tiles Dedicated tiles with pre-built intersections (, , ) Low
Dynamic crossing detection Algorithm detects intersections and inserts appropriate crossing tiles Medium
Multi-layer compositing Generate paths on separate layers, composite with crossing markers High

Use cases:

  • Multiple simultaneous routes across the same grid
  • Paths that weave over/under each other
  • Complex network topologies (circuit boards, pipe systems, dungeon layouts)

License

MIT License - Use it however you want.


Horizontal paths. Guaranteed delivery. Zero dead ends.

Built with backtracking and zero patience for failures.

psenger/random-path-generator | GitHunt