TileMapGenerator.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. package pathgame.tilemap;
  2. import java.util.ArrayList;
  3. import java.util.Random;
  4. public class TileMapGenerator
  5. {
  6. private static long seed = 1;
  7. /** Returns a random generated map
  8. *
  9. * @param width the width of the map
  10. * @param height the height of the map
  11. * @param towns the amount of towns
  12. * @return a random generated map
  13. */
  14. public static TileMap getMap(int width, int height, int towns)
  15. {
  16. return getMap(width, height, seed++, towns);
  17. }
  18. /** Returns a random generated map
  19. *
  20. * @param width the width of the map
  21. * @param height the height of the map
  22. * @param seed the seed for the random number generator
  23. * @param towns the amount of towns
  24. * @return a random generated map
  25. */
  26. public static TileMap getMap(int width, int height, long seed, int towns)
  27. {
  28. Random r = new Random(seed);
  29. HighMap highMap = HighMap.generate(seed, width, height);
  30. TileMap map = new TileMap(width, height);
  31. for(int x = 0; x < width; x++)
  32. {
  33. for(int y = 0; y < height; y++)
  34. {
  35. if(highMap.get(x, y) < 0.15f)
  36. {
  37. map.setTile(x, y, Tiles.DEEP_WATER);
  38. }
  39. else if(highMap.get(x, y) < 0.3f)
  40. {
  41. map.setTile(x, y, Tiles.SHALLOW_WATER);
  42. }
  43. else if(highMap.get(x, y) < 0.7f)
  44. {
  45. map.setTile(x, y, randomGrass(r));
  46. }
  47. else if(highMap.get(x, y) < 0.85f)
  48. {
  49. map.setTile(x, y, Tiles.HILL);
  50. }
  51. else
  52. {
  53. map.setTile(x, y, Tiles.MOUNTAIN);
  54. }
  55. }
  56. }
  57. generateHomeTown(map, r);
  58. int forestSize = ((width + height) / 2) / 10;
  59. forestSize *= forestSize;
  60. generateForest(map, r, forestSize, 10, 2, Tiles.FOREST);
  61. generateForest(map, r, forestSize, 5, 2, Tiles.SWAMP, Tiles.SWAMP, Tiles.SWAMP_DECO, Tiles.SWAMP_TREE);
  62. generateTowns(map, r, towns);
  63. generatePorts(map, r);
  64. generatePaths(map, r);
  65. removeBadSwampTree(map);
  66. return map;
  67. }
  68. private static Tile randomGrass(Random r)
  69. {
  70. if(r.nextFloat() < 0.3f)
  71. {
  72. return randomTile(Tiles.GRASS_VARIANTS, r);
  73. }
  74. return Tiles.GRASS;
  75. }
  76. private static void generateForest(TileMap map, Random r, int depth, int placements, int jumpRadius, Tile... t)
  77. {
  78. for(int i = 0; i < placements; i++)
  79. {
  80. int x = r.nextInt(map.getWidth());
  81. int y = r.nextInt(map.getHeight());
  82. while(map.getTile(x, y).getForestReplaceChance() < 1.0f)
  83. {
  84. x = r.nextInt(map.getWidth());
  85. y = r.nextInt(map.getHeight());
  86. }
  87. for(int j = 0; j < depth; j++)
  88. {
  89. int oldX = x;
  90. int oldY = y;
  91. x += r.nextInt(jumpRadius * 2 + 1) - jumpRadius;
  92. y += r.nextInt(jumpRadius * 2 + 1) - jumpRadius;
  93. x = Math.min(Math.max(x, 0), map.getWidth() - 1);
  94. y = Math.min(Math.max(y, 0), map.getHeight() - 1);
  95. if(r.nextFloat() < map.getTile(x, y).getForestReplaceChance())
  96. {
  97. map.setTile(x, y, randomTile(t, r));
  98. placeForest(map, r, x - 1, y, t);
  99. placeForest(map, r, x + 1, y, t);
  100. placeForest(map, r, x, y - 1, t);
  101. placeForest(map, r, x, y + 1, t);
  102. }
  103. else
  104. {
  105. x = oldX;
  106. y = oldY;
  107. }
  108. }
  109. }
  110. }
  111. private static void removeBadSwampTree(TileMap map)
  112. {
  113. for(int x = 0; x < map.getWidth(); x++)
  114. {
  115. for(int y = 0; y < map.getHeight(); y++)
  116. {
  117. if(map.getTile(x, y) == Tiles.SWAMP_TREE &&
  118. ((x > 0 && map.getTile(x - 1, y).getRenderType() != TileRenderType.SWAMP) ||
  119. (y > 0 && map.getTile(x, y - 1).getRenderType() != TileRenderType.SWAMP)))
  120. {
  121. map.setTile(x, y, Tiles.SWAMP);
  122. }
  123. }
  124. }
  125. }
  126. private static Tile randomTile(Tile[] tiles, Random r)
  127. {
  128. if(tiles.length == 1)
  129. {
  130. return tiles[0];
  131. }
  132. return tiles[r.nextInt(tiles.length)];
  133. }
  134. private static void placeForest(TileMap map, Random r, int x, int y, Tile... t)
  135. {
  136. if(x >= 0 && x < map.getWidth() && y >= 0 && y < map.getHeight())
  137. {
  138. if(r.nextFloat() * 2 < map.getTile(x, y).getForestReplaceChance())
  139. {
  140. map.setTile(x, y, randomTile(t, r));
  141. }
  142. }
  143. }
  144. private static void generateTowns(TileMap map, Random r, int towns)
  145. {
  146. int failCounter = 0;
  147. while(towns > 0 && failCounter < 100)
  148. {
  149. int x = r.nextInt(map.getWidth());
  150. int y = r.nextInt(map.getHeight());
  151. if(map.getTile(x, y).canHostTown() && checkNearbyTowns(map, x, y, 2))
  152. {
  153. map.setTile(x, y, Tiles.TOWN);
  154. towns--;
  155. failCounter = 0;
  156. }
  157. else
  158. {
  159. failCounter++;
  160. }
  161. }
  162. }
  163. private static boolean checkNearbyTowns(TileMap map, int x, int y, int radius)
  164. {
  165. int startX = Math.max(x - radius, 0);
  166. int startY = Math.max(y - radius, 0);
  167. int endX = Math.min(x + radius, map.getWidth() - 1);
  168. int endY = Math.min(y + radius, map.getHeight() - 1);
  169. for(int mx = startX; mx <= endX; mx++)
  170. {
  171. for(int my = startY; my <= endY; my++)
  172. {
  173. if(map.getTile(mx, my) == Tiles.TOWN)
  174. {
  175. return false;
  176. }
  177. }
  178. }
  179. return true;
  180. }
  181. private static boolean isWater(TileMap map, int x, int y)
  182. {
  183. TileType type = map.getTile(x, y).getType();
  184. return type == TileType.DEEP_WATER || type == TileType.SHALLOW_WATER;
  185. }
  186. private static boolean isNeighbourWater(TileMap map, int x, int y)
  187. {
  188. return (x - 1 >= 0 && isWater(map, x - 1, y)) ||
  189. (x + 1 < map.getWidth() && isWater(map, x + 1, y)) ||
  190. (y - 1 >= 0 && isWater(map, x, y - 1)) ||
  191. (y + 1 < map.getHeight() && isWater(map, x, y + 1));
  192. }
  193. private static void generatePorts(TileMap map, Random r)
  194. {
  195. boolean[][] visited = new boolean[map.getWidth()][map.getHeight()];
  196. for(int x = 0; x < map.getWidth(); x++)
  197. {
  198. for(int y = 0; y < map.getHeight(); y++)
  199. {
  200. if(!visited[x][y] && isWater(map, x, y))
  201. {
  202. getLake(map, r, x, y, visited);
  203. }
  204. visited[x][y] = true;
  205. }
  206. }
  207. }
  208. private static int waterSize;
  209. private static int waterMinX;
  210. private static int waterMinY;
  211. private static int waterMaxX;
  212. private static int waterMaxY;
  213. private static class Location
  214. {
  215. private final int x;
  216. private final int y;
  217. public Location(int x, int y)
  218. {
  219. this.x = x;
  220. this.y = y;
  221. }
  222. public double getQuaredDistance(int ox, int oy)
  223. {
  224. return (ox - x) * (ox - x) + (oy - y) * (oy - y);
  225. }
  226. }
  227. private static double minSquaredDistance(ArrayList<Location> locs, int x, int y)
  228. {
  229. double min = Double.MAX_VALUE;
  230. for(Location loc : locs)
  231. {
  232. double d = loc.getQuaredDistance(x, y);
  233. if(d < min)
  234. {
  235. min = d;
  236. }
  237. }
  238. return min;
  239. }
  240. private static void getLake(TileMap map, Random r, int x, int y, boolean[][] visited)
  241. {
  242. waterSize = 0;
  243. waterMinX = x;
  244. waterMinY = y;
  245. waterMaxX = x;
  246. waterMaxY = y;
  247. scanWaterTiles(map, x, y, visited);
  248. // water outlines can be a port too
  249. waterMinX = Math.max(0, waterMinX - 1);
  250. waterMinY = Math.max(0, waterMinY - 1);
  251. waterMaxX = Math.min(map.getWidth() - 1, waterMaxX + 1);
  252. waterMaxY = Math.min(map.getHeight() - 1, waterMaxY + 1);
  253. ArrayList<Location> locs = new ArrayList<>();
  254. //System.out.println(String.format("Lake Size: %d, (%d, %d) -> (%d, %d)",
  255. // waterSize, waterMinX, waterMinY, waterMaxX, waterMaxY));
  256. int ports = waterSize / 30;
  257. int diffX = waterMaxX - waterMinX + 1;
  258. int diffY = waterMaxY - waterMinY + 1;
  259. int failCounter = 0;
  260. while(ports > 0 && failCounter < 100)
  261. {
  262. int rx = waterMinX + r.nextInt(diffX);
  263. int ry = waterMinY + r.nextInt(diffY);
  264. if(map.getTile(rx, ry).canHostTown() && isNeighbourWater(map, rx, ry) && minSquaredDistance(locs, rx, ry) > 25)
  265. {
  266. locs.add(new Location(rx, ry));
  267. map.setTile(rx, ry, Tiles.PORT);
  268. ports--;
  269. failCounter = 0;
  270. }
  271. else
  272. {
  273. failCounter++;
  274. }
  275. }
  276. }
  277. private static void scanWaterTiles(TileMap map, int x, int y, boolean[][] visited)
  278. {
  279. if(!visited[x][y] && isWater(map, x, y))
  280. {
  281. visited[x][y] = true;
  282. waterSize++;
  283. waterMinX = Math.min(x, waterMinX);
  284. waterMinY = Math.min(y, waterMinY);
  285. waterMaxX = Math.max(x, waterMaxX);
  286. waterMaxY = Math.max(y, waterMaxY);
  287. if(x - 1 >= 0)
  288. {
  289. scanWaterTiles(map, x - 1, y, visited);
  290. }
  291. if(x + 1 < map.getWidth())
  292. {
  293. scanWaterTiles(map, x + 1, y, visited);
  294. }
  295. if(y - 1 >= 0)
  296. {
  297. scanWaterTiles(map, x, y - 1, visited);
  298. }
  299. if(y + 1 < map.getHeight())
  300. {
  301. scanWaterTiles(map, x, y + 1, visited);
  302. }
  303. }
  304. }
  305. private static void generateHomeTown(TileMap map, Random r)
  306. {
  307. int failCounter = 0;
  308. while(failCounter < 100)
  309. {
  310. int x = r.nextInt(map.getWidth());
  311. int y = r.nextInt(map.getHeight());
  312. if(map.getTile(x, y).canHostTown())
  313. {
  314. map.setTile(x, y, Tiles.HOME_TOWN);
  315. map.setHomeTown(x, y);
  316. return;
  317. }
  318. else
  319. {
  320. failCounter++;
  321. }
  322. }
  323. map.setTile(0, 0, Tiles.HOME_TOWN);
  324. map.setHomeTown(0, 0);
  325. }
  326. private static boolean isPath(TileMap map, int x, int y)
  327. {
  328. return x >= 0 && y >= 0 && x < map.getWidth() && y < map.getHeight() && map.getTile(x, y).isPath();
  329. }
  330. private static void generatePaths(TileMap map, Random r)
  331. {
  332. int paths = (map.getHeight() + map.getWidth()) / 6 + 2;
  333. // generate paths with random direction
  334. for(int i = 0; i < paths; i++)
  335. {
  336. int x = r.nextInt(map.getWidth());
  337. int y = r.nextInt(map.getHeight());
  338. while(!map.getTile(x, y).canHostPath())
  339. {
  340. x = r.nextInt(map.getWidth());
  341. y = r.nextInt(map.getHeight());
  342. }
  343. float dx = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1);
  344. float dy = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1);
  345. generatePathDiretion(map, r, x, y, dx, dy);
  346. }
  347. // destroy path 2x2 blocks
  348. destroyPathBlocks(map);
  349. destroyPathBlocks(map);
  350. // swap paths depending on neighbours
  351. for(int x = 0; x < map.getWidth(); x++)
  352. {
  353. for(int y = 0; y < map.getHeight(); y++)
  354. {
  355. if(map.getTile(x, y).isPath())
  356. {
  357. map.setTile(x, y, Tiles.getPath(
  358. isPath(map, x, y - 1), isPath(map, x + 1, y),
  359. isPath(map, x, y + 1), isPath(map, x - 1, y)));
  360. }
  361. }
  362. }
  363. }
  364. private static void destroyPathBlocks(TileMap map)
  365. {
  366. for(int x = 0; x < map.getWidth() - 1; x++)
  367. {
  368. for(int y = 0; y < map.getHeight() - 1; y++)
  369. {
  370. if(map.getTile(x, y).isPath() && map.getTile(x + 1, y).isPath() &&
  371. map.getTile(x, y + 1).isPath() && map.getTile(x + 1, y + 1).isPath())
  372. {
  373. if(!isPath(map, x - 1, y) && !isPath(map, x, y - 1))
  374. {
  375. map.setTile(x, y, Tiles.GRASS);
  376. continue;
  377. }
  378. if(!isPath(map, x + 1, y - 1) && !isPath(map, x + 2, y))
  379. {
  380. map.setTile(x + 1, y, Tiles.GRASS);
  381. continue;
  382. }
  383. if(!isPath(map, x - 1, y + 1) && !isPath(map, x, y + 2))
  384. {
  385. map.setTile(x, y + 1, Tiles.GRASS);
  386. continue;
  387. }
  388. if(!isPath(map, x + 2, y + 1) && !isPath(map, x + 1, y + 2))
  389. {
  390. map.setTile(x + 1, y + 1, Tiles.GRASS);
  391. }
  392. }
  393. }
  394. }
  395. }
  396. private static void generatePathDiretion(TileMap map, Random r, float x, float y, float dx, float dy)
  397. {
  398. while(true)
  399. {
  400. int tileX = (int) x;
  401. int tileY = (int) y;
  402. if(tileX < 0 || tileY < 0 || tileX >= map.getWidth() || tileY >= map.getHeight() || !map.getTile(tileX, tileY).canHostPath())
  403. {
  404. break;
  405. }
  406. map.setTile(tileX, tileY, Tiles.PATH_N_E_S_W);
  407. while(tileX == (int) x && tileY == (int) y)
  408. {
  409. if(r.nextBoolean())
  410. {
  411. x += dx;
  412. }
  413. else
  414. {
  415. y += dy;
  416. }
  417. }
  418. }
  419. }
  420. }