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}