TileMapGenerator.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. package pathgame.tilemap;
  2. import java.util.ArrayList;
  3. import java.util.Random;
  4. public class TileMapGenerator {
  5. private static long seed = 1;
  6. public static TileMap getMap(int width, int height, int towns) {
  7. return getMap(width, height, seed++, towns);
  8. }
  9. public static TileMap getMap(int width, int height, long seed, int towns) {
  10. Random r = new Random(seed);
  11. HighMap highMap = HighMap.generate(seed, width, height);
  12. TileMap map = new TileMap(width, height);
  13. for(int x = 0; x < width; x++) {
  14. for(int y = 0; y < height; y++) {
  15. if(highMap.get(x, y) < 0.15f) {
  16. map.setTile(x, y, Tiles.DEEP_WATER);
  17. } else if(highMap.get(x, y) < 0.3f) {
  18. map.setTile(x, y, Tiles.SHALLOW_WATER);
  19. } else if(highMap.get(x, y) < 0.7f) {
  20. map.setTile(x, y, randomGrass(r));
  21. } else if(highMap.get(x, y) < 0.85f) {
  22. map.setTile(x, y, Tiles.HILL);
  23. } else {
  24. map.setTile(x, y, Tiles.MOUNTAIN);
  25. }
  26. }
  27. }
  28. generateHomeTown(map, r);
  29. int forestSize = ((width + height) / 2) / 10;
  30. forestSize *= forestSize;
  31. generateForest(map, r, forestSize, 10, 2, Tiles.FOREST);
  32. generateForest(map, r, forestSize, 5, 2, Tiles.SWAMP, Tiles.SWAMP, Tiles.SWAMP_DECO, Tiles.SWAMP_TREE, Tiles.SWAMP_BONES);
  33. generateTowns(map, r, towns);
  34. generatePorts(map, r);
  35. generatePaths(map, r);
  36. removeBadSwampTree(map);
  37. return map;
  38. }
  39. private static Tile randomGrass(Random r) {
  40. if(r.nextFloat() < 0.3f) {
  41. return randomTile(Tiles.GRASS_VARIANTS, r);
  42. }
  43. return Tiles.GRASS;
  44. }
  45. private static void generateForest(TileMap map, Random r, int depth, int placements, int jumpRadius, Tile... t) {
  46. for(int i = 0; i < placements; i++) {
  47. int x = r.nextInt(map.getWidth());
  48. int y = r.nextInt(map.getHeight());
  49. while(map.getTile(x, y).getForestReplaceChance() < 1.0f) {
  50. x = r.nextInt(map.getWidth());
  51. y = r.nextInt(map.getHeight());
  52. }
  53. for(int j = 0; j < depth; j++) {
  54. int oldX = x;
  55. int oldY = y;
  56. x += r.nextInt(jumpRadius * 2 + 1) - jumpRadius;
  57. y += r.nextInt(jumpRadius * 2 + 1) - jumpRadius;
  58. x = Math.min(Math.max(x, 0), map.getWidth() - 1);
  59. y = Math.min(Math.max(y, 0), map.getHeight() - 1);
  60. if(r.nextFloat() < map.getTile(x, y).getForestReplaceChance()) {
  61. map.setTile(x, y, randomTile(t, r));
  62. placeForest(map, r, x - 1, y, t);
  63. placeForest(map, r, x + 1, y, t);
  64. placeForest(map, r, x, y - 1, t);
  65. placeForest(map, r, x, y + 1, t);
  66. } else {
  67. x = oldX;
  68. y = oldY;
  69. }
  70. }
  71. }
  72. }
  73. private static void removeBadSwampTree(TileMap map) {
  74. for(int x = 0; x < map.getWidth(); x++) {
  75. for(int y = 0; y < map.getHeight(); y++) {
  76. Tile t = map.getTile(x, y);
  77. if((t == Tiles.SWAMP_TREE || t == Tiles.SWAMP_BONES)
  78. && ((x > 0 && map.getTile(x - 1, y).getRenderType() != TileRenderType.SWAMP)
  79. || (y > 0 && map.getTile(x, y - 1).getRenderType() != TileRenderType.SWAMP))) {
  80. map.setTile(x, y, Tiles.SWAMP);
  81. }
  82. }
  83. }
  84. }
  85. private static Tile randomTile(Tile[] tiles, Random r) {
  86. if(tiles.length == 1) {
  87. return tiles[0];
  88. }
  89. return tiles[r.nextInt(tiles.length)];
  90. }
  91. private static void placeForest(TileMap map, Random r, int x, int y, Tile... t) {
  92. if(x >= 0 && x < map.getWidth() && y >= 0 && y < map.getHeight()) {
  93. if(r.nextFloat() * 2 < map.getTile(x, y).getForestReplaceChance()) {
  94. map.setTile(x, y, randomTile(t, r));
  95. }
  96. }
  97. }
  98. private static void generateTowns(TileMap map, Random r, int towns) {
  99. int failCounter = 0;
  100. while(towns > 0 && failCounter < 100) {
  101. int x = r.nextInt(map.getWidth());
  102. int y = r.nextInt(map.getHeight());
  103. if(map.getTile(x, y).canHostTown() && checkNearbyTowns(map, x, y, 2)) {
  104. map.setTile(x, y, Tiles.TOWN);
  105. towns--;
  106. failCounter = 0;
  107. } else {
  108. failCounter++;
  109. }
  110. }
  111. }
  112. private static boolean checkNearbyTowns(TileMap map, int x, int y, int radius) {
  113. int startX = Math.max(x - radius, 0);
  114. int startY = Math.max(y - radius, 0);
  115. int endX = Math.min(x + radius, map.getWidth() - 1);
  116. int endY = Math.min(y + radius, map.getHeight() - 1);
  117. for(int mx = startX; mx <= endX; mx++) {
  118. for(int my = startY; my <= endY; my++) {
  119. if(map.getTile(mx, my) == Tiles.TOWN) {
  120. return false;
  121. }
  122. }
  123. }
  124. return true;
  125. }
  126. private static boolean isWater(TileMap map, int x, int y) {
  127. TileType type = map.getTile(x, y).getType();
  128. return type == TileType.DEEP_WATER || type == TileType.SHALLOW_WATER;
  129. }
  130. private static boolean isNeighbourWater(TileMap map, int x, int y) {
  131. return (x - 1 >= 0 && isWater(map, x - 1, y))
  132. || (x + 1 < map.getWidth() && isWater(map, x + 1, y))
  133. || (y - 1 >= 0 && isWater(map, x, y - 1))
  134. || (y + 1 < map.getHeight() && isWater(map, x, y + 1));
  135. }
  136. private static void generatePorts(TileMap map, Random r) {
  137. boolean[][] visited = new boolean[map.getWidth()][map.getHeight()];
  138. for(int x = 0; x < map.getWidth(); x++) {
  139. for(int y = 0; y < map.getHeight(); y++) {
  140. if(!visited[x][y] && isWater(map, x, y)) {
  141. getLake(map, r, x, y, visited);
  142. }
  143. visited[x][y] = true;
  144. }
  145. }
  146. }
  147. private static int waterSize;
  148. private static int waterMinX;
  149. private static int waterMinY;
  150. private static int waterMaxX;
  151. private static int waterMaxY;
  152. private static class Location {
  153. private final int x;
  154. private final int y;
  155. public Location(int x, int y) {
  156. this.x = x;
  157. this.y = y;
  158. }
  159. public double getQuaredDistance(int ox, int oy) {
  160. return (ox - x) * (ox - x) + (oy - y) * (oy - y);
  161. }
  162. }
  163. private static double minSquaredDistance(ArrayList<Location> locs, int x, int y) {
  164. double min = Double.MAX_VALUE;
  165. for(Location loc : locs) {
  166. double d = loc.getQuaredDistance(x, y);
  167. if(d < min) {
  168. min = d;
  169. }
  170. }
  171. return min;
  172. }
  173. private static void getLake(TileMap map, Random r, int x, int y, boolean[][] visited) {
  174. waterSize = 0;
  175. waterMinX = x;
  176. waterMinY = y;
  177. waterMaxX = x;
  178. waterMaxY = y;
  179. scanWaterTiles(map, x, y, visited);
  180. // water outlines can be a port too
  181. waterMinX = Math.max(0, waterMinX - 1);
  182. waterMinY = Math.max(0, waterMinY - 1);
  183. waterMaxX = Math.min(map.getWidth() - 1, waterMaxX + 1);
  184. waterMaxY = Math.min(map.getHeight() - 1, waterMaxY + 1);
  185. ArrayList<Location> locs = new ArrayList<>();
  186. int ports = waterSize / 30;
  187. int diffX = waterMaxX - waterMinX + 1;
  188. int diffY = waterMaxY - waterMinY + 1;
  189. int failCounter = 0;
  190. while(ports > 0 && failCounter < 100) {
  191. int rx = waterMinX + r.nextInt(diffX);
  192. int ry = waterMinY + r.nextInt(diffY);
  193. if(map.getTile(rx, ry).canHostTown() && isNeighbourWater(map, rx, ry) && minSquaredDistance(locs, rx, ry) > 25) {
  194. locs.add(new Location(rx, ry));
  195. map.setTile(rx, ry, Tiles.PORT);
  196. ports--;
  197. failCounter = 0;
  198. } else {
  199. failCounter++;
  200. }
  201. }
  202. }
  203. private static void scanWaterTiles(TileMap map, int x, int y, boolean[][] visited) {
  204. if(!visited[x][y] && isWater(map, x, y)) {
  205. visited[x][y] = true;
  206. waterSize++;
  207. waterMinX = Math.min(x, waterMinX);
  208. waterMinY = Math.min(y, waterMinY);
  209. waterMaxX = Math.max(x, waterMaxX);
  210. waterMaxY = Math.max(y, waterMaxY);
  211. if(x - 1 >= 0) {
  212. scanWaterTiles(map, x - 1, y, visited);
  213. }
  214. if(x + 1 < map.getWidth()) {
  215. scanWaterTiles(map, x + 1, y, visited);
  216. }
  217. if(y - 1 >= 0) {
  218. scanWaterTiles(map, x, y - 1, visited);
  219. }
  220. if(y + 1 < map.getHeight()) {
  221. scanWaterTiles(map, x, y + 1, visited);
  222. }
  223. }
  224. }
  225. private static void generateHomeTown(TileMap map, Random r) {
  226. int failCounter = 0;
  227. while(failCounter < 100) {
  228. int x = r.nextInt(map.getWidth());
  229. int y = r.nextInt(map.getHeight());
  230. if(map.getTile(x, y).canHostTown()) {
  231. map.setTile(x, y, Tiles.HOME_TOWN);
  232. map.setHomeTown(x, y);
  233. return;
  234. } else {
  235. failCounter++;
  236. }
  237. }
  238. map.setTile(0, 0, Tiles.HOME_TOWN);
  239. map.setHomeTown(0, 0);
  240. }
  241. private static boolean isPath(TileMap map, int x, int y) {
  242. return x >= 0 && y >= 0 && x < map.getWidth() && y < map.getHeight() && map.getTile(x, y).isPath();
  243. }
  244. private static void generatePaths(TileMap map, Random r) {
  245. int paths = (map.getHeight() + map.getWidth()) / 6 + 2;
  246. // generate paths with random direction
  247. for(int i = 0; i < paths; i++) {
  248. int x = r.nextInt(map.getWidth());
  249. int y = r.nextInt(map.getHeight());
  250. while(!map.getTile(x, y).canHostPath()) {
  251. x = r.nextInt(map.getWidth());
  252. y = r.nextInt(map.getHeight());
  253. }
  254. float dx = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1);
  255. float dy = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1);
  256. generatePathDiretion(map, r, x, y, dx, dy);
  257. }
  258. // destroy path 2x2 blocks
  259. destroyPathBlocks(map);
  260. destroyPathBlocks(map);
  261. // swap paths depending on neighbours
  262. for(int x = 0; x < map.getWidth(); x++) {
  263. for(int y = 0; y < map.getHeight(); y++) {
  264. if(map.getTile(x, y).isPath()) {
  265. map.setTile(x, y, Tiles.getPath(
  266. isPath(map, x, y - 1), isPath(map, x + 1, y),
  267. isPath(map, x, y + 1), isPath(map, x - 1, y)));
  268. }
  269. }
  270. }
  271. }
  272. private static void destroyPathBlocks(TileMap map) {
  273. for(int x = 0; x < map.getWidth() - 1; x++) {
  274. for(int y = 0; y < map.getHeight() - 1; y++) {
  275. if(map.getTile(x, y).isPath() && map.getTile(x + 1, y).isPath()
  276. && map.getTile(x, y + 1).isPath() && map.getTile(x + 1, y + 1).isPath()) {
  277. if(!isPath(map, x - 1, y) && !isPath(map, x, y - 1)) {
  278. map.setTile(x, y, Tiles.GRASS);
  279. continue;
  280. }
  281. if(!isPath(map, x + 1, y - 1) && !isPath(map, x + 2, y)) {
  282. map.setTile(x + 1, y, Tiles.GRASS);
  283. continue;
  284. }
  285. if(!isPath(map, x - 1, y + 1) && !isPath(map, x, y + 2)) {
  286. map.setTile(x, y + 1, Tiles.GRASS);
  287. continue;
  288. }
  289. if(!isPath(map, x + 2, y + 1) && !isPath(map, x + 1, y + 2)) {
  290. map.setTile(x + 1, y + 1, Tiles.GRASS);
  291. }
  292. }
  293. }
  294. }
  295. }
  296. private static void generatePathDiretion(TileMap map, Random r, float x, float y, float dx, float dy) {
  297. while(true) {
  298. int tileX = (int) x;
  299. int tileY = (int) y;
  300. if(tileX < 0 || tileY < 0 || tileX >= map.getWidth() || tileY >= map.getHeight() || !map.getTile(tileX, tileY).canHostPath()) {
  301. break;
  302. }
  303. map.setTile(tileX, tileY, Tiles.PATH_N_E_S_W);
  304. while(tileX == (int) x && tileY == (int) y) {
  305. if(r.nextBoolean()) {
  306. x += dx;
  307. } else {
  308. y += dy;
  309. }
  310. }
  311. }
  312. }
  313. }