001package squidpony.squidgrid.mapping;
002
003import squidpony.squidmath.*;
004
005import java.util.ArrayList;
006import java.util.List;
007
008/**
009 * Map generator using Perlin/Simplex noise for the formation of "rooms" and then WobblyLine to connect with corridors.
010 * Created by Tommy Ettinger on 4/18/2016.
011 */
012public class OrganicMapGenerator {
013    public char[][] map;
014    public int[][] environment;
015    public RNG rng;
016    protected int width, height;
017    public double noiseMin, noiseMax;
018    public OrganicMapGenerator()
019    {
020        this(0.55, 0.65, 80, 30, new RNG());
021    }
022    public OrganicMapGenerator(int width, int height)
023    {
024        this(0.55, 0.65, width, height, new RNG());
025    }
026    public OrganicMapGenerator(int width, int height, RNG rng)
027    {
028        this(0.55, 0.65, width, height, rng);
029    }
030    public OrganicMapGenerator(double noiseMin, double noiseMax, int width, int height, RNG rng)
031    {
032        this.rng = rng;
033        this.width = Math.max(3, width);
034        this.height = Math.max(3, height);
035        this.noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin));
036        this.noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax));
037        map = new char[this.width][this.height];
038        environment = new int[this.width][this.height];
039    }
040
041    /**
042     * Generate a map as a 2D char array using the width and height specified in the constructor.
043     * Should produce an organic, cave-like map.
044     * @return a 2D char array for the map that should be organic-looking.
045     */
046    public char[][] generate()
047    {
048        double shift, shift2, temp;
049        boolean[][] working = new boolean[width][height], blocks = new boolean[8][8];
050        int ctr = 0, frustration = 0;
051        REDO:
052        while (frustration < 10) {
053            shift = rng.nextDouble(2048);
054            shift2 = rng.between(4096, 8192);
055            ctr = 0;
056            for (int x = 0; x < width; x++) {
057                for (int y = 0; y < height; y++) {
058                    map[x][y] = '#';
059                    temp = (PerlinNoise.noise(x / 1.7 + shift, y / 1.7 + shift) * 5
060                            + PerlinNoise.noise(x / 0.7 + shift2, y / 0.7 + shift2) * 3) / 8.0;
061                    if (temp >= noiseMin && temp <= noiseMax) {
062                        working[x][y] = true;
063                        ctr++;
064                        blocks[x * 8 / width][y * 8 / height] = true;
065                    }
066                    else
067                    {
068                        working[x][y] = false;
069                    }
070                }
071            }
072            for (int x = 0; x < 8; x++) {
073                for (int y = 0; y < 8; y++) {
074                    if (!blocks[x][y]) {
075                        frustration++;
076                        ctr = 0;
077                        continue REDO;
078                    }
079                }
080            }
081            break;
082        }
083        if(ctr < (width + height) * 3 || frustration >= 10) {
084            noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05));
085            noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05));
086            return generate();
087        }
088        ctr = 0;
089        ArrayList<short[]> allRegions = CoordPacker.split(CoordPacker.pack(working)),
090                regions = new ArrayList<>(allRegions.size());
091        short[] region, linking, tempPacked;
092        List<Coord> path, path2;
093        Coord start, end;
094        char[][] t;
095        for (short[] r : allRegions) {
096            if (CoordPacker.count(r) > 5) {
097                region = CoordPacker.expand(r, 1, width, height, false);
098                if(CoordPacker.isEmpty(region))
099                    continue;
100                regions.add(region);
101                ctr += CoordPacker.count(region);
102                tempPacked = CoordPacker.negatePacked(region);
103                map = CoordPacker.mask(map, tempPacked, '.');
104            }
105        }
106        int oldSize = regions.size();
107        if(oldSize < 4 || ctr < (width + height) * 3) {
108            noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05));
109            noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05));
110            return generate();
111        }
112
113        for (int x = 0; x < width; x++) {
114            for (int y = 0; y < height; y++) {
115                environment[x][y] = (map[x][y] == '.') ? MixedGenerator.CAVE_FLOOR : MixedGenerator.CAVE_WALL;
116            }
117        }
118        tempPacked = CoordPacker.pack(map, '.');
119        int tick = 1;
120        regions = rng.shuffle(regions);
121        while (regions.size() > 1)
122        {
123
124            region = regions.remove(0);
125            /*
126            tick = (tick + 1) % 5;
127            if(tick == 0) {
128                ctr -= CoordPacker.count(region);
129                continue;
130            }*/
131            linking = regions.get(0);
132            start = CoordPacker.singleRandom(region, rng);
133            end = CoordPacker.singleRandom(linking, rng);
134            path = WobblyLine.line(start.x, start.y, end.x, end.y, width, height, 0.75, rng);
135            for(Coord elem : path)
136            {
137                if(elem.x < width && elem.y < height) {
138                    if (map[elem.x][elem.y] == '#') {
139                        map[elem.x][elem.y] = '.';
140                        environment[elem.x][elem.y] = MixedGenerator.CORRIDOR_FLOOR;
141                        ctr++;
142                    } /*else if (rng.nextBoolean() &&
143                            CoordPacker.queryPacked(CoordPacker.differencePacked(tempPacked, region), elem.x, elem.y)) {
144                        linking = regions.get(rng.nextInt(regions.size()));
145                        start = elem;
146                        end = CoordPacker.singleRandom(linking, rng);
147                        path2 = WobblyLine.line(start.x, start.y, end.x, end.y, width, height, 0.75, rng);
148                        for(Coord elem2 : path2)
149                        {
150                            if(elem2.x < width && elem2.y < height) {
151                                if (map[elem2.x][elem2.y] == '#') {
152                                    map[elem2.x][elem2.y] = '.';
153                                    environment[elem2.x][elem2.y] = MixedGenerator.CORRIDOR_FLOOR;
154                                    ctr++;
155                                }
156                            }
157                        }
158                        break;
159                    }*/
160                }
161            }
162        }
163        int upperY = height - 1;
164        int upperX = width - 1;
165        for (int i = 0; i < width; i++) {
166            map[i][0] = '#';
167            map[i][upperY] = '#';
168            environment[i][0] = MixedGenerator.UNTOUCHED;
169            environment[i][upperY] = MixedGenerator.UNTOUCHED;
170        }
171        for (int i = 0; i < height; i++) {
172            map[0][i] = '#';
173            map[upperX][i] = '#';
174            environment[0][i] = MixedGenerator.UNTOUCHED;
175            environment[upperX][i] = MixedGenerator.UNTOUCHED;
176        }
177
178        if(ctr < (width + height) * 3) {
179            noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05));
180            noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05));
181            return generate();
182        }
183        return map;
184    }
185
186
187    /**
188     * Gets a 2D array of int constants, each representing a type of environment corresponding to a static field of
189     * MixedGenerator. This array will have the same size as the last char 2D array produced by generate(); the value
190     * of this method if called before generate() is undefined, but probably will be a 2D array of all 0 (UNTOUCHED).
191     * <ul>
192     *     <li>MixedGenerator.UNTOUCHED, equal to 0, is used for any cells that aren't near a floor.</li>
193     *     <li>MixedGenerator.ROOM_FLOOR, equal to 1, is used for floor cells inside wide room areas.</li>
194     *     <li>MixedGenerator.ROOM_WALL, equal to 2, is used for wall cells around wide room areas.</li>
195     *     <li>MixedGenerator.CAVE_FLOOR, equal to 3, is used for floor cells inside rough cave areas.</li>
196     *     <li>MixedGenerator.CAVE_WALL, equal to 4, is used for wall cells around rough cave areas.</li>
197     *     <li>MixedGenerator.CORRIDOR_FLOOR, equal to 5, is used for floor cells inside narrow corridor areas.</li>
198     *     <li>MixedGenerator.CORRIDOR_WALL, equal to 6, is used for wall cells around narrow corridor areas.</li>
199     * </ul>
200     * @return a 2D int array where each element is an environment type constant in MixedGenerator
201     */
202    public int[][] getEnvironment()
203    {
204        return environment;
205    }
206}