001package squidpony.squidgrid.mapping; 002 003import squidpony.ArrayTools; 004import squidpony.squidgrid.Direction; 005import squidpony.squidmath.GWTRNG; 006import squidpony.squidmath.IRNG; 007import squidpony.squidmath.IntVLA; 008 009import java.util.ArrayDeque; 010 011/** 012 * Recursively divided maze. Creates only walls and passages, as the chars {@code '#'} and {@code '.'}. You may get 013 * better mazes from using {@link GrowingTreeMazeGenerator}; this generator produces lots of narrow dead-end hallways. 014 * <p> 015 * This dungeon generator is based on a port of the rot.js version. 016 * 017 * @author Eben Howard - http://squidpony.com - howard@squidpony.com 018 */ 019public class DividedMazeGenerator implements IDungeonGenerator { 020 021 private static class DividedMazeRoom { 022 023 private final int left, top, right, bottom; 024 025 public DividedMazeRoom(int left, int top, int right, int bottom) { 026 this.left = left; 027 this.top = top; 028 this.right = right; 029 this.bottom = bottom; 030 } 031 } 032 033 private int width, height; 034 private char[][] map; 035 private IRNG rng; 036 037 /** 038 * Sets up the generator to make mazes the given width and height. The mazes 039 * have a solid wall border. 040 * 041 * @param width 042 * @param height 043 */ 044 public DividedMazeGenerator(int width, int height) { 045 this.width = width; 046 this.height = height; 047 rng = new GWTRNG(); 048 } 049 050 /** 051 * Sets up the generator to make mazes the given width and height. The mazes 052 * have a solid wall border. 053 * 054 * @param width in cells 055 * @param height in cells 056 * @param rng the random number generator to use 057 */ 058 public DividedMazeGenerator(int width, int height, IRNG rng) { 059 this.width = width; 060 this.height = height; 061 this.rng = rng; 062 } 063 064 /** 065 * Builds a maze. As usual, {@code '#'} represents a wall, and {@code '.'} represents a floor. 066 * 067 * @return 068 */ 069 public char[][] generate() { 070 map = ArrayTools.fill('.', width, height); 071 072 for (int x = 0; x < width; x++) { 073 for (int y = 0; y < height; y++) { 074 if(x == 0 || y == 0 || x + 1 == width || y + 1 == height) 075 map[x][y] = '#'; 076 } 077 } 078 079 process(); 080 081 return map; 082 } 083 084 /** 085 * Gets the most recently-produced dungeon as a 2D char array, usually produced by calling {@link #generate()}. This 086 * may reuturn null if generate() has not been called. This passes a direct reference and not a copy, 087 * so you can normally modify the returned array to propagate changes back into this IDungeonGenerator. 088 * 089 * @return the most recently-produced dungeon/map as a 2D char array 090 */ 091 @Override 092 public char[][] getDungeon() { 093 return map; 094 } 095 096 private void process() { 097 ArrayDeque<DividedMazeRoom> stack = new ArrayDeque<>(); 098 stack.offer(new DividedMazeRoom(1, 1, width - 2, height - 2)); 099 IntVLA availX = new IntVLA(), availY = new IntVLA(); 100 Direction[] dirs = new Direction[4]; 101 System.arraycopy(Direction.CARDINALS, 0, dirs, 0, 4); 102 while (!stack.isEmpty()) { 103 DividedMazeRoom room = stack.removeFirst(); 104 availX.clear(); 105 availY.clear(); 106 107 for (int x = room.left + 1; x < room.right; x++) { 108 boolean top = '#' == map[x][room.top - 1]; 109 boolean bottom = '#' == map[x][room.bottom + 1]; 110 if (top && bottom && (x & 1) == 0) { 111 availX.add(x); 112 } 113 } 114 115 for (int y = room.top + 1; y < room.bottom; y++) { 116 boolean left = '#' == map[room.left - 1][y]; 117 boolean right = '#' == map[room.right + 1][y]; 118 if (left && right && (y & 1) == 0) { 119 availY.add(y); 120 } 121 } 122 123 if (availX.isEmpty() || availY.isEmpty()) { 124 continue; 125 } 126 127 int x2 = availX.getRandomElement(rng); 128 int y2 = availY.getRandomElement(rng); 129 130 map[x2][y2] = '#'; 131 132 for (int x = room.left; x < x2; x++) { 133 map[x][y2] = '#'; 134 } 135 for (int x = x2 + 1; x <= room.right; x++) { 136 map[x][y2] = '#'; 137 } 138 for (int y = room.top; y < y2; y++) { 139 map[x2][y] = '#'; 140 } 141 for (int y = y2 + 1; y <= room.bottom; y++) { 142 map[x2][y] = '#'; 143 } 144 145 rng.shuffleInPlace(dirs); 146 147 for (int i = 0; i < 3; i++) { 148 Direction dir = dirs[i]; 149 switch (dir) { 150 case LEFT: 151 map[rng.between(room.left, x2)][y2] = '.'; 152 break; 153 case RIGHT: 154 map[rng.between(x2 + 1, room.right + 1)][y2] = '.'; 155 break; 156 case UP: 157 map[x2][rng.between(room.top, y2)] = '.'; 158 break; 159 case DOWN: 160 map[x2][rng.between(y2 + 1, room.bottom + 1)] = '.'; 161 break; 162 default: 163 throw new IllegalStateException("There should only be cardinal directions here"); 164 } 165 } 166 167 stack.offer(new DividedMazeRoom(room.left, room.top, x2 - 1, y2 - 1)); 168 stack.offer(new DividedMazeRoom(x2 + 1, room.top, room.right, y2 - 1)); 169 stack.offer(new DividedMazeRoom(room.left, y2 + 1, x2 - 1, room.bottom)); 170 stack.offer(new DividedMazeRoom(x2 + 1, y2 + 1, room.right, room.bottom)); 171 } 172 } 173 174}