package ttftcuts.physis.common.worldgen.structure.layout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class LayoutGrid {
public final int gridsize;
public final int gridrange;
public final int celllength;
Cell[][] cells;
List<Room> rooms;
public LayoutGrid(int chunkrange, int gridsize) {
this.gridsize = gridsize;
this.gridrange = (int) Math.floor((chunkrange*16) / gridsize);
this.celllength = this.gridrange*2+1;
cells = new Cell[celllength][celllength];
this.rooms = new ArrayList<Room>();
}
public Cell get(int x, int y) {
return this.cells[c(x)][c(y)];
}
public Cell put(int x, int y) {
if (x < -this.gridrange || x > this.gridrange || y < -gridrange || y > gridrange) { return null; }
Cell cell = this.get(x,y);
if (cell != null) {
return cell;
}
cell = new Cell(x,y);
cell.parent = this;
this.cells[c(x)][c(y)] = cell;
return cell;
}
public void clear(int x, int y) {
Cell cell = this.get(x, y);
if (cell == null) { return; }
this.clearCell(cell);
}
public void clearCell(Cell cell) {
if (cell.node != null) {
cell.node.parent.removeNode(cell.node);
}
this.cells[c(cell.x)][c(cell.y)] = null;
}
public int c(int o) {
return o + gridrange;
}
public LayoutGraph buildGraph() {
LayoutGraph graph = new LayoutGraph();
for (int x=0; x<this.celllength; x++) {
for (int y=0; y<this.celllength; y++) {
Cell c = this.cells[x][y];
if (c != null && c.node == null) {
c.node = graph.addNode((x-gridrange)*gridsize, (y-gridrange)*gridsize);
}
}
}
for (int x=0; x<this.celllength; x++) {
for (int y=0; y<this.celllength; y++) {
Cell c = this.cells[x][y];
if (c == null) { continue; }
if (x > 0) {
Cell other = this.cells[x-1][y];
if (other != null) {
graph.addEdge(c.node, other.node);
}
}
if (y > 0) {
Cell other = this.cells[x][y-1];
if (other != null) {
graph.addEdge(c.node, other.node);
}
}
}
}
return graph;
}
public Cell findEntry() {
List<Cell> candidates = new ArrayList<Cell>();
for (int x=0; x<this.celllength; x++) {
for (int y=0; y<this.celllength; y++) {
Cell c = this.cells[x][y];
if (c == null) { continue; }
if (x <= 0 || x >= gridrange*2 || y <= 0 || y >= gridrange*2) {
candidates.add(c);
continue;
}
if (this.cells[x-1][y] == null || this.cells[x+1][y] == null || this.cells[x][y-1] == null || this.cells[x][y+1] == null) {
candidates.add(c);
}
}
}
Collections.shuffle(candidates);
for (int i=0; i<candidates.size(); i++) {
Cell cell = candidates.get(i);
if (cell.x <= 0 || cell.x >= gridrange*2 || cell.y <= 0 || cell.y >= gridrange*2) {
return cell;
}
boolean found = false;
for(int x = cell.x-1; x>=0; x--) {
if (this.cells[x][cell.y] != null) {
found = true;
break;
}
}
if (!found) { return cell; }
for(int x = cell.x+1; x<=gridrange*2; x++) {
if (this.cells[x][cell.y] != null) {
found = true;
break;
}
}
if (!found) { return cell; }
for(int y = cell.y-1; y>=0; y--) {
if (this.cells[cell.x][y] != null) {
found = true;
break;
}
}
if (!found) { return cell; }
for(int y = cell.y+1; y<=gridrange*2; y++) {
if (this.cells[cell.x][y] != null) {
found = true;
break;
}
}
if (!found) { return cell; }
}
return null;
}
public void roomify(Random rand) {
List<Cell> unroomed = new ArrayList<Cell>();
List<Cell> retry = new ArrayList<Cell>();
for (int x=0; x<this.celllength; x++) {
for (int y=0; y<this.celllength; y++) {
Cell c = this.cells[x][y];
if (c != null) {
unroomed.add(c);
}
}
}
while (!unroomed.isEmpty()) {
Cell cell = unroomed.remove(rand.nextInt(unroomed.size()));
boolean done = this.growRoom(cell, unroomed, retry, rand);
if (!done) {
retry.add(cell);
}
}
unroomed = retry;
retry = new ArrayList<Cell>();
while (!unroomed.isEmpty()) {
Cell cell = unroomed.remove(rand.nextInt(unroomed.size()));
boolean done = this.growCorridor(cell, unroomed, retry, rand);
if (!done) {
retry.add(cell);
}
}
unroomed = retry;
while (!unroomed.isEmpty()) {
Cell cell = unroomed.remove(rand.nextInt(unroomed.size()));
this.singleRoom(cell, rand);
}
}
public boolean growRoom(Cell cell, List<Cell> unroomed, List<Cell> retry, Random rand) {
int xmin = 0;
int xmax = 0;
int ymin = 0;
int ymax = 0;
int dx = c(cell.x);
int dy = c(cell.y);
List<String> dirs = new ArrayList<String>();
for (int i=0; i<4; i++) {
if (cell.connects(i)) {
dirs.add(Integer.toString(i));
}
}
while (!dirs.isEmpty()) {
String dir = dirs.remove(rand.nextInt(dirs.size()));
//Physis.logger.info("dir: "+dir+", x: "+(dx+xmin)+","+(dx+xmax)+", y: "+(dy+ymin)+","+(dy+ymax));
if (dir.equals("0")) {
// up
if(dy+ymin <= 0) {
dirs.remove("0");
} else {
boolean expand = true;
for (int i=xmin; i<=xmax; i++) {
Cell c = this.cells[dx+i][dy+ymin-1];
if (c == null || c.room != null || !c.connectsSouth() || (i<xmax && !c.connectsEast())) {
expand = false;
dirs.remove("0");
break;
}
}
if (expand) {
ymin--;
}
}
}
else if (dir.equals("1")) {
// right
if(dx+xmax >= gridrange*2) {
dirs.remove("1");
} else {
boolean expand = true;
for (int i=ymin; i<=ymax; i++) {
Cell c = this.cells[dx+xmax+1][dy+i];
if (c == null || c.room != null || !c.connectsWest() || (i<ymax && !c.connectsSouth())) {
expand = false;
dirs.remove("1");
break;
}
}
if (expand) {
xmax++;
}
}
}
else if (dir.equals("2")) {
// down
if(dy+ymax >= gridrange*2) {
dirs.remove("2");
} else {
boolean expand = true;
for (int i=xmin; i<=xmax; i++) {
Cell c = this.cells[dx+i][dy+ymax+1];
if (c == null || c.room != null || !c.connectsNorth() || (i<xmax && !c.connectsEast())) {
expand = false;
dirs.remove("2");
break;
}
}
if (expand) {
ymax++;
}
}
}
else {
// left
if(dx+xmin <= 0) {
dirs.remove("3");
} else {
boolean expand = true;
for (int i=ymin; i<=ymax; i++) {
Cell c = this.cells[dx+xmin-1][dy+i];
if (c == null || c.room != null || !c.connectsEast() || (i<ymax && !c.connectsSouth())) {
expand = false;
dirs.remove("3");
break;
}
}
if (expand) {
xmin--;
}
}
}
}
int xdiff = xmax-xmin;
int ydiff = ymax-ymin;
if (xdiff > 0 && ydiff > 0) {
Room r = new Room("large");
for (int x=xmin; x<=xmax; x++) {
for (int y=ymin; y<=ymax; y++) {
Cell rc = cells[dx+x][dy+y];
r.addCell(rc);
rc.room = r;
unroomed.remove(rc);
retry.remove(rc);
}
}
this.rooms.add(r);
return true;
}
return false;
}
public boolean growCorridor(Cell cell, List<Cell> unroomed, List<Cell> retry, Random rand) {
if((cell.connectsWest() && cell.connectsEast()) || (cell.connectsNorth() && cell.connectsSouth())) {
int xmin = 0;
int xmax = 0;
int ymin = 0;
int ymax = 0;
int dx = c(cell.x);
int dy = c(cell.y);
// up
while(true) {
if (dy+ymin <= 0) { break; }
Cell c = this.cells[dx][dy+ymin-1];
if (c == null || c.room != null || !c.connectsSouth()) {
break;
} else {
ymin--;
}
}
// right
while(true) {
if (dx+xmax >= gridrange*2) { break; }
Cell c = this.cells[dx+xmax+1][dy];
if (c == null || c.room != null || !c.connectsWest()) {
break;
} else {
xmax++;
}
}
// down
while(true) {
if (dy+ymax >= gridrange*2) { break; }
Cell c = this.cells[dx][dy+ymax+1];
if (c == null || c.room != null || !c.connectsNorth()) {
break;
} else {
ymax++;
}
}
// left
while(true) {
if (dx+xmin <= 0) { break; }
Cell c = this.cells[dx+xmin-1][dy];
if (c == null || c.room != null || !c.connectsEast()) {
break;
} else {
xmin--;
}
}
int xdiff = xmax-xmin;
int ydiff = ymax-ymin;
int maxdiff = Math.max(xdiff,ydiff);
if (maxdiff >= 2) {
if (xdiff > ydiff) {
ymin = 0;
ymax = 0;
} else {
xmin = 0;
xmax = 0;
}
Room r = new Room("corridor");
for (int x=xmin; x<=xmax; x++) {
for (int y=ymin; y<=ymax; y++) {
Cell rc = cells[dx+x][dy+y];
r.addCell(rc);
rc.room = r;
unroomed.remove(rc);
retry.remove(rc);
}
}
this.rooms.add(r);
return true;
}
}
return false;
}
public void singleRoom(Cell cell, Random rand) {
Room r = new Room("single");
cell.room = r;
r.addCell(cell);
this.rooms.add(r);
}
public static class Cell {
LayoutGrid parent;
LayoutGraph.Node node;
int x;
int y;
Room room;
public Cell(int x, int y) {
this.x = x;
this.y = y;
}
public boolean connects(int dir) {
int dx = parent.c(this.x);
int dy = parent.c(this.y);
Cell other = null;
if (dir == 0) {
if (dy <= 0) { return false; }
other = parent.cells[dx][dy-1];
}
else if (dir == 1) {
if (dx >= parent.gridrange*2) { return false; }
other = parent.cells[dx+1][dy];
}
else if (dir == 2) {
if (dy >= parent.gridrange*2) { return false; }
other = parent.cells[dx][dy+1];
}
else if (dir == 3) {
if (dx <= 0) { return false; }
other = parent.cells[dx-1][dy];
}
if (other != null) {
if (this.node.edges.containsKey(other.node) && this.node.edges.get(other.node).active) {
return true;
}
}
return false;
}
boolean connectsNorth() { return this.connects(0); }
boolean connectsEast() { return this.connects(1); }
boolean connectsSouth() { return this.connects(2); }
boolean connectsWest() { return this.connects(3); }
}
public static class Room {
List<Cell> cells;
List<Door> doors;
String type;
int depth = 0;
int xmin = 0;
int xmax = 0;
int ymin = 0;
int ymax = 0;
public Room(String type) {
this.type = type;
this.cells = new ArrayList<Cell>();
this.doors = new ArrayList<Door>();
}
void addCell(Cell cell) {
if (this.cells.contains(cell)) { return; }
if (this.cells.size() == 0) {
this.xmin = cell.x;
this.xmax = cell.x;
this.ymin = cell.y;
this.ymax = cell.y;
}
this.cells.add(cell);
this.xmin = Math.min(xmin, cell.x);
this.xmax = Math.max(xmax, cell.x);
this.ymin = Math.min(ymin, cell.y);
this.ymax = Math.max(ymax, cell.y);
}
void calcDepth() {
int total = 0;
for (Cell c : this.cells){
total += c.node.depth;
}
total = (int) Math.floor(total / this.cells.size());
this.depth = total;
}
void calcDoors() {
this.doors.clear();
for (Cell c : this.cells) {
if (c.x == this.xmin && c.connectsWest()) {
this.doors.add(new Door(c.x-this.xmin, c.y-this.ymin, 3));
}
if (c.x == this.xmax && c.connectsEast()) {
this.doors.add(new Door(c.x-this.xmin, c.y-this.ymin, 1));
}
if (c.y == this.ymin && c.connectsNorth()) {
this.doors.add(new Door(c.x-this.xmin, c.y-this.ymin, 0));
}
if (c.y == this.ymax && c.connectsSouth()) {
this.doors.add(new Door(c.x-this.xmin, c.y-this.ymin, 2));
}
}
}
public static class Door {
public final int x;
public final int y;
public final int dir;
public Door(int x, int y, int dir) {
this.x = x;
this.y = y;
this.dir = dir;
}
}
}
}