001package squidpony.squidgrid.mapping;
002
003import squidpony.ArrayTools;
004import squidpony.annotation.Beta;
005import squidpony.squidmath.Coord;
006import squidpony.squidmath.CoordPacker;
007import squidpony.squidmath.GWTRNG;
008import squidpony.squidmath.IRNG;
009import squidpony.squidmath.IStatefulRNG;
010import squidpony.squidmath.OrderedMap;
011import squidpony.squidmath.OrthoLine;
012import squidpony.squidmath.RegionMap;
013import squidpony.squidmath.StatefulRNG;
014
015import java.util.*;
016
017/**
018 * Generator for maps of high-tech areas like space stations or starships, with repeated modules laid out in random ways.
019 * Different from traditional fantasy dungeon generation in that it should seem generally less chaotic in how it's laid
020 * out, and repeated elements with minor tweaks should be especially common. May also be useful in fantasy games for
021 * regimented areas built by well-organized military forces.
022 * <br>
023 * This is marked Beta because both {@link MapModule} and ModularMapGenerator need improvement to be actually usable,
024 * but it might be a while before there's a clear pathway towards how they can be improved. 
025 * <br>
026 * Preview: https://gist.github.com/tommyettinger/c711f8fc83fa9919245d88092444bf7f
027 * Created by Tommy Ettinger on 4/2/2016.
028 */
029@Beta
030public class ModularMapGenerator implements IDungeonGenerator {
031    public DungeonUtility utility;
032    protected int height, width;
033    public IStatefulRNG rng;
034    protected long rebuildSeed;
035
036    protected char[][] map;
037    protected int[][] environment;
038    //public RegionMap<MapModule> layout, modules, inverseModules;
039    public RegionMap<MapModule> layout;
040    public OrderedMap<Integer, ArrayList<MapModule>> modules;
041    public OrderedMap<Coord, MapModule> displacement;
042
043    private void putModule(short[] module) {
044        char[][] unp = CoordPacker.unpackChar(module, '.', '#');
045        MapModule mm = new MapModule(ArrayTools.insert(unp,
046                ArrayTools.fill('#', unp.length + 2, unp[0].length + 2), 1, 1));
047        //short[] b = CoordPacker.rectangle(1 + mm.max.x, 1 + mm.max.y);
048        //modules.put(b, mm);
049        //inverseModules.put(CoordPacker.negatePacked(b), mm);
050        ArrayList<MapModule> mms = modules.get(mm.category);
051        if (mms == null) {
052            mms = new ArrayList<>(16);
053            mms.add(mm);
054            modules.put(mm.category, mms);
055        } else
056            mms.add(mm);
057    }
058
059    private void putRectangle(int width, int height, float multiplier) {
060        putModule(CoordPacker.rectangle(Math.round(width * multiplier), Math.round(height * multiplier)));
061    }
062
063    private void putCircle(int radius, float multiplier) {
064        putModule(CoordPacker.circle(Coord.get(Math.round(radius * multiplier + 1), Math.round(radius * multiplier + 1)),
065                Math.round(radius * multiplier),
066                Math.round((radius + 1) * 2 * multiplier + 1), Math.round((radius + 1) * 2 * multiplier + 1)));
067    }
068
069    private void initModules() {
070        layout = new RegionMap<>(64);
071        //modules = new RegionMap<>(64);
072        //inverseModules = new RegionMap<>(64);
073        modules = new OrderedMap<>(64);
074        for (int i = 1; i <= 64; i <<= 1) {
075            ArrayList<MapModule> mms = new ArrayList<>(16);
076            modules.put(i, mms);
077        }
078        displacement = new OrderedMap<>(64);
079        float multiplier = 1;//(float) Math.sqrt(Math.max(1f, Math.min(width, height) / 24f));
080        putRectangle(2, 2, multiplier);
081        putRectangle(3, 3, multiplier);
082        putRectangle(4, 4, multiplier);
083        putRectangle(4, 2, multiplier);
084        putRectangle(2, 4, multiplier);
085        putRectangle(6, 6, multiplier);
086        putRectangle(6, 3, multiplier);
087        putRectangle(3, 6, multiplier);
088        putCircle(2, multiplier);
089
090        putRectangle(8, 8, multiplier);
091        putRectangle(6, 12, multiplier);
092        putRectangle(12, 6, multiplier);
093        putCircle(4, multiplier);
094
095        putRectangle(14, 14, multiplier);
096        putRectangle(9, 18, multiplier);
097        putRectangle(18, 9, multiplier);
098        putRectangle(14, 18, multiplier);
099        putRectangle(18, 14, multiplier);
100        putCircle(7, multiplier);
101    }
102
103    /**
104     * Make a ModularMapGenerator with a GWTRNG using a random seed, height 30, and width 60.
105     */
106    public ModularMapGenerator() {
107        this(60, 30);
108    }
109
110    /**
111     * Make a ModularMapGenerator with the given height and width; the RNG used for generating a dungeon and
112     * adding features will be a GWTRNG using a random seed.
113     *
114     * @param width  The width of the dungeon in cells
115     * @param height The height of the dungeon in cells
116     */
117    public ModularMapGenerator(int width, int height) {
118        this(width, height, new GWTRNG());
119    }
120
121    /**
122     * Make a ModularMapGenerator with the given height, width, and RNG. Use this if you want to seed the RNG.
123     *
124     * @param width  The width of the dungeon in cells
125     * @param height The height of the dungeon in cells
126     * @param rng    The RNG to use for all purposes in this class; if it is any kind of IStatefulRNG, then it will be
127     *               used as-is; otherwise, a new GWTRNG will be used, randomly seeded by this parameter
128     */
129    public ModularMapGenerator(int width, int height, IRNG rng) {
130        CoordPacker.init();
131        this.rng = (rng instanceof IStatefulRNG) ? (IStatefulRNG) rng : new GWTRNG(rng.nextLong());
132        utility = new DungeonUtility(this.rng);
133        rebuildSeed = this.rng.getState();
134        this.height = height;
135        this.width = width;
136        map = new char[width][height];
137        environment = new int[width][height];
138        for (int x = 0; x < this.width; x++) {
139            Arrays.fill(map[x], '#');
140        }
141        initModules();
142    }
143
144    /**
145     * Copies all fields from copying and makes a new DungeonGenerator.
146     *
147     * @param copying the DungeonGenerator to copy
148     */
149    public ModularMapGenerator(ModularMapGenerator copying) {
150        CoordPacker.init();
151        rng = new StatefulRNG(copying.rng.getState());
152        utility = new DungeonUtility(rng);
153        rebuildSeed = rng.getState();
154        height = copying.height;
155        width = copying.width;
156        map = ArrayTools.copy(copying.map);
157        environment = ArrayTools.copy(copying.environment);
158        layout = new RegionMap<>(copying.layout);
159        modules = new OrderedMap<>(copying.modules);
160        displacement = new OrderedMap<>(copying.displacement);
161    }
162
163    /**
164     * Get the most recently generated char[][] map out of this class. The
165     * map may be null if generate() or setMap() have not been called.
166     *
167     * @return a char[][] map, or null.
168     */
169    public char[][] getMap() {
170        return map;
171    }
172    /**
173     * Get the most recently generated char[][] map out of this class. The
174     * map may be null if generate() or setMap() have not been called.
175     *
176     * @return a char[][] map, or null.
177     */
178    public char[][] getDungeon() {
179        return map;
180    }
181
182    /**
183     * Get the most recently generated char[][] map out of this class without any chars other than '#' or '.', for
184     * walls and floors respectively. The map may be null if generate() or setMap() have not been called.
185     *
186     * @return a char[][] map with only '#' for walls and '.' for floors, or null.
187     */
188    public char[][] getBareMap() {
189        return DungeonUtility.simplifyDungeon(map);
190    }
191
192    public char[][] generate() {
193        MapModule mm, mm2;
194        int xPos, yPos, categorySize = 32, alteredSize = (categorySize * 3) >>> 1, bits = 5, ctr;
195        Coord[][] grid = new Coord[1][1];
196        // find biggest category and drop down as many modules as we can fit
197        for (; categorySize >= 4; categorySize >>= 1, alteredSize = (categorySize * 3) >>> 1, bits--) {
198            if (width / alteredSize <= 0 || height / alteredSize <= 0)
199                continue;
200            grid = new Coord[width / alteredSize][height / alteredSize];
201            ctr = 0;
202            for (int xLimit = alteredSize - 1, x = 0; xLimit < width; xLimit += alteredSize, x += alteredSize) {
203                for (int yLimit = alteredSize - 1, y = 0; yLimit < height; yLimit += alteredSize, y += alteredSize) {
204                    if (layout.allAt(x + alteredSize / 2, y + alteredSize / 2).isEmpty()) // && (bits <= 3 || rng.nextInt(5) < bits)
205                    {
206                        if (rng.between(2, grid.length * grid[0].length + 3) == ctr++)
207                            continue;
208                        mm = rng.getRandomElement(modules.get(categorySize));
209                        if (mm == null) break;
210                        mm = mm.rotate(rng.nextInt(4));
211                        xPos = rng.nextInt(3) << (bits - 2);
212                        yPos = rng.nextInt(3) << (bits - 2);
213                        for (int px = 0; px <= mm.max.x; px++) {
214                            System.arraycopy(mm.map[px], 0, map[px + x + xPos], y + yPos, mm.max.y + 1);
215                            System.arraycopy(mm.environment[px], 0, environment[px + x + xPos], y + yPos, mm.max.y + 1);
216                        }
217                        layout.put(CoordPacker.rectangle(x + xPos, y + yPos, categorySize, categorySize), mm);
218                        displacement.put(Coord.get(x + xPos, y + yPos), mm);
219                        grid[x / alteredSize][y / alteredSize] = Coord.get(x + xPos, y + yPos);
220                    }
221                }
222            }
223            if (!layout.isEmpty())
224                break;
225        }
226        Coord a, b;
227        int gw = grid.length;
228        if (gw > 0) {
229            int gh = grid[0].length;
230            for (int w = 0; w < gw; w++) {
231                for (int h = 0; h < gh; h++) {
232                    a = grid[w][h];
233                    if (a == null)
234                        continue;
235                    int connectors = rng.nextInt(16) | rng.nextInt(16);
236                    if ((connectors & 1) == 1 && w > 0 && grid[w - 1][h] != null) {
237                        b = grid[w - 1][h];
238                        connectLeftRight(displacement.get(b), b.x, b.y, displacement.get(a), a.x, a.y);
239                    }
240                    if ((connectors & 2) == 2 && w < gw - 1 && grid[w + 1][h] != null) {
241                        b = grid[w + 1][h];
242                        connectLeftRight(displacement.get(a), a.x, a.y, displacement.get(b), b.x, b.y);
243                    }
244                    if ((connectors & 4) == 4 && h > 0 && grid[w][h - 1] != null) {
245                        b = grid[w][h - 1];
246                        connectTopBottom(displacement.get(b), b.x, b.y, displacement.get(a), a.x, a.y);
247                    }
248                    if ((connectors & 8) == 8 && h < gh - 1 && grid[w][h + 1] != null) {
249                        b = grid[w][h + 1];
250                        connectTopBottom(displacement.get(a), a.x, a.y, displacement.get(b), b.x, b.y);
251                    }
252                }
253            }
254        }
255        Coord begin;
256        short[] packed;
257        for (Map.Entry<Coord, MapModule> dmm : displacement.entrySet()) {
258            begin = dmm.getKey();
259            mm = dmm.getValue();
260            //int newCat = mm.category;
261            //if(newCat >= 16) newCat >>>= 1;
262            //if(newCat >= 8) newCat >>>= 1;
263            //mm2 = rng.getRandomElement(modules.get(newCat));
264            int shiftsX = begin.x - (mm.category * 3 / 2) * ((begin.x * 2) / (3 * mm.category)),
265                    shiftsY = begin.y - (mm.category * 3 / 2) * ((begin.y * 2) / (3 * mm.category)),
266                    leftSize = Integer.highestOneBit(shiftsX),
267                    rightSize = Integer.highestOneBit((mm.category >>> 1) - shiftsX),
268                    topSize = Integer.highestOneBit(shiftsY),
269                    bottomSize = Integer.highestOneBit((mm.category >>> 1) - shiftsY);
270            if (leftSize >= 4 && !mm.leftDoors.isEmpty()) {
271                mm2 = rng.getRandomElement(modules.get(leftSize));
272                if (mm2 == null) continue;
273                if (mm2.rightDoors.isEmpty()) {
274                    if (!mm2.topDoors.isEmpty())
275                        mm2 = mm2.rotate(1);
276                    else if (!mm2.leftDoors.isEmpty())
277                        mm2 = mm2.flip(true, false);
278                    else if (!mm2.bottomDoors.isEmpty())
279                        mm2 = mm2.rotate(3);
280                    else continue;
281                }
282                for (int i = 0; i < 4; i++) {
283                    packed = CoordPacker.rectangle(begin.x - shiftsX, begin.y + i * mm.category / 4, leftSize, leftSize);
284                    if (layout.containsRegion(packed))
285                        continue;
286                    for (int px = 0; px <= mm2.max.x; px++) {
287                        System.arraycopy(mm2.map[px], 0, map[px + begin.x - shiftsX], begin.y + i * mm.category / 4, mm2.max.y + 1);
288                        System.arraycopy(mm2.environment[px], 0, environment[px + begin.x - shiftsX], begin.y + i * mm.category / 4, mm2.max.y + 1);
289                    }
290                    layout.put(packed, mm2);
291                    connectLeftRight(mm2, begin.x - shiftsX, begin.y + i * mm.category / 4, mm, begin.x, begin.y);
292                }
293            }
294            if (rightSize >= 4 && !mm.rightDoors.isEmpty()) {
295                mm2 = rng.getRandomElement(modules.get(rightSize));
296                if (mm2 == null) continue;
297                if (mm2.leftDoors.isEmpty()) {
298                    if (!mm2.bottomDoors.isEmpty())
299                        mm2 = mm2.rotate(1);
300                    else if (!mm2.rightDoors.isEmpty())
301                        mm2 = mm2.flip(true, false);
302                    else if (!mm2.topDoors.isEmpty())
303                        mm2 = mm2.rotate(3);
304                    else continue;
305                }
306                for (int i = 0; i < 4; i++) {
307                    packed = CoordPacker.rectangle(begin.x + mm.category, begin.y + i * mm.category / 4, rightSize, rightSize);
308                    if (layout.containsRegion(packed))
309                        continue;
310                    for (int px = 0; px <= mm2.max.x; px++) {
311                        System.arraycopy(mm2.map[px], 0, map[px + begin.x + mm.category], begin.y + i * mm.category / 4, mm2.max.y + 1);
312                        System.arraycopy(mm2.environment[px], 0, environment[px + begin.x + mm.category], begin.y + i * mm.category / 4, mm2.max.y + 1);
313                    }
314                    layout.put(packed, mm2);
315                    connectLeftRight(mm, begin.x, begin.y, mm2, begin.x + mm.category, begin.y + i * mm.category / 4);
316                }
317            }
318            if (topSize >= 4 && !mm.topDoors.isEmpty()) {
319                mm2 = rng.getRandomElement(modules.get(topSize));
320                if (mm2 == null) continue;
321                if (mm2.bottomDoors.isEmpty()) {
322                    if (!mm2.leftDoors.isEmpty())
323                        mm2 = mm2.rotate(3);
324                    else if (!mm2.topDoors.isEmpty())
325                        mm2 = mm2.flip(false, true);
326                    else if (!mm2.rightDoors.isEmpty())
327                        mm2 = mm2.rotate(1);
328                    else continue;
329                }
330                for (int i = 0; i < 4; i++) {
331                    packed = CoordPacker.rectangle(begin.x + i * mm.category / 4, begin.y - shiftsY, topSize, topSize);
332                    if (layout.containsRegion(packed))
333                        continue;
334                    for (int px = 0; px <= mm2.max.x; px++) {
335                        System.arraycopy(mm2.map[px], 0, map[px + begin.x + i * mm.category / 4], begin.y - shiftsY, mm2.max.y + 1);
336                        System.arraycopy(mm2.environment[px], 0, environment[px + begin.x + i * mm.category / 4], begin.y - shiftsY, mm2.max.y + 1);
337                    }
338                    layout.put(packed, mm2);
339                    connectTopBottom(mm2, begin.x + i * mm.category / 4, begin.y - shiftsY, mm, begin.x, begin.y);
340                }
341            }
342            if (bottomSize >= 4 && !mm.bottomDoors.isEmpty()) {
343                mm2 = rng.getRandomElement(modules.get(bottomSize));
344                if (mm2 == null) continue;
345                if (mm2.topDoors.isEmpty()) {
346                    if (!mm2.rightDoors.isEmpty())
347                        mm2 = mm2.rotate(1);
348                    else if (!mm2.leftDoors.isEmpty())
349                        mm2 = mm2.rotate(3);
350                    else continue;
351                }
352                for (int i = 0; i < 4; i++) {
353                    packed = CoordPacker.rectangle(begin.x + i * mm.category / 4, begin.y + mm.category, bottomSize, bottomSize);
354                    if (layout.containsRegion(packed))
355                        continue;
356                    for (int px = 0; px <= mm2.max.x; px++) {
357                        System.arraycopy(mm2.map[px], 0, map[px + begin.x + i * mm.category / 4], begin.y + mm.category, mm2.max.y + 1);
358                        System.arraycopy(mm2.environment[px], 0, environment[px + begin.x + i * mm.category / 4], begin.y + mm.category, mm2.max.y + 1);
359                    }
360                    layout.put(packed, mm2);
361                    connectTopBottom(mm, begin.x, begin.y, mm2, begin.x + i * mm.category / 4, begin.y + mm.category);
362                }
363            }
364        }
365        return map;
366    }
367
368    /**
369     * Change the underlying char[][]; only affects the toString method, and of course getMap
370     *
371     * @param map a char[][], probably produced by an earlier call to this class and then modified.
372     */
373    public void setMap(char[][] map) {
374        this.map = map;
375        if (map == null) {
376            width = 0;
377            height = 0;
378            return;
379        }
380        width = map.length;
381        if (width > 0)
382            height = map[0].length;
383    }
384
385    /**
386     * Height of the map in cells.
387     *
388     * @return Height of the map in cells.
389     */
390    public int getHeight() {
391        return height;
392    }
393
394    /**
395     * Width of the map in cells.
396     *
397     * @return Width of the map in cells.
398     */
399    public int getWidth() {
400        return width;
401    }
402
403    /**
404     * Gets the environment int 2D array for use with classes like RoomFinder.
405     *
406     * @return the environment int 2D array
407     */
408    public int[][] getEnvironment() {
409        return environment;
410    }
411
412    /**
413     * Sets the environment int 2D array.
414     *
415     * @param environment a 2D array of int, where each int corresponds to a constant in MixedGenerator.
416     */
417    public void setEnvironment(int[][] environment) {
418        this.environment = environment;
419    }
420
421    private void connectLeftRight(MapModule left, int leftX, int leftY, MapModule right, int rightX, int rightY) {
422        if (left.rightDoors == null || left.rightDoors.isEmpty()
423                || right.leftDoors == null || right.leftDoors.isEmpty())
424            return;
425        List<Coord> line = new ArrayList<>(1), temp;
426        int best = 1024;
427        Coord tl, tr, twl, twr, wl = null, wr = null;
428        for (Coord l : left.rightDoors) {
429            tl = twl = l.translate(leftX, leftY);
430            if (tl.x > 0 && tl.x < width - 1 && map[tl.x - 1][tl.y] != '#')
431                tl = Coord.get(tl.x + 1, tl.y);
432            else if (tl.x > 0 && tl.x < width - 1 && map[tl.x + 1][tl.y] != '#')
433                tl = Coord.get(tl.x - 1, tl.y);
434            else if (tl.y > 0 && tl.y < height - 1 && map[tl.x][tl.y - 1] != '#')
435                tl = Coord.get(tl.x, tl.y + 1);
436            else if (tl.y > 0 && tl.y < height - 1 && map[tl.x][tl.y + 1] != '#')
437                tl = Coord.get(tl.x, tl.y - 1);
438            else
439                continue;
440
441            for (Coord r : right.leftDoors) {
442                tr = twr = r.translate(rightX, rightY);
443                if (tr.x > 0 && tr.x < width - 1 && map[tr.x - 1][tr.y] != '#')
444                    tr = Coord.get(tr.x + 1, tr.y);
445                else if (tr.x > 0 && tr.x < width - 1 && map[tr.x + 1][tr.y] != '#')
446                    tr = Coord.get(tr.x - 1, tr.y);
447                else if (tr.y > 0 && tr.y < height - 1 && map[tr.x][tr.y - 1] != '#')
448                    tr = Coord.get(tr.x, tr.y + 1);
449                else if (tr.y > 0 && tr.y < height - 1 && map[tr.x][tr.y + 1] != '#')
450                    tr = Coord.get(tr.x, tr.y - 1);
451                else
452                    continue;
453                temp = OrthoLine.line(tl, tr);
454                if (temp.size() < best) {
455                    line = temp;
456                    best = line.size();
457                    wl = twl;
458                    wr = twr;
459                }
460            }
461        }
462        temp = new ArrayList<>(line.size());
463        for (Coord c : line) {
464            if (map[c.x][c.y] == '#') {
465                map[c.x][c.y] = '.';
466                environment[c.x][c.y] = DungeonUtility.CORRIDOR_FLOOR;
467                temp.add(c);
468            }
469        }
470        if (wl != null && map[wl.x][wl.y] == '#') {
471            //if(line.isEmpty())
472            map[wl.x][wl.y] = '.';
473            environment[wl.x][wl.y] = DungeonUtility.ROOM_FLOOR;
474            //else
475            //    map[wl.x][wl.y] = '+';
476        }
477        if (wr != null && map[wr.x][wr.y] == '#') {
478            //if(line.isEmpty())
479            map[wr.x][wr.y] = '.';
480            environment[wr.x][wr.y] = DungeonUtility.ROOM_FLOOR;
481            //else
482            //    map[wr.x][wr.y] = '+';
483        }
484        layout.put(CoordPacker.packSeveral(temp), null);
485
486    }
487
488    private void connectTopBottom(MapModule top, int topX, int topY, MapModule bottom, int bottomX, int bottomY) {
489        if (top.bottomDoors == null || top.bottomDoors.isEmpty()
490                || bottom.topDoors == null || bottom.topDoors.isEmpty())
491            return;
492        List<Coord> line = new ArrayList<>(1), temp;
493        int best = 1024;
494        Coord tt, tb, twt, twb, wt = null, wb = null;
495        for (Coord l : top.bottomDoors) {
496            tt = twt = l.translate(topX, topY);
497            if (tt.y > 0 && tt.y < height - 1 && map[tt.x][tt.y - 1] != '#')
498                tt = Coord.get(tt.x, tt.y + 1);
499            else if (tt.y > 0 && tt.y < height - 1 && map[tt.x][tt.y + 1] != '#')
500                tt = Coord.get(tt.x, tt.y - 1);
501            else if (tt.x > 0 && tt.x < width - 1 && map[tt.x - 1][tt.y] != '#')
502                tt = Coord.get(tt.x + 1, tt.y);
503            else if (tt.x > 0 && tt.x < width - 1 && map[tt.x + 1][tt.y] != '#')
504                tt = Coord.get(tt.x - 1, tt.y);
505            else
506                continue;
507
508            for (Coord r : bottom.topDoors) {
509                tb = twb = r.translate(bottomX, bottomY);
510                if (tb.y > 0 && tb.y < height - 1 && map[tb.x][tb.y - 1] != '#')
511                    tb = Coord.get(tb.x, tb.y + 1);
512                else if (tb.y > 0 && tb.y < height - 1 && map[tb.x][tb.y + 1] != '#')
513                    tb = Coord.get(tb.x, tb.y - 1);
514                else if (tb.x > 0 && tb.x < width - 1 && map[tb.x - 1][tb.y] != '#')
515                    tb = Coord.get(tb.x + 1, tb.y);
516                else if (tb.x > 0 && tb.x < width - 1 && map[tb.x + 1][tb.y] != '#')
517                    tb = Coord.get(tb.x - 1, tb.y);
518                else
519                    continue;
520                temp = OrthoLine.line(tt, tb);
521                if (temp.size() < best) {
522                    line = temp;
523                    best = line.size();
524                    wt = twt;
525                    wb = twb;
526                }
527            }
528        }
529        temp = new ArrayList<>(line.size());
530        for (Coord c : line) {
531            if (map[c.x][c.y] == '#') {
532                map[c.x][c.y] = '.';
533                environment[c.x][c.y] = DungeonUtility.CORRIDOR_FLOOR;
534                temp.add(c);
535            }
536        }
537        if (wt != null && map[wt.x][wt.y] == '#') {
538            //if(line.isEmpty())
539            map[wt.x][wt.y] = '.';
540            environment[wt.x][wt.y] = DungeonUtility.ROOM_FLOOR;
541            //else
542            //    map[wl.x][wl.y] = '+';
543
544        }
545        if (wb != null && map[wb.x][wb.y] == '#') {
546            //if(line.isEmpty())
547            map[wb.x][wb.y] = '.';
548            environment[wb.x][wb.y] = DungeonUtility.ROOM_FLOOR;
549            //else
550            //    map[wb.x][wb.y] = '+';
551        }
552        layout.put(CoordPacker.packSeveral(temp), null);
553    }
554}