001package squidpony.squidgrid.mapping;
002
003import squidpony.ArrayTools;
004import squidpony.squidmath.Coord;
005import squidpony.squidmath.GWTRNG;
006import squidpony.squidmath.GreasedRegion;
007import squidpony.squidmath.IRNG;
008
009/**
010 * Map generator that constructs a large number of overlapping rectangular rooms.
011 * Meant for the kind of crowded architecture that might fit the dungeon equivalent of urban areas.
012 * Likely to have many dead-end sections with only one door-like area, similar to hotel rooms or closets depending on
013 * size, and the distance required to reach a particular room may be... cruel and/or unusual. Whole winding areas of a
014 * large map may only be accessible from outside by one door, for instance.
015 * <br>
016 * An example of what this outputs:
017 * https://gist.github.com/tommyettinger/3144b56a3a8e5bbe5ee401c1a93989f4
018 * Created by Tommy Ettinger on 5/4/2016.
019 */
020public class DenseRoomMapGenerator implements IDungeonGenerator {
021    public char[][] map;
022    public int[][] environment;
023    public IRNG rng;
024    protected int width, height;
025    public DenseRoomMapGenerator()
026    {
027        this(80, 30, new GWTRNG());
028    }
029    public DenseRoomMapGenerator(int width, int height)
030    {
031        this(width, height, new GWTRNG());
032    }
033    public DenseRoomMapGenerator(int width, int height, IRNG rng)
034    {
035        this.rng = rng;
036        this.width = Math.max(3, width);
037        this.height = Math.max(3, height);
038        map = ArrayTools.fill('#', this.width, this.height);
039        environment = new int[this.width][this.height];
040    }
041
042    public char[][] getDungeon() {
043        return map;
044    }
045    /**
046     * Generate a map as a 2D char array using the width and height specified in the constructor.
047     * Should produce a crowded arrangement of rectangular rooms that overlap with each other.
048     * @return a 2D char array for the map of densely-packed rectangular rooms.
049     */
050    public char[][] generate()
051    {
052        //ArrayList<short[]> regions = new ArrayList<>();
053        short[] tempPacked;
054        int nh, nw, nx, ny, hnw, hnh;
055//        Collection<Coord> sampled = PoissonDisk.sampleRectangle(Coord.get(1, 1), Coord.get(width - 2, height - 2),
056//                6f, width, height, 35, rng);
057//        sampled.addAll(PoissonDisk.sampleRectangle(Coord.get(1, 1), Coord.get(width - 2, height - 2),
058//                9, width, height, 40, rng));
059        GreasedRegion sampled = new GreasedRegion(width, height).allOn().removeEdges();
060        sampled.remake(sampled.copy().randomScatter(rng, 6).or(sampled.randomScatter(rng,8)));
061
062        for(Coord center : sampled) {
063            nw = rng.between(4, 16);
064            nh = rng.between(4, 16);
065            hnw = (nw + 1) / 2;
066            hnh = (nh + 1) / 2;
067            nx = Math.max(0, Math.min(width - 2 - hnw, center.x - hnw));
068            ny = Math.max(0, Math.min(height - 2 - hnh, center.y - hnh));
069            if (center.x - hnw != nx)
070                nw -= Math.abs(center.x - hnw - nx);
071            if (center.y - hnh != ny)
072                nh -= Math.abs(center.y - hnh - ny);
073            if (nw >= 0 && nh >= 0) {
074                ArrayTools.insert(DungeonUtility.wallWrap(ArrayTools.fill('.', nw, nh)),
075                        map, nx, ny);
076                //regions.add(CoordPacker.rectangle(nx, ny, nw, nh));
077            }
078        }
079        for (int x = 0; x < width; x++) {
080            for (int y = 0; y < height; y++) {
081                environment[x][y] = (map[x][y] == '.') ? DungeonUtility.ROOM_FLOOR : DungeonUtility.ROOM_WALL;
082            }
083        }
084//        tempPacked = CoordPacker.intersectPacked(
085//                CoordPacker.rectangle(1, 1, width - 2, height - 2),
086//                CoordPacker.pack(map, '#'));
087//        Coord[] holes = CoordPacker.randomSeparated(tempPacked, 3, rng);
088        for(Coord hole : new GreasedRegion(map, '.').fringe().removeEdges().mixedRandomSeparated(0.25, -1, rng.nextLong())) {
089            if (((map[hole.x - 1][hole.y] == '.' && map[hole.x + 1][hole.y] == '.') ||
090                            (map[hole.x][hole.y - 1] == '.' && map[hole.x][hole.y + 1] == '.'))) {
091                map[hole.x][hole.y] = '.';
092                environment[hole.x][hole.y] = DungeonUtility.CORRIDOR_FLOOR;
093            }
094        }
095
096        /*
097        regions = rng.shuffle(regions);
098        while (regions.size() > 1)
099        {
100
101            region = regions.remove(0);
102            linking = regions.get(0);
103            start = CoordPacker.singleRandom(region, rng);
104            end = CoordPacker.singleRandom(linking, rng);
105            path = OrthoLine.line(start, end);
106            for(Coord elem : path)
107            {
108                if(elem.x < width && elem.y < height) {
109                    if (map[elem.x][elem.y] == '#') {
110                        map[elem.x][elem.y] = '.';
111                        environment[elem.x][elem.y] = MixedGenerator.CORRIDOR_FLOOR;
112                        ctr++;
113                    }
114                }
115            }
116        }
117        */
118
119        int upperY = height - 1;
120        int upperX = width - 1;
121        for (int i = 0; i < width; i++) {
122            map[i][0] = '#';
123            map[i][upperY] = '#';
124            environment[i][0] = DungeonUtility.UNTOUCHED;
125            environment[i][upperY] = DungeonUtility.UNTOUCHED;
126        }
127        for (int i = 0; i < height; i++) {
128            map[0][i] = '#';
129            map[upperX][i] = '#';
130            environment[0][i] = DungeonUtility.UNTOUCHED;
131            environment[upperX][i] = DungeonUtility.UNTOUCHED;
132        }
133
134        return map;
135    }
136
137
138    /**
139     * Gets a 2D array of int constants, each representing a type of environment corresponding to a static field of
140     * MixedGenerator. This array will have the same size as the last char 2D array produced by generate(); the value
141     * of this method if called before generate() is undefined, but probably will be a 2D array of all 0 (UNTOUCHED).
142     * <ul>
143     *     <li>MixedGenerator.UNTOUCHED, equal to 0, is used for any cells that aren't near a floor.</li>
144     *     <li>MixedGenerator.ROOM_FLOOR, equal to 1, is used for floor cells inside wide room areas.</li>
145     *     <li>MixedGenerator.ROOM_WALL, equal to 2, is used for wall cells around wide room areas.</li>
146     *     <li>MixedGenerator.CAVE_FLOOR, equal to 3, is used for floor cells inside rough cave areas.</li>
147     *     <li>MixedGenerator.CAVE_WALL, equal to 4, is used for wall cells around rough cave areas.</li>
148     *     <li>MixedGenerator.CORRIDOR_FLOOR, equal to 5, is used for floor cells inside narrow corridor areas.</li>
149     *     <li>MixedGenerator.CORRIDOR_WALL, equal to 6, is used for wall cells around narrow corridor areas.</li>
150     * </ul>
151     * @return a 2D int array where each element is an environment type constant in MixedGenerator
152     */
153    public int[][] getEnvironment()
154    {
155        return environment;
156    }
157}