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