google-site-verification: google61fe8ba583a51912.html
Dungeon Generator Algorithms in Java: Complete Guide

Dungeon generation is a fundamental part of roguelike games and procedural content generation. Here are several algorithms for generating dungeons, caves, and other game environments in Java.


Dungeon Generation Algorithms Overview

Common Algorithms

  1. Random Walk - Simple but unpredictable
  2. Binary Space Partitioning (BSP) - Organized room placement
  3. Cellular Automata - Natural-looking cave systems
  4. Drunkard's Walk - Organic corridor generation
  5. Wave Function Collapse - Pattern-based generation

Basic Random Dungeon Generator

Example 1: Simple Random Room Placement

import java.util.*; import java.awt.Point; public class SimpleDungeonGenerator { private final int width; private final int height; private final char[][] map; private final Random random; // Tile types public static final char WALL = '#'; public static final char FLOOR = '.'; public static final char DOOR = '+'; public static final char CORRIDOR = ','; public static final char WATER = '~'; public static final char EMPTY = ' '; public SimpleDungeonGenerator(int width, int height, long seed) { this.width = width; this.height = height; this.map = new char[height][width]; this.random = new Random(seed); initializeMap(); } private void initializeMap() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { map[y][x] = WALL; } } } public void generateRandomRooms(int minRooms, int maxRooms, int minRoomSize, int maxRoomSize) { int roomCount = random.nextInt(maxRooms - minRooms + 1) + minRooms; List<Room> rooms = new ArrayList<>(); for (int i = 0; i < roomCount; i++) { int attempts = 0; boolean placed = false; while (attempts < 100 && !placed) { int roomWidth = random.nextInt(maxRoomSize - minRoomSize + 1) + minRoomSize; int roomHeight = random.nextInt(maxRoomSize - minRoomSize + 1) + minRoomSize; int x = random.nextInt(width - roomWidth - 2) + 1; int y = random.nextInt(height - roomHeight - 2) + 1; Room newRoom = new Room(x, y, roomWidth, roomHeight); if (!intersectsAnyRoom(newRoom, rooms)) { carveRoom(newRoom); rooms.add(newRoom); placed = true; } attempts++; } } // Connect rooms with corridors connectRooms(rooms); // Add some water features addWaterFeatures(5); } private boolean intersectsAnyRoom(Room newRoom, List<Room> rooms) { for (Room room : rooms) { if (newRoom.intersects(room, 1)) { // 1 tile padding return true; } } return false; } private void carveRoom(Room room) { for (int y = room.y + 1; y < room.y + room.height - 1; y++) { for (int x = room.x + 1; x < room.x + room.width - 1; x++) { map[y][x] = FLOOR; } } } private void connectRooms(List<Room> rooms) { if (rooms.size() < 2) return; // Connect each room to the next one for (int i = 0; i < rooms.size() - 1; i++) { Room room1 = rooms.get(i); Room room2 = rooms.get(i + 1); Point center1 = room1.getCenter(); Point center2 = room2.getCenter(); // 50% chance to do L-shaped corridor, 50% straight then turn if (random.nextBoolean()) { createLShapedCorridor(center1.x, center1.y, center2.x, center2.y); } else { createCorridor(center1.x, center1.y, center2.x, center2.y); } } // Add some random connections for loops for (int i = 0; i < rooms.size() / 2; i++) { int idx1 = random.nextInt(rooms.size()); int idx2 = random.nextInt(rooms.size()); if (idx1 != idx2) { Room room1 = rooms.get(idx1); Room room2 = rooms.get(idx2); Point center1 = room1.getCenter(); Point center2 = room2.getCenter(); createCorridor(center1.x, center1.y, center2.x, center2.y); } } } private void createCorridor(int x1, int y1, int x2, int y2) { // Horizontal then vertical createHorizontalCorridor(x1, x2, y1); createVerticalCorridor(y1, y2, x2); } private void createLShapedCorridor(int x1, int y1, int x2, int y2) { // Vertical then horizontal createVerticalCorridor(y1, y2, x1); createHorizontalCorridor(x1, x2, y2); } private void createHorizontalCorridor(int x1, int x2, int y) { int start = Math.min(x1, x2); int end = Math.max(x1, x2); for (int x = start; x <= end; x++) { if (map[y][x] == WALL) { map[y][x] = CORRIDOR; } // Add walls above and below if (y > 0 && map[y-1][x] == EMPTY) map[y-1][x] = WALL; if (y < height-1 && map[y+1][x] == EMPTY) map[y+1][x] = WALL; } } private void createVerticalCorridor(int y1, int y2, int x) { int start = Math.min(y1, y2); int end = Math.max(y1, y2); for (int y = start; y <= end; y++) { if (map[y][x] == WALL) { map[y][x] = CORRIDOR; } // Add walls left and right if (x > 0 && map[y][x-1] == EMPTY) map[y][x-1] = WALL; if (x < width-1 && map[y][x+1] == EMPTY) map[y][x+1] = WALL; } } private void addWaterFeatures(int count) { for (int i = 0; i < count; i++) { int x = random.nextInt(width - 4) + 2; int y = random.nextInt(height - 4) + 2; if (map[y][x] == FLOOR || map[y][x] == CORRIDOR) { floodFillWater(x, y, 5 + random.nextInt(10)); } } } private void floodFillWater(int startX, int startY, int maxTiles) { Queue<Point> queue = new LinkedList<>(); Set<Point> visited = new HashSet<>(); queue.add(new Point(startX, startY)); int filled = 0; while (!queue.isEmpty() && filled < maxTiles) { Point p = queue.poll(); if (visited.contains(p)) continue; int x = p.x, y = p.y; if (x < 1 || x >= width-1 || y < 1 || y >= height-1) continue; if (map[y][x] == FLOOR || map[y][x] == CORRIDOR) { map[y][x] = WATER; filled++; visited.add(p); // Add neighbors queue.add(new Point(x+1, y)); queue.add(new Point(x-1, y)); queue.add(new Point(x, y+1)); queue.add(new Point(x, y-1)); } } } public void printMap() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { System.out.print(map[y][x]); } System.out.println(); } } public char[][] getMap() { return map; } // Room class public static class Room { public final int x, y, width, height; public Room(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } public boolean intersects(Room other, int padding) { return x - padding < other.x + other.width && x + width + padding > other.x && y - padding < other.y + other.height && y + height + padding > other.y; } public Point getCenter() { return new Point(x + width / 2, y + height / 2); } } public static void main(String[] args) { SimpleDungeonGenerator generator = new SimpleDungeonGenerator(80, 40, System.currentTimeMillis()); generator.generateRandomRooms(8, 15, 4, 8); generator.printMap(); } }

Binary Space Partitioning (BSP) Dungeon

Example 2: BSP-Based Dungeon Generator

import java.util.*; import java.awt.Rectangle; public class BSPDungeonGenerator { private final int width; private final int height; private final char[][] map; private final Random random; private List<Room> rooms; private List<Corridor> corridors; public BSPDungeonGenerator(int width, int height, long seed) { this.width = width; this.height = height; this.map = new char[height][width]; this.random = new Random(seed); this.rooms = new ArrayList<>(); this.corridors = new ArrayList<>(); initializeMap(); } private void initializeMap() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { map[y][x] = SimpleDungeonGenerator.WALL; } } } public void generateBSPDungeon(int minRoomSize, int maxRoomSize, int splitAttempts) { // Start with the entire dungeon as one partition List<Partition> partitions = new ArrayList<>(); partitions.add(new Partition(1, 1, width - 2, height - 2)); // Recursively split partitions for (int i = 0; i < splitAttempts; i++) { List<Partition> newPartitions = new ArrayList<>(); for (Partition partition : partitions) { if (shouldSplit(partition, minRoomSize)) { Partition[] children = splitPartition(partition); if (children != null) { newPartitions.add(children[0]); newPartitions.add(children[1]); } else { newPartitions.add(partition); } } else { newPartitions.add(partition); } } partitions = newPartitions; } // Create rooms in leaf partitions for (Partition partition : partitions) { if (partition.isLeaf()) { Room room = createRoomInPartition(partition, minRoomSize, maxRoomSize); if (room != null) { rooms.add(room); carveRoom(room); } } } // Build BSP tree and connect rooms BSPTree tree = buildBSPTree(partitions); connectBSPRooms(tree); // Add some variety addDoors(); addRandomFeatures(); } private boolean shouldSplit(Partition partition, int minRoomSize) { // Only split if both resulting partitions would be large enough boolean canSplitHorizontal = partition.height > minRoomSize * 2; boolean canSplitVertical = partition.width > minRoomSize * 2; if (!canSplitHorizontal && !canSplitVertical) return false; if (canSplitHorizontal && canSplitVertical) { return random.nextBoolean(); // Random choice } return canSplitHorizontal; } private Partition[] splitPartition(Partition partition) { boolean splitHorizontal = partition.height > partition.width; if (splitHorizontal) { // Split horizontally int splitPoint = random.nextInt(partition.height - 4) + 2; Partition top = new Partition(partition.x, partition.y, partition.width, splitPoint); Partition bottom = new Partition(partition.x, partition.y + splitPoint, partition.width, partition.height - splitPoint); return new Partition[]{top, bottom}; } else { // Split vertically int splitPoint = random.nextInt(partition.width - 4) + 2; Partition left = new Partition(partition.x, partition.y, splitPoint, partition.height); Partition right = new Partition(partition.x + splitPoint, partition.y, partition.width - splitPoint, partition.height); return new Partition[]{left, right}; } } private Room createRoomInPartition(Partition partition, int minSize, int maxSize) { int roomWidth = Math.min(random.nextInt(maxSize - minSize + 1) + minSize, partition.width - 2); int roomHeight = Math.min(random.nextInt(maxSize - minSize + 1) + minSize, partition.height - 2); if (roomWidth < minSize || roomHeight < minSize) return null; int roomX = partition.x + random.nextInt(partition.width - roomWidth); int roomY = partition.y + random.nextInt(partition.height - roomHeight); return new Room(roomX, roomY, roomWidth, roomHeight); } private BSPTree buildBSPTree(List<Partition> partitions) { // This is a simplified version - in practice you'd build the tree during splitting List<BSPTree> trees = new ArrayList<>(); for (Partition partition : partitions) { trees.add(new BSPTree(partition)); } // Combine trees (simplified) while (trees.size() > 1) { BSPTree tree1 = trees.remove(0); BSPTree tree2 = trees.remove(0); BSPTree parent = new BSPTree(new Partition( Math.min(tree1.partition.x, tree2.partition.x), Math.min(tree1.partition.y, tree2.partition.y), Math.max(tree1.partition.x + tree1.partition.width, tree2.partition.x + tree2.partition.width) - Math.min(tree1.partition.x, tree2.partition.x), Math.max(tree1.partition.y + tree1.partition.height, tree2.partition.y + tree2.partition.height) - Math.min(tree1.partition.y, tree2.partition.y) )); parent.left = tree1; parent.right = tree2; trees.add(parent); } return trees.get(0); } private void connectBSPRooms(BSPTree tree) { if (tree == null || tree.isLeaf()) return; connectBSPRooms(tree.left); connectBSPRooms(tree.right); // Connect rooms from left and right subtrees Room leftRoom = findRandomRoomInTree(tree.left); Room rightRoom = findRandomRoomInTree(tree.right); if (leftRoom != null && rightRoom != null) { Point center1 = leftRoom.getCenter(); Point center2 = rightRoom.getCenter(); Corridor corridor = new Corridor(center1, center2); corridors.add(corridor); carveCorridor(corridor); } } private Room findRandomRoomInTree(BSPTree tree) { if (tree == null) return null; if (tree.isLeaf()) { // Find a room in this partition for (Room room : rooms) { if (tree.partition.contains(room.x, room.y, room.width, room.height)) { return room; } } return null; } // Randomly choose left or right subtree return random.nextBoolean() ? findRandomRoomInTree(tree.left) : findRandomRoomInTree(tree.right); } private void carveRoom(Room room) { for (int y = room.y; y < room.y + room.height; y++) { for (int x = room.x; x < room.x + room.width; x++) { if (x >= 0 && x < width && y >= 0 && y < height) { map[y][x] = SimpleDungeonGenerator.FLOOR; } } } } private void carveCorridor(Corridor corridor) { Point start = corridor.start; Point end = corridor.end; // L-shaped corridor if (random.nextBoolean()) { // Horizontal then vertical for (int x = Math.min(start.x, end.x); x <= Math.max(start.x, end.x); x++) { setFloorSafe(x, start.y); } for (int y = Math.min(start.y, end.y); y <= Math.max(start.y, end.y); y++) { setFloorSafe(end.x, y); } } else { // Vertical then horizontal for (int y = Math.min(start.y, end.y); y <= Math.max(start.y, end.y); y++) { setFloorSafe(start.x, y); } for (int x = Math.min(start.x, end.x); x <= Math.max(start.x, end.x); x++) { setFloorSafe(x, end.y); } } } private void setFloorSafe(int x, int y) { if (x >= 0 && x < width && y >= 0 && y < height) { map[y][x] = SimpleDungeonGenerator.CORRIDOR; } } private void addDoors() { for (Corridor corridor : corridors) { // Find where corridor meets rooms and place doors List<Point> potentialDoorLocations = findCorridorEnds(corridor); for (Point doorLoc : potentialDoorLocations) { if (isGoodDoorLocation(doorLoc.x, doorLoc.y)) { map[doorLoc.y][doorLoc.x] = SimpleDungeonGenerator.DOOR; } } } } private List<Point> findCorridorEnds(Corridor corridor) { List<Point> ends = new ArrayList<>(); // Simplified - check points near room boundaries ends.add(corridor.start); ends.add(corridor.end); return ends; } private boolean isGoodDoorLocation(int x, int y) { // Check if this location connects a corridor to a room if (x <= 0 || x >= width-1 || y <= 0 || y >= height-1) return false; int floorCount = 0; int wallCount = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (dx == 0 && dy == 0) continue; char tile = map[y+dy][x+dx]; if (tile == SimpleDungeonGenerator.FLOOR || tile == SimpleDungeonGenerator.CORRIDOR) { floorCount++; } else if (tile == SimpleDungeonGenerator.WALL) { wallCount++; } } } return floorCount >= 2 && wallCount >= 2; } private void addRandomFeatures() { // Add some water or other features int featureCount = random.nextInt(3) + 1; for (int i = 0; i < featureCount; i++) { int x = random.nextInt(width - 4) + 2; int y = random.nextInt(height - 4) + 2; if (map[y][x] == SimpleDungeonGenerator.FLOOR) { map[y][x] = SimpleDungeonGenerator.WATER; } } } public void printMap() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { System.out.print(map[y][x]); } System.out.println(); } } // Data classes public static class Partition { public final int x, y, width, height; public Partition(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } public boolean isLeaf() { return true; // Simplified } public boolean contains(int rx, int ry, int rw, int rh) { return x <= rx && y <= ry && x + width >= rx + rw && y + height >= ry + rh; } } public static class BSPTree { public Partition partition; public BSPTree left, right; public BSPTree(Partition partition) { this.partition = partition; } public boolean isLeaf() { return left == null && right == null; } } public static class Corridor { public final Point start, end; public Corridor(Point start, Point end) { this.start = start; this.end = end; } } public static class Room extends SimpleDungeonGenerator.Room { public Room(int x, int y, int width, int height) { super(x, y, width, height); } } public static void main(String[] args) { BSPDungeonGenerator generator = new BSPDungeonGenerator(60, 30, System.currentTimeMillis()); generator.generateBSPDungeon(4, 8, 5); generator.printMap(); } }

Cellular Automata Cave Generator

Example 3: Cave System Generator

import java.util.*; public class CaveGenerator { private final int width; private final int height; private final char[][] map; private final Random random; private static final double INITIAL_WALL_CHANCE = 0.45; private static final int BIRTH_LIMIT = 4; private static final int DEATH_LIMIT = 3; private static final int SMOOTHING_STEPS = 5; public CaveGenerator(int width, int height, long seed) { this.width = width; this.height = height; this.map = new char[height][width]; this.random = new Random(seed); } public void generateCaveSystem() { initializeRandomMap(); // Apply cellular automata rules for (int i = 0; i < SMOOTHING_STEPS; i++) { smoothMap(); } // Remove small isolated caves removeSmallCaves(15); // Ensure connectivity ensureConnectivity(); // Add some variety addCaveFeatures(); } private void initializeRandomMap() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Borders are always walls if (x == 0 || y == 0 || x == width - 1 || y == height - 1) { map[y][x] = SimpleDungeonGenerator.WALL; } else { map[y][x] = random.nextDouble() < INITIAL_WALL_CHANCE ? SimpleDungeonGenerator.WALL : SimpleDungeonGenerator.FLOOR; } } } } private void smoothMap() { char[][] newMap = new char[height][width]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int wallNeighbors = countWallNeighbors(x, y); if (map[y][x] == SimpleDungeonGenerator.WALL) { // Wall cell dies if too few wall neighbors newMap[y][x] = (wallNeighbors < DEATH_LIMIT) ? SimpleDungeonGenerator.FLOOR : SimpleDungeonGenerator.WALL; } else { // Floor cell becomes wall if enough wall neighbors newMap[y][x] = (wallNeighbors > BIRTH_LIMIT) ? SimpleDungeonGenerator.WALL : SimpleDungeonGenerator.FLOOR; } } } // Copy new map back for (int y = 0; y < height; y++) { System.arraycopy(newMap[y], 0, map[y], 0, width); } } private int countWallNeighbors(int x, int y) { int wallCount = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (dx == 0 && dy == 0) continue; int nx = x + dx; int ny = y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { if (map[ny][nx] == SimpleDungeonGenerator.WALL) { wallCount++; } } else { // Count borders as walls wallCount++; } } } return wallCount; } private void removeSmallCaves(int minCaveSize) { List<Set<Point>> caves = findCaves(); for (Set<Point> cave : caves) { if (cave.size() < minCaveSize) { // Fill small cave with walls for (Point p : cave) { map[p.y][p.x] = SimpleDungeonGenerator.WALL; } } } } private List<Set<Point>> findCaves() { List<Set<Point>> caves = new ArrayList<>(); boolean[][] visited = new boolean[height][width]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (!visited[y][x] && map[y][x] == SimpleDungeonGenerator.FLOOR) { Set<Point> cave = floodFillCave(x, y, visited); caves.add(cave); } } } return caves; } private Set<Point> floodFillCave(int startX, int startY, boolean[][] visited) { Set<Point> cave = new HashSet<>(); Queue<Point> queue = new LinkedList<>(); queue.add(new Point(startX, startY)); while (!queue.isEmpty()) { Point p = queue.poll(); int x = p.x, y = p.y; if (x < 0 || x >= width || y < 0 || y >= height || visited[y][x]) continue; if (map[y][x] != SimpleDungeonGenerator.FLOOR) continue; visited[y][x] = true; cave.add(p); queue.add(new Point(x+1, y)); queue.add(new Point(x-1, y)); queue.add(new Point(x, y+1)); queue.add(new Point(x, y-1)); } return cave; } private void ensureConnectivity() { List<Set<Point>> caves = findCaves(); if (caves.size() <= 1) return; // Already connected or no caves // Find the largest cave Set<Point> mainCave = Collections.max(caves, Comparator.comparing(Set::size)); caves.remove(mainCave); // Connect other caves to main cave for (Set<Point> cave : caves) { connectCaves(mainCave, cave); } } private void connectCaves(Set<Point> cave1, Set<Point> cave2) { // Find closest points between caves Point closest1 = null; Point closest2 = null; double minDistance = Double.MAX_VALUE; for (Point p1 : cave1) { for (Point p2 : cave2) { double distance = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); if (distance < minDistance) { minDistance = distance; closest1 = p1; closest2 = p2; } } } if (closest1 != null && closest2 != null) { // Create corridor between closest points createCorridor(closest1.x, closest1.y, closest2.x, closest2.y); } } private void createCorridor(int x1, int y1, int x2, int y2) { int currentX = x1; int currentY = y1; while (currentX != x2 || currentY != y2) { if (random.nextBoolean() && currentX != x2) { currentX += (x2 > currentX) ? 1 : -1; } else if (currentY != y2) { currentY += (y2 > currentY) ? 1 : -1; } if (currentX >= 0 && currentX < width && currentY >= 0 && currentY < height) { map[currentY][currentX] = SimpleDungeonGenerator.CORRIDOR; // Add some width to corridor if (random.nextDouble() < 0.3) { for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { int nx = currentX + dx; int ny = currentY + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height && map[ny][nx] == SimpleDungeonGenerator.WALL) { map[ny][nx] = SimpleDungeonGenerator.CORRIDOR; } } } } } } } private void addCaveFeatures() { addWaterPools(3); addStalactites(10); addTreasureRooms(2); } private void addWaterPools(int count) { for (int i = 0; i < count; i++) { int startX = random.nextInt(width - 4) + 2; int startY = random.nextInt(height - 4) + 2; if (map[startY][startX] == SimpleDungeonGenerator.FLOOR) { floodFillWater(startX, startY, 8 + random.nextInt(15)); } } } private void floodFillWater(int startX, int startY, int maxTiles) { Queue<Point> queue = new LinkedList<>(); Set<Point> visited = new HashSet<>(); queue.add(new Point(startX, startY)); int filled = 0; while (!queue.isEmpty() && filled < maxTiles) { Point p = queue.poll(); if (visited.contains(p)) continue; int x = p.x, y = p.y; if (x < 1 || x >= width-1 || y < 1 || y >= height-1) continue; if (map[y][x] == SimpleDungeonGenerator.FLOOR || map[y][x] == SimpleDungeonGenerator.CORRIDOR) { map[y][x] = SimpleDungeonGenerator.WATER; filled++; visited.add(p); // Add neighbors with some randomness if (random.nextDouble() < 0.7) queue.add(new Point(x+1, y)); if (random.nextDouble() < 0.7) queue.add(new Point(x-1, y)); if (random.nextDouble() < 0.7) queue.add(new Point(x, y+1)); if (random.nextDouble() < 0.7) queue.add(new Point(x, y-1)); } } } private void addStalactites(int count) { for (int i = 0; i < count; i++) { int x = random.nextInt(width - 2) + 1; int y = random.nextInt(height - 2) + 1; // Place stalactite if surrounded by walls (on ceiling) if (map[y][x] == SimpleDungeonGenerator.FLOOR && map[y-1][x] == SimpleDungeonGenerator.WALL) { map[y][x] = '^'; // Stalactite } // Place stalagmite if surrounded by walls (on floor) if (map[y][x] == SimpleDungeonGenerator.FLOOR && map[y+1][x] == SimpleDungeonGenerator.WALL) { map[y][x] = 'v'; // Stalagmite } } } private void addTreasureRooms(int count) { for (int i = 0; i < count; i++) { // Find a dead-end corridor or isolated area Point location = findTreasureLocation(); if (location != null) { map[location.y][location.x] = 'T'; // Treasure // Add some protective walls for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { int nx = location.x + dx; int ny = location.y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height && map[ny][nx] == SimpleDungeonGenerator.FLOOR) { map[ny][nx] = SimpleDungeonGenerator.WALL; } } } } } } private Point findTreasureLocation() { // Look for dead ends for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (map[y][x] == SimpleDungeonGenerator.FLOOR || map[y][x] == SimpleDungeonGenerator.CORRIDOR) { int openNeighbors = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (dx == 0 && dy == 0) continue; if (Math.abs(dx) + Math.abs(dy) != 1) continue; // Only cardinal directions int nx = x + dx; int ny = y + dy; if (map[ny][nx] == SimpleDungeonGenerator.FLOOR || map[ny][nx] == SimpleDungeonGenerator.CORRIDOR) { openNeighbors++; } } } if (openNeighbors == 1) { // Dead end return new Point(x, y); } } } } return null; } public void printMap() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { System.out.print(map[y][x]); } System.out.println(); } } public char[][] getMap() { return map; } public static void main(String[] args) { CaveGenerator generator = new CaveGenerator(50, 25, System.currentTimeMillis()); generator.generateCaveSystem(); generator.printMap(); } }

Advanced Dungeon Features

Example 4: Multi-Level Dungeon with Special Features

import java.util.*; public class AdvancedDungeonGenerator { private final int width; private final int height; private final int levels; private final List<char[][]> maps; private final Random random; private final DungeonConfig config; public AdvancedDungeonGenerator(int width, int height, int levels, long seed, DungeonConfig config) { this.width = width; this.height = height; this.levels = levels; this.maps = new ArrayList<>(); this.random = new Random(seed); this.config = config; } public void generateMultiLevelDungeon() { for (int level = 0; level < levels; level++) { char[][] levelMap = new char[height][width]; initializeLevel(levelMap); // Choose generation method based on level if (level == 0) { generateEntranceLevel(levelMap); } else if (level == levels - 1) { generateBossLevel(levelMap); } else if (random.nextDouble() < 0.7) { generateRoomLevel(levelMap); } else { generateCaveLevel(levelMap); } // Add level-specific features addLevelFeatures(levelMap, level); maps.add(levelMap); } // Connect levels with stairs connectLevels(); // Add monsters and treasures populateDungeon(); } private void generateEntranceLevel(char[][] map) { SimpleDungeonGenerator generator = new SimpleDungeonGenerator(width, height, random.nextLong()); generator.generateRandomRooms(3, 6, 5, 8); char[][] generatedMap = generator.getMap(); // Copy to our map for (int y = 0; y < height; y++) { System.arraycopy(generatedMap[y], 0, map[y], 0, width); } // Add entrance addEntrance(map); } private void generateBossLevel(char[][] map) { BSPDungeonGenerator generator = new BSPDungeonGenerator(width, height, random.nextLong()); generator.generateBSPDungeon(6, 12, 4); char[][] generatedMap = generator.getMap(); for (int y = 0; y < height; y++) { System.arraycopy(generatedMap[y], 0, map[y], 0, width); } // Add boss room addBossRoom(map); } private void generateRoomLevel(char[][] map) { SimpleDungeonGenerator generator = new SimpleDungeonGenerator(width, height, random.nextLong()); generator.generateRandomRooms(5, 10, 4, 7); char[][] generatedMap = generator.getMap(); for (int y = 0; y < height; y++) { System.arraycopy(generatedMap[y], 0, map[y], 0, width); } } private void generateCaveLevel(char[][] map) { CaveGenerator generator = new CaveGenerator(width, height, random.nextLong()); generator.generateCaveSystem(); char[][] generatedMap = generator.getMap(); for (int y = 0; y < height; y++) { System.arraycopy(generatedMap[y], 0, map[y], 0, width); } } private void addEntrance(char[][] map) { // Place entrance at top of map for (int x = width / 2 - 2; x <= width / 2 + 2; x++) { map[0][x] = 'E'; // Entrance map[1][x] = SimpleDungeonGenerator.FLOOR; } } private void addBossRoom(char[][] map) { // Create large central room for boss int roomWidth = 10; int roomHeight = 8; int roomX = (width - roomWidth) / 2; int roomY = (height - roomHeight) / 2; // Carve boss room for (int y = roomY; y < roomY + roomHeight; y++) { for (int x = roomX; x < roomX + roomWidth; x++) { if (y >= 0 && y < height && x >= 0 && x < width) { if (y == roomY || y == roomY + roomHeight - 1 || x == roomX || x == roomX + roomWidth - 1) { map[y][x] = SimpleDungeonGenerator.WALL; } else { map[y][x] = SimpleDungeonGenerator.FLOOR; } } } } // Place boss in center map[roomY + roomHeight / 2][roomX + roomWidth / 2] = 'B'; // Add treasure near boss map[roomY + roomHeight / 2][roomX + roomWidth / 2 - 2] = 'T'; map[roomY + roomHeight / 2][roomX + roomWidth / 2 + 2] = 'T'; } private void addLevelFeatures(char[][] map, int level) { // Add stairs addStairs(map, level); // Add water/lava based on depth if (level > levels / 2) { addLavaPools(map, 2 + level / 2); } else { addWaterPools(map, 2); } // Add traps in deeper levels if (level > 1) { addTraps(map, level); } // Add secret rooms occasionally if (random.nextDouble() < 0.3) { addSecretRoom(map); } } private void addStairs(char[][] map, int level) { // Find suitable location for stairs Point stairLocation = findStairLocation(map); if (stairLocation != null) { if (level < levels - 1) { map[stairLocation.y][stairLocation.x] = '>'; // Down stairs } if (level > 0) { // Also add up stairs from previous level char[][] prevMap = maps.get(level - 1); Point upStairLocation = findStairLocation(prevMap); if (upStairLocation != null) { prevMap[upStairLocation.y][upStairLocation.x] = '<'; // Up stairs } } } } private Point findStairLocation(char[][] map) { // Look for a floor tile with only one neighbor (dead end) for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (map[y][x] == SimpleDungeonGenerator.FLOOR || map[y][x] == SimpleDungeonGenerator.CORRIDOR) { int neighbors = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (Math.abs(dx) + Math.abs(dy) != 1) continue; if (map[y+dy][x+dx] == SimpleDungeonGenerator.FLOOR || map[y+dy][x+dx] == SimpleDungeonGenerator.CORRIDOR) { neighbors++; } } } if (neighbors == 1) { return new Point(x, y); } } } } // If no dead end found, use any floor tile for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (map[y][x] == SimpleDungeonGenerator.FLOOR) { return new Point(x, y); } } } return null; } private void addLavaPools(char[][] map, int count) { for (int i = 0; i < count; i++) { int x = random.nextInt(width - 4) + 2; int y = random.nextInt(height - 4) + 2; if (map[y][x] == SimpleDungeonGenerator.FLOOR) { floodFillLava(map, x, y, 5 + random.nextInt(8)); } } } private void floodFillLava(char[][] map, int startX, int startY, int maxTiles) { Queue<Point> queue = new LinkedList<>(); Set<Point> visited = new HashSet<>(); queue.add(new Point(startX, startY)); int filled = 0; while (!queue.isEmpty() && filled < maxTiles) { Point p = queue.poll(); if (visited.contains(p)) continue; int x = p.x, y = p.y; if (x < 1 || x >= width-1 || y < 1 || y >= height-1) continue; if (map[y][x] == SimpleDungeonGenerator.FLOOR || map[y][x] == SimpleDungeonGenerator.CORRIDOR) { map[y][x] = 'L'; // Lava filled++; visited.add(p); if (random.nextDouble() < 0.6) queue.add(new Point(x+1, y)); if (random.nextDouble() < 0.6) queue.add(new Point(x-1, y)); if (random.nextDouble() < 0.6) queue.add(new Point(x, y+1)); if (random.nextDouble() < 0.6) queue.add(new Point(x, y-1)); } } } private void addTraps(char[][] map, int level) { int trapCount = level / 2; for (int i = 0; i < trapCount; i++) { Point trapLocation = findTrapLocation(map); if (trapLocation != null) { map[trapLocation.y][trapLocation.x] = 'X'; // Trap } } } private Point findTrapLocation(char[][] map) { // Place traps in corridors List<Point> corridorTiles = new ArrayList<>(); for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (map[y][x] == SimpleDungeonGenerator.CORRIDOR) { corridorTiles.add(new Point(x, y)); } } } if (!corridorTiles.isEmpty()) { return corridorTiles.get(random.nextInt(corridorTiles.size())); } return null; } private void addSecretRoom(char[][] map) { // Look for a wall that could hide a secret room for (int y = 2; y < height - 2; y++) { for (int x = 2; x < width - 2; x++) { if (map[y][x] == SimpleDungeonGenerator.WALL && isGoodSecretRoomLocation(map, x, y)) { // Carve secret room carveSecretRoom(map, x, y); return; } } } } private boolean isGoodSecretRoomLocation(char[][] map, int x, int y) { // Check if this wall is between two floor areas boolean hasFloorNeighbor = false; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (Math.abs(dx) + Math.abs(dy) != 1) continue; if (map[y+dy][x+dx] == SimpleDungeonGenerator.FLOOR) { hasFloorNeighbor = true; break; } } } return hasFloorNeighbor; } private void carveSecretRoom(char[][] map, int x, int y) { int roomSize = 3; // Determine direction to carve for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (Math.abs(dx) + Math.abs(dy) != 1) continue; if (map[y+dy][x+dx] == SimpleDungeonGenerator.FLOOR) { // Carve in opposite direction int carveX = x - dx * roomSize; int carveY = y - dy * roomSize; if (carveX >= 1 && carveX < width-1 && carveY >= 1 && carveY < height-1) { // Carve small room for (int ry = carveY - 1; ry <= carveY + 1; ry++) { for (int rx = carveX - 1; rx <= carveX + 1; rx++) { if (rx >= 0 && rx < width && ry >= 0 && ry < height) { map[ry][rx] = SimpleDungeonGenerator.FLOOR; } } } // Add treasure map[carveY][carveX] = 'T'; // Create hidden door map[y][x] = 'S'; // Secret door return; } } } } } private void connectLevels() { // Stairs are already placed in addLevelFeatures // This method could handle special connections between levels } private void populateDungeon() { for (int level = 0; level < levels; level++) { char[][] map = maps.get(level); populateLevel(map, level); } } private void populateLevel(char[][] map, int level) { int monsterCount = config.baseMonsters + level * 2; int treasureCount = config.baseTreasure + level; // Place monsters for (int i = 0; i < monsterCount; i++) { Point location = findMonsterLocation(map); if (location != null) { char monsterType = getMonsterTypeForLevel(level); map[location.y][location.x] = monsterType; } } // Place treasures for (int i = 0; i < treasureCount; i++) { Point location = findTreasureLocation(map); if (location != null) { map[location.y][location.x] = 'T'; } } } private Point findMonsterLocation(char[][] map) { List<Point> floorTiles = new ArrayList<>(); for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (map[y][x] == SimpleDungeonGenerator.FLOOR || map[y][x] == SimpleDungeonGenerator.CORRIDOR) { floorTiles.add(new Point(x, y)); } } } if (!floorTiles.isEmpty()) { return floorTiles.get(random.nextInt(floorTiles.size())); } return null; } private Point findTreasureLocation(char[][] map) { // Prefer dead ends for treasure Point deadEnd = findDeadEnd(map); if (deadEnd != null) { return deadEnd; } return findMonsterLocation(map); // Fallback to any floor tile } private Point findDeadEnd(char[][] map) { for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (map[y][x] == SimpleDungeonGenerator.FLOOR || map[y][x] == SimpleDungeonGenerator.CORRIDOR) { int neighbors = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if (Math.abs(dx) + Math.abs(dy) != 1) continue; if (map[y+dy][x+dx] == SimpleDungeonGenerator.FLOOR || map[y+dy][x+dx] == SimpleDungeonGenerator.CORRIDOR) { neighbors++; } } } if (neighbors == 1) { return new Point(x, y); } } } } return null; } private char getMonsterTypeForLevel(int level) { if (level == 0) return 'g'; // Goblin if (level == 1) return 'o'; // Orc if (level == 2) return 't'; // Troll if (level == 3) return 'd'; // Dragon return 'm'; // Generic monster } public void printLevel(int level) { if (level >= 0 && level < maps.size()) { char[][] map = maps.get(level); System.out.println("=== Level " + (level + 1) + " ==="); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { System.out.print(map[y][x]); } System.out.println(); } } } public static class DungeonConfig { public int baseMonsters = 5; public int baseTreasure = 3; public double secretRoomChance = 0.3; public double trapChance = 0.4; } public static void main(String[] args) { DungeonConfig config = new DungeonConfig(); AdvancedDungeonGenerator generator = new AdvancedDungeonGenerator( 40, 20, 4, System.currentTimeMillis(), config); generator.generateMultiLevelDungeon(); for (int level = 0; level < 4; level++) { generator.printLevel(level); System.out.println(); } } }

Key Features and Best Practices

Algorithm Comparison

AlgorithmProsConsBest For
Random RoomsSimple, fastCan be disjointedBasic dungeons
BSPOrganized, guaranteed connectivityCan be predictableStructured dungeons
Cellular AutomataNatural-looking, organicMay create isolated areasCaves, natural areas
Drunkard's WalkVery organic, winding pathsCan be inefficientMines, natural tunnels

Performance Optimization Tips

  1. Use efficient data structures for room and corridor tracking
  2. Implement spatial partitioning for large dungeons
  3. Cache frequently used random numbers
  4. Use flood fill algorithms for connectivity checks
  5. Limit recursion depth in BSP algorithms

Customization Options

  1. Theme Variants: Castle, cave, temple, forest
  2. Size Scaling: From small rooms to massive caverns
  3. Feature Density: Control room count, corridor length, etc.
  4. Biome Mixing: Combine different generation algorithms
  5. Procedural Content: Monsters, treasures, traps based on depth

These dungeon generation algorithms provide a solid foundation for creating varied and interesting game environments that can be customized for different game styles and requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper