Skip to content

Maps & Procedural Generation

A PGTG map is a grid of tiles, each a block of cells (9×9 in the default tile set). PGTG can either generate a random map every episode or load a fixed map from a file.

Tiles

Maps are assembled from a small set of road tiles. Each tile is characterized by its exits, i.e., which of the four sides (north, east, south, west) connect to a neighbor:

Straight Turn T-crossing Crossing Dead end

From left to right: straight, turn, T-crossing, four-way crossing, and dead end. Every rotation of these five shapes is a distinct tile, identified by its (north, east, south, west) exit tuple of 0/1 flags. For instance, (1, 0, 1, 0) is a vertical straight and (1, 1, 1, 1) is a four-way crossing.

Procedural generation

When map_path is None (the default), a new random map is generated each episode from the RandomMapContext. The pipeline is:

  1. Build a connectivity graph. Start from a grid where every adjacent tile pair is an edge, attach virtual start and goal nodes on the requested borders, then randomly remove edges down to connections_percentage of the total.
  2. Convert the graph to tiles. Each tile's exits are read off from which neighbors remain connected.
  3. Place obstacles. If obstacle_probability > 0, each non-empty tile independently receives an obstacle type with that probability; the type is drawn from obs_feature_weights and stamped onto the tile through an obstacle mask (traffic-light masks are chosen to match the tile's exits).

Key parameters (see Configuration Reference):

Parameter Effect
width, height Map size (in tiles).
connections_percentage Density of connections. 0.0 gives a minimal start→goal path; 1.0 a fully connected grid.
start_position, goal_position Where start/goal sit on the border, or "random".
min_dist_start_goal Minimum path length between start and goal (only with random positions).
obstacle_probability Per-tile chance of an obstacle.
obs_feature_weights Relative weights for which obstacle type is chosen.
from pgtg import PGTGEnv
from pgtg.contexts import RandomMapContext

env = PGTGEnv(
    random_map_context=RandomMapContext(
        width=3,
        height=3,
        connections_percentage=0.6,
        obstacle_probability=0.3,
        obs_feature_weights={"ice": 2.0, "sand": 1.0},  # ice twice as likely as sand
    ),
)

or equivalently:

import gymnasium as gym
import pgtg

env = gym.make(
    "pgtg-v5",
    random_map_context={
        "width": 3,
        "height": 3,
        "connections_percentage": 0.6,
        "obstacle_probability": 0.3,
        "obs_feature_weights": {"ice": 2.0, "sand": 1.0},
    },
)
A procedurally generated map with obstacles and traffic.

Default benchmark maps

For reproducible evaluation, PGTG ships four fixed, preconstructed maps. Unlike random maps they are identical across runs, which makes them well suited for controlled comparison. Each is a square grid of tiles, generated with a high connection rate (connections_percentage of 0.8), so they are densely connected and offer many alternative routes:

Name Size (tiles) Size (cells) Discoverable cells
benchmark_2x2 2 × 2 18 × 18 163
benchmark_3x3 3 × 3 27 × 27 353
benchmark_4x4 4 × 4 36 × 36 641
benchmark_5x5 5 × 5 45 × 45 1007

Discoverable cells counts the number of (x,y) pairs the agent can actually occupy: everything that is not a wall and is reachable from the start. In these four maps every non-wall cell is reachable, so the count equals the road area.

Load one by passing its name as map_path. Note that this disables procedural generation:

import gymnasium as gym

env = gym.make("pgtg-v5", map_path="benchmark_3x3")

benchmark_2x2 benchmark_3x3 benchmark_4x4 benchmark_5x5

Loading and authoring map files

map_path also accepts a path to your own map file (the .json extension is optional):

env = PGTGEnv(map_path="path/to/my_map.json")

Generating and saving a map

The pgtg.map.authoring module can generate a single random map and save it to disk, so you can freeze a specific layout for reuse:

from pgtg.map.authoring import create_custom_map

create_custom_map(
    "my_map.json",
    width=3,
    height=3,
    seed=999,
    connections_percentage=0.8,
    start_position=(0, 2, "west"),
    goal_position=(2, 0, "east"),
    obstacle_probability=0.0,
)

Extra keyword arguments are forwarded to RandomMapContext.

Note

We plan to publish a tile/map editor tool soon, which will allow interactive editing of tilests and maps, including obstacle placement and traffic lange placement.

Map file format

A saved map is a JSON object:

{
  "width": 3,
  "height": 3,
  "map": [
    [ {"exits": [1, 0, 0, 0]}, {"exits": [1, 1, 0, 0]}, ... ],
    ...
  ],
  "start": [0, 0, "west"],
  "goal": [2, 2, "east"]
}
  • width / height — map size in tiles.
  • map — a height × width grid of tile objects. Each tile has an exits list [north, east, south, west]. A tile may also carry an authored obstacle via "obstacle_type" ("ice", "sand", "broken_road", "traffic_light", or a registered custom obstacle name) and an "obstacle_mask" (see Custom Tiles & Maps).
  • start / goal[x, y, direction], where direction is "north", "east", "south", or "west". Start and goal must lie on a map border.

For customizing the contents of tiles (walls, lanes, obstacle masks), see Custom Tiles & Maps.