ruby-perlin-2D-map-generator 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +22 -4
- data/lib/CLI/command.rb +37 -1
- data/lib/biome.rb +10 -10
- data/lib/building.rb +20 -0
- data/lib/flora_generator.rb +28 -0
- data/lib/map.rb +24 -5
- data/lib/map_config.rb +23 -4
- data/lib/map_tile_generator.rb +1 -0
- data/lib/pathfinding/a_star_finder.rb +26 -13
- data/lib/pathfinding/grid.rb +5 -5
- data/lib/pathfinding/priority_queue.rb +73 -0
- data/lib/poisson_disk_sampling/sample_area.rb +43 -0
- data/lib/poisson_disk_sampling/sampler.rb +108 -0
- data/lib/road_generator.rb +8 -1
- data/lib/tile.rb +25 -19
- data/lib/town_generator.rb +154 -0
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bef2e8554e39c0e315b940f4a98a8927348ae576a7f12168c86c74f969c241b
|
4
|
+
data.tar.gz: b3debfe8269979c1f1f0be2a8def6a86de9fcd2a8ad6f548295113068ec04566
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c91f9810eda61b360e26b31944820b2587ff9e6a10776cdc84347475470573b381853e63b629781fbc0f18eb9fc33299fad31e2a436ef7f7a66d67db8dbb2ea2
|
7
|
+
data.tar.gz: 8c12388e0e7965165557bc5680f030d6d2da9a42e35cc066fbff2390de0e6076c2318e7d62000d17026fa91206adb1ff631eff0ff6af5d670ef81e3d206e45a0
|
data/README.md
CHANGED
@@ -3,16 +3,18 @@
|
|
3
3
|
[](https://badge.fury.io/rb/ruby-perlin-2D-map-generator)
|
4
4
|

|
5
5
|

|
6
|
+
<a href="https://codeclimate.com/github/matthewstyler/ruby-perlin-2D-map-generator/test_coverage"><img src="https://api.codeclimate.com/v1/badges/b99aae29d02b7a8a4cc6/test_coverage" /></a>
|
7
|
+
<a href="https://codeclimate.com/github/matthewstyler/ruby-perlin-2D-map-generator/maintainability"><img src="https://api.codeclimate.com/v1/badges/b99aae29d02b7a8a4cc6/maintainability" /></a>
|
6
8
|
[](https://rubygems.org/gems/ruby-perlin-2D-map-generator)
|
7
9
|
|
8
|
-
A gem that procedurally generates seeded and customizable 2D map with optional roads using perlin noise.
|
10
|
+
A gem that procedurally generates seeded and customizable 2D map with optional roads and towns using perlin noise.
|
9
11
|
|
10
12
|
Include the gem in your project, or use the executable from the command line.
|
11
13
|
|
12
14
|
Map can be rendered in console using ansi colors or returned as 2D array of hashes describing each tile and binome. Completely customizable, use the --help option for full usage details.
|
13
15
|
|
14
16
|
|
15
|
-

|
16
18
|
|
17
19
|
|
18
20
|
# Installation
|
@@ -48,7 +50,8 @@ See Command line Usage for full customization, below are some examples. Alter th
|
|
48
50
|
--roads=int Add this many roads through the map,
|
49
51
|
starting and ending at edges
|
50
52
|
(default 0)
|
51
|
-
|
53
|
+
--towns=int Add this randomly sized towns
|
54
|
+
(default 0)
|
52
55
|
--hs=int The seed for a terrains height perlin generation
|
53
56
|
(default 10)
|
54
57
|
--ms=int The seed for a terrains moist perlin generation
|
@@ -70,7 +73,7 @@ See Command line Usage for full customization, below are some examples. Alter th
|
|
70
73
|
Roads can be generated by providing a positive integer to the `roads=` argument. Roads are randomly seeded to begin
|
71
74
|
and start at an axis (but not the same axis).
|
72
75
|
|
73
|
-
A* pathfinding is used to generate the roads
|
76
|
+
A* pathfinding with a priority queue is used to generate the roads. The heuristic uses manhattan distance, and favours existing roads and similar elevations in adjacent tiles.
|
74
77
|
|
75
78
|
Roads can be configured to include/exclude generating paths thorugh water, mountains and flora.
|
76
79
|
|
@@ -78,6 +81,8 @@ Tiles containing roads are of type `road`, those without are of type `terrain`.
|
|
78
81
|
|
79
82
|
The `--roads_to_make` option allows you to specify multiple pairs of coordinates to attempt to build paths, subject to the heuristic and other option constraints. Expects a a single list, but must be sets of 4, example of two roads: `--roads_to_make=0,0,50,50,0,0,75,75`
|
80
83
|
|
84
|
+
## Towns
|
85
|
+
With Poisson Disk Sampling, towns can be generated randomly or with a provided x,y coordinate used as a centroid with radius. The result will be tiles that contain `Building` `items`. Buildings in a town are connected by roads, and additionally towns are connected to other towns by roads.
|
81
86
|
# Generate without rendering
|
82
87
|
|
83
88
|
```irb
|
@@ -201,6 +206,16 @@ Options:
|
|
201
206
|
(default 100)
|
202
207
|
--temp=float Adjust each generated temperature by
|
203
208
|
this percent (-100 - 100) (default 0.0)
|
209
|
+
--town_seed=int The seed for generating towns
|
210
|
+
(default 500)
|
211
|
+
--towns=int Add this many randomly sized towns
|
212
|
+
throughout the map
|
213
|
+
(default 0)
|
214
|
+
--towns_to_make ints Attempt to create a town at given x,y
|
215
|
+
coordinate, with z points and v radius
|
216
|
+
(4 integers). Can be supplied multiple
|
217
|
+
towns.
|
218
|
+
(default [])
|
204
219
|
--ts=int The seed for a terrains temperature
|
205
220
|
perlin generation (default 3000)
|
206
221
|
--width=int The width of the generated map
|
@@ -215,6 +230,9 @@ Examples:
|
|
215
230
|
|
216
231
|
Render with roads
|
217
232
|
$ ruby-perlin-2D-map-generator render --roads=2
|
233
|
+
|
234
|
+
Render with 5 roads, 1 provided road, 10 random towns and 1 provided town
|
235
|
+
$ ruby-perlin-2D-map-generator render --roads=5 --roads_to_make=0,0,50,50 --towns=10 --towns_to_make=5,5,10,3
|
218
236
|
|
219
237
|
Describe tile [1, 1]
|
220
238
|
$ ruby-perlin-2D-map-generator describe coordinates=1,1
|
data/lib/CLI/command.rb
CHANGED
@@ -53,6 +53,15 @@ module CLI
|
|
53
53
|
default MapConfig::DEFAULT_ROADS_TO_MAKE
|
54
54
|
end
|
55
55
|
|
56
|
+
option :towns_to_make do
|
57
|
+
arity one
|
58
|
+
long '--towns_to_make ints'
|
59
|
+
convert :int_list
|
60
|
+
validate ->(v) { v >= 0 }
|
61
|
+
desc 'Attempt to create a town at given x,y coordinate, with z points and v radius (4 integers). Can be supplied multiple towns.'
|
62
|
+
default MapConfig::DEFAULT_TOWNS_TO_MAKE
|
63
|
+
end
|
64
|
+
|
56
65
|
option :height_seed do
|
57
66
|
long '--hs int'
|
58
67
|
# or
|
@@ -294,6 +303,31 @@ module CLI
|
|
294
303
|
default MapConfig::DEFAULT_ROAD_EXCLUDE_FLORA_PATH
|
295
304
|
end
|
296
305
|
|
306
|
+
option :town_seed do
|
307
|
+
long '--town_seed int'
|
308
|
+
long '--town_seed=int'
|
309
|
+
|
310
|
+
desc 'The seed for generating towns'
|
311
|
+
convert Integer
|
312
|
+
default MapConfig::DEFAULT_TOWN_SEED
|
313
|
+
end
|
314
|
+
|
315
|
+
option :towns do
|
316
|
+
long '--towns int'
|
317
|
+
long '--towns=int'
|
318
|
+
|
319
|
+
desc 'Add this many randomly sized towns throughout the map'
|
320
|
+
convert Integer
|
321
|
+
validate ->(val) { val >= 0 }
|
322
|
+
default MapConfig::DEFAULT_NUM_OF_TOWNS
|
323
|
+
end
|
324
|
+
|
325
|
+
option :verbose do
|
326
|
+
arity one
|
327
|
+
long '--verbose'
|
328
|
+
desc 'Used with the render command, outputs loading information'
|
329
|
+
end
|
330
|
+
|
297
331
|
flag :help do
|
298
332
|
short '-h'
|
299
333
|
long '--help'
|
@@ -319,7 +353,9 @@ module CLI
|
|
319
353
|
height: params[:height],
|
320
354
|
all_perlin_configs: MapConfig::AllPerlinConfigs.new(perlin_height_config, perlin_moist_config, perlin_temp_config),
|
321
355
|
generate_flora: params[:generate_flora],
|
322
|
-
road_config: MapConfig::RoadConfig.new(*params.to_h.slice(:road_seed, :roads, :road_exclude_water_path, :road_exclude_mountain_path, :road_exclude_flora_path, :roads_to_make).values)
|
356
|
+
road_config: MapConfig::RoadConfig.new(*params.to_h.slice(:road_seed, :roads, :road_exclude_water_path, :road_exclude_mountain_path, :road_exclude_flora_path, :roads_to_make).values),
|
357
|
+
town_config: MapConfig::TownConfig.new(*params.to_h.slice(:town_seed, :towns, :towns_to_make).values),
|
358
|
+
verbose: params[:verbose]
|
323
359
|
))
|
324
360
|
case params[:command]
|
325
361
|
when 'render' then map.render
|
data/lib/biome.rb
CHANGED
@@ -151,21 +151,21 @@ class Biome
|
|
151
151
|
case elevation
|
152
152
|
when 0.95..1
|
153
153
|
SNOW
|
154
|
-
when 0.9
|
154
|
+
when 0.9...0.95
|
155
155
|
ROCKS
|
156
|
-
when 0.8
|
156
|
+
when 0.8...0.9
|
157
157
|
if moist < 0.9
|
158
158
|
MOUNTAIN
|
159
159
|
else
|
160
160
|
SHOAL
|
161
161
|
end
|
162
|
-
when 0.7
|
162
|
+
when 0.7...0.8
|
163
163
|
if moist < 0.9
|
164
164
|
MOUNTAIN_FOOT
|
165
165
|
else
|
166
166
|
SHOAL
|
167
167
|
end
|
168
|
-
when 0.6
|
168
|
+
when 0.6...0.7
|
169
169
|
if moist < 0.8
|
170
170
|
if desert_condition?(moist, temp)
|
171
171
|
STEPPE_DESERT
|
@@ -179,7 +179,7 @@ class Biome
|
|
179
179
|
else
|
180
180
|
SHOAL
|
181
181
|
end
|
182
|
-
when 0.3
|
182
|
+
when 0.3...0.6
|
183
183
|
if desert_condition?(moist, temp)
|
184
184
|
DESERT
|
185
185
|
elsif taiga_condition?(moist, temp)
|
@@ -187,7 +187,7 @@ class Biome
|
|
187
187
|
else
|
188
188
|
VALLEY
|
189
189
|
end
|
190
|
-
when 0.2
|
190
|
+
when 0.2...0.3
|
191
191
|
if desert_condition?(moist, temp)
|
192
192
|
DEEP_DESERT
|
193
193
|
elsif taiga_condition?(moist, temp)
|
@@ -195,25 +195,25 @@ class Biome
|
|
195
195
|
else
|
196
196
|
DEEP_VALLEY
|
197
197
|
end
|
198
|
-
when 0.15
|
198
|
+
when 0.15...0.2
|
199
199
|
if taiga_condition?(moist, temp)
|
200
200
|
TAIGA_COAST
|
201
201
|
else
|
202
202
|
COASTLINE
|
203
203
|
end
|
204
|
-
when 0.05
|
204
|
+
when 0.05...0.15
|
205
205
|
if taiga_condition?(moist, temp)
|
206
206
|
ICE
|
207
207
|
else
|
208
208
|
SHOAL
|
209
209
|
end
|
210
|
-
when 0.025
|
210
|
+
when 0.025...0.05
|
211
211
|
if taiga_condition?(moist, temp)
|
212
212
|
ICE
|
213
213
|
else
|
214
214
|
OCEAN
|
215
215
|
end
|
216
|
-
when 0.0
|
216
|
+
when 0.0...0.025
|
217
217
|
if taiga_condition?(moist, temp)
|
218
218
|
ICE
|
219
219
|
else
|
data/lib/building.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tile_item'
|
4
|
+
|
5
|
+
#
|
6
|
+
# Represents a building item on a tile
|
7
|
+
#
|
8
|
+
class Building < TileItem
|
9
|
+
TOWN_RENDER_PRIORITY = DEFAULT_RENDER_PRIORITY + 1
|
10
|
+
|
11
|
+
HOUSE = "\u{1F3E0}"
|
12
|
+
|
13
|
+
def initialize(render_symbol)
|
14
|
+
super self, render_symbol: render_symbol, render_priority: TOWN_RENDER_PRIORITY
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.random_town_building(_seed)
|
18
|
+
Building.new(HOUSE)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Generates flora for the given tiles
|
5
|
+
#
|
6
|
+
class FloraGenerator
|
7
|
+
attr_reader :tiles
|
8
|
+
|
9
|
+
def initialize(tiles)
|
10
|
+
@tiles = tiles
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate(config)
|
14
|
+
return unless config.generate_flora
|
15
|
+
|
16
|
+
puts 'generating flora...' if config.verbose
|
17
|
+
tiles.each do |row|
|
18
|
+
row.each do |tile|
|
19
|
+
next unless tile.biome.flora_available
|
20
|
+
|
21
|
+
range_max_value = tiles[(tile.y - tile.biome.flora_range)...(tile.y + tile.biome.flora_range)]&.map do |r|
|
22
|
+
r[(tile.x - tile.biome.flora_range)...(tile.x + tile.biome.flora_range)]
|
23
|
+
end&.flatten&.map(&:height)&.max
|
24
|
+
tile.add_flora if range_max_value == tile.height
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/map.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'map_tile_generator'
|
4
4
|
require 'map_config'
|
5
5
|
require 'road_generator'
|
6
|
+
require 'town_generator'
|
7
|
+
require 'flora_generator'
|
6
8
|
|
7
9
|
class Map
|
8
10
|
attr_reader :config
|
@@ -33,16 +35,33 @@ class Map
|
|
33
35
|
def tiles
|
34
36
|
return @tiles if @tiles
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
generate_tiles
|
39
|
+
generate_flora
|
40
|
+
generate_roads
|
41
|
+
generate_towns
|
42
|
+
|
40
43
|
@tiles
|
41
44
|
end
|
42
45
|
|
43
46
|
private
|
44
47
|
|
45
48
|
def generate_tiles
|
46
|
-
MapTileGenerator.new(map: self).generate
|
49
|
+
@tiles = MapTileGenerator.new(map: self).generate
|
50
|
+
end
|
51
|
+
|
52
|
+
def generate_flora
|
53
|
+
FloraGenerator.new(@tiles).generate(config)
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate_roads
|
57
|
+
road_generator = RoadGenerator.new(@tiles)
|
58
|
+
road_generator.generate_num_of_random_roads(config.road_config)
|
59
|
+
road_generator.generate_roads_from_coordinate_list(config.road_config.roads_to_make, config.verbose)
|
60
|
+
end
|
61
|
+
|
62
|
+
def generate_towns
|
63
|
+
town_generator = TownGenerator.new(@tiles, seed: config.town_config.town_seed)
|
64
|
+
town_generator.generate_random_towns(config.town_config)
|
65
|
+
town_generator.generate_towns_from_coordinate_list(config.town_config)
|
47
66
|
end
|
48
67
|
end
|
data/lib/map_config.rb
CHANGED
@@ -32,18 +32,27 @@ class MapConfig
|
|
32
32
|
DEFAULT_ROAD_EXCLUDE_FLORA_PATH = true
|
33
33
|
DEFAULT_ROADS_TO_MAKE = [].freeze
|
34
34
|
|
35
|
+
DEFAULT_TOWN_SEED = 500
|
36
|
+
DEFAULT_NUM_OF_TOWNS = 0
|
37
|
+
DEFAULT_TOWNS_TO_MAKE = [].freeze
|
38
|
+
|
39
|
+
DEFAULT_VERBOSE = false
|
40
|
+
|
35
41
|
PERLIN_CONFIG_OPTIONS = %i[width height noise_seed octaves x_frequency y_frequency persistance adjustment].freeze
|
36
42
|
ALL_PERLIN_CONFIGS = %i[perlin_height_config perlin_moist_config perlin_temp_config].freeze
|
37
|
-
ROAD_CONFIG_OPTIONS = %i[road_seed roads road_exclude_water_path road_exclude_mountain_path road_exclude_flora_path roads_to_make].freeze
|
43
|
+
ROAD_CONFIG_OPTIONS = %i[road_seed roads road_exclude_water_path road_exclude_mountain_path road_exclude_flora_path roads_to_make verbose].freeze
|
44
|
+
TOWN_CONFIG_OPTIONS = %i[town_seed towns towns_to_make verbose].freeze
|
38
45
|
|
39
46
|
PerlinConfig = Struct.new(*PERLIN_CONFIG_OPTIONS)
|
40
47
|
AllPerlinConfigs = Struct.new(*ALL_PERLIN_CONFIGS)
|
41
48
|
RoadConfig = Struct.new(*ROAD_CONFIG_OPTIONS)
|
49
|
+
TownConfig = Struct.new(*TOWN_CONFIG_OPTIONS)
|
42
50
|
|
43
|
-
attr_reader :generate_flora, :perlin_height_config, :perlin_moist_config, :perlin_temp_config, :width, :height, :road_config
|
51
|
+
attr_reader :generate_flora, :perlin_height_config, :perlin_moist_config, :perlin_temp_config, :width, :height, :road_config, :town_config, :verbose
|
44
52
|
|
53
|
+
# rubocop:disable Metrics/ParameterLists:
|
45
54
|
def initialize(all_perlin_configs: default_perlin_configs, width: DEFAULT_TILE_COUNT,
|
46
|
-
height: DEFAULT_TILE_COUNT, generate_flora: DEFAULT_GENERATE_FLORA, road_config: default_road_config)
|
55
|
+
height: DEFAULT_TILE_COUNT, generate_flora: DEFAULT_GENERATE_FLORA, road_config: default_road_config, town_config: default_town_config, verbose: DEFAULT_VERBOSE)
|
47
56
|
validate(all_perlin_configs)
|
48
57
|
@generate_flora = generate_flora
|
49
58
|
@perlin_height_config = all_perlin_configs.perlin_height_config
|
@@ -52,7 +61,12 @@ class MapConfig
|
|
52
61
|
@width = width
|
53
62
|
@height = height
|
54
63
|
@road_config = road_config
|
64
|
+
@town_config = town_config
|
65
|
+
@verbose = verbose
|
66
|
+
town_config.verbose = verbose
|
67
|
+
road_config.verbose = verbose
|
55
68
|
end
|
69
|
+
# rubocop:enable Metrics/ParameterLists:
|
56
70
|
|
57
71
|
private
|
58
72
|
|
@@ -78,7 +92,12 @@ class MapConfig
|
|
78
92
|
end
|
79
93
|
|
80
94
|
def default_road_config
|
81
|
-
RoadConfig.new(DEFAULT_ROAD_SEED, DEFAULT_NUM_OF_ROADS, DEFAULT_ROAD_EXCLUDE_WATER_PATH, DEFAULT_ROAD_EXCLUDE_MOUNTAIN_PATH, DEFAULT_ROAD_EXCLUDE_FLORA_PATH, DEFAULT_ROADS_TO_MAKE
|
95
|
+
RoadConfig.new(DEFAULT_ROAD_SEED, DEFAULT_NUM_OF_ROADS, DEFAULT_ROAD_EXCLUDE_WATER_PATH, DEFAULT_ROAD_EXCLUDE_MOUNTAIN_PATH, DEFAULT_ROAD_EXCLUDE_FLORA_PATH, DEFAULT_ROADS_TO_MAKE,
|
96
|
+
DEFAULT_VERBOSE)
|
97
|
+
end
|
98
|
+
|
99
|
+
def default_town_config
|
100
|
+
TownConfig.new(DEFAULT_TOWN_SEED, DEFAULT_NUM_OF_TOWNS, DEFAULT_TOWNS_TO_MAKE, DEFAULT_VERBOSE)
|
82
101
|
end
|
83
102
|
|
84
103
|
def default_perlin_configs
|
data/lib/map_tile_generator.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
require 'pathfinding/priority_queue'
|
5
|
+
|
3
6
|
module Pathfinding
|
4
7
|
#
|
5
8
|
# An A* Pathfinder to build roads/paths between two coordinates containing
|
@@ -7,28 +10,35 @@ module Pathfinding
|
|
7
10
|
#
|
8
11
|
class AStarFinder
|
9
12
|
def find_path(start_node, end_node, grid)
|
10
|
-
open_set = [start_node]
|
11
13
|
came_from = {}
|
12
14
|
g_score = { start_node => 0 }
|
13
|
-
f_score = { start_node =>
|
15
|
+
f_score = { start_node => manhattan_distance(start_node, end_node) }
|
16
|
+
|
17
|
+
open_set = Pathfinding::PriorityQueue.new
|
18
|
+
open_set.push(start_node, f_score[start_node])
|
14
19
|
|
20
|
+
closed_set = Set.new
|
15
21
|
until open_set.empty?
|
16
|
-
|
22
|
+
current = open_set.pop
|
23
|
+
|
24
|
+
# Early exit if the current node is in the closed set
|
25
|
+
next if closed_set.include?(current)
|
17
26
|
|
18
|
-
|
27
|
+
# Mark the current node as visited
|
28
|
+
closed_set.add(current)
|
19
29
|
|
20
|
-
|
30
|
+
return reconstruct_path(came_from, current) if current == end_node
|
21
31
|
|
22
|
-
grid.neighbors(
|
23
|
-
tentative_g_score = g_score[
|
32
|
+
grid.neighbors(current).each do |neighbor|
|
33
|
+
tentative_g_score = g_score[current] + 1
|
24
34
|
|
25
|
-
next
|
35
|
+
next if closed_set.include?(neighbor) || (g_score[neighbor] && tentative_g_score >= g_score[neighbor])
|
26
36
|
|
27
|
-
came_from[neighbor] =
|
37
|
+
came_from[neighbor] = current
|
28
38
|
g_score[neighbor] = tentative_g_score
|
29
39
|
f_score[neighbor] = g_score[neighbor] + heuristic_cost_estimate(neighbor, end_node)
|
30
40
|
|
31
|
-
open_set
|
41
|
+
open_set.push(neighbor, f_score[neighbor])
|
32
42
|
end
|
33
43
|
end
|
34
44
|
|
@@ -39,10 +49,13 @@ module Pathfinding
|
|
39
49
|
private
|
40
50
|
|
41
51
|
def heuristic_cost_estimate(node, end_node)
|
42
|
-
(node
|
43
|
-
(node.y - end_node.y).abs +
|
52
|
+
manhattan_distance(node, end_node) +
|
44
53
|
(node.path_heuristic - end_node.path_heuristic) + # elevation for natural roads
|
45
|
-
(node.road? ? 0 :
|
54
|
+
(node.road? ? 0 : 1000) # share existing roads
|
55
|
+
end
|
56
|
+
|
57
|
+
def manhattan_distance(node, end_node)
|
58
|
+
(node.x - end_node.x).abs + (node.y - end_node.y).abs
|
46
59
|
end
|
47
60
|
|
48
61
|
def reconstruct_path(came_from, current_node)
|
data/lib/pathfinding/grid.rb
CHANGED
@@ -20,19 +20,19 @@ module Pathfinding
|
|
20
20
|
|
21
21
|
def neighbors(node)
|
22
22
|
neighbors = []
|
23
|
-
return neighbors unless node.
|
23
|
+
return neighbors unless node.can_haz_road?
|
24
24
|
|
25
25
|
x = node.x
|
26
26
|
y = node.y
|
27
27
|
|
28
28
|
node_lookup = node(x - 1, y) if x.positive?
|
29
|
-
neighbors << node_lookup if !node_lookup.nil? && node_lookup.
|
29
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_haz_road?
|
30
30
|
node_lookup = node(x + 1, y) if x < @nodes[0].size - 1
|
31
|
-
neighbors << node_lookup if !node_lookup.nil? && node_lookup.
|
31
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_haz_road?
|
32
32
|
node_lookup = node(x, y - 1) if y.positive?
|
33
|
-
neighbors << node_lookup if !node_lookup.nil? && node_lookup.
|
33
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_haz_road?
|
34
34
|
node_lookup = node(x, y + 1) if y < @nodes.size - 1
|
35
|
-
neighbors << node_lookup if !node_lookup.nil? && node_lookup.
|
35
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_haz_road?
|
36
36
|
|
37
37
|
neighbors
|
38
38
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pathfinding
|
4
|
+
#
|
5
|
+
# A Priority Queue implementation for managing elements with associated priorities.
|
6
|
+
# Elements are stored and retrieved based on their priority values.
|
7
|
+
#
|
8
|
+
# This Priority Queue is implemented using a binary heap, which provides efficient
|
9
|
+
# insertion, extraction of the minimum element, and deletion operations.
|
10
|
+
#
|
11
|
+
class PriorityQueue
|
12
|
+
def initialize(&block)
|
13
|
+
@heap = []
|
14
|
+
@compare = block || proc { |a, b| @priority_hash[a] < @priority_hash[b] }
|
15
|
+
@priority_hash = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def push(item, priority)
|
19
|
+
@heap << item
|
20
|
+
@priority_hash[item] = priority
|
21
|
+
heapify_up(@heap.length - 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pop
|
25
|
+
return nil if @heap.empty?
|
26
|
+
|
27
|
+
swap(0, @heap.length - 1)
|
28
|
+
popped = @heap.pop
|
29
|
+
heapify_down(0)
|
30
|
+
popped
|
31
|
+
end
|
32
|
+
|
33
|
+
def peek
|
34
|
+
@heap[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty?
|
38
|
+
@heap.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def heapify_up(index)
|
44
|
+
parent_index = (index - 1) / 2
|
45
|
+
|
46
|
+
return unless index.positive? && @compare.call(@heap[index], @heap[parent_index])
|
47
|
+
|
48
|
+
swap(index, parent_index)
|
49
|
+
heapify_up(parent_index)
|
50
|
+
end
|
51
|
+
|
52
|
+
def heapify_down(index)
|
53
|
+
left_child_index = 2 * index + 1
|
54
|
+
right_child_index = 2 * index + 2
|
55
|
+
smallest = index
|
56
|
+
|
57
|
+
smallest = left_child_index if left_child_index < @heap.length && @compare.call(@heap[left_child_index], @heap[smallest])
|
58
|
+
|
59
|
+
smallest = right_child_index if right_child_index < @heap.length && @compare.call(@heap[right_child_index], @heap[smallest])
|
60
|
+
|
61
|
+
return unless smallest != index
|
62
|
+
|
63
|
+
swap(index, smallest)
|
64
|
+
heapify_down(smallest)
|
65
|
+
end
|
66
|
+
|
67
|
+
# rubocop:disable Naming/MethodParameterName:
|
68
|
+
def swap(i, j)
|
69
|
+
@heap[i], @heap[j] = @heap[j], @heap[i]
|
70
|
+
end
|
71
|
+
# rubocop:enable Naming/MethodParameterName:
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PoissonDiskSampling
|
4
|
+
#
|
5
|
+
# Encapsulates area to be sampled by poisson disk sampling
|
6
|
+
#
|
7
|
+
class SampleArea
|
8
|
+
def initialize(grid:)
|
9
|
+
@grid = grid.dup
|
10
|
+
@points = Array.new(height) { Array.new(width) { false } }
|
11
|
+
end
|
12
|
+
|
13
|
+
def height
|
14
|
+
@grid.length
|
15
|
+
end
|
16
|
+
|
17
|
+
def width
|
18
|
+
@grid[0].length
|
19
|
+
end
|
20
|
+
|
21
|
+
# rubocop:disable Naming/MethodParameterName:
|
22
|
+
def set_sampled_point(x, y)
|
23
|
+
@points[y][x] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](x, y)
|
27
|
+
@grid[y][x]
|
28
|
+
end
|
29
|
+
|
30
|
+
def sampled_point?(x, y)
|
31
|
+
@points[y][x]
|
32
|
+
end
|
33
|
+
|
34
|
+
def point_within_bounds?(x, y)
|
35
|
+
y >= 0 && y < height && x >= 0 && x < width
|
36
|
+
end
|
37
|
+
|
38
|
+
def point_within_bounds_and_can_have_road?(x, y)
|
39
|
+
point_within_bounds?(x, y) && @grid[y][x].can_haz_road?
|
40
|
+
end
|
41
|
+
# rubocop:enable Naming/MethodParameterName:
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry-byebug'
|
4
|
+
module PoissonDiskSampling
|
5
|
+
#
|
6
|
+
# Generates X randomly distributed points within given 2D space
|
7
|
+
# using possion disk sampling
|
8
|
+
#
|
9
|
+
class Sampler
|
10
|
+
attr_reader :sample_area, :num_attempts, :seed
|
11
|
+
|
12
|
+
def initialize(sample_area:, num_attempts: 20, seed: rand)
|
13
|
+
@sample_area = sample_area
|
14
|
+
@num_attempts = num_attempts
|
15
|
+
@seed = seed
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_points(num_of_points, radius, intial_start_point = nil)
|
19
|
+
raise ArgumentError, "invalid start argument #{intial_start_point}" if !intial_start_point.nil? && !intial_start_point.is_a?(Array) && intial_start_point.length != 1
|
20
|
+
|
21
|
+
retreive_points_until_active_list_empty_or_num_points_reached(intial_start_point || generate_and_assign_initial_point, num_of_points, radius)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def retreive_points_until_active_list_empty_or_num_points_reached(active_list, num_of_points, radius)
|
27
|
+
points = []
|
28
|
+
retrieve_points(active_list, points, num_of_points, radius) until active_list.empty? || points.length == num_of_points
|
29
|
+
points
|
30
|
+
end
|
31
|
+
|
32
|
+
def retrieve_points(active_list, points, num_of_points, radius)
|
33
|
+
return if active_list.empty?
|
34
|
+
|
35
|
+
current_point, active_index = retreive_current_point(active_list)
|
36
|
+
found = false
|
37
|
+
|
38
|
+
num_attempts.times do
|
39
|
+
new_point = generate_random_point_around(current_point, radius)
|
40
|
+
next if new_point.nil?
|
41
|
+
next unless new_point.can_haz_town? && neighbours_empty?(new_point, radius)
|
42
|
+
|
43
|
+
sample_area.set_sampled_point(new_point.x, new_point.y)
|
44
|
+
active_list << new_point
|
45
|
+
points << new_point
|
46
|
+
return points if points.length == num_of_points
|
47
|
+
|
48
|
+
found = true
|
49
|
+
break
|
50
|
+
end
|
51
|
+
|
52
|
+
active_list.delete_at(active_index) unless found
|
53
|
+
end
|
54
|
+
|
55
|
+
def random_value_and_increment_seed(max)
|
56
|
+
val = Random.new(seed).rand(max)
|
57
|
+
@seed += 1
|
58
|
+
val
|
59
|
+
end
|
60
|
+
|
61
|
+
def retreive_current_point(active_list)
|
62
|
+
active_index = random_value_and_increment_seed(active_list.length)
|
63
|
+
current_point = active_list[active_index]
|
64
|
+
[current_point, active_index]
|
65
|
+
end
|
66
|
+
|
67
|
+
def neighbours_empty?(new_point, radius)
|
68
|
+
cell_empty = true
|
69
|
+
(-radius..radius).each do |dy|
|
70
|
+
(-radius..radius).each do |dx|
|
71
|
+
x = new_point.x + dx
|
72
|
+
y = new_point.y + dy
|
73
|
+
next unless sample_area.point_within_bounds?(x, y) && sample_area[x, y]
|
74
|
+
|
75
|
+
if sample_area.sampled_point?(x, y) && distance(new_point, sample_area[x, y]) < radius
|
76
|
+
cell_empty = false
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
break unless cell_empty
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_and_assign_initial_point
|
85
|
+
num_attempts.times do
|
86
|
+
initial_point_coords = [random_value_and_increment_seed(sample_area.width), random_value_and_increment_seed(sample_area.height)]
|
87
|
+
|
88
|
+
if sample_area[initial_point_coords[0], initial_point_coords[1]].can_haz_town?
|
89
|
+
sample_area.set_sampled_point(initial_point_coords[0], initial_point_coords[1])
|
90
|
+
return [sample_area[initial_point_coords[0], initial_point_coords[1]]]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_random_point_around(point, radius)
|
97
|
+
distance = radius * (random_value_and_increment_seed(1.0) + 1)
|
98
|
+
angle = 2 * Math::PI * random_value_and_increment_seed(1.0)
|
99
|
+
generated = [point.y + distance * Math.cos(angle), point.x + distance * Math.sin(angle)].map(&:round)
|
100
|
+
|
101
|
+
sample_area.point_within_bounds?(generated[1], generated[0]) ? sample_area[generated[1], generated[0]] : nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def distance(point1, point2)
|
105
|
+
Math.sqrt((point1.y - point2.y)**2 + (point1.x - point2.x)**2)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/road_generator.rb
CHANGED
@@ -17,8 +17,11 @@ class RoadGenerator
|
|
17
17
|
def generate_num_of_random_roads(config)
|
18
18
|
return if config.roads <= 0
|
19
19
|
|
20
|
+
puts "generating #{config.roads} random roads..." if config.verbose
|
21
|
+
|
20
22
|
seed = config.road_seed
|
21
23
|
(1..config.roads).each do |n|
|
24
|
+
puts "generating road #{n}..." if config.verbose
|
22
25
|
random_objects_at_edges = random_nodes_not_on_same_edge(seed + n) # add n otherwise each road is the same
|
23
26
|
generate_path(
|
24
27
|
random_objects_at_edges[0].x,
|
@@ -29,7 +32,11 @@ class RoadGenerator
|
|
29
32
|
end
|
30
33
|
end
|
31
34
|
|
32
|
-
def generate_roads_from_coordinate_list(road_paths)
|
35
|
+
def generate_roads_from_coordinate_list(road_paths, verbose)
|
36
|
+
return unless (road_paths.length % 4).zero?
|
37
|
+
|
38
|
+
puts "generating #{road_paths.length / 4} coordinate roads..." if verbose
|
39
|
+
|
33
40
|
road_paths.each_slice(4) do |road_coordinates|
|
34
41
|
generate_path(
|
35
42
|
road_coordinates[0],
|
data/lib/tile.rb
CHANGED
@@ -4,6 +4,7 @@ require 'biome'
|
|
4
4
|
require 'flora'
|
5
5
|
require 'ansi_colours'
|
6
6
|
require 'pry-byebug'
|
7
|
+
require 'building'
|
7
8
|
|
8
9
|
class Tile
|
9
10
|
attr_reader :x, :y, :height, :moist, :temp, :map, :type
|
@@ -42,7 +43,7 @@ class Tile
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def items
|
45
|
-
@items ||=
|
46
|
+
@items ||= []
|
46
47
|
end
|
47
48
|
|
48
49
|
def render_to_standard_output
|
@@ -61,7 +62,15 @@ class Tile
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def items_contain_flora?
|
64
|
-
|
65
|
+
items_contain?(Flora)
|
66
|
+
end
|
67
|
+
|
68
|
+
def items_contain_building?
|
69
|
+
items_contain?(Building)
|
70
|
+
end
|
71
|
+
|
72
|
+
def items_contain?(item_class)
|
73
|
+
items.any? { |i| i.is_a?(item_class) }
|
65
74
|
end
|
66
75
|
|
67
76
|
def to_h
|
@@ -77,6 +86,10 @@ class Tile
|
|
77
86
|
}
|
78
87
|
end
|
79
88
|
|
89
|
+
def add_town_item(seed)
|
90
|
+
add_item(Building.random_town_building(seed))
|
91
|
+
end
|
92
|
+
|
80
93
|
def make_road
|
81
94
|
@type = :road
|
82
95
|
end
|
@@ -89,8 +102,16 @@ class Tile
|
|
89
102
|
height
|
90
103
|
end
|
91
104
|
|
92
|
-
def
|
93
|
-
|
105
|
+
def can_haz_town?
|
106
|
+
!road? && !biome.water? && !biome.high_mountain? && !items_contain_flora?
|
107
|
+
end
|
108
|
+
|
109
|
+
def can_haz_road?
|
110
|
+
true unless biome_is_water_and_is_excluded? || biome_is_high_mountain_and_is_excluded? || tile_contains_flora_and_is_excluded? || items_contain_building?
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_flora
|
114
|
+
add_item(biome.flora)
|
94
115
|
end
|
95
116
|
|
96
117
|
private
|
@@ -121,19 +142,4 @@ class Tile
|
|
121
142
|
end
|
122
143
|
end
|
123
144
|
end
|
124
|
-
|
125
|
-
def items_generated_with_flora_if_applicable
|
126
|
-
if map.config.generate_flora && biome.flora_available
|
127
|
-
range_max_value = map.tiles[(y - biome.flora_range)...(y + biome.flora_range)]&.map do |r|
|
128
|
-
r[(x - biome.flora_range)...(x + biome.flora_range)]
|
129
|
-
end&.flatten&.map(&:height)&.max
|
130
|
-
if range_max_value == height
|
131
|
-
[biome.flora]
|
132
|
-
else
|
133
|
-
[]
|
134
|
-
end
|
135
|
-
else
|
136
|
-
[]
|
137
|
-
end
|
138
|
-
end
|
139
145
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'poisson_disk_sampling/sampler'
|
4
|
+
require 'poisson_disk_sampling/sample_area'
|
5
|
+
require 'road_generator'
|
6
|
+
require 'map_config'
|
7
|
+
|
8
|
+
#
|
9
|
+
# Generates building tile items using Poisson Disk Sampling for the given tiles
|
10
|
+
# Roads are generated between the buildings and between towns using A* pathfinding
|
11
|
+
#
|
12
|
+
class TownGenerator
|
13
|
+
attr_reader :sample_area, :road_generator
|
14
|
+
|
15
|
+
def initialize(tiles, seed: MapConfig::DEFAULT_TOWN_SEED)
|
16
|
+
@sample_area = PoissonDiskSampling::SampleArea.new(grid: tiles)
|
17
|
+
@road_generator = RoadGenerator.new(tiles)
|
18
|
+
@seed = seed
|
19
|
+
@all_town_points = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_random_towns(config)
|
23
|
+
return if config.towns <= 0
|
24
|
+
|
25
|
+
puts "generating #{config.towns} random towns..." if config.verbose
|
26
|
+
|
27
|
+
@all_town_points.concat(iterate_through_towns(config.towns) do |n|
|
28
|
+
generate_random_town(n, config.verbose)
|
29
|
+
end)
|
30
|
+
|
31
|
+
generate_roads_between_towns(config.verbose)
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate_towns_from_coordinate_list(config)
|
35
|
+
return unless (config.towns_to_make.length % 4).zero?
|
36
|
+
|
37
|
+
puts "generating #{config.towns_to_make.length / 4} coordinate towns..." if config.verbose
|
38
|
+
|
39
|
+
@all_town_points.concat(iterate_through_towns((config.towns_to_make.length / 4)) do |n|
|
40
|
+
town_values = config.towns_to_make[(n - 1) * 4..].take(4)
|
41
|
+
generate_town(n, town_values[2], town_values[3], [sample_area[town_values[0], town_values[1]]], config.verbose)
|
42
|
+
end)
|
43
|
+
|
44
|
+
generate_roads_between_towns(config.verbose)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def iterate_through_towns(num_of_towns)
|
50
|
+
(1..num_of_towns).map do |n|
|
51
|
+
town_points = yield n
|
52
|
+
@seed += 1000
|
53
|
+
town_points
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_random_town(town_num, verbose)
|
58
|
+
random_town_gen = Random.new(@seed)
|
59
|
+
generate_town(town_num, random_town_gen.rand(10..40), random_town_gen.rand(2..4), nil, verbose)
|
60
|
+
end
|
61
|
+
|
62
|
+
def generate_town(town_num, num_of_points, radius, initial_coords, verbose)
|
63
|
+
puts "generating town #{town_num}..." if verbose
|
64
|
+
|
65
|
+
points = generate_points_for_town(num_of_points, radius, initial_coords)
|
66
|
+
generate_town_roads(points, town_num, verbose)
|
67
|
+
points
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_points_for_town(num_of_points, radius, intial_coordinates)
|
71
|
+
points =
|
72
|
+
PoissonDiskSampling::Sampler.new(
|
73
|
+
sample_area: sample_area,
|
74
|
+
seed: @seed
|
75
|
+
).generate_points(num_of_points, radius, intial_coordinates)
|
76
|
+
points.each do |point|
|
77
|
+
@seed += 1
|
78
|
+
point.add_town_item(@seed)
|
79
|
+
end
|
80
|
+
points
|
81
|
+
end
|
82
|
+
|
83
|
+
def generate_town_roads(points, town_num, verbose)
|
84
|
+
# TODO: slow, bad (complete graph) will update to use minimum tree spanning algorithm instead
|
85
|
+
puts "generating town #{town_num} roads..." if verbose
|
86
|
+
|
87
|
+
connected_pairs = Set.new
|
88
|
+
points.each_with_index do |point_one, idx_one|
|
89
|
+
points[idx_one + 1..].each do |point_two|
|
90
|
+
next if connected_pairs.include?([point_one, point_two]) || connected_pairs.include?([point_two, point_one])
|
91
|
+
|
92
|
+
road_to_building_one = place_in_front_or_behind(point_one)
|
93
|
+
road_to_building_two = place_in_front_or_behind(point_two)
|
94
|
+
|
95
|
+
connected_pairs.add([point_one, point_two])
|
96
|
+
connected_pairs.add([point_two, point_one])
|
97
|
+
|
98
|
+
next if road_to_building_one.nil? || road_to_building_two.nil?
|
99
|
+
|
100
|
+
road_generator.generate_roads_from_coordinate_list(road_to_building_one.concat(road_to_building_two), false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def place_in_front_or_behind(point)
|
106
|
+
return [point.x, point.y - 1] if sample_area.point_within_bounds_and_can_have_road?(point.x, point.y - 1)
|
107
|
+
return [point.x, point.y + 1] if sample_area.point_within_bounds_and_can_have_road?(point.x, point.y + 1)
|
108
|
+
return [point.x - 1, point.y] if sample_area.point_within_bounds_and_can_have_road?(point.x - 1, point.y)
|
109
|
+
return [point.x + 1, point.y] if sample_area.point_within_bounds_and_can_have_road?(point.x + 1, point.y)
|
110
|
+
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def generate_roads_between_towns(verbose)
|
115
|
+
return if @all_town_points.length < 2
|
116
|
+
|
117
|
+
puts 'generating roads between towns...' if verbose
|
118
|
+
|
119
|
+
connected_pairs = Set.new
|
120
|
+
town_centroids = {}
|
121
|
+
|
122
|
+
@all_town_points.each_with_index do |town_one, idx_one|
|
123
|
+
find_town_centroid(town_one)
|
124
|
+
|
125
|
+
@all_town_points[idx_one + 1..].each do |town_two|
|
126
|
+
next if connected_pairs.include?([town_one, town_two]) || connected_pairs.include?([town_two, town_one])
|
127
|
+
|
128
|
+
town_one_center_x, town_one_center_y = (town_centroids[town_one] ||= find_town_centroid(town_one))
|
129
|
+
town_two_center_x, town_two_center_y = (town_centroids[town_two] ||= find_town_centroid(town_two))
|
130
|
+
|
131
|
+
road_generator.generate_roads_from_coordinate_list([town_one_center_x, town_one_center_y, town_two_center_x, town_two_center_y], false)
|
132
|
+
|
133
|
+
connected_pairs.add([town_one, town_two])
|
134
|
+
connected_pairs.add([town_two, town_one])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def find_town_centroid(points)
|
140
|
+
total_x = 0
|
141
|
+
total_y = 0
|
142
|
+
num_coordinates = points.length
|
143
|
+
|
144
|
+
points.each do |point|
|
145
|
+
total_x += point.x
|
146
|
+
total_y += point.y
|
147
|
+
end
|
148
|
+
|
149
|
+
average_x = total_x / num_coordinates.to_f
|
150
|
+
average_y = total_y / num_coordinates.to_f
|
151
|
+
|
152
|
+
[average_x, average_y]
|
153
|
+
end
|
154
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-perlin-2D-map-generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tyler Matthews (matthewstyler)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: perlin
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 1.
|
103
|
+
version: 1.56.0
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 1.
|
110
|
+
version: 1.56.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: simplecov
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,23 +138,30 @@ files:
|
|
138
138
|
- lib/CLI/command.rb
|
139
139
|
- lib/ansi_colours.rb
|
140
140
|
- lib/biome.rb
|
141
|
+
- lib/building.rb
|
141
142
|
- lib/flora.rb
|
143
|
+
- lib/flora_generator.rb
|
142
144
|
- lib/map.rb
|
143
145
|
- lib/map_config.rb
|
144
146
|
- lib/map_tile_generator.rb
|
145
147
|
- lib/pathfinding/a_star_finder.rb
|
146
148
|
- lib/pathfinding/grid.rb
|
149
|
+
- lib/pathfinding/priority_queue.rb
|
150
|
+
- lib/poisson_disk_sampling/sample_area.rb
|
151
|
+
- lib/poisson_disk_sampling/sampler.rb
|
147
152
|
- lib/road_generator.rb
|
148
153
|
- lib/tile.rb
|
149
154
|
- lib/tile_item.rb
|
150
155
|
- lib/tile_perlin_generator.rb
|
156
|
+
- lib/town_generator.rb
|
151
157
|
homepage: https://github.com/matthewstyler/ruby-perlin-2D-map-generator
|
152
158
|
licenses:
|
153
159
|
- MIT
|
154
160
|
metadata:
|
155
161
|
source_code_uri: https://github.com/matthewstyler/ruby-perlin-2D-map-generator
|
156
162
|
bug_tracker_uri: https://github.com/matthewstyler/ruby-perlin-2D-map-generator/issues
|
157
|
-
post_install_message:
|
163
|
+
post_install_message: Thanks for installing! Star on github if you found this useful,
|
164
|
+
or raise issues and requests.
|
158
165
|
rdoc_options: []
|
159
166
|
require_paths:
|
160
167
|
- lib
|
@@ -172,6 +179,6 @@ requirements: []
|
|
172
179
|
rubygems_version: 3.2.3
|
173
180
|
signing_key:
|
174
181
|
specification_version: 4
|
175
|
-
summary: Procedurally generate seeded and customizable 2D maps with optional roads
|
176
|
-
rendered with ansi colours or described in a 2D array of hashes
|
182
|
+
summary: Procedurally generate seeded and customizable 2D maps with optional roads
|
183
|
+
and towns, rendered with ansi colours or described in a 2D array of hashes
|
177
184
|
test_files: []
|