Start of world generator

v5stable
Ondřej Hruška 11 years ago
parent 147f499e62
commit 69f55dcc12
  1. 2
      src/mightypork/rogue/screens/gamescreen/world/MapView.java
  2. 31
      src/mightypork/rogue/world/MapGenerator.java
  3. 6
      src/mightypork/rogue/world/World.java
  4. 5
      src/mightypork/rogue/world/WorldPos.java
  5. 57
      src/mightypork/rogue/world/gen/Coord.java
  6. 48
      src/mightypork/rogue/world/gen/LevelGenerator.java
  7. 12
      src/mightypork/rogue/world/gen/RoomBuilder.java
  8. 56
      src/mightypork/rogue/world/gen/RoomDesc.java
  9. 188
      src/mightypork/rogue/world/gen/ScratchMap.java
  10. 15
      src/mightypork/rogue/world/gen/Theme.java
  11. 49
      src/mightypork/rogue/world/gen/rooms/IntersectionRoom.java
  12. 98
      src/mightypork/rogue/world/gen/rooms/SquareRoom.java
  13. 29
      src/mightypork/rogue/world/gen/themes/ThemeDungeon.java
  14. 15
      src/mightypork/rogue/world/level/Level.java
  15. 5
      src/mightypork/rogue/world/tile/Tiles.java
  16. 4
      src/mightypork/util/math/constraints/rect/builders/TiledRect.java

@ -31,7 +31,7 @@ public class MapView extends InputComponent implements KeyListener, MouseButtonL
public MapView(World world) public MapView(World world)
{ {
this.world = world; this.world = world;
this.worldRenderer = new WorldRenderer(world, this, 8, 6, 72); this.worldRenderer = new WorldRenderer(world, this, 8, 8, 64);
pc = world.getPlayerControl(); pc = world.getPlayerControl();
pc.addMoveListener(this); pc.addMoveListener(this);
} }

@ -3,6 +3,7 @@ package mightypork.rogue.world;
import java.util.Random; import java.util.Random;
import mightypork.rogue.world.gen.LevelGenerator;
import mightypork.rogue.world.level.Level; import mightypork.rogue.world.level.Level;
import mightypork.rogue.world.tile.Tiles; import mightypork.rogue.world.tile.Tiles;
import mightypork.rogue.world.tile.models.TileModel; import mightypork.rogue.world.tile.models.TileModel;
@ -21,33 +22,15 @@ public class MapGenerator {
final World w = new World(); final World w = new World();
w.setSeed(seed); w.setSeed(seed);
w.addLevel(createLevel(rand.nextLong(), Tiles.FLOOR_DARK, Tiles.WALL_BRICK)); Level l;
//w.addLevel(createLevel(rand.nextLong(), Tiles.BRCOBBLE_FLOOR, Tiles.BRCOBBLE_WALL));
// TODO place on start position // first level
w.createPlayer(10, 10, 0); l = LevelGenerator.build(rand.nextLong(), LevelGenerator.DUNGEON_THEME);
return w; w.addLevel(l);
} w.createPlayer(l.getEnterPoint(), 0);
}
private static Level createLevel(long seed, TileModel floor, TileModel wall)
{
// TODO
final Level lm = new Level(20, 20); return w;
lm.setSeed(seed);
lm.fill(floor);
final Random rand = new Random();
rand.setSeed(seed);
for (int i = 0; i < 150; i++) {
lm.setTile(wall, rand.nextInt(20), rand.nextInt(20));
} }
return lm;
} }
} }

@ -86,6 +86,11 @@ public class World implements IonBundled, Updateable {
} }
public void createPlayer(WorldPos pos, int level)
{
createPlayer(pos.x, pos.y, level);
}
public void createPlayer(int x, int y, int level) public void createPlayer(int x, int y, int level)
{ {
if (playerInfo.isInitialized()) { if (playerInfo.isInitialized()) {
@ -120,4 +125,5 @@ public class World implements IonBundled, Updateable {
{ {
return playerEntity; return playerEntity;
} }
} }

@ -107,6 +107,11 @@ public class WorldPos implements IonBundled, Updateable {
setTo(other.x, other.y); setTo(other.x, other.y);
} }
@Override
public String toString()
{
return "WorldPos("+x+","+y+")";
}
@Override @Override
public int hashCode() public int hashCode()

@ -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
}
}

@ -9,6 +9,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import mightypork.rogue.world.World; import mightypork.rogue.world.World;
import mightypork.rogue.world.WorldPos;
import mightypork.rogue.world.entity.Entity; import mightypork.rogue.world.entity.Entity;
import mightypork.rogue.world.tile.Tile; import mightypork.rogue.world.tile.Tile;
import mightypork.rogue.world.tile.Tiles; import mightypork.rogue.world.tile.Tiles;
@ -31,6 +32,8 @@ public class Level implements MapAccess, IonBinary {
private int width, height; private int width, height;
private WorldPos enterPoint;
/** Array of tiles [y][x] */ /** Array of tiles [y][x] */
private Tile[][] tiles; private Tile[][] tiles;
@ -278,4 +281,16 @@ public class Level implements MapAccess, IonBinary {
{ {
return entity_set; return entity_set;
} }
public void setEnterPoint(WorldPos enterPoint)
{
this.enterPoint = enterPoint;
}
public WorldPos getEnterPoint()
{
return enterPoint;
}
} }

@ -19,9 +19,10 @@ public final class Tiles {
public static final TileModel NULL_SOLID = new NullWall(0); public static final TileModel NULL_SOLID = new NullWall(0);
public static final TileModel NULL_EMPTY = new NullFloor(1); public static final TileModel NULL_EMPTY = new NullFloor(1);
public static final TileModel NULL_EMPTY_RESERVED = new NullFloor(2);
public static final TileModel FLOOR_DARK = new Floor(2).setTexture("tile16.floor.dark"); public static final TileModel FLOOR_DARK = new Floor(10).setTexture("tile16.floor.dark");
public static final TileModel WALL_BRICK = new Wall(3).setTexture("tile16.wall.brick"); public static final TileModel WALL_BRICK = new Wall(11).setTexture("tile16.wall.brick");
// public static final TileModel BRICK_FLOOR_VINES = new Floor(2).setTexture("tile.floor.mossy_bricks"); // public static final TileModel BRICK_FLOOR_VINES = new Floor(2).setTexture("tile.floor.mossy_bricks");

@ -59,11 +59,11 @@ public class TiledRect extends RectProxy {
public Rect tile(int x, int y) public Rect tile(int x, int y)
{ {
if (x >= tilesX || x < 0) { if (x >= tilesX || x < 0) {
throw new IndexOutOfBoundsException("X coordinate out fo range."); throw new IndexOutOfBoundsException("X coordinate out fo range: "+x);
} }
if (y >= tilesY || y < 0) { if (y >= tilesY || y < 0) {
throw new IndexOutOfBoundsException("Y coordinate out of range."); throw new IndexOutOfBoundsException("Y coordinate out of range: "+y);
} }
return aTile.move(perCol.mul(x), perRow.mul(y)); return aTile.move(perCol.mul(x), perRow.mul(y));

Loading…
Cancel
Save