package squidpony.squidgrid.mapping.locks.constraints;
import squidpony.squidgrid.Direction;
import squidpony.squidgrid.mapping.locks.IRoomLayout;
import squidpony.squidgrid.mapping.locks.util.GenerationFailureException;
import squidpony.squidmath.Coord;
import squidpony.squidmath.IntVLA;
import squidpony.squidmath.OrderedMap;
import squidpony.squidmath.OrderedSet;
import java.util.Collections;
import java.util.Set;
public class FreeformConstraints implements ILayoutConstraints {
public static final int DEFAULT_MAX_KEYS = 8;
protected static class Group {
public int id;
public OrderedSet<Coord> coords;
public OrderedSet<Integer> adjacentGroups;
public Group(int id) {
this.id = id;
this.coords = new OrderedSet<>();
this.adjacentGroups = new OrderedSet<>();
}
}
protected ColorMap colorMap;
protected OrderedMap<Integer, Group> groups;
protected int maxKeys;
public FreeformConstraints(ColorMap colorMap) {
this.colorMap = colorMap;
this.groups = new OrderedMap<>();
this.maxKeys = DEFAULT_MAX_KEYS;
analyzeMap();
}
protected void analyzeMap() {
colorMap.checkConnected();
for (int x = colorMap.getLeft(); x <= colorMap.getRight(); ++x)
for (int y = colorMap.getTop(); y <= colorMap.getBottom(); ++y) {
Integer val = colorMap.get(x,y);
if (val == null) continue;
Group group = groups.get(val);
if (group == null) {
group = new Group(val);
groups.put(val, group);
}
group.coords.add(Coord.get(x+127,y+127));
}
System.out.println(groups.size() + " groups");
for (Group group: groups.values()) {
for (Coord xy: group.coords) {
for (Direction d: Direction.CARDINALS) {
Coord neighbor = xy.translate(d);
if (group.coords.contains(neighbor)) continue;
Integer val = colorMap.get(neighbor.x, neighbor.y);
if (val != null && allowRoomsToBeAdjacent(group.id, val)) {
group.adjacentGroups.add(val);
}
}
}
}
checkConnected();
}
protected boolean isConnected() {
// This is different from ColorMap.checkConnected because it also checks
// what the client says for allowRoomsToBeAdjacent allows the map to be
// full connected.
// Do a breadth first search starting at the top left to check if
// every position is reachable.
OrderedSet<Integer> world = groups.keysAsOrderedSet(),
queue = new OrderedSet<Integer>();
Integer first = world.first();
world.remove(first);
queue.add(first);
while (!queue.isEmpty()) {
Integer pos = queue.removeFirst();
IntVLA rooms = getAdjacentRooms(pos, getMaxKeys()+1);
for (int i = 0; i < rooms.size; i++) {
Integer adjId = rooms.get(i);
if (world.contains(adjId)) {
world.remove(adjId);
queue.add(adjId);
}
}
}
return world.size() == 0;
}
protected void checkConnected() {
if (!isConnected()) {
// Parts of the map are unreachable!
throw new GenerationFailureException("ColorMap is not fully connected");
}
}
@Override
public int getMaxRooms() {
return groups.size();
}
@Override
public int getMaxKeys() {
return maxKeys;
}
public void setMaxKeys(int maxKeys) {
this.maxKeys = maxKeys;
}
@Override
public int getMaxSwitches() {
return 0;
}
@Override
public IntVLA initialRooms() {
return IntVLA.with(groups.getAt(0).id);
}
@Override
public IntVLA getAdjacentRooms(int id, int keyLevel) {
IntVLA options = new IntVLA();
for (int i: groups.get(id).adjacentGroups) {
options.add(i);
}
return options;
}
/* The reason for this being separate from getAdjacentRooms is that this
* method is called at most once for each pair of rooms during analyzeMap,
* while getAdjacentRooms is called many times during generation under the
* assumption that it's simply a cheap "getter". Subclasses may override
* this method to perform more expensive checks than with getAdjacentRooms.
*/
protected boolean allowRoomsToBeAdjacent(int id0, int id1) {
return true;
}
@Override
public Set<Coord> getCoords(int id) {
return Collections.unmodifiableSet(groups.get(id).coords);
}
@Override
public boolean isAcceptable(IRoomLayout dungeon) {
return true;
}
@Override
public double edgeGraphifyProbability(int id, int nextId) {
return 0.2;
}
@Override
public boolean roomCanFitItem(int id, int key) {
return true;
}
}