All Downloads are FREE. Search and download functionalities are using the official Maven repository.

squidpony.squidgrid.mapping.ClassicRogueMapGenerator Maven / Gradle / Ivy

Go to download

SquidLib platform-independent logic and utility code. Please refer to https://github.com/SquidPony/SquidLib .

There is a newer version: 3.0.6
Show newest version
package squidpony.squidgrid.mapping;


import squidpony.squidgrid.Direction;
import squidpony.squidmath.Coord;
import squidpony.squidmath.GWTRNG;
import squidpony.squidmath.IRNG;
import squidpony.squidmath.OrderedSet;

import java.util.ArrayDeque;
import java.util.ArrayList;

/**
 * Creates a dungeon in the style of the original Rogue game. It will always
 * make a grid style of rooms where there are a certain number horizontally and
 * vertically and it will link them only next to each other.
 *
 * This dungeon generator is based on a port of the rot.js version.
 *
 * @author hyakugei
 * @author Eben Howard - http://squidpony.com - [email protected]
 */
public class ClassicRogueMapGenerator implements IDungeonGenerator{

    /**
     * Holds the information needed to track rooms in the classic rogue
     * generation algorithm.
     */
    private static class ClassicRogueRoom {

        private int x, y, width, height, cellx, celly;
        private final OrderedSet connections = new OrderedSet<>(4);
        ClassicRogueRoom(int x, int y, int width, int height, int cellx, int celly) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.cellx = cellx;
            this.celly = celly;
        }

        @Override
        public int hashCode() {
            // same as Coord.hashCode(); has extremely good resistance to collisions on common points
            int r = cellx ^ celly;
            r ^= (cellx << 13 | cellx >>> 19) ^ (r << 5) ^ (r << 28 | r >>> 4);
            r = cellx ^ (r << 11 | r >>> 21);
            return r ^ (r << 25 | r >>> 7);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ClassicRogueRoom other = (ClassicRogueRoom) obj;
            if (cellx != other.cellx) {
                return false;
            }
            return celly == other.celly;
        }
    }

    private IRNG rng;

    private int horizontalRooms, verticalRooms, dungeonWidth, dungeonHeight,
            minRoomWidth, maxRoomWidth, minRoomHeight, maxRoomHeight;
    private ClassicRogueRoom[][] rooms;
    private char[][] dungeon;
    private Direction[] dirToCheck = new Direction[4];
    /**
     * Initializes the generator to turn out random dungeons within the specific
     * parameters.
     *
     * Will size down the room width and height parameters if needed to ensure
     * the desired number of rooms will fit both horizontally and vertically.
     *
     * @param horizontalRooms How many rooms will be made horizontally
     * @param verticalRooms How many rooms will be made vertically
     * @param dungeonWidth How wide the total dungeon will be
     * @param dungeonHeight How high the total dungeon will be
     * @param minRoomWidth The minimum width a room can be
     * @param maxRoomWidth The maximum width a room can be
     * @param minRoomHeight The minimum height a room can be
     * @param maxRoomHeight The maximum height a room can be
     */
    public ClassicRogueMapGenerator(int horizontalRooms, int verticalRooms, int dungeonWidth, int dungeonHeight,
                                    int minRoomWidth, int maxRoomWidth, int minRoomHeight, int maxRoomHeight) {
        this(horizontalRooms, verticalRooms, dungeonWidth, dungeonHeight, minRoomWidth, maxRoomWidth, minRoomHeight, maxRoomHeight, new GWTRNG());
    }

    /**
     * Initializes the generator to turn out random dungeons within the specific
     * parameters.
     *
     * Will size down the room width and height parameters if needed to ensure
     * the desired number of rooms will fit both horizontally and vertically.
     *
     * @param horizontalRooms How many rooms will be made horizontally
     * @param verticalRooms How many rooms will be made vertically
     * @param dungeonWidth How wide the total dungeon will be
     * @param dungeonHeight How high the total dungeon will be
     * @param minRoomWidth The minimum width a room can be
     * @param maxRoomWidth The maximum width a room can be
     * @param minRoomHeight The minimum height a room can be
     * @param maxRoomHeight The maximum height a room can be
     */
    public ClassicRogueMapGenerator(int horizontalRooms, int verticalRooms, int dungeonWidth, int dungeonHeight,
                                    int minRoomWidth, int maxRoomWidth, int minRoomHeight, int maxRoomHeight, IRNG rng)
    {
        this.rng = rng;
        this.horizontalRooms = horizontalRooms;
        this.verticalRooms = verticalRooms;
        this.dungeonWidth = dungeonWidth;
        this.dungeonHeight = dungeonHeight;
        this.minRoomWidth = minRoomWidth;
        this.maxRoomWidth = maxRoomWidth;
        this.minRoomHeight = minRoomHeight;
        this.maxRoomHeight = maxRoomHeight;

        sanitizeRoomDimensions();
    }

    private void sanitizeRoomDimensions() {
        int test = (dungeonWidth - 3 * horizontalRooms) / horizontalRooms;//have to leave space for hallways
        maxRoomWidth = Math.min(test, maxRoomWidth);
        minRoomWidth = Math.max(minRoomWidth, 2);
        minRoomWidth = Math.min(minRoomWidth, maxRoomWidth);

        test = (dungeonHeight - 3 * verticalRooms) / verticalRooms;//have to leave space for hallways
        maxRoomHeight = Math.min(test, maxRoomHeight);
        minRoomHeight = Math.max(minRoomHeight, 2);
        minRoomHeight = Math.min(minRoomHeight, maxRoomHeight);
    }

    /**
     * Builds and returns a map in the Classic Rogue style, returned as a 2D char array.
     *
     * Only includes rooms ('.' for floor and '#' for walls), corridors (using the same chars as rooms) and doors ('+'
     * for closed doors, does not generate open doors).
     * 
* There is also a create method that produces a 2D array of Terrain objects, which could (maybe) be what you want. * More methods in SquidLib expect char 2D arrays than Terrain anything, particularly in DungeonUtility. * @return a 2D char array version of the map */ public char[][] generate() { initRooms(); connectRooms(); connectUnconnectedRooms(); fullyConnect(); createRooms(); createCorridors(); return dungeon; } public char[][] getDungeon() { return dungeon; } private void initRooms() { rooms = new ClassicRogueRoom[horizontalRooms][verticalRooms]; dungeon = new char[dungeonWidth][dungeonHeight]; for (int x = 0; x < horizontalRooms; x++) { for (int y = 0; y < verticalRooms; y++) { rooms[x][y] = new ClassicRogueRoom(0, 0, 0, 0, x, y); } } for (int x = 0; x < dungeonWidth; x++) { for (int y = 0; y < dungeonHeight; y++) { dungeon[x][y] = '#'; } } } private void connectRooms() { ArrayList unconnected = new ArrayList<>(); for (int x = 0; x < horizontalRooms; x++) { for (int y = 0; y < verticalRooms; y++) { unconnected.add(rooms[x][y]); } } rng.shuffleInPlace(unconnected); for (int i = 0; i < unconnected.size(); i++) { ClassicRogueRoom room = unconnected.get(i); rng.shuffle(Direction.CARDINALS, dirToCheck); for (Direction dir : dirToCheck) { int nextX = room.x + dir.deltaX; int nextY = room.y + dir.deltaY; if (nextX < 0 || nextX >= horizontalRooms || nextY < 0 || nextY >= verticalRooms) { continue; } ClassicRogueRoom otherRoom = rooms[nextX][nextY]; if (room.connections.contains(otherRoom)) { break;//already connected to this room } if (!otherRoom.connections.isEmpty()) { room.connections.add(otherRoom); break; } } } } private void connectUnconnectedRooms() { for (int x = 0; x < horizontalRooms; x++) { for (int y = 0; y < verticalRooms; y++) { ClassicRogueRoom room = rooms[x][y]; if (room.connections.isEmpty()) { rng.shuffle(Direction.CARDINALS, dirToCheck); boolean validRoom = false; ClassicRogueRoom otherRoom = null; int idx = 0; do { Direction dir = dirToCheck[idx++]; int nextX = x + dir.deltaX; if (nextX < 0 || nextX >= horizontalRooms) { continue; } int nextY = y + dir.deltaY; if (nextY < 0 || nextY >= verticalRooms) { continue; } otherRoom = rooms[nextX][nextY]; validRoom = true; if (otherRoom.connections.contains(room)) { validRoom = false; } else { break; } } while (idx < 4); if (validRoom) { room.connections.add(otherRoom); } } } } } private void fullyConnect() { boolean allGood; do { ArrayDeque deq = new ArrayDeque<>(horizontalRooms * verticalRooms); for (int x = 0; x < horizontalRooms; x++) { for (int y = 0; y < verticalRooms; y++) { deq.offer(rooms[x][y]); } } ArrayList connected = new ArrayList<>(); connected.add(deq.removeFirst()); boolean changed = true; TESTING: while (changed) { changed = false; for (ClassicRogueRoom test : deq) { for (int i = 0, connectedSize = connected.size(); i < connectedSize; i++) { ClassicRogueRoom r = connected.get(i); if (test.connections.contains(r) || r.connections.contains(test)) { connected.add(test); deq.remove(test); changed = true; continue TESTING; } } } } allGood = true; if (!deq.isEmpty()) { TESTING: for (ClassicRogueRoom room : deq) { for (Direction dir : Direction.CARDINALS) { int x = room.cellx + dir.deltaX; int y = room.celly + dir.deltaY; if (x >= 0 && y >= 0 && x < horizontalRooms && y < verticalRooms) { ClassicRogueRoom otherRoom = rooms[x][y]; if (connected.contains(otherRoom)) { room.connections.add(otherRoom); allGood = false; break TESTING; } } } } } } while (!allGood); } private void createRooms() { int cwp = dungeonWidth / horizontalRooms; int chp = dungeonHeight / verticalRooms; ClassicRogueRoom otherRoom; for (int x = 0; x < horizontalRooms; x++) { for (int y = 0; y < verticalRooms; y++) { int sx = cwp * x; int sy = chp * y; sx = Math.max(sx, 2); sy = Math.max(sy, 2); int roomw = rng.between(minRoomWidth, maxRoomWidth + 1); int roomh = rng.between(minRoomHeight, maxRoomHeight + 1); if (y > 0) { otherRoom = rooms[x][y - 1]; while (sy - (otherRoom.y + otherRoom.height) < 3) { sy++; } } if (x > 0) { otherRoom = rooms[x - 1][y]; while (sx - (otherRoom.x + otherRoom.width) < 3) { sx++; } } int sxOffset = Math.round(rng.nextInt(cwp - roomw) * 0.5f); int syOffset = Math.round(rng.nextInt(chp - roomh) * 0.5f); while (sx + sxOffset + roomw >= dungeonWidth) { if (sxOffset > 0) { sxOffset--; } else { roomw--; } } while (sy + syOffset + roomh >= dungeonHeight) { if (syOffset > 0) { syOffset--; } else { roomh--; } } sx += sxOffset; sy += syOffset; ClassicRogueRoom r = rooms[x][y]; r.x = sx; r.y = sy; r.width = roomw; r.height = roomh; for (int xx = sx; xx < sx + roomw; xx++) { for (int yy = sy; yy < sy + roomh; yy++) { dungeon[xx][yy] = '.'; } } } } } private Coord randomWallPosition(ClassicRogueRoom room, Direction dir) { int x, y; Coord p = null; switch (dir) { case LEFT: y = rng.between(room.y + 1, room.y + room.height); x = room.x - 1; dungeon[x][y] = '+'; p = Coord.get(x - 1, y); break; case RIGHT: y = rng.between(room.y + 1, room.y + room.height); x = room.x + room.width; dungeon[x][y] = '+'; p = Coord.get(x + 1, y); break; case DOWN: x = rng.between(room.x + 1, room.x + room.width); y = room.y - 1; dungeon[x][y] = '+'; p = Coord.get(x, y - 1); break; case UP: x = rng.between(room.x + 1, room.x + room.width); y = room.y + room.height; dungeon[x][y] = '+'; p = Coord.get(x, y + 1); break; case NONE: break; case DOWN_LEFT: case DOWN_RIGHT: case UP_LEFT: case UP_RIGHT: throw new IllegalStateException("There should only be cardinal positions here"); } return p; } /** * Draws a corridor between the two points with a zig-zag in between. * * @param start * @param end */ private void digPath(Coord start, Coord end) { int xOffset = end.x - start.x; int yOffset = end.y - start.y; int xpos = start.x; int ypos = start.y; ArrayDeque moves = new ArrayDeque<>(); int xAbs = Math.abs(xOffset); int yAbs = Math.abs(yOffset); double firstHalf = rng.nextDouble(); double secondHalf = 1 - firstHalf; Direction xDir = xOffset < 0 ? Direction.LEFT : Direction.RIGHT; Direction yDir = yOffset > 0 ? Direction.DOWN : Direction.UP; if (xAbs < yAbs) { int tempDist = (int) Math.ceil(yAbs * firstHalf); moves.add(new Magnitude(yDir, tempDist)); moves.add(new Magnitude(xDir, xAbs)); tempDist = (int) Math.floor(yAbs * secondHalf); moves.add(new Magnitude(yDir, tempDist)); } else { int tempDist = (int) Math.ceil(xAbs * firstHalf); moves.add(new Magnitude(xDir, tempDist)); moves.add(new Magnitude(yDir, yAbs)); tempDist = (int) Math.floor(xAbs * secondHalf); moves.add(new Magnitude(xDir, tempDist)); } dungeon[xpos][ypos] = '.'; while (!moves.isEmpty()) { Magnitude move = moves.removeFirst(); Direction dir = move.dir; int dist = move.distance; while (dist > 0) { xpos += dir.deltaX; ypos += dir.deltaY; dungeon[xpos][ypos] = '.'; dist--; } } } private void createCorridors() { for (int x = 0; x < horizontalRooms; x++) { for (int y = 0; y < verticalRooms; y++) { ClassicRogueRoom room = rooms[x][y]; for (ClassicRogueRoom otherRoom : room.connections) { Direction dir = Direction.getCardinalDirection(otherRoom.cellx - room.cellx, otherRoom.celly - room.celly); digPath(randomWallPosition(room, dir), randomWallPosition(otherRoom, dir.opposite())); } } } } private static class Magnitude { public Direction dir; public int distance; public Magnitude(Direction dir, int distance) { this.dir = dir; this.distance = distance; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy