parent
147f499e62
commit
69f55dcc12
@ -0,0 +1,57 @@ |
||||
package mightypork.rogue.world.gen; |
||||
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod; |
||||
|
||||
|
||||
// coord
|
||||
public class Coord { |
||||
|
||||
public int x; |
||||
public int y; |
||||
|
||||
|
||||
@FactoryMethod |
||||
public static Coord make(int x, int y) |
||||
{ |
||||
return new Coord(x, y); |
||||
} |
||||
|
||||
@FactoryMethod |
||||
public static Coord make(Coord other) |
||||
{ |
||||
return new Coord(other); |
||||
} |
||||
|
||||
public Coord(int x, int y) |
||||
{ |
||||
super(); |
||||
this.x = x; |
||||
this.y = y; |
||||
} |
||||
|
||||
|
||||
public Coord(Coord other) |
||||
{ |
||||
this.x = other.x; |
||||
this.y = other.y; |
||||
} |
||||
|
||||
|
||||
public Coord add(int addX, int addY) |
||||
{ |
||||
return new Coord(x + addX, y + addY); |
||||
} |
||||
|
||||
|
||||
public Coord copy() |
||||
{ |
||||
return make(this); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() |
||||
{ |
||||
return "Coord("+x+","+y+")"; |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
package mightypork.rogue.world.gen; |
||||
|
||||
|
||||
import java.util.Random; |
||||
|
||||
import mightypork.rogue.world.WorldPos; |
||||
import mightypork.rogue.world.gen.rooms.IntersectionRoom; |
||||
import mightypork.rogue.world.gen.rooms.SquareRoom; |
||||
import mightypork.rogue.world.gen.themes.ThemeDungeon; |
||||
import mightypork.rogue.world.level.Level; |
||||
import mightypork.rogue.world.tile.Tile; |
||||
import mightypork.rogue.world.tile.Tiles; |
||||
import mightypork.util.logging.Log; |
||||
|
||||
|
||||
public class LevelGenerator { |
||||
|
||||
public static final Theme DUNGEON_THEME = new ThemeDungeon(); |
||||
|
||||
public static final RoomBuilder ROOM_SQUARE = new SquareRoom(); |
||||
public static final RoomBuilder ROOM_INTERSECTION = new IntersectionRoom(); |
||||
|
||||
|
||||
public static Level build(long seed, Theme theme) |
||||
{ |
||||
Random rand = new Random(seed + 47); |
||||
|
||||
final int max_size = 500; |
||||
|
||||
ScratchMap map = new ScratchMap(max_size, theme, rand); |
||||
|
||||
// start
|
||||
map.addRoom(ROOM_SQUARE); |
||||
|
||||
for (int i = 0; i < 5+rand.nextInt(4); i++) { |
||||
map.addRoom(ROOM_SQUARE); |
||||
for(int j=0;j<4;j++) map.addRoom(ROOM_INTERSECTION); |
||||
} |
||||
|
||||
|
||||
Coord size = map.getNeededSize(); |
||||
Level lvl = new Level(size.x, size.y); |
||||
|
||||
map.writeToLevel(lvl); |
||||
|
||||
return lvl; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
package mightypork.rogue.world.gen; |
||||
|
||||
import java.util.List; |
||||
import java.util.Random; |
||||
|
||||
import mightypork.rogue.world.tile.Tile; |
||||
|
||||
// room builder interface
|
||||
public interface RoomBuilder { |
||||
|
||||
RoomDesc buildToFit(ScratchMap map, Theme theme, Random rand, Coord center); |
||||
} |
@ -0,0 +1,56 @@ |
||||
package mightypork.rogue.world.gen; |
||||
|
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
|
||||
// room info
|
||||
public class RoomDesc { |
||||
|
||||
final List<Coord> doors = new ArrayList<>(); |
||||
final Coord min; |
||||
final Coord max; |
||||
|
||||
|
||||
public RoomDesc(Coord min, Coord max, List<Coord> doors) |
||||
{ |
||||
super(); |
||||
this.min = min; |
||||
this.max = max; |
||||
|
||||
this.doors.addAll(doors); |
||||
} |
||||
|
||||
|
||||
public boolean intersectsWith(Coord amin, Coord amax) |
||||
{ |
||||
int tw = max.x - min.x; |
||||
int th = max.y - min.y; |
||||
int rw = amax.x - amin.x; |
||||
int rh = amax.y - amin.y; |
||||
|
||||
if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) { |
||||
return false; |
||||
} |
||||
|
||||
final int tx = min.x; |
||||
final int ty = min.y; |
||||
final int rx = amin.x; |
||||
final int ry = amin.y; |
||||
|
||||
rw += rx; |
||||
rh += ry; |
||||
tw += tx; |
||||
th += ty; |
||||
|
||||
return ((rw <= rx || rw >= tx) && (rh <= ry || rh >= ty) && (tw <= tx || tw >= rx) && (th <= ty || th >= ry)); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() |
||||
{ |
||||
return "Room [" + min.x + "," + min.y + " .. " + max.x + "," + max.y + "]"; |
||||
} |
||||
} |
@ -0,0 +1,188 @@ |
||||
package mightypork.rogue.world.gen; |
||||
|
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Random; |
||||
|
||||
import mightypork.rogue.world.WorldPos; |
||||
import mightypork.rogue.world.level.Level; |
||||
import mightypork.rogue.world.tile.Tile; |
||||
import mightypork.rogue.world.tile.Tiles; |
||||
import mightypork.rogue.world.tile.models.TileModel; |
||||
|
||||
|
||||
public class ScratchMap { |
||||
|
||||
private Tile[][] map; |
||||
private int width; |
||||
private int height; |
||||
|
||||
private List<RoomDesc> rooms = new ArrayList<>(); |
||||
|
||||
Coord genMin; |
||||
Coord genMax; |
||||
private Theme theme; |
||||
private Random rand; |
||||
private Coord enterPoint; |
||||
|
||||
|
||||
public ScratchMap(int max_size, Theme theme, Random rand) |
||||
{ |
||||
map = new Tile[max_size][max_size]; |
||||
|
||||
genMin = Coord.make(max_size / 2, max_size / 2); |
||||
genMax = genMin.add(1, 1); |
||||
|
||||
width = max_size; |
||||
height = max_size; |
||||
this.rand = rand; |
||||
this.theme = theme; |
||||
|
||||
fill(Coord.make(0, 0), Coord.make(width - 1, height - 1), Tiles.NULL_EMPTY); |
||||
} |
||||
|
||||
|
||||
public void addRoom(RoomBuilder rb) |
||||
{ |
||||
Coord center = Coord.make(0, 0); |
||||
|
||||
int failed = 0; |
||||
|
||||
while (true) { |
||||
center.x = genMin.x + rand.nextInt(genMax.x - genMin.x); |
||||
center.y = genMin.y + rand.nextInt(genMax.y - genMin.y); |
||||
|
||||
RoomDesc rd = rb.buildToFit(this, theme, rand, center); |
||||
if (rd != null) { |
||||
if (rooms.isEmpty()) { |
||||
enterPoint = center.copy(); |
||||
} |
||||
|
||||
rooms.add(rd); |
||||
|
||||
genMin.x = Math.min(genMin.x, rd.min.x); |
||||
genMin.y = Math.min(genMin.y, rd.min.y); |
||||
|
||||
genMax.x = Math.max(genMax.x, rd.max.x); |
||||
genMax.y = Math.max(genMax.y, rd.max.y); |
||||
|
||||
return; |
||||
} else { |
||||
failed++; |
||||
if (failed % 5 == 0) { |
||||
switch(rand.nextInt(4)) { |
||||
case 0: genMin.x--; break; |
||||
case 1: genMin.y--; break; |
||||
case 2: genMax.x++; break; |
||||
case 3: genMax.y++; break; |
||||
} |
||||
} |
||||
|
||||
if (failed > 200) { |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
public boolean isIn(Coord pos) |
||||
{ |
||||
return pos.x >= 0 && pos.x < width && pos.y >= 0 && pos.y < height; |
||||
} |
||||
|
||||
|
||||
public Tile get(Coord pos) |
||||
{ |
||||
if (!isIn(pos)) { |
||||
return Tiles.NULL_SOLID.createTile(); |
||||
} |
||||
|
||||
return map[pos.y][pos.x]; |
||||
} |
||||
|
||||
|
||||
public boolean set(Coord pos, TileModel tm) |
||||
{ |
||||
return set(pos, tm.createTile()); |
||||
} |
||||
|
||||
|
||||
public boolean set(Coord pos, Tile tile) |
||||
{ |
||||
if (!isIn(pos)) { |
||||
return false; |
||||
} |
||||
|
||||
map[pos.y][pos.x] = tile; |
||||
return true; |
||||
} |
||||
|
||||
|
||||
public boolean canBuild(Coord pos) |
||||
{ |
||||
if (!isIn(pos)) return false; |
||||
TileModel tm = get(pos).getModel(); |
||||
return tm.isNullTile() && tm.isWalkable(); |
||||
} |
||||
|
||||
|
||||
public boolean isClear(Coord min, Coord max) |
||||
{ |
||||
if (!isIn(min)) return false; |
||||
if (!isIn(max)) return false; |
||||
|
||||
for (RoomDesc r : rooms) { |
||||
if (r.intersectsWith(min, max)) return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
|
||||
public void fill(Coord min, Coord max, TileModel tm) |
||||
{ |
||||
Coord c = Coord.make(0, 0); |
||||
for (c.y = min.y; c.y <= max.y; c.y++) |
||||
for (c.x = min.x; c.x <= max.x; c.x++) |
||||
set(c, tm.createTile()); |
||||
} |
||||
|
||||
|
||||
public void border(Coord min, Coord max, TileModel tm) |
||||
{ |
||||
Coord c = Coord.make(0, 0); |
||||
for (c.y = min.y; c.y <= max.y; c.y++) { |
||||
for (c.x = min.x; c.x <= max.x; c.x++) { |
||||
|
||||
if (c.y > min.y && c.y < max.y && c.x > min.x && c.x < max.x) continue; |
||||
|
||||
set(c, tm.createTile()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
public Coord getNeededSize() |
||||
{ |
||||
return Coord.make(genMax.x - genMin.x + 1, genMax.y - genMin.y + 1); |
||||
} |
||||
|
||||
|
||||
public void writeToLevel(Level level) |
||||
{ |
||||
|
||||
Coord c1 = Coord.make(0, 0); |
||||
Coord c = Coord.make(0, 0); |
||||
for (c.x = genMin.x, c1.x = 0; c.x <= genMax.x; c.x++, c1.x++) { |
||||
for (c.y = genMin.y, c1.y = 0; c.y <= genMax.y; c.y++, c1.y++) { |
||||
level.setTile(get(c), c1.x, c1.y); |
||||
} |
||||
} |
||||
|
||||
WorldPos p = new WorldPos(enterPoint.x - genMin.x, enterPoint.y - genMin.y); |
||||
level.setEnterPoint(p); |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
package mightypork.rogue.world.gen; |
||||
|
||||
import mightypork.rogue.world.tile.models.TileModel; |
||||
|
||||
// map theme
|
||||
public interface Theme { |
||||
|
||||
TileModel wall(); |
||||
|
||||
|
||||
TileModel floor(); |
||||
|
||||
|
||||
TileModel door(); |
||||
} |
@ -0,0 +1,49 @@ |
||||
package mightypork.rogue.world.gen.rooms; |
||||
|
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Random; |
||||
|
||||
import mightypork.rogue.world.gen.Coord; |
||||
import mightypork.rogue.world.gen.RoomBuilder; |
||||
import mightypork.rogue.world.gen.RoomDesc; |
||||
import mightypork.rogue.world.gen.Theme; |
||||
import mightypork.rogue.world.gen.ScratchMap; |
||||
|
||||
|
||||
public class IntersectionRoom extends SquareRoom { |
||||
|
||||
@Override |
||||
protected int getMinHalfSide() |
||||
{ |
||||
return 1; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected int getMaxHalfSide() |
||||
{ |
||||
return 1; |
||||
} |
||||
|
||||
|
||||
protected int[] getDoorTypes() |
||||
{ |
||||
//@formatter:off
|
||||
return new int[] { |
||||
// dead ends
|
||||
0b1000,0b0100,0b0010,0b0001, |
||||
|
||||
// corridor pieces
|
||||
0b0011, 0b0101, 0b0110, 0b1010, 0b1100, 0b1001, |
||||
|
||||
// crossings
|
||||
0b0111, 0b1101, 0b1011, 0b1110, 0b1111, |
||||
|
||||
// repeat to get more
|
||||
0b0111, 0b1101, 0b1011, 0b1110, 0b1111, |
||||
}; |
||||
//@formatter:on
|
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
package mightypork.rogue.world.gen.rooms; |
||||
|
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Random; |
||||
|
||||
import mightypork.rogue.world.gen.Coord; |
||||
import mightypork.rogue.world.gen.RoomBuilder; |
||||
import mightypork.rogue.world.gen.RoomDesc; |
||||
import mightypork.rogue.world.gen.Theme; |
||||
import mightypork.rogue.world.gen.ScratchMap; |
||||
|
||||
|
||||
public class SquareRoom implements RoomBuilder { |
||||
|
||||
@Override |
||||
public RoomDesc buildToFit(ScratchMap map, Theme theme, Random rand, Coord center) |
||||
{ |
||||
int hside = getMinHalfSide(); |
||||
if (getMaxHalfSide() > getMinHalfSide()) hside += rand.nextInt(getMaxHalfSide() - getMinHalfSide()); |
||||
|
||||
Coord min = new Coord(center.x - hside, center.y - hside); |
||||
Coord max = new Coord(center.x + hside, center.y + hside); |
||||
|
||||
for (;; hside--) { |
||||
if (hside < getMinHalfSide()) return null; |
||||
if (map.isClear(min, max)) break; |
||||
} |
||||
|
||||
map.fill(min, max, theme.floor()); |
||||
map.border(min, max, theme.wall()); |
||||
|
||||
List<Coord> doors = new ArrayList<>(); |
||||
|
||||
int door_types[] = getDoorTypes(); |
||||
|
||||
int drs = door_types[rand.nextInt(door_types.length)]; |
||||
|
||||
Coord door; |
||||
|
||||
if ((drs & 1) != 0) { |
||||
door = min.add(hside, 0); |
||||
map.set(door, theme.door()); |
||||
doors.add(door); |
||||
} |
||||
|
||||
if ((drs & 2) != 0) { |
||||
door = max.add(-hside, 0); |
||||
map.set(door, theme.door()); |
||||
} |
||||
|
||||
if ((drs & 4) != 0) { |
||||
door = min.add(0, hside); |
||||
map.set(door, theme.door()); |
||||
} |
||||
|
||||
if ((drs & 8) != 0) { |
||||
door = max.add(0, -hside); |
||||
map.set(door, theme.door()); |
||||
} |
||||
|
||||
return new RoomDesc(min.add(-1, -1), max.add(1, 1), doors); |
||||
} |
||||
|
||||
|
||||
protected int[] getDoorTypes() |
||||
{ |
||||
//@formatter:off
|
||||
return new int[] { |
||||
// one
|
||||
0b0001, 0b0010, 0b0100, 0b1000, |
||||
|
||||
// two
|
||||
0b0011, 0b0101, 0b0110, 0b1010, 0b1100, 0b1001, |
||||
0b0011, 0b0101, 0b0110, 0b1010, 0b1100, 0b1001, |
||||
|
||||
//three+four
|
||||
0b0111, 0b1101, 0b1011, 0b1110, 0b1111, |
||||
0b0111, 0b1101, 0b1011, 0b1110, 0b1111, |
||||
0b0111, 0b1101, 0b1011, 0b1110, 0b1111 |
||||
}; |
||||
//@formatter:on
|
||||
} |
||||
|
||||
|
||||
protected int getMinHalfSide() |
||||
{ |
||||
return 2; |
||||
} |
||||
|
||||
|
||||
protected int getMaxHalfSide() |
||||
{ |
||||
return 4; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package mightypork.rogue.world.gen.themes; |
||||
|
||||
import mightypork.rogue.world.gen.Theme; |
||||
import mightypork.rogue.world.tile.Tiles; |
||||
import mightypork.rogue.world.tile.models.TileModel; |
||||
|
||||
// basic dungeon theme
|
||||
public class ThemeDungeon implements Theme { |
||||
|
||||
@Override |
||||
public TileModel wall() |
||||
{ |
||||
return Tiles.WALL_BRICK; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public TileModel floor() |
||||
{ |
||||
return Tiles.FLOOR_DARK; |
||||
} |
||||
|
||||
@Override |
||||
public TileModel door() |
||||
{ |
||||
return floor(); // TODO
|
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue