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