From d5e5e12cf920ddc9c742a57ddeea8dfbd38ee5e4 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Mon, 7 Apr 2014 09:25:20 -0400 Subject: [PATCH] Refactored Maze Generation to use RoomData Rewrote portions of our maze generation code to use RoomData. This provides an object that unifies all room data instead of having it spread across various data structures and linked loosely by hash maps. We'll need this to implement the remaining generation features. --- .../experimental/MazeBuilder.java | 17 +- .../experimental/MazeDesign.java | 24 +-- .../experimental/MazeDesigner.java | 202 +++++++++--------- .../experimental/MazeLinkData.java | 6 + .../experimental/PartitionNode.java | 13 +- .../StevenDimDoors/experimental/RoomData.java | 75 +++++++ 6 files changed, 206 insertions(+), 131 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/MazeLinkData.java create mode 100644 src/main/java/StevenDimDoors/experimental/RoomData.java diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 9e31e9a..00348c1 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -18,8 +18,8 @@ public class MazeBuilder Point3D offset = new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2); SphereDecayOperation decay = new SphereDecayOperation(random, 0, 0, Block.stoneBrick.blockID, 2); - buildRooms(design.getRoomGraph(), world, offset); - carveDoorways(design.getRoomGraph(), world, offset, decay, random); + buildRooms(design.getLayout(), world, offset); + carveDoorways(design.getLayout(), world, offset, decay, random); //placeDoors(design, world, offset); @@ -32,25 +32,25 @@ public class MazeBuilder //final int DECAY_BOX_SIZE = 8 } - private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) + private static void buildRooms(DirectedGraph layout, World world, Point3D offset) { - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - PartitionNode room = node.data(); + PartitionNode room = node.data().getPartitionNode(); buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); } } - private static void carveDoorways(DirectedGraph roomGraph, World world, + private static void carveDoorways(DirectedGraph layout, World world, Point3D offset, SphereDecayOperation decay, Random random) { char axis; Point3D lower; DoorwayData doorway; - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - for (IEdge passage : node.outbound()) + for (IEdge passage : node.outbound()) { doorway = passage.data(); axis = doorway.axis(); @@ -165,7 +165,6 @@ public class MazeBuilder setBlockDirectly(world, x, y, z, 0, 0); setBlockDirectly(world, x, y + 1, z, 0, 0); } - private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) { diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesign.java b/src/main/java/StevenDimDoors/experimental/MazeDesign.java index e7d4b4c..c88f1cd 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesign.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesign.java @@ -5,16 +5,12 @@ import java.util.ArrayList; public class MazeDesign { private PartitionNode root; - private DirectedGraph rooms; - private ArrayList> cores; - private ArrayList protectedAreas; + private DirectedGraph layout; - public MazeDesign(PartitionNode root, DirectedGraph rooms, - ArrayList> cores) + public MazeDesign(PartitionNode root, DirectedGraph layout) { this.root = root; - this.rooms = rooms; - this.cores = cores; + this.layout = layout; } public PartitionNode getRootPartition() @@ -22,19 +18,9 @@ public class MazeDesign return root; } - public DirectedGraph getRoomGraph() + public DirectedGraph getLayout() { - return rooms; - } - - public ArrayList> getCoreNodes() - { - return cores; - } - - public ArrayList getProtectedAreas() - { - return protectedAreas; + return layout; } public int width() diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 289931a..e9d7c31 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -25,55 +25,66 @@ public class MazeDesigner public static MazeDesign generate(Random random) { // Construct a random binary space partitioning of our maze volume - PartitionNode root = partitionRooms(MAZE_WIDTH, MAZE_HEIGHT, MAZE_LENGTH, SPLIT_COUNT, random); + PartitionNode root = partitionRooms(MAZE_WIDTH, MAZE_HEIGHT, MAZE_LENGTH, SPLIT_COUNT, random); - // List all the leaf nodes of the partition tree, which denote individual rooms - ArrayList partitions = new ArrayList(1 << SPLIT_COUNT); - listRoomPartitions(root, partitions); + // Attach rooms to all the leaf nodes of the partition tree + ArrayList rooms = new ArrayList(1 << SPLIT_COUNT); + attachRooms(root, rooms); + + // Shuffle the list of rooms so that they're not listed in any ordered way in the room graph + // This is the only convenient way of randomizing the maze sections generated later + Collections.shuffle(rooms, random); // Construct an adjacency graph of the rooms we've carved out. Two rooms are // considered adjacent if and only if a doorway could connect them. Their // common boundary must be large enough for a doorway. - DirectedGraph rooms = createRoomGraph(root, partitions, random); + DirectedGraph layout = createRoomGraph(root, rooms, random); // Cut out random subgraphs from the adjacency graph - ArrayList> cores = createMazeSections(rooms, random); + ArrayList cores = createMazeSections(layout, random); // Remove unnecessary passages through floors/ceilings and some from the walls - for (IGraphNode core : cores) + for (RoomData core : cores) { - pruneDoorways(core, rooms, random); + pruneDoorways(core.getLayoutNode(), layout, random); } - return new MazeDesign(root, rooms, cores); + return new MazeDesign(root, layout); } - private static void listRoomPartitions(PartitionNode node, ArrayList partitions) + private static void attachRooms(PartitionNode node, ArrayList partitions) { if (node.isLeaf()) { - partitions.add(node); + partitions.add(new RoomData(node)); } else { - listRoomPartitions(node.leftChild(), partitions); - listRoomPartitions(node.rightChild(), partitions); + attachRooms(node.leftChild(), partitions); + attachRooms(node.rightChild(), partitions); } } - private static void removeRoomPartitions(PartitionNode node) + private static void removeRoom(RoomData room, DirectedGraph layout) { - // Remove a node and any of its ancestors that become leaf nodes + // Remove the room from the partition tree and from the layout graph. + // Also remove any ancestors that become leaf nodes. PartitionNode parent; PartitionNode current; - current = node; + current = room.getPartitionNode(); while (current != null && current.isLeaf()) { parent = current.parent(); current.remove(); current = parent; } + + // Remove the room from the layout graph + layout.removeNode(room.getLayoutNode()); + + // Wipe the room's data, as a precaution. + room.clear(); } private static PartitionNode partitionRooms(int width, int height, int length, int maxLevels, Random random) @@ -140,35 +151,25 @@ public class MazeDesigner } } - private static DirectedGraph createRoomGraph(PartitionNode root, ArrayList partitions, Random random) + private static DirectedGraph createRoomGraph(PartitionNode root, ArrayList rooms, Random random) { - DirectedGraph roomGraph = new DirectedGraph(); - HashMap> roomsToGraph = new HashMap>(2 * partitions.size()); - - // Shuffle the list of rooms so that they're not listed in any ordered way in the room graph - // This is the only convenient way of randomizing the maze sections generated later - Collections.shuffle(partitions, random); + DirectedGraph layout = new DirectedGraph(); // Add all rooms to a graph - // Also add them to a map so we can associate rooms with their graph nodes - // The map is needed for linking graph nodes based on adjacent partitions - for (PartitionNode partition : partitions) + for (RoomData room : rooms) { - roomsToGraph.put(partition, roomGraph.addNode(partition)); + room.addToLayout(layout); } - // Add edges for each room - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - findDoorways(node, root, roomsToGraph, roomGraph); + findDoorways(node.data(), root, layout); } - - return roomGraph; + return layout; } - private static void findDoorways(IGraphNode roomNode, PartitionNode root, - HashMap> roomsToGraph, - DirectedGraph roomGraph) + private static void findDoorways(RoomData room, PartitionNode root, + DirectedGraph layout) { // This function finds rooms adjacent to a specified room that could be connected // to it through a doorway. Edges are added to the room graph to denote rooms that @@ -186,7 +187,7 @@ public class MazeDesigner // there will always be a way to walk from any room to any other room. boolean[][] detected; - PartitionNode adjacent; + PartitionNode adjacent; int a, b, c; int p, q, r; @@ -195,11 +196,10 @@ public class MazeDesigner Point3D otherMin; Point3D otherMax; DoorwayData doorway; - IGraphNode adjacentNode; - PartitionNode room = roomNode.data(); - Point3D minCorner = room.minCorner(); - Point3D maxCorner = room.maxCorner(); + PartitionNode partition = room.getPartitionNode(); + Point3D minCorner = partition.minCorner(); + Point3D maxCorner = partition.maxCorner(); int minX = minCorner.getX(); int minY = minCorner.getY(); @@ -209,9 +209,9 @@ public class MazeDesigner int maxY = maxCorner.getY(); int maxZ = maxCorner.getZ(); - int width = room.width(); - int height = room.height(); - int length = room.length(); + int width = partition.width(); + int height = partition.height(); + int length = partition.length(); if (maxZ < root.maxCorner().getZ()) { @@ -247,8 +247,7 @@ public class MazeDesigner otherMin = new Point3D(minXI, minYI, maxZ); otherMax = new Point3D(maxXI, maxYI, maxZ + 1); doorway = new DoorwayData(otherMin, otherMax, DoorwayData.Z_AXIS); - adjacentNode = roomsToGraph.get(adjacent); - roomGraph.addEdge(roomNode, adjacentNode, doorway); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -295,8 +294,7 @@ public class MazeDesigner otherMin = new Point3D(maxX, minYI, minZI); otherMax = new Point3D(maxX + 1, maxYI, maxZI); doorway = new DoorwayData(otherMin, otherMax, DoorwayData.X_AXIS); - adjacentNode = roomsToGraph.get(adjacent); - roomGraph.addEdge(roomNode, adjacentNode, doorway); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -343,8 +341,7 @@ public class MazeDesigner otherMin = new Point3D(minXI, maxY, minZI); otherMax = new Point3D(maxXI, maxY + 1, maxZI); doorway = new DoorwayData(otherMin, otherMax, DoorwayData.Y_AXIS); - adjacentNode = roomsToGraph.get(adjacent); - roomGraph.addEdge(roomNode, adjacentNode, doorway); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -359,7 +356,7 @@ public class MazeDesigner //Done! } - private static ArrayList> createMazeSections(DirectedGraph roomGraph, Random random) + private static ArrayList createMazeSections(DirectedGraph layout, Random random) { // The randomness of the sections generated here hinges on // the nodes in the graph being in a random order. We assume @@ -369,66 +366,68 @@ public class MazeDesigner final int MIN_SECTION_ROOMS = 5; int distance; - IGraphNode current; - IGraphNode neighbor; + RoomData room; + RoomData neighbor; + IGraphNode current; - ArrayList> cores = new ArrayList>(); - ArrayList> removals = new ArrayList>(); - ArrayList> section = new ArrayList>(); + ArrayList cores = new ArrayList(); + ArrayList removals = new ArrayList(); + ArrayList section = new ArrayList(); - Queue> ordering = new LinkedList>(); - HashMap, Integer> distances = new HashMap, Integer>(); + Queue ordering = new LinkedList(); // Repeatedly generate sections until all nodes have been visited - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - // If this node hasn't been visited, then use it as the core of a new section + // If this room hasn't been visited (distance = -1), then use it as the core of a new section // Otherwise, ignore it, since it was already processed - if (!distances.containsKey(node)) + room = node.data(); + if (room.getDistance() < 0) { // Perform a breadth-first search to tag surrounding nodes with distances - distances.put(node, 0); - ordering.add(node); + room.setDistance(0); + ordering.add(room); section.clear(); while (!ordering.isEmpty()) { - current = ordering.remove(); - distance = distances.get(current) + 1; + room = ordering.remove(); + distance = room.getDistance() + 1; if (distance <= MAX_DISTANCE + 1) { - section.add(current); + section.add(room); + current = room.getLayoutNode(); - // Visit neighboring nodes and assign them distances, if they don't - // have a distance assigned already - for (IEdge edge : current.inbound()) + // Visit neighboring rooms and assign them distances, if they don't + // have a proper distance assigned already + for (IEdge edge : current.inbound()) { - neighbor = edge.head(); - if (!distances.containsKey(neighbor)) + neighbor = edge.head().data(); + if (neighbor.getDistance() < 0) { - distances.put(neighbor, distance); + neighbor.setDistance(distance); ordering.add(neighbor); } } - for (IEdge edge : current.outbound()) + for (IEdge edge : current.outbound()) { - neighbor = edge.tail(); - if (!distances.containsKey(neighbor)) + neighbor = edge.tail().data(); + if (neighbor.getDistance() < 0) { - distances.put(neighbor, distance); + neighbor.setDistance(distance); ordering.add(neighbor); } } } else { - removals.add(current); + removals.add(room); break; } } - // List nodes that have a distance of exactly MAX_DISTANCE + 1 + // List rooms that have a distance of exactly MAX_DISTANCE + 1 // Those are precisely the nodes that remain in the queue // We can't remove them immediately because that could break // the iterator for the graph. @@ -440,7 +439,7 @@ public class MazeDesigner // Check if this section contains enough rooms if (section.size() >= MIN_SECTION_ROOMS) { - cores.add(node); + cores.add(node.data()); } else { @@ -449,18 +448,17 @@ public class MazeDesigner } } - // Remove all the nodes that were listed for removal + // Remove all the rooms that were listed for removal // Also remove unused partitions from the partition tree - for (IGraphNode node : removals) + for (RoomData target : removals) { - removeRoomPartitions(node.data()); - roomGraph.removeNode(node); + removeRoom(target, layout); } return cores; } - private static void pruneDoorways(IGraphNode core, - DirectedGraph rooms, Random random) + private static void pruneDoorways(IGraphNode core, + DirectedGraph layout, Random random) { // We receive a node for one of the rooms in a section of the maze // and we need to remove as many floor doorways as possible while @@ -478,12 +476,12 @@ public class MazeDesigner // idea applies for the other doorways, plus some randomness. // First, list all nodes in the subgraph - IGraphNode current; - IGraphNode neighbor; + IGraphNode current; + IGraphNode neighbor; - Stack> ordering = new Stack>(); - ArrayList> subgraph = new ArrayList>(64); - DisjointSet> components = new DisjointSet>(128); + Stack> ordering = new Stack>(); + ArrayList> subgraph = new ArrayList>(64); + DisjointSet> components = new DisjointSet>(128); ordering.add(core); components.makeSet(core); @@ -492,7 +490,7 @@ public class MazeDesigner current = ordering.pop(); subgraph.add(current); - for (IEdge edge : current.inbound()) + for (IEdge edge : current.inbound()) { neighbor = edge.head(); if (components.makeSet(neighbor)) @@ -500,7 +498,7 @@ public class MazeDesigner ordering.add(neighbor); } } - for (IEdge edge : current.outbound()) + for (IEdge edge : current.outbound()) { neighbor = edge.tail(); if (components.makeSet(neighbor)) @@ -513,12 +511,12 @@ public class MazeDesigner // Now iterate over the list of nodes and merge their sets // We only have to look at outbound edges since inbound edges mirror them // Also list any Y_AXIS doorways we come across - ArrayList> targets = - new ArrayList>(); + ArrayList> targets = + new ArrayList>(); - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { - for (IEdge passage : room.outbound()) + for (IEdge passage : room.outbound()) { if (passage.data().axis() != DoorwayData.Y_AXIS) { @@ -535,11 +533,11 @@ public class MazeDesigner Collections.shuffle(targets, random); // Merge sets together and remove unnecessary doorways - for (IEdge passage : targets) + for (IEdge passage : targets) { if (!components.mergeSets(passage.head(), passage.tail())) { - rooms.removeEdge(passage); + layout.removeEdge(passage); } } @@ -548,13 +546,13 @@ public class MazeDesigner components.clear(); targets.clear(); - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { components.makeSet(room); } - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { - for (IEdge passage : room.outbound()) + for (IEdge passage : room.outbound()) { if (passage.data().axis() == DoorwayData.Y_AXIS) { @@ -567,11 +565,11 @@ public class MazeDesigner } } Collections.shuffle(targets, random); - for (IEdge passage : targets) + for (IEdge passage : targets) { if (!components.mergeSets(passage.head(), passage.tail()) && random.nextBoolean()) { - rooms.removeEdge(passage); + layout.removeEdge(passage); } } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeLinkData.java b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java new file mode 100644 index 0000000..efff141 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java @@ -0,0 +1,6 @@ +package StevenDimDoors.experimental; + +public class MazeLinkData +{ + +} diff --git a/src/main/java/StevenDimDoors/experimental/PartitionNode.java b/src/main/java/StevenDimDoors/experimental/PartitionNode.java index df53854..5226378 100644 --- a/src/main/java/StevenDimDoors/experimental/PartitionNode.java +++ b/src/main/java/StevenDimDoors/experimental/PartitionNode.java @@ -2,11 +2,12 @@ package StevenDimDoors.experimental; import StevenDimDoors.mod_pocketDim.Point3D; -public class PartitionNode extends BoundingBox +public class PartitionNode extends BoundingBox { private PartitionNode parent; private PartitionNode leftChild = null; private PartitionNode rightChild = null; + private T data = null; public PartitionNode(int width, int height, int length) { @@ -122,4 +123,14 @@ public class PartitionNode extends BoundingBox return this; } } + + public void setData(T value) + { + this.data = value; + } + + public T getData() + { + return data; + } } diff --git a/src/main/java/StevenDimDoors/experimental/RoomData.java b/src/main/java/StevenDimDoors/experimental/RoomData.java new file mode 100644 index 0000000..eacfcaf --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/RoomData.java @@ -0,0 +1,75 @@ +package StevenDimDoors.experimental; + +import java.util.ArrayList; + +public class RoomData +{ + private int distance; + private boolean decayed; + private PartitionNode partitionNode; + private ArrayList inboundLinks; + private ArrayList outboundLinks; + private IGraphNode layoutNode; + + public RoomData(PartitionNode partitionNode) + { + this.partitionNode = partitionNode; + this.inboundLinks = new ArrayList(); + this.outboundLinks = new ArrayList(); + this.layoutNode = null; + this.distance = -1; + this.decayed = false; + partitionNode.setData(this); + } + + public PartitionNode getPartitionNode() + { + return this.partitionNode; + } + + public IGraphNode getLayoutNode() + { + return this.layoutNode; + } + + public void addToLayout(DirectedGraph layout) + { + this.layoutNode = layout.addNode(this); + } + + public boolean isDecayed() + { + return decayed; + } + + public void setDecayed(boolean value) + { + this.decayed = value; + } + + public ArrayList getInboundLinks() + { + return this.inboundLinks; + } + + public ArrayList getOutboundLinks() + { + return this.outboundLinks; + } + + public int getDistance() + { + return distance; + } + + public void setDistance(int value) + { + distance = value; + } + + public void clear() + { + partitionNode = null; + layoutNode = null; + } +}