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