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
- Random Walk - Simple but unpredictable
- Binary Space Partitioning (BSP) - Organized room placement
- Cellular Automata - Natural-looking cave systems
- Drunkard's Walk - Organic corridor generation
- 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
| Algorithm | Pros | Cons | Best For |
|---|---|---|---|
| Random Rooms | Simple, fast | Can be disjointed | Basic dungeons |
| BSP | Organized, guaranteed connectivity | Can be predictable | Structured dungeons |
| Cellular Automata | Natural-looking, organic | May create isolated areas | Caves, natural areas |
| Drunkard's Walk | Very organic, winding paths | Can be inefficient | Mines, natural tunnels |
Performance Optimization Tips
- Use efficient data structures for room and corridor tracking
- Implement spatial partitioning for large dungeons
- Cache frequently used random numbers
- Use flood fill algorithms for connectivity checks
- Limit recursion depth in BSP algorithms
Customization Options
- Theme Variants: Castle, cave, temple, forest
- Size Scaling: From small rooms to massive caverns
- Feature Density: Control room count, corridor length, etc.
- Biome Mixing: Combine different generation algorithms
- 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.