From 53b5591149f1883291c57f08a221d75493430da4 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Fri, 11 Apr 2014 19:28:05 -0400 Subject: [PATCH] Partial Implementation of Doors in Mazes Made progress on implementing the placement of doors in mazes. Still incomplete. --- .../StevenDimDoors/experimental/LinkPlan.java | 87 ++++++++++++ .../experimental/MazeBuilder.java | 20 ++- .../experimental/MazeDesigner.java | 128 ++++++++++++++---- .../experimental/MazeLinkData.java | 6 - .../StevenDimDoors/experimental/RoomData.java | 69 ++++++++-- 5 files changed, 270 insertions(+), 40 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/LinkPlan.java delete mode 100644 src/main/java/StevenDimDoors/experimental/MazeLinkData.java diff --git a/src/main/java/StevenDimDoors/experimental/LinkPlan.java b/src/main/java/StevenDimDoors/experimental/LinkPlan.java new file mode 100644 index 0000000..3fe6f75 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/LinkPlan.java @@ -0,0 +1,87 @@ +package StevenDimDoors.experimental; + +public class LinkPlan +{ + private RoomData source; + private RoomData destination; + private boolean entrance; + + private LinkPlan(RoomData source, RoomData destination, boolean entrance) + { + this.source = source; + this.destination = destination; + this.entrance = entrance; + } + + public static LinkPlan createInternalLink(RoomData source, RoomData destination) + { + if (source == null) + { + throw new IllegalArgumentException("source cannot be null."); + } + if (destination == null) + { + throw new IllegalArgumentException("destination cannot be null."); + } + LinkPlan plan = new LinkPlan(source, destination, false); + source.getOutboundLinks().add(plan); + destination.getInboundLinks().add(plan); + return plan; + } + + public static LinkPlan createEntranceLink(RoomData source) + { + if (source == null) + { + throw new IllegalArgumentException("source cannot be null."); + } + LinkPlan plan = new LinkPlan(source, null, true); + source.getOutboundLinks().add(plan); + return plan; + } + + public static LinkPlan createDungeonLink(RoomData source) + { + if (source == null) + { + throw new IllegalArgumentException("source cannot be null."); + } + LinkPlan plan = new LinkPlan(source, null, false); + source.getOutboundLinks().add(plan); + return plan; + } + + public RoomData source() + { + return this.source; + } + + public RoomData destination() + { + return this.destination; + } + + public boolean isEntrance() + { + return entrance; + } + + public boolean isInternal() + { + return (destination != null); + } + + public void remove() + { + if (source != null) + { + source.getOutboundLinks().remove(this); + source = null; + } + if (destination != null) + { + destination.getInboundLinks().remove(this); + destination = null; + } + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 00348c1..6505f99 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -14,6 +14,8 @@ public class MazeBuilder public static void generate(World world, int x, int y, int z, Random random) { + // ISSUE FOR LATER: The room needs to be shifted so as to be centered on its entrance + MazeDesign design = MazeDesigner.generate(random); 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); @@ -21,7 +23,7 @@ public class MazeBuilder buildRooms(design.getLayout(), world, offset); carveDoorways(design.getLayout(), world, offset, decay, random); - //placeDoors(design, world, offset); + placeDoors(design.getLayout(), world, offset); applyRandomDestruction(design, world, offset, decay, random); } @@ -41,6 +43,22 @@ public class MazeBuilder } } + private static void placeDoors(DirectedGraph layout, World world, Point3D offset) + { + for (IGraphNode node : layout.nodes()) + { + RoomData room = node.data(); + Point3D minCorner = room.getPartitionNode().minCorner(); + if (!room.getOutboundLinks().isEmpty()) + { + setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 1, + offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0); + setBlockDirectly(world, offset.getX() + minCorner.getX(), offset.getY() + minCorner.getY() + 2, + offset.getZ() + minCorner.getZ(), Block.glowStone.blockID, 0); + } + } + } + private static void carveDoorways(DirectedGraph layout, World world, Point3D offset, SphereDecayOperation decay, Random random) { diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index ff01d9f..0026303 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -1,7 +1,9 @@ package StevenDimDoors.experimental; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Random; @@ -48,6 +50,9 @@ public class MazeDesigner pruneDoorways(core.getLayoutNode(), layout, random); } + // Set up the placement of dimensional doors within the maze + createMazeLinks(layout, cores, random); + return new MazeDesign(root, layout); } @@ -64,28 +69,6 @@ public class MazeDesigner } } - private static void removeRoom(RoomData room, DirectedGraph layout) - { - // 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 = 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) { PartitionNode root = new PartitionNode(width, height, length); @@ -468,7 +451,7 @@ public class MazeDesigner // Remove all the rooms that were listed for removal for (RoomData target : removals) { - removeRoom(target, layout); + target.remove(); } return cores; } @@ -529,7 +512,6 @@ public class MazeDesigner // at outbound edges since inbound edges mirror them. List any Y_AXIS // doorways we come across to consider removing them later, depending // on their impact on connectedness. - // doorways. ArrayList> targets = new ArrayList>(); @@ -557,6 +539,7 @@ public class MazeDesigner if (!components.mergeSets(passage.head(), passage.tail())) { layout.removeEdge(passage); + } } @@ -593,4 +576,101 @@ public class MazeDesigner } } + private static void createMazeLinks(DirectedGraph layout, + ArrayList cores, Random random) + { + // We have 4 objectives here... + // 1. Place the entrance to the maze + // 2. Place internal links connecting the different sections of the maze + // 3. Place links to other dungeons + // 4. Place more internal links to confuse people + + // We need to start by counting the door capacity of each section and + // listing which rooms can have doors or destinations for each section. + int index; + int[] capacity = new int[cores.size()]; + ArrayList[] sourceRooms = (ArrayList[]) Array.newInstance(cores.getClass(), cores.size()); + ArrayList[] destinationRooms = (ArrayList[]) Array.newInstance(cores.getClass(), cores.size()); + + for (index = 0; index < sourceRooms.length; index++) + { + sourceRooms[index] = new ArrayList(); + destinationRooms[index] = new ArrayList(); + capacity[index] = listLinkRooms(cores.get(index).getLayoutNode(), sourceRooms[index], destinationRooms[index]); + } + + // Now we select the room in which to place the entrance. + // We can safely assume all source room lists are non-empty because + // createMazeSections() guarantees that each section has at least + // the capacity for 2 doors. + index = random.nextInt(sourceRooms.length); + createEntranceLink(sourceRooms[index], random.nextInt(sourceRooms[index].size())); + + // The next task is to place internal links. These links must connect + // the different maze sections to create a strongly connected graph. + + } + + private static int listLinkRooms(IGraphNode core, + ArrayList sourceRooms, ArrayList destinationRooms) + { + int capacity = 0; + boolean hasHoles; + RoomData currentRoom; + IGraphNode current; + IGraphNode neighbor; + Stack> ordering = new Stack>(); + HashSet> visited = new HashSet>(); + + visited.add(core); + ordering.add(core); + while (!ordering.isEmpty()) + { + current = ordering.pop(); + hasHoles = false; + + for (IEdge edge : current.outbound()) + { + neighbor = edge.tail(); + if (visited.add(neighbor)) + { + ordering.add(neighbor); + } + } + for (IEdge edge : current.inbound()) + { + neighbor = edge.head(); + if (visited.add(neighbor)) + { + ordering.add(neighbor); + } + if (edge.data().axis() == DoorwayData.Y_AXIS) + { + hasHoles = true; + } + } + + if (!hasHoles) + { + currentRoom = current.data(); + destinationRooms.add(currentRoom); + if (currentRoom.estimateDoorCapacity() > 0) + { + capacity += currentRoom.estimateDoorCapacity(); + sourceRooms.add(currentRoom); + } + } + } + return capacity; + } + + private static void createEntranceLink(ArrayList sources, int index) + { + RoomData entranceRoom = sources.get(index); + LinkPlan.createEntranceLink(entranceRoom); + if (entranceRoom.getRemainingDoorCapacity() == 0) + { + sources.remove(index); + } + } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeLinkData.java b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java deleted file mode 100644 index efff141..0000000 --- a/src/main/java/StevenDimDoors/experimental/MazeLinkData.java +++ /dev/null @@ -1,6 +0,0 @@ -package StevenDimDoors.experimental; - -public class MazeLinkData -{ - -} diff --git a/src/main/java/StevenDimDoors/experimental/RoomData.java b/src/main/java/StevenDimDoors/experimental/RoomData.java index 0334d33..c87c406 100644 --- a/src/main/java/StevenDimDoors/experimental/RoomData.java +++ b/src/main/java/StevenDimDoors/experimental/RoomData.java @@ -4,20 +4,32 @@ import java.util.ArrayList; public class RoomData { + /* Implementation Note: + * Plans for links between rooms are stored in lists rather than a graph, + * even though they duplicate some graph functionality, because there are + * relatively few of them compared to the number of rooms. Moreover, some + * links don't even have destinations because they're intended to lead to + * other dungeons. + */ + + private int capacity; private int distance; private boolean decayed; private PartitionNode partitionNode; - private ArrayList inboundLinks; - private ArrayList outboundLinks; + private ArrayList inboundLinks; + private ArrayList outboundLinks; + private DirectedGraph layout; private IGraphNode layoutNode; public RoomData(PartitionNode partitionNode) { this.partitionNode = partitionNode; - this.inboundLinks = new ArrayList(); - this.outboundLinks = new ArrayList(); + this.inboundLinks = new ArrayList(); + this.outboundLinks = new ArrayList(); this.layoutNode = null; + this.layout = null; this.distance = -1; + this.capacity = -1; this.decayed = false; partitionNode.setData(this); } @@ -34,6 +46,7 @@ public class RoomData public void addToLayout(DirectedGraph layout) { + this.layout = layout; this.layoutNode = layout.addNode(this); } @@ -47,12 +60,12 @@ public class RoomData this.decayed = value; } - public ArrayList getInboundLinks() + public ArrayList getInboundLinks() { return this.inboundLinks; } - public ArrayList getOutboundLinks() + public ArrayList getOutboundLinks() { return this.outboundLinks; } @@ -67,16 +80,54 @@ public class RoomData distance = value; } - public void clear() + public void remove() { + // Remove the room from the partition tree and from the layout graph. + // Also remove any ancestors that become leaf nodes. + PartitionNode parent; + PartitionNode current = partitionNode; + while (current != null && current.isLeaf()) + { + parent = current.parent(); + current.remove(); + current = parent; + } + + // Remove the room from the layout graph + layout.removeNode(layoutNode); + + // Remove any links + while (!inboundLinks.isEmpty()) + inboundLinks.get(inboundLinks.size() - 1).remove(); + + while (!outboundLinks.isEmpty()) + outboundLinks.get(outboundLinks.size() - 1).remove(); + + // Wipe the room's data, as a precaution + layout = null; partitionNode = null; - layoutNode = null; + inboundLinks = null; + outboundLinks = null; } public int estimateDoorCapacity() { + if (capacity >= 0) + return capacity; + int cellsX = (partitionNode.width() - 3) / 2; int cellsZ = (partitionNode.length() - 3) / 2; - return Math.min(cellsX * cellsZ, 3); + capacity = Math.min(cellsX * cellsZ, 3); + return capacity; + } + + public int getRemainingDoorCapacity() + { + return (estimateDoorCapacity() - outboundLinks.size()); + } + + public boolean isProtected() + { + return !inboundLinks.isEmpty() || !outboundLinks.isEmpty(); } }