package squidpony.squidgrid.mapping; import squidpony.squidmath.*; import java.util.ArrayList; import java.util.List; /** * Map generator using Perlin/Simplex noise for the formation of "rooms" and then WobblyLine to connect with corridors. * Created by Tommy Ettinger on 4/18/2016. */ public class OrganicMapGenerator { public char[][] map; public int[][] environment; public RNG rng; protected int width, height; public double noiseMin, noiseMax; public OrganicMapGenerator() { this(0.55, 0.65, 80, 30, new RNG()); } public OrganicMapGenerator(int width, int height) { this(0.55, 0.65, width, height, new RNG()); } public OrganicMapGenerator(int width, int height, RNG rng) { this(0.55, 0.65, width, height, rng); } public OrganicMapGenerator(double noiseMin, double noiseMax, int width, int height, RNG rng) { this.rng = rng; this.width = Math.max(3, width); this.height = Math.max(3, height); this.noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin)); this.noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax)); map = new char[this.width][this.height]; environment = new int[this.width][this.height]; } /** * Generate a map as a 2D char array using the width and height specified in the constructor. * Should produce an organic, cave-like map. * @return a 2D char array for the map that should be organic-looking. */ public char[][] generate() { double shift, shift2, temp; boolean[][] working = new boolean[width][height], blocks = new boolean[8][8]; int ctr = 0, frustration = 0; REDO: while (frustration < 10) { shift = rng.nextDouble(2048); shift2 = rng.between(4096, 8192); ctr = 0; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { map[x][y] = '#'; temp = (PerlinNoise.noise(x * 4.7 + shift, y * 4.7 + shift) * 5 + PerlinNoise.noise(x * 11.4 + shift2, y * 11.4 + shift2) * 3) / 8.0; if (temp >= noiseMin && temp <= noiseMax) { working[x][y] = true; ctr++; blocks[x * 8 / width][y * 8 / height] = true; } else { working[x][y] = false; } } } for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { if (!blocks[x][y]) { frustration++; ctr = 0; continue REDO; } } } break; } if(ctr < (width + height) * 3 || frustration >= 10) { noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); return generate(); } ctr = 0; ArrayList<short[]> allRegions = CoordPacker.split(CoordPacker.pack(working)), regions = new ArrayList<>(allRegions.size()); short[] region, linking, tempPacked; List<Coord> path, path2; Coord start, end; char[][] t; for (short[] r : allRegions) { if (CoordPacker.count(r) > 5) { region = CoordPacker.expand(r, 1, width, height, false); if(CoordPacker.isEmpty(region)) continue; regions.add(region); ctr += CoordPacker.count(region); tempPacked = CoordPacker.negatePacked(region); map = CoordPacker.mask(map, tempPacked, '.'); } } int oldSize = regions.size(); if(oldSize < 4 || ctr < (width + height) * 3) { noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); return generate(); } for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { environment[x][y] = (map[x][y] == '.') ? MixedGenerator.CAVE_FLOOR : MixedGenerator.CAVE_WALL; } } //tempPacked = CoordPacker.pack(map, '.'); //int tick = 1; regions = rng.shuffle(regions); while (regions.size() > 1) { region = regions.remove(0); /* tick = (tick + 1) % 5; if(tick == 0) { ctr -= CoordPacker.count(region); continue; }*/ linking = regions.get(0); start = CoordPacker.singleRandom(region, rng); end = CoordPacker.singleRandom(linking, rng); path = WobblyLine.line(start.x, start.y, end.x, end.y, width, height, 0.75, rng); for(Coord elem : path) { if(elem.x < width && elem.y < height) { if (map[elem.x][elem.y] == '#') { map[elem.x][elem.y] = '.'; environment[elem.x][elem.y] = MixedGenerator.CORRIDOR_FLOOR; ctr++; } /*else if (rng.nextBoolean() && CoordPacker.queryPacked(CoordPacker.differencePacked(tempPacked, region), elem.x, elem.y)) { linking = regions.get(rng.nextInt(regions.size())); start = elem; end = CoordPacker.singleRandom(linking, rng); path2 = WobblyLine.line(start.x, start.y, end.x, end.y, width, height, 0.75, rng); for(Coord elem2 : path2) { if(elem2.x < width && elem2.y < height) { if (map[elem2.x][elem2.y] == '#') { map[elem2.x][elem2.y] = '.'; environment[elem2.x][elem2.y] = MixedGenerator.CORRIDOR_FLOOR; ctr++; } } } break; }*/ } } } int upperY = height - 1; int upperX = width - 1; for (int i = 0; i < width; i++) { map[i][0] = '#'; map[i][upperY] = '#'; environment[i][0] = MixedGenerator.UNTOUCHED; environment[i][upperY] = MixedGenerator.UNTOUCHED; } for (int i = 0; i < height; i++) { map[0][i] = '#'; map[upperX][i] = '#'; environment[0][i] = MixedGenerator.UNTOUCHED; environment[upperX][i] = MixedGenerator.UNTOUCHED; } if(ctr < (width + height) * 3) { noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); return generate(); } return map; } /** * Gets a 2D array of int constants, each representing a type of environment corresponding to a static field of * MixedGenerator. This array will have the same size as the last char 2D array produced by generate(); the value * of this method if called before generate() is undefined, but probably will be a 2D array of all 0 (UNTOUCHED). * <ul> * <li>MixedGenerator.UNTOUCHED, equal to 0, is used for any cells that aren't near a floor.</li> * <li>MixedGenerator.ROOM_FLOOR, equal to 1, is used for floor cells inside wide room areas.</li> * <li>MixedGenerator.ROOM_WALL, equal to 2, is used for wall cells around wide room areas.</li> * <li>MixedGenerator.CAVE_FLOOR, equal to 3, is used for floor cells inside rough cave areas.</li> * <li>MixedGenerator.CAVE_WALL, equal to 4, is used for wall cells around rough cave areas.</li> * <li>MixedGenerator.CORRIDOR_FLOOR, equal to 5, is used for floor cells inside narrow corridor areas.</li> * <li>MixedGenerator.CORRIDOR_WALL, equal to 6, is used for wall cells around narrow corridor areas.</li> * </ul> * @return a 2D int array where each element is an environment type constant in MixedGenerator */ public int[][] getEnvironment() { return environment; } }