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}