squidpony.squidgrid.mapping.SymmetryDungeonGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of squidlib-util Show documentation
Show all versions of squidlib-util Show documentation
SquidLib platform-independent logic and utility code. Please refer to
https://github.com/SquidPony/SquidLib .
package squidpony.squidgrid.mapping;
import squidpony.squidmath.*;
import java.util.*;
/**
* A variant on {@link MixedGenerator} that creates bi-radially symmetric maps (basically a yin-yang shape). Useful for
* strategy games and possibly competitive multi-player games. The Coords passed to constructors as room positions do
* not necessarily need to be
*
* Created by Tommy Ettinger on 11/20/2015.
*/
public class SymmetryDungeonGenerator extends MixedGenerator {
public static OrderedMap> removeSomeOverlap(int width, int height, Collection sequence)
{
List s2 = new ArrayList<>(sequence.size());
for(Coord c : sequence)
{
if(c.x * 1.0 / width + c.y * 1.0 / height <= 1.0)
s2.add(c);
}
return listToMap(s2);
}
public static OrderedMap> removeSomeOverlap(int width, int height, Map> connections) {
OrderedMap> om2 = new OrderedMap<>(connections.size());
Set keyset = connections.keySet(), newkeys = new OrderedSet<>(connections.size());
for (Coord c : keyset) {
if (c.x * 1.0 / width + c.y * 1.0 / height <= 1.0) {
newkeys.add(c);
}
}
Coord[] keys = newkeys.toArray(new Coord[newkeys.size()]);
for (int i = 0; i < keys.length; i++) {
Coord c = keys[i];
if (c.x * 1.0 / width + c.y * 1.0 / height <= 1.0) {
List cs = new ArrayList<>(4);
for (Coord c2 : connections.get(c)) {
if (c2.x * 1.0 / width + c2.y * 1.0 / height <= 1.0) {
cs.add(c2);
} else if (keys[(i + 1) % keys.length].x * 1.0 / width +
keys[(i + 1) % keys.length].y * 1.0 / height <= 1.0) {
cs.add(keys[(i + 1) % keys.length]);
}
}
om2.put(c, cs);
}
}
return om2;
}
/**
* This prepares a map generator that will generate a map with the given width and height, using the given RNG.
* This version of the constructor uses Poisson Disk sampling to generate the points it will draw caves and
* corridors between, ensuring a minimum distance between points, but it does not ensure that paths between points
* will avoid overlapping with rooms or other paths. You call the different carver-adding methods to affect what the
* dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
* caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
*
* @param width the width of the final map in cells
* @param height the height of the final map in cells
* @param rng an RNG object to use for random choices; this make a lot of random choices.
* @see PoissonDisk used to ensure spacing for the points.
*/
public SymmetryDungeonGenerator(int width, int height, IRNG rng) {
this(width, height, rng, basicPoints(width, height, rng));
}
/**
* This prepares a map generator that will generate a map with the given width and height, using the given RNG.
* This version of the constructor uses a List of Coord points from some other source to determine the path to add
* rooms or caves to and then connect. You call the different carver-adding methods to affect what the
* dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
* caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
*
* @param width the width of the final map in cells
* @param height the height of the final map in cells
* @param rng an IRNG, such as an RNG, to use for random choices; this make a lot of random choices.
* @param sequence a List of Coord to connect in order; index 0 is the start, index size() - 1 is the end.
* @see SerpentMapGenerator a class that uses this technique
*/
public SymmetryDungeonGenerator(int width, int height, IRNG rng, List sequence) {
this(width, height, rng, listToMap(sequence), 1f);
}
/**
* This prepares a map generator that will generate a map with the given width and height, using the given RNG.
* This version of the constructor uses a List of Coord points from some other source to determine the path to add
* rooms or caves to and then connect. You call the different carver-adding methods to affect what the
* dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
* caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
*
* @param width the width of the final map in cells
* @param height the height of the final map in cells
* @param rng an IRNG, such as an RNG, to use for random choices; this make a lot of random choices.
* @param sequence a List of Coord to connect in order; index 0 is the start, index size() - 1 is the end.
* @see SerpentMapGenerator a class that uses this technique
*/
public SymmetryDungeonGenerator(int width, int height, IRNG rng, OrderedSet sequence) {
this(width, height, rng, setToMap(sequence), 1f);
}
/**
* This prepares a map generator that will generate a map with the given width and height, using the given RNG.
* This version of the constructor uses a LinkedHashMap with Coord keys and Coord array values to determine a
* branching path for the dungeon to take; each key will connect once to each of the Coords in its value, and you
* usually don't want to connect in both directions. You call the different carver-adding methods to affect what the
* dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
* caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
*
* @param width the width of the final map in cells
* @param height the height of the final map in cells
* @param rng an RNG object to use for random choices; this make a lot of random choices.
* @param connections a Map of Coord keys to arrays of Coord to connect to next; shouldn't connect both ways
* @see SerpentMapGenerator a class that uses this technique
*/
public SymmetryDungeonGenerator(int width, int height, IRNG rng, OrderedMap> connections) {
this(width, height, rng, connections, 0.8f);
}
/**
* This prepares a map generator that will generate a map with the given width and height, using the given RNG.
* This version of the constructor uses a LinkedHashMap with Coord keys and Coord array values to determine a
* branching path for the dungeon to take; each key will connect once to each of the Coords in its value, and you
* usually don't want to connect in both directions. You call the different carver-adding methods to affect what the
* dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
* caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
*
* @param width the width of the final map in cells
* @param height the height of the final map in cells
* @param rng an RNG object to use for random choices; this make a lot of random choices.
* @param connections a Map of Coord keys to arrays of Coord to connect to next; shouldn't connect both ways
* @param roomSizeMultiplier a float multiplier that will be applied to each room's width and height
* @see SerpentMapGenerator a class that uses this technique
*/
public SymmetryDungeonGenerator(int width, int height, IRNG rng, OrderedMap> connections, float roomSizeMultiplier) {
super(width, height, rng, crossConnect(width, height, connections), roomSizeMultiplier);
}
protected static OrderedMap> listToMap(List sequence)
{
OrderedMap> conns = new OrderedMap<>(sequence.size() - 1);
for (int i = 0; i < sequence.size() - 1; i++) {
Coord c1 = sequence.get(i), c2 = sequence.get(i+1);
List cs = new ArrayList<>(1);
cs.add(c2);
conns.put(c1, cs);
}
return conns;
}
protected static OrderedMap> setToMap(OrderedSet sequence)
{
OrderedMap> conns = new OrderedMap<>(sequence.size() - 1);
for (int i = 0; i < sequence.size() - 1; i++) {
Coord c1 = sequence.getAt(i), c2 = sequence.getAt(i+1);
List cs = new ArrayList<>(1);
cs.add(c2);
conns.put(c1, cs);
}
return conns;
}
protected static OrderedMap> crossConnect(int width, int height, Map> connections)
{
OrderedMap> conns = new OrderedMap<>(connections.size());
for(Map.Entry> entry : connections.entrySet())
{
conns.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
double lowest1 = 9999, lowest2 = 9999, lowest3 = 9999, lowest4 = 9999;
Coord l1 = null, l2 = null, l3 = null, l4 = null, r1 = null, r2 = null, r3 = null, r4 = null;
for(List left : connections.values())
{
for(List right : connections.values())
{
for(Coord lc : left)
{
for(Coord rc : right)
{
Coord rc2 = Coord.get(width - 1 - rc.x, height - 1 - rc.y);
double dist = lc.distance(rc2);
if(dist < 0.001)
continue;
if(dist < lowest1)
{
lowest1 = dist;
l1 = lc;
r1 = rc2;
}
else if(dist < lowest2 && !lc.equals(l1) && !rc2.equals(r1))
{
lowest2 = dist;
l2 = lc;
r2 = rc2;
}
else if(dist < lowest3
&& !lc.equals(l1) && !rc2.equals(r1) && !lc.equals(l2) && !rc2.equals(r2))
{
lowest3 = dist;
l3 = lc;
r3 = rc2;
}
else if(dist < lowest4
&& !lc.equals(l1) && !rc2.equals(r1)
&& !lc.equals(l2) && !rc2.equals(r2)
&& !lc.equals(l3) && !rc2.equals(r3))
{
lowest4 = dist;
l4 = lc;
r4 = rc2;
}
}
}
}
}
if(l1 != null && r1 != null)
{
if(conns.containsKey(l1))
{
conns.get(l1).add(r1);
}
else if(conns.containsKey(r1))
{
conns.get(r1).add(l1);
}
}
if(l2 != null && r2 != null)
{
if(conns.containsKey(l2))
{
conns.get(l2).add(r2);
}
else if(conns.containsKey(r2))
{
conns.get(r2).add(l2);
}
}
if(l3 != null && r3 != null)
{
if(conns.containsKey(l3))
{
conns.get(l3).add(r3);
}
else if(conns.containsKey(r3))
{
conns.get(r3).add(l3);
}
}
if(l4 != null && r4 != null)
{
if(conns.containsKey(l4))
{
conns.get(l4).add(r4);
}
else if(conns.containsKey(r4))
{
conns.get(r4).add(l4);
}
}
return conns;
}
/**
* Internal use. Marks a point to be made into floor.
*
* @param x x position to mark
* @param y y position to mark
* @return false if everything is normal, true if and only if this failed to mark because the position is walled
*/
@Override
protected boolean mark(int x, int y) {
return super.mark(x, y) || super.mark(width - 1 - x, height - 1 - y);
}
/**
* Internal use. Marks a point to be made into floor.
*
* @param x x position to mark
* @param y y position to mark
*/
@Override
protected void markPiercing(int x, int y) {
super.markPiercing(x, y);
super.markPiercing(width - 1 - x, height - 1 - y);
}
/**
* Internal use. Marks a point to be made into floor.
*
* @param x x position to mark
* @param y y position to mark
*/
@Override
protected void wallOff(int x, int y) {
super.wallOff(x, y);
super.wallOff(width - 1 - x, height - 1 - y);
}
/**
* Internal use. Marks a point's environment type as the appropriate kind of environment.
* @param x x position to mark
* @param y y position to mark
* @param kind an int that should be one of the constants in MixedGenerator for environment types.
*/
@Override
protected void markEnvironment(int x, int y, int kind) {
super.markEnvironment(x, y, kind);
super.markEnvironment(width - 1 - x, height - 1 - y, kind);
}
}