Mazes #155
87
src/main/java/StevenDimDoors/experimental/LinkPlan.java
Normal file
87
src/main/java/StevenDimDoors/experimental/LinkPlan.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ public class MazeBuilder
|
|||||||
|
|
||||||
public static void generate(World world, int x, int y, int z, Random random)
|
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);
|
MazeDesign design = MazeDesigner.generate(random);
|
||||||
Point3D offset = new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2);
|
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);
|
SphereDecayOperation decay = new SphereDecayOperation(random, 0, 0, Block.stoneBrick.blockID, 2);
|
||||||
@@ -21,7 +23,7 @@ public class MazeBuilder
|
|||||||
buildRooms(design.getLayout(), world, offset);
|
buildRooms(design.getLayout(), world, offset);
|
||||||
carveDoorways(design.getLayout(), world, offset, decay, random);
|
carveDoorways(design.getLayout(), world, offset, decay, random);
|
||||||
|
|
||||||
//placeDoors(design, world, offset);
|
placeDoors(design.getLayout(), world, offset);
|
||||||
|
|
||||||
applyRandomDestruction(design, world, offset, decay, random);
|
applyRandomDestruction(design, world, offset, decay, random);
|
||||||
}
|
}
|
||||||
@@ -41,6 +43,22 @@ public class MazeBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void placeDoors(DirectedGraph<RoomData, DoorwayData> layout, World world, Point3D offset)
|
||||||
|
{
|
||||||
|
for (IGraphNode<RoomData, DoorwayData> 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<RoomData, DoorwayData> layout, World world,
|
private static void carveDoorways(DirectedGraph<RoomData, DoorwayData> layout, World world,
|
||||||
Point3D offset, SphereDecayOperation decay, Random random)
|
Point3D offset, SphereDecayOperation decay, Random random)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package StevenDimDoors.experimental;
|
package StevenDimDoors.experimental;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
@@ -48,6 +50,9 @@ public class MazeDesigner
|
|||||||
pruneDoorways(core.getLayoutNode(), layout, random);
|
pruneDoorways(core.getLayoutNode(), layout, random);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up the placement of dimensional doors within the maze
|
||||||
|
createMazeLinks(layout, cores, random);
|
||||||
|
|
||||||
return new MazeDesign(root, layout);
|
return new MazeDesign(root, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,28 +69,6 @@ public class MazeDesigner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void removeRoom(RoomData room, DirectedGraph<RoomData, DoorwayData> 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)
|
private static PartitionNode partitionRooms(int width, int height, int length, int maxLevels, Random random)
|
||||||
{
|
{
|
||||||
PartitionNode root = new PartitionNode(width, height, length);
|
PartitionNode root = new PartitionNode(width, height, length);
|
||||||
@@ -468,7 +451,7 @@ public class MazeDesigner
|
|||||||
// Remove all the rooms that were listed for removal
|
// Remove all the rooms that were listed for removal
|
||||||
for (RoomData target : removals)
|
for (RoomData target : removals)
|
||||||
{
|
{
|
||||||
removeRoom(target, layout);
|
target.remove();
|
||||||
}
|
}
|
||||||
return cores;
|
return cores;
|
||||||
}
|
}
|
||||||
@@ -529,7 +512,6 @@ public class MazeDesigner
|
|||||||
// at outbound edges since inbound edges mirror them. List any Y_AXIS
|
// at outbound edges since inbound edges mirror them. List any Y_AXIS
|
||||||
// doorways we come across to consider removing them later, depending
|
// doorways we come across to consider removing them later, depending
|
||||||
// on their impact on connectedness.
|
// on their impact on connectedness.
|
||||||
// doorways.
|
|
||||||
ArrayList<IEdge<RoomData, DoorwayData>> targets =
|
ArrayList<IEdge<RoomData, DoorwayData>> targets =
|
||||||
new ArrayList<IEdge<RoomData, DoorwayData>>();
|
new ArrayList<IEdge<RoomData, DoorwayData>>();
|
||||||
|
|
||||||
@@ -557,6 +539,7 @@ public class MazeDesigner
|
|||||||
if (!components.mergeSets(passage.head(), passage.tail()))
|
if (!components.mergeSets(passage.head(), passage.tail()))
|
||||||
{
|
{
|
||||||
layout.removeEdge(passage);
|
layout.removeEdge(passage);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,4 +576,101 @@ public class MazeDesigner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void createMazeLinks(DirectedGraph<RoomData, DoorwayData> layout,
|
||||||
|
ArrayList<RoomData> 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<RoomData>[] sourceRooms = (ArrayList<RoomData>[]) Array.newInstance(cores.getClass(), cores.size());
|
||||||
|
ArrayList<RoomData>[] destinationRooms = (ArrayList<RoomData>[]) Array.newInstance(cores.getClass(), cores.size());
|
||||||
|
|
||||||
|
for (index = 0; index < sourceRooms.length; index++)
|
||||||
|
{
|
||||||
|
sourceRooms[index] = new ArrayList<RoomData>();
|
||||||
|
destinationRooms[index] = new ArrayList<RoomData>();
|
||||||
|
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<RoomData, DoorwayData> core,
|
||||||
|
ArrayList<RoomData> sourceRooms, ArrayList<RoomData> destinationRooms)
|
||||||
|
{
|
||||||
|
int capacity = 0;
|
||||||
|
boolean hasHoles;
|
||||||
|
RoomData currentRoom;
|
||||||
|
IGraphNode<RoomData, DoorwayData> current;
|
||||||
|
IGraphNode<RoomData, DoorwayData> neighbor;
|
||||||
|
Stack<IGraphNode<RoomData, DoorwayData>> ordering = new Stack<IGraphNode<RoomData, DoorwayData>>();
|
||||||
|
HashSet<IGraphNode<RoomData, DoorwayData>> visited = new HashSet<IGraphNode<RoomData, DoorwayData>>();
|
||||||
|
|
||||||
|
visited.add(core);
|
||||||
|
ordering.add(core);
|
||||||
|
while (!ordering.isEmpty())
|
||||||
|
{
|
||||||
|
current = ordering.pop();
|
||||||
|
hasHoles = false;
|
||||||
|
|
||||||
|
for (IEdge<RoomData, DoorwayData> edge : current.outbound())
|
||||||
|
{
|
||||||
|
neighbor = edge.tail();
|
||||||
|
if (visited.add(neighbor))
|
||||||
|
{
|
||||||
|
ordering.add(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (IEdge<RoomData, DoorwayData> 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<RoomData> sources, int index)
|
||||||
|
{
|
||||||
|
RoomData entranceRoom = sources.get(index);
|
||||||
|
LinkPlan.createEntranceLink(entranceRoom);
|
||||||
|
if (entranceRoom.getRemainingDoorCapacity() == 0)
|
||||||
|
{
|
||||||
|
sources.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package StevenDimDoors.experimental;
|
|
||||||
|
|
||||||
public class MazeLinkData
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,20 +4,32 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
public class RoomData
|
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 int distance;
|
||||||
private boolean decayed;
|
private boolean decayed;
|
||||||
private PartitionNode partitionNode;
|
private PartitionNode partitionNode;
|
||||||
private ArrayList<MazeLinkData> inboundLinks;
|
private ArrayList<LinkPlan> inboundLinks;
|
||||||
private ArrayList<MazeLinkData> outboundLinks;
|
private ArrayList<LinkPlan> outboundLinks;
|
||||||
|
private DirectedGraph<RoomData, DoorwayData> layout;
|
||||||
private IGraphNode<RoomData, DoorwayData> layoutNode;
|
private IGraphNode<RoomData, DoorwayData> layoutNode;
|
||||||
|
|
||||||
public RoomData(PartitionNode partitionNode)
|
public RoomData(PartitionNode partitionNode)
|
||||||
{
|
{
|
||||||
this.partitionNode = partitionNode;
|
this.partitionNode = partitionNode;
|
||||||
this.inboundLinks = new ArrayList<MazeLinkData>();
|
this.inboundLinks = new ArrayList<LinkPlan>();
|
||||||
this.outboundLinks = new ArrayList<MazeLinkData>();
|
this.outboundLinks = new ArrayList<LinkPlan>();
|
||||||
this.layoutNode = null;
|
this.layoutNode = null;
|
||||||
|
this.layout = null;
|
||||||
this.distance = -1;
|
this.distance = -1;
|
||||||
|
this.capacity = -1;
|
||||||
this.decayed = false;
|
this.decayed = false;
|
||||||
partitionNode.setData(this);
|
partitionNode.setData(this);
|
||||||
}
|
}
|
||||||
@@ -34,6 +46,7 @@ public class RoomData
|
|||||||
|
|
||||||
public void addToLayout(DirectedGraph<RoomData, DoorwayData> layout)
|
public void addToLayout(DirectedGraph<RoomData, DoorwayData> layout)
|
||||||
{
|
{
|
||||||
|
this.layout = layout;
|
||||||
this.layoutNode = layout.addNode(this);
|
this.layoutNode = layout.addNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,12 +60,12 @@ public class RoomData
|
|||||||
this.decayed = value;
|
this.decayed = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<MazeLinkData> getInboundLinks()
|
public ArrayList<LinkPlan> getInboundLinks()
|
||||||
{
|
{
|
||||||
return this.inboundLinks;
|
return this.inboundLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<MazeLinkData> getOutboundLinks()
|
public ArrayList<LinkPlan> getOutboundLinks()
|
||||||
{
|
{
|
||||||
return this.outboundLinks;
|
return this.outboundLinks;
|
||||||
}
|
}
|
||||||
@@ -67,16 +80,54 @@ public class RoomData
|
|||||||
distance = value;
|
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;
|
partitionNode = null;
|
||||||
layoutNode = null;
|
inboundLinks = null;
|
||||||
|
outboundLinks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int estimateDoorCapacity()
|
public int estimateDoorCapacity()
|
||||||
{
|
{
|
||||||
|
if (capacity >= 0)
|
||||||
|
return capacity;
|
||||||
|
|
||||||
int cellsX = (partitionNode.width() - 3) / 2;
|
int cellsX = (partitionNode.width() - 3) / 2;
|
||||||
int cellsZ = (partitionNode.length() - 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user