package pathgame.tilemap; import java.util.ArrayList; import java.util.Random; public class TileMapGenerator { private static long seed = 1; /** Returns a random generated map * * @param width the width of the map * @param height the height of the map * @param towns the amount of towns * @return a random generated map */ public static TileMap getMap(int width, int height, int towns) { return getMap(width, height, seed++, towns); } /** Returns a random generated map * * @param width the width of the map * @param height the height of the map * @param seed the seed for the random number generator * @param towns the amount of towns * @return a random generated map */ public static TileMap getMap(int width, int height, long seed, int towns) { Random r = new Random(seed); HighMap highMap = HighMap.generate(seed, width, height); TileMap map = new TileMap(width, height); for(int x = 0; x < width; x++) { for(int y = 0; y < height; y++) { if(highMap.get(x, y) < 0.15f) { map.setTile(x, y, Tiles.DEEP_WATER); } else if(highMap.get(x, y) < 0.3f) { map.setTile(x, y, Tiles.SHALLOW_WATER); } else if(highMap.get(x, y) < 0.7f) { map.setTile(x, y, randomGrass(r)); } else if(highMap.get(x, y) < 0.85f) { map.setTile(x, y, Tiles.HILL); } else { map.setTile(x, y, Tiles.MOUNTAIN); } } } generateHomeTown(map, r); int forestSize = ((width + height) / 2) / 10; forestSize *= forestSize; generateForest(map, r, forestSize, 10, 2, Tiles.FOREST); generateForest(map, r, forestSize, 5, 2, Tiles.SWAMP, Tiles.SWAMP, Tiles.SWAMP_DECO, Tiles.SWAMP_TREE); generateTowns(map, r, towns); generatePorts(map, r); generatePaths(map, r); removeBadSwampTree(map); return map; } private static Tile randomGrass(Random r) { if(r.nextFloat() < 0.3f) { return randomTile(Tiles.GRASS_VARIANTS, r); } return Tiles.GRASS; } private static void generateForest(TileMap map, Random r, int depth, int placements, int jumpRadius, Tile... t) { for(int i = 0; i < placements; i++) { int x = r.nextInt(map.getWidth()); int y = r.nextInt(map.getHeight()); while(map.getTile(x, y).getForestReplaceChance() < 1.0f) { x = r.nextInt(map.getWidth()); y = r.nextInt(map.getHeight()); } for(int j = 0; j < depth; j++) { int oldX = x; int oldY = y; x += r.nextInt(jumpRadius * 2 + 1) - jumpRadius; y += r.nextInt(jumpRadius * 2 + 1) - jumpRadius; x = Math.min(Math.max(x, 0), map.getWidth() - 1); y = Math.min(Math.max(y, 0), map.getHeight() - 1); if(r.nextFloat() < map.getTile(x, y).getForestReplaceChance()) { map.setTile(x, y, randomTile(t, r)); placeForest(map, r, x - 1, y, t); placeForest(map, r, x + 1, y, t); placeForest(map, r, x, y - 1, t); placeForest(map, r, x, y + 1, t); } else { x = oldX; y = oldY; } } } } private static void removeBadSwampTree(TileMap map) { for(int x = 0; x < map.getWidth(); x++) { for(int y = 0; y < map.getHeight(); y++) { if(map.getTile(x, y) == Tiles.SWAMP_TREE && ((x > 0 && map.getTile(x - 1, y).getRenderType() != TileRenderType.SWAMP) || (y > 0 && map.getTile(x, y - 1).getRenderType() != TileRenderType.SWAMP))) { map.setTile(x, y, Tiles.SWAMP); } } } } private static Tile randomTile(Tile[] tiles, Random r) { if(tiles.length == 1) { return tiles[0]; } return tiles[r.nextInt(tiles.length)]; } private static void placeForest(TileMap map, Random r, int x, int y, Tile... t) { if(x >= 0 && x < map.getWidth() && y >= 0 && y < map.getHeight()) { if(r.nextFloat() * 2 < map.getTile(x, y).getForestReplaceChance()) { map.setTile(x, y, randomTile(t, r)); } } } private static void generateTowns(TileMap map, Random r, int towns) { int failCounter = 0; while(towns > 0 && failCounter < 100) { int x = r.nextInt(map.getWidth()); int y = r.nextInt(map.getHeight()); if(map.getTile(x, y).canHostTown() && checkNearbyTowns(map, x, y, 2)) { map.setTile(x, y, Tiles.TOWN); towns--; failCounter = 0; } else { failCounter++; } } } private static boolean checkNearbyTowns(TileMap map, int x, int y, int radius) { int startX = Math.max(x - radius, 0); int startY = Math.max(y - radius, 0); int endX = Math.min(x + radius, map.getWidth() - 1); int endY = Math.min(y + radius, map.getHeight() - 1); for(int mx = startX; mx <= endX; mx++) { for(int my = startY; my <= endY; my++) { if(map.getTile(mx, my) == Tiles.TOWN) { return false; } } } return true; } private static boolean isWater(TileMap map, int x, int y) { TileType type = map.getTile(x, y).getType(); return type == TileType.DEEP_WATER || type == TileType.SHALLOW_WATER; } private static boolean isNeighbourWater(TileMap map, int x, int y) { return (x - 1 >= 0 && isWater(map, x - 1, y)) || (x + 1 < map.getWidth() && isWater(map, x + 1, y)) || (y - 1 >= 0 && isWater(map, x, y - 1)) || (y + 1 < map.getHeight() && isWater(map, x, y + 1)); } private static void generatePorts(TileMap map, Random r) { boolean[][] visited = new boolean[map.getWidth()][map.getHeight()]; for(int x = 0; x < map.getWidth(); x++) { for(int y = 0; y < map.getHeight(); y++) { if(!visited[x][y] && isWater(map, x, y)) { getLake(map, r, x, y, visited); } visited[x][y] = true; } } } private static int waterSize; private static int waterMinX; private static int waterMinY; private static int waterMaxX; private static int waterMaxY; private static class Location { private final int x; private final int y; public Location(int x, int y) { this.x = x; this.y = y; } public double getQuaredDistance(int ox, int oy) { return (ox - x) * (ox - x) + (oy - y) * (oy - y); } } private static double minSquaredDistance(ArrayList locs, int x, int y) { double min = Double.MAX_VALUE; for(Location loc : locs) { double d = loc.getQuaredDistance(x, y); if(d < min) { min = d; } } return min; } private static void getLake(TileMap map, Random r, int x, int y, boolean[][] visited) { waterSize = 0; waterMinX = x; waterMinY = y; waterMaxX = x; waterMaxY = y; scanWaterTiles(map, x, y, visited); // water outlines can be a port too waterMinX = Math.max(0, waterMinX - 1); waterMinY = Math.max(0, waterMinY - 1); waterMaxX = Math.min(map.getWidth() - 1, waterMaxX + 1); waterMaxY = Math.min(map.getHeight() - 1, waterMaxY + 1); ArrayList locs = new ArrayList<>(); //System.out.println(String.format("Lake Size: %d, (%d, %d) -> (%d, %d)", // waterSize, waterMinX, waterMinY, waterMaxX, waterMaxY)); int ports = waterSize / 30; int diffX = waterMaxX - waterMinX + 1; int diffY = waterMaxY - waterMinY + 1; int failCounter = 0; while(ports > 0 && failCounter < 100) { int rx = waterMinX + r.nextInt(diffX); int ry = waterMinY + r.nextInt(diffY); if(map.getTile(rx, ry).canHostTown() && isNeighbourWater(map, rx, ry) && minSquaredDistance(locs, rx, ry) > 25) { locs.add(new Location(rx, ry)); map.setTile(rx, ry, Tiles.PORT); ports--; failCounter = 0; } else { failCounter++; } } } private static void scanWaterTiles(TileMap map, int x, int y, boolean[][] visited) { if(!visited[x][y] && isWater(map, x, y)) { visited[x][y] = true; waterSize++; waterMinX = Math.min(x, waterMinX); waterMinY = Math.min(y, waterMinY); waterMaxX = Math.max(x, waterMaxX); waterMaxY = Math.max(y, waterMaxY); if(x - 1 >= 0) { scanWaterTiles(map, x - 1, y, visited); } if(x + 1 < map.getWidth()) { scanWaterTiles(map, x + 1, y, visited); } if(y - 1 >= 0) { scanWaterTiles(map, x, y - 1, visited); } if(y + 1 < map.getHeight()) { scanWaterTiles(map, x, y + 1, visited); } } } private static void generateHomeTown(TileMap map, Random r) { int failCounter = 0; while(failCounter < 100) { int x = r.nextInt(map.getWidth()); int y = r.nextInt(map.getHeight()); if(map.getTile(x, y).canHostTown()) { map.setTile(x, y, Tiles.HOME_TOWN); map.setHomeTown(x, y); return; } else { failCounter++; } } map.setTile(0, 0, Tiles.HOME_TOWN); map.setHomeTown(0, 0); } private static boolean isPath(TileMap map, int x, int y) { return x >= 0 && y >= 0 && x < map.getWidth() && y < map.getHeight() && map.getTile(x, y).isPath(); } private static void generatePaths(TileMap map, Random r) { int paths = (map.getHeight() + map.getWidth()) / 6 + 2; // generate paths with random direction for(int i = 0; i < paths; i++) { int x = r.nextInt(map.getWidth()); int y = r.nextInt(map.getHeight()); while(!map.getTile(x, y).canHostPath()) { x = r.nextInt(map.getWidth()); y = r.nextInt(map.getHeight()); } float dx = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1); float dy = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1); generatePathDiretion(map, r, x, y, dx, dy); } // destroy path 2x2 blocks destroyPathBlocks(map); destroyPathBlocks(map); // swap paths depending on neighbours for(int x = 0; x < map.getWidth(); x++) { for(int y = 0; y < map.getHeight(); y++) { if(map.getTile(x, y).isPath()) { map.setTile(x, y, Tiles.getPath( isPath(map, x, y - 1), isPath(map, x + 1, y), isPath(map, x, y + 1), isPath(map, x - 1, y))); } } } } private static void destroyPathBlocks(TileMap map) { for(int x = 0; x < map.getWidth() - 1; x++) { for(int y = 0; y < map.getHeight() - 1; y++) { if(map.getTile(x, y).isPath() && map.getTile(x + 1, y).isPath() && map.getTile(x, y + 1).isPath() && map.getTile(x + 1, y + 1).isPath()) { if(!isPath(map, x - 1, y) && !isPath(map, x, y - 1)) { map.setTile(x, y, Tiles.GRASS); continue; } if(!isPath(map, x + 1, y - 1) && !isPath(map, x + 2, y)) { map.setTile(x + 1, y, Tiles.GRASS); continue; } if(!isPath(map, x - 1, y + 1) && !isPath(map, x, y + 2)) { map.setTile(x, y + 1, Tiles.GRASS); continue; } if(!isPath(map, x + 2, y + 1) && !isPath(map, x + 1, y + 2)) { map.setTile(x + 1, y + 1, Tiles.GRASS); } } } } } private static void generatePathDiretion(TileMap map, Random r, float x, float y, float dx, float dy) { while(true) { int tileX = (int) x; int tileY = (int) y; if(tileX < 0 || tileY < 0 || tileX >= map.getWidth() || tileY >= map.getHeight() || !map.getTile(tileX, tileY).canHostPath()) { break; } map.setTile(tileX, tileY, Tiles.PATH_N_E_S_W); while(tileX == (int) x && tileY == (int) y) { if(r.nextBoolean()) { x += dx; } else { y += dy; } } } } }