/******************************************************************************* * Copyright 2014 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.ai.tests.pfa.tests.tiled; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.StringBuilder; /** Utility class to generate flat and hierarchical random dungeons. * * @author davebaol */ public final class DungeonUtils { public static void main (String[] args) { // int mapSizeX = 60; // int mapSizeY = 100; // int map[][] = DungeonUtils.generate(mapSizeX, mapSizeY, MathUtils.random(70, 120), 3, 15, 100); // System.out.println(mapToString(map)); int mapSizeX = 75; int mapSizeY = 125; int map[][] = new int[mapSizeX][mapSizeY]; int submapSizeX = mapSizeX / 2; int submapSizeY = mapSizeY / 3; for (int x = 0; x < 2; x++) { for (int y = 0; y < 3; y++) { int submap[][] = DungeonUtils.generate(submapSizeX, submapSizeY, MathUtils.random(15, 67), 1, 8, 96, false); for (int x0 = 0; x0 < submapSizeX; x0++) { for (int y0 = 0; y0 < submapSizeY; y0++) { if (submap[x0][y0] != TILE_EMPTY) map[x*submapSizeX+x0][y*submapSizeY+y0] = submap[x0][y0]; } } } } // Generate corridors to connect buildings for (int x = 0; x < 2 ; x++) { for (int y = 0; y < 3; y++) { // Generates 1 or 2 corridors per building boolean corridor1 = y < 3 - 1; boolean corridor2 = x < 2 - 1; if (corridor1 && corridor2 && MathUtils.randomBoolean(.4f)) { if (MathUtils.randomBoolean()) corridor1 = false; else corridor2 = false; } if (corridor1) { int x0 = MathUtils.random(x * submapSizeX + 1, x * submapSizeX + submapSizeX / 2); int y0 = y * submapSizeY + 1; for (int i = submapSizeY + 5; i > 0 ; i--) { if (i < submapSizeY && map[x0][y0 + i] == TILE_FLOOR) break; map[x0][y0 + i] = TILE_FLOOR; } } if (corridor2) { int x1 = x * submapSizeX + 1; int y1 = MathUtils.random(y * submapSizeY + 1, y * submapSizeY + submapSizeY / 2); for (int i = submapSizeX + 5; i > 0 ; i--) { if (i < submapSizeX && map[x1 + i][y1] == TILE_FLOOR) break; map[x1 + i][y1] = TILE_FLOOR; } } } } addMissingWalls(map); System.out.println(mapToString(map)); } static final int TILE_EMPTY = 0; static final int TILE_FLOOR = 1; static final int TILE_WALL = 2; private DungeonUtils () { } public static int[][] generate (int mapSizeX, int mapSizeY, int roomCount, int roomMinSize, int roomMaxSize, int squashIterations) { return generate (mapSizeX, mapSizeY, roomCount, roomMinSize, roomMaxSize, squashIterations, true); } public static int[][] generate (int mapSizeX, int mapSizeY, int roomCount, int roomMinSize, int roomMaxSize, int squashIterations, boolean addMissingWalls) { int[][] map = new int[mapSizeX][mapSizeY]; for (int x = 0; x < mapSizeX; x++) { for (int y = 0; y < mapSizeY; y++) { map[x][y] = TILE_EMPTY; } } // Generate random rooms and make sure they don't collide each other. // Also decrease the room width and height by 1 so as to make sure that no two rooms // are directly next to one another (making one big room). Array<Room> rooms = new Array<Room>(); for (int k = 0; k < squashIterations; k++) { System.out.println("k:" + k + ", rooms:" + rooms.size); int failures = 0; for (int i = rooms.size; i < roomCount; i++) { if (failures > 1000) { break; } Room room = new Room(); room.x = MathUtils.random(1, mapSizeX - roomMaxSize - 1); room.y = MathUtils.random(1, mapSizeY - roomMaxSize - 1); room.w = MathUtils.random(roomMinSize, roomMaxSize); room.h = MathUtils.random(roomMinSize, roomMaxSize); if (collides(rooms, room)) { i--; failures++; continue; } room.w--; room.h--; rooms.add(room); } // Move all the rooms closer to one another to get rid of some large gaps squashRooms(rooms); } roomCount = rooms.size; // Build corridors between rooms that are near to one another. // We choose a random point in each room and then move the second point towards the // first one (in the while loop). for (int i = 0; i < roomCount; i++) { Room roomA = rooms.get(i); Room roomB = findClosestRoom(rooms, roomA); int pointAx = MathUtils.random(roomA.x, roomA.x + roomA.w); int pointAy = MathUtils.random(roomA.y, roomA.y + roomA.h); int pointBx = MathUtils.random(roomB.x, roomB.x + roomB.w); int pointBy = MathUtils.random(roomB.y, roomB.y + roomB.h); while ((pointBx != pointAx) || (pointBy != pointAy)) { if (pointBx != pointAx) { if (pointBx > pointAx) pointBx--; else pointBx++; } else if (pointBy != pointAy) { if (pointBy > pointAy) pointBy--; else pointBy++; } map[pointBx][pointBy] = TILE_FLOOR; } } // Iterate through all the rooms and set the tile to FLOOR for every tile within a room for (int i = 0; i < roomCount; i++) { Room room = rooms.get(i); for (int x = room.x; x < room.x + room.w; x++) { for (int y = room.y; y < room.y + room.h; y++) { map[x][y] = TILE_FLOOR; } } } // Convert to a wall tile any empty tile touching a floor tile if (addMissingWalls) addMissingWalls(map); return map; } public static TwoLevelHierarchy generate2LevelHierarchy (int mapSizeX, int mapSizeY, int buildingsX, int buildingsY, int roomMinCount, int roomMaxCount, int roomMinSize, int roomMaxSize, int squashIterations) { int map[][] = new int[mapSizeX][mapSizeY]; boolean level1Con1[][] = new boolean[buildingsX][buildingsY]; boolean level1Con2[][] = new boolean[buildingsX][buildingsY]; int submapSizeX = mapSizeX / buildingsX; int submapSizeY = mapSizeY / buildingsY; // Generate buildings for (int x = 0; x < buildingsX; x++) { for (int y = 0; y < buildingsY; y++) { int submap[][] = DungeonUtils.generate(submapSizeX, submapSizeY, MathUtils.random(roomMinCount, roomMaxCount), roomMinSize, roomMaxSize, squashIterations, false); for (int x0 = 0; x0 < submapSizeX; x0++) { for (int y0 = 0; y0 < submapSizeY; y0++) { if (submap[x0][y0] != TILE_EMPTY) map[x*submapSizeX+x0][y*submapSizeY+y0] = submap[x0][y0]; } } } } // Generate corridors to connect buildings for (int x = 0; x < buildingsX ; x++) { for (int y = 0; y < buildingsY; y++) { // Generates 1 or 2 corridors per building boolean corridor1 = y < buildingsY - 1; boolean corridor2 = x < buildingsX - 1; if (corridor1 && corridor2 && MathUtils.randomBoolean(.5f)) { if (MathUtils.randomBoolean()) corridor1 = false; else corridor2 = false; } if (corridor1) { level1Con1[x][y] = true; int x0 = MathUtils.random(x * submapSizeX + 1, x * submapSizeX + submapSizeX / 2); int y0 = y * submapSizeY + 1; for (int i = submapSizeY + 5; i > 0 ; i--) { if (i < submapSizeY && map[x0][y0 + i] == TILE_FLOOR) break; map[x0][y0 + i] = TILE_FLOOR; } } if (corridor2) { level1Con2[x][y] = true; int x1 = x * submapSizeX + 1; int y1 = MathUtils.random(y * submapSizeY + 1, y * submapSizeY + submapSizeY / 2); for (int i = submapSizeX + 5; i > 0 ; i--) { if (i < submapSizeX && map[x1 + i][y1] == TILE_FLOOR) break; map[x1 + i][y1] = TILE_FLOOR; } } } } addMissingWalls(map); // System.out.println(mapToString(map)); return new TwoLevelHierarchy(map, level1Con1, level1Con2); } // Iterates through all the tiles in the map and if it finds a tile that is a FLOOR // we check all the surrounding tiles for empty values. If we find an empty tile (that // touches the floor) we build a WALL. public static void addMissingWalls(int[][] map) { int mapSizeX = map.length; int mapSizeY = map[0].length; for (int x = 0; x < mapSizeX; x++) { for (int y = 0; y < mapSizeY; y++) { if (map[x][y] == TILE_FLOOR) { for (int xx = x - 1; xx <= x + 1; xx++) { for (int yy = y - 1; yy <= y + 1; yy++) { if (map[xx][yy] == TILE_EMPTY) map[xx][yy] = TILE_WALL; } } } } } } private static boolean collides (Array<Room> rooms, Room room) { return collides(rooms, room, -1); } private static boolean collides (Array<Room> rooms, Room room, int ignore) { for (int i = 0; i < rooms.size; i++) { if (i == ignore) continue; Room check = rooms.get(i); if (!((room.x + room.w < check.x) || (room.x > check.x + check.w) || (room.y + room.h < check.y) || (room.y > check.y + check.h))) return true; } return false; } private static void squashRooms (Array<Room> rooms) { for (int i = 0; i < 20; i++) { for (int j = 0; j < rooms.size; j++) { Room room = rooms.get(j); while (true) { int oldX = room.x; int oldY = room.y; if (room.x > 1) room.x--; if (room.y > 1) room.y--; if ((room.x == 1) && (room.y == 1)) break; if (collides(rooms, room, j)) { room.x = oldX; room.y = oldY; break; } } } } } private static Room findClosestRoom (Array<Room> rooms, Room room) { float midX = room.x + (room.w / 2f); float midY = room.y + (room.h / 2f); Room closest = null; float closestDistance = Float.POSITIVE_INFINITY; for (int i = 0; i < rooms.size; i++) { Room check = rooms.get(i); if (check == room) continue; float checkMidX = check.x + (check.w / 2f); float checkMidY = check.y + (check.h / 2f); float distance = Math.min(Math.abs(midX - checkMidX) - (room.w / 2f) - (check.w / 2f), Math.abs(midY - checkMidY) - (room.h / 2f) - (check.h / 2f)); if (distance < closestDistance) { closestDistance = distance; closest = check; } } return closest; } public static String mapToString (int[][] map) { StringBuilder sb = new StringBuilder(map.length * (map[0].length + 1)); // +1 is due to the new line char for (int x = 0; x < map.length; x++) { for (int y = 0; y < map[0].length; y++) { switch (map[x][y]) { case TILE_EMPTY: sb.append(' '); break; case TILE_FLOOR: sb.append('.'); break; case TILE_WALL: sb.append('#'); break; default: sb.append('?'); break; } } sb.append('\n'); } return sb.toString(); } private static class Room { int x, y, w, h; } public static class TwoLevelHierarchy { public int[][] level0; public boolean[][] level1Con1; public boolean[][] level1Con2; public TwoLevelHierarchy(int[][] level0, boolean[][] level1Con1, boolean[][] level1Con2) { this.level0 = level0; this.level1Con1 = level1Con1; this.level1Con2 = level1Con2; } } }