ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

GDScript Procedural Dungeon Generator

GDScript

Goal -- WPM

Ready
Exercise Algorithm Area
1extends Node
2
3# --- Configuration ---
4
5@export var grid_width: int = 100
6@export var grid_height: int = 100
7@export var max_rooms: int = 20
8@export var min_room_size: Vector2i = Vector2i(5, 5)
9@export var max_room_size: Vector2i = Vector2i(15, 15)
10@export var max_corridor_attempts: int = 500 # To prevent infinite loops
11
12# Tile types (example)
13const TILE_WALL = 0
14const TILE_FLOOR = 1
15const TILE_CORRIDOR = 2
16
17var map_grid: Array = []
18var rooms: Array = [] # Stores Rect2 for each room
19
20# --- Room Class (for convenience) ---
21
22class Room:
23var rect: Rect2
24
25func _init(r: Rect2):
26rect = r
27
28func intersects(other_room: Room) -> bool:
29return rect.intersects(other_room.rect)
30
31func get_center() -> Vector2:
32return rect.position + rect.size / 2.0
33
34# --- Map Initialization ---
35
36func initialize_map():
37map_grid.resize(grid_height)
38for y in range(grid_height):
39map_grid[y] = Array()
40map_grid[y].resize(grid_width)
41for x in range(grid_width):
42map_grid[y][x] = TILE_WALL # Initialize entire map as walls
43
44# --- Room Placement ---
45
46func place_rooms():
47rooms.clear()
48for i in range(max_rooms):
49var room_w = randi_range(min_room_size.x, max_room_size.x)
50var room_h = randi_range(min_room_size.y, max_room_size.y)
51var room_pos_x = randi_range(1, grid_width - room_w - 1) # Ensure room is within bounds, not touching edges
52var room_pos_y = randi_range(1, grid_height - room_h - 1)
53var new_rect = Rect2(room_pos_x, room_pos_y, room_w, room_h)
54var new_room = Room.new(new_rect)
55
56var overlaps = false
57for existing_room in rooms:
58# Add a buffer around rooms to prevent corridors from touching walls directly
59if new_room.rect.grow(1).intersects(existing_room.rect.grow(1)):
60overlaps = true
61break
62
63if not overlaps:
64rooms.append(new_room)
65carve_room(new_room)
66
67# Carves a room into the map grid
68func carve_room(room: Room):
69var r = room.rect
70for y in range(int(r.position.y), int(r.position.y + r.size.y)):
71for x in range(int(r.position.x), int(r.position.x + r.size.x)):
72# Ensure we don't go out of bounds (should be handled by placement, but good safety)
73if y >= 0 and y < grid_height and x >= 0 and x < grid_width:
74map_grid[y][x] = TILE_FLOOR
75
76# --- Corridor Generation ---
77
78func connect_rooms():
79if rooms.size() < 2:
80return # Need at least two rooms to connect
81
82# Connect each room to the next one in the list sequentially
83# For more complex dungeons, consider connecting to a random room or using MST
84for i in range(rooms.size() - 1):
85var room1 = rooms[i]
86var room2 = rooms[i+1]
87create_corridor(room1.get_center(), room2.get_center())
88
89# Optional: Connect the last room back to the first for a loop, or to a random room
90# create_corridor(rooms.back().get_center(), rooms[0].get_center())
91
92# Creates a corridor between two points using a simple L-shaped path
93func create_corridor(start: Vector2, end: Vector2):
94var current_pos = Vector2i(start.x, start.y)
95var target_pos = Vector2i(end.x, end.y)
96
97var attempts = 0
98while current_pos != target_pos and attempts < max_corridor_attempts:
99var dx = target_pos.x - current_pos.x
100var dy = target_pos.y - current_pos.y
101
102# Decide whether to move horizontally or vertically first
103# Prioritize moving towards the target's axis with the larger difference
104if abs(dx) > abs(dy):
105if dx > 0:
106current_pos.x += 1
107elif dx < 0:
108current_pos.x -= 1
109else:
110if dy > 0:
111current_pos.y += 1
112elif dy < 0:
113current_pos.y -= 1
114
115# Ensure we don't carve out of bounds
116if current_pos.x >= 0 and current_pos.x < grid_width and current_pos.y >= 0 and current_pos.y < grid_height:
117# Only carve if it's not already a floor (avoids overwriting room floors unnecessarily)
118if map_grid[current_pos.y][current_pos.x] == TILE_WALL:
119map_grid[current_pos.y][current_pos.x] = TILE_CORRIDOR
120else:
121# If we hit a boundary, stop this corridor attempt
122printerr("Corridor carving hit boundary. Stopping.")
123break
124attempts += 1
125
126# Ensure the end point is also carved as floor
127if target_pos.x >= 0 and target_pos.x < grid_width and target_pos.y >= 0 and target_pos.y < grid_height:
128map_grid[target_pos.y][target_pos.x] = TILE_FLOOR
129
130# --- Generation Function ---
131
132func generate_dungeon() -> Array:
133initialize_map()
134place_rooms()
135connect_rooms()
136return map_grid
137
138# --- Example Usage ---
139
140func _ready():
141var dungeon_data = generate_dungeon()
142# Now you can use dungeon_data to render your dungeon
143# For example, print a simple text representation:
144print("Generated Dungeon (W: %d, H: %d):" % [grid_width, grid_height])
145for y in range(grid_height):
146var row_str = ""
147for x in range(grid_width):
148match map_grid[y][x]:
149TILE_WALL:
150row_str += "#"
151TILE_FLOOR:
152row_str += "."
153TILE_CORRIDOR:
154row_str += "-"
155print(row_str)
Algorithm description viewbox

GDScript Procedural Dungeon Generator

Algorithm description:

This GDScript code generates a procedural dungeon map. It starts by filling the map with walls, then places a specified number of non-overlapping rooms of random sizes. After rooms are placed, it connects them with L-shaped corridors. The output is a 2D array representing the map, where each cell is marked as a wall, floor, or corridor. This technique is widely used in roguelike games and other procedural content generation scenarios.

Algorithm explanation:

The dungeon generation process involves several steps. First, `initialize_map` creates a grid filled with walls. `place_rooms` attempts to place `max_rooms` by randomly selecting positions and sizes, ensuring rooms do not overlap using `rect.intersects` and a grow buffer. Overlapping rooms are discarded. `carve_room` then marks the cells within a valid room's rectangle as floor tiles. `connect_rooms` iterates through the placed rooms and calls `create_corridor` to link their centers. `create_corridor` uses a simple L-shaped pathfinding logic: it moves horizontally towards the target's X coordinate, then vertically towards the target's Y coordinate (or vice-versa, prioritizing the axis with the larger distance), carving corridors. This ensures connectivity between all rooms. Time complexity is dominated by room placement and corridor generation. Room placement is roughly O(R^2 * S) where R is max_rooms and S is the average room size for intersection checks. Corridor generation is O(R * C) where C is the average corridor length. Space complexity is O(W * H) for the map grid and O(R) for storing room data.

Pseudocode:

class Room:
  rect
  center
  intersects(otherRoom)

function initializeMap(width, height):
  create grid of size width x height
  fill grid with WALL tiles

function placeRooms(maxRooms, minSize, maxSize):
  rooms = empty list
  for i from 1 to maxRooms:
    randomly determine room width and height
    randomly determine room position
    create new Room object
    overlaps = false
    for each existingRoom in rooms:
      if newRoom.rect intersects existingRoom.rect (with buffer):
        overlaps = true
        break
    if not overlaps:
      add newRoom to rooms
      carveRoom(newRoom)

function carveRoom(room):
  for each cell in room.rect:
    set cell to FLOOR tile

function connectRooms():
  if number of rooms < 2:
    return
  for i from 0 to number of rooms - 2:
    createCorridor(rooms[i].center, rooms[i+1].center)

function createCorridor(startPoint, endPoint):
  currentPos = startPoint
  while currentPos != endPoint:
    determine next move (horizontal or vertical, towards endPoint)
    move currentPos
    if currentPos is within bounds:
      if mapGrid[currentPos] is WALL:
        set mapGrid[currentPos] to CORRIDOR tile
    else:
      break # Hit boundary
  set mapGrid[endPoint] to FLOOR tile

function generateDungeon():
  initializeMap()
  placeRooms()
  connectRooms()
  return mapGrid