ruby-perlin-2D-map-generator 0.0.4 → 0.0.5
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 +85 -39
- data/lib/CLI/command.rb +65 -8
- data/lib/ansi_colours.rb +3 -0
- data/lib/biome.rb +10 -0
- data/lib/map.rb +14 -1
- data/lib/map_config.rb +34 -8
- data/lib/pathfinding/a_star_finder.rb +57 -0
- data/lib/pathfinding/grid.rb +78 -0
- data/lib/road_generator.rb +63 -0
- data/lib/tile.rb +62 -4
- metadata +29 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a4341c2583eba83e95d6ea4a9c1ee05a857bc1f5c77c6d3aeeba41f6995a656
|
4
|
+
data.tar.gz: 599b47f78c83dc6acd99dcde0e9aadbeb8d445fc7d9b6ef7ba8b1acbb3c15d7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbabe795544e432d7cf4e5dac2fd6f2b14dd17239d3eb2a59013ce3655bcee1873ef4edb5e6048be372c474af04234e5f7ef33806b517de1b160ea5d59b3d62f
|
7
|
+
data.tar.gz: 246cc08e1413e89c6929eacf4201a7895e64595067367f02ed8e758a239e18b098186e16c1c8a85900e1519fa84eb5ace115d15e094894b62825664b1059e57d
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|

|
6
6
|
[](https://rubygems.org/gems/ruby-perlin-2D-map-generator)
|
7
7
|
|
8
|
-
A gem that procedurally generates seeded and customizable 2D map using perlin noise.
|
8
|
+
A gem that procedurally generates seeded and customizable 2D map with optional roads using perlin noise.
|
9
9
|
|
10
10
|
Include the gem in your project, or use the executable from the command line.
|
11
11
|
|
@@ -34,6 +34,7 @@ gem install ruby-perlin-2D-map-generator
|
|
34
34
|
See Command line Usage for full customization, below are some examples. Alter the temperature, moisture or elevation seeds to alter these maps:
|
35
35
|
|
36
36
|
- Plains with random terrain evens: `ruby-perlin-2D-map-generator render`
|
37
|
+
- Plains with random terrain events and two roads: `ruby-perlin-2D-map-generator render --roads=2`
|
37
38
|
- Desert (increase temperature, decrease moisture): `ruby-perlin-2D-map-generator render --temp=100 --moisture=-100`
|
38
39
|
- Mountainous with lakes (increase elevation, increase moisture) `ruby-perlin-2D-map-generator render --elevation=25 --moisture=25`
|
39
40
|
- Islands (decreaes elevation, increase moisture): `ruby-perlin-2D-map-generator render --elevation=-40 --moisture=25`
|
@@ -44,21 +45,39 @@ See Command line Usage for full customization, below are some examples. Alter th
|
|
44
45
|
--width=int The width of the generated map (default 128)
|
45
46
|
--height=int The height of the generated map (default 128)
|
46
47
|
|
48
|
+
--roads=int Add this many roads through the map,
|
49
|
+
starting and ending at edges
|
50
|
+
(default 0)
|
51
|
+
|
47
52
|
--hs=int The seed for a terrains height perlin generation
|
48
53
|
(default 10)
|
49
54
|
--ms=int The seed for a terrains moist perlin generation
|
50
55
|
(default 300)
|
51
56
|
--ts=int The seed for a terrains temperature perlin generation
|
52
57
|
(default 3000)
|
58
|
+
--rs=int The seed for generating roads
|
59
|
+
(default 100)
|
53
60
|
|
54
|
-
--elevation=float Adjust each generated elevation by this percent (
|
61
|
+
--elevation=float Adjust each generated elevation by this percent (-100 -
|
55
62
|
100) (default 0.0)
|
56
|
-
--moisture=float Adjust each generated moisture by this percent (
|
63
|
+
--moisture=float Adjust each generated moisture by this percent (-100 -
|
57
64
|
100) (default 0.0)
|
58
|
-
--temp=float Adjust each generated temperature by this percent (
|
65
|
+
--temp=float Adjust each generated temperature by this percent (-100
|
59
66
|
- 100) (default 0.0)
|
60
67
|
```
|
61
68
|
|
69
|
+
## Roads and the heuristic
|
70
|
+
Roads can be generated by providing a positive integer to the `roads=` argument. Roads are randomly seeded to begin
|
71
|
+
and start at an axis (but not the same axis).
|
72
|
+
|
73
|
+
A* pathfinding is used to generate the roads with a heuristic that uses manhattan distance, favours existing roads and similar elevations in adjacent tiles.
|
74
|
+
|
75
|
+
Roads can be configured to include/exclude generating paths thorugh water, mountains and flora.
|
76
|
+
|
77
|
+
Tiles containing roads are of type `road`, those without are of type `terrain`.
|
78
|
+
|
79
|
+
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
|
+
|
62
81
|
# Generate without rendering
|
63
82
|
|
64
83
|
```irb
|
@@ -112,7 +131,7 @@ $ ruby-perlin-2D-map-generator --help
|
|
112
131
|
```bash
|
113
132
|
Usage: ruby-perlin-2D-map-generator [OPTIONS] (DESCRIBE | RENDER)
|
114
133
|
|
115
|
-
Generate a seeded customizable procedurally generated 2D map.
|
134
|
+
Generate a seeded customizable procedurally generated 2D map with optional roads.
|
116
135
|
Rendered in the console using ansi colours, or described as a 2D array of
|
117
136
|
hashes with each tiles information.
|
118
137
|
|
@@ -128,40 +147,64 @@ Keywords:
|
|
128
147
|
coordinate tile details
|
129
148
|
|
130
149
|
Options:
|
131
|
-
--elevation=float
|
132
|
-
|
133
|
-
--fhx=float
|
134
|
-
|
135
|
-
--fhy=float
|
136
|
-
|
137
|
-
--fmx=float
|
138
|
-
|
139
|
-
--fmy=float
|
140
|
-
|
141
|
-
--ftx=float
|
142
|
-
|
143
|
-
--fty=float
|
144
|
-
|
145
|
-
--gf=bool
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
--
|
151
|
-
|
152
|
-
--
|
153
|
-
|
154
|
-
--
|
155
|
-
|
156
|
-
--
|
157
|
-
|
158
|
-
--
|
159
|
-
|
160
|
-
--
|
161
|
-
|
162
|
-
--
|
163
|
-
|
164
|
-
--
|
150
|
+
--elevation=float Adjust each generated elevation by
|
151
|
+
this percent (-100 - 100) (default 0.0)
|
152
|
+
--fhx=float The frequency for height generation
|
153
|
+
across the x-axis (default 2.5)
|
154
|
+
--fhy=float The frequency for height generation
|
155
|
+
across the y-axis (default 2.5)
|
156
|
+
--fmx=float The frequency for moist generation
|
157
|
+
across the x-axis (default 2.5)
|
158
|
+
--fmy=float The frequency for moist generation
|
159
|
+
across the y-axis (default 2.5)
|
160
|
+
--ftx=float The frequency for temp generation
|
161
|
+
across the x-axis (default 2.5)
|
162
|
+
--fty=float The frequency for temp generation
|
163
|
+
across the y-axis (default 2.5)
|
164
|
+
--gf=bool Generate flora, significantly affects
|
165
|
+
performance
|
166
|
+
--height=int The height of the generated map
|
167
|
+
(default 128)
|
168
|
+
-h, --help Print usage
|
169
|
+
--hs=int The seed for a terrains height perlin
|
170
|
+
generation (default 10)
|
171
|
+
--moisture=float Adjust each generated moisture by
|
172
|
+
this percent (-100 - 100) (default 0.0)
|
173
|
+
--ms=int The seed for a terrains moist perlin
|
174
|
+
generation (default 300)
|
175
|
+
--oh=int Octaves for height generation
|
176
|
+
(default 3)
|
177
|
+
--om=int Octaves for moist generation (default
|
178
|
+
3)
|
179
|
+
--ot=int Octaves for temp generation (default
|
180
|
+
3)
|
181
|
+
--ph=float Persistance for height generation
|
182
|
+
(default 1.0)
|
183
|
+
--pm=float Persistance for moist generation
|
184
|
+
(default 1.0)
|
185
|
+
--pt=float Persistance for temp generation
|
186
|
+
(default 1.0)
|
187
|
+
--road_exclude_flora_path=bool Controls if roads will run tiles
|
188
|
+
containing flora
|
189
|
+
--road_exclude_mountain_path=bool Controls if roads will run through
|
190
|
+
high mountains
|
191
|
+
--road_exclude_water_path=bool Controls if roads will run through
|
192
|
+
water
|
193
|
+
--roads=int Add this many roads through the map,
|
194
|
+
starting and ending at edges (default
|
195
|
+
0)
|
196
|
+
--roads_to_make ints Attempt to create a road from a start
|
197
|
+
and end point (4 integers), can be
|
198
|
+
supplied multiple paths
|
199
|
+
(default [])
|
200
|
+
--rs=int The seed for generating roads
|
201
|
+
(default 100)
|
202
|
+
--temp=float Adjust each generated temperature by
|
203
|
+
this percent (-100 - 100) (default 0.0)
|
204
|
+
--ts=int The seed for a terrains temperature
|
205
|
+
perlin generation (default 3000)
|
206
|
+
--width=int The width of the generated map
|
207
|
+
(default 128)
|
165
208
|
|
166
209
|
Examples:
|
167
210
|
Render with defaults
|
@@ -169,6 +212,9 @@ Examples:
|
|
169
212
|
|
170
213
|
Render with options
|
171
214
|
$ ruby-perlin-2D-map-generator render --elevation=-40 --moisture=25 --hs=1
|
215
|
+
|
216
|
+
Render with roads
|
217
|
+
$ ruby-perlin-2D-map-generator render --roads=2
|
172
218
|
|
173
219
|
Describe tile [1, 1]
|
174
220
|
$ ruby-perlin-2D-map-generator describe coordinates=1,1
|
data/lib/CLI/command.rb
CHANGED
@@ -12,7 +12,7 @@ module CLI
|
|
12
12
|
|
13
13
|
no_command
|
14
14
|
|
15
|
-
desc 'Generate a seeded customizable procedurally generated 2D map. Rendered in the console ' \
|
15
|
+
desc 'Generate a seeded customizable procedurally generated 2D map with optional roads. Rendered in the console ' \
|
16
16
|
' using ansi colours, or described as a 2D array of hashes with each tiles information.'
|
17
17
|
|
18
18
|
example 'Render with defaults',
|
@@ -21,6 +21,9 @@ module CLI
|
|
21
21
|
example 'Render with options',
|
22
22
|
' $ ruby-perlin-2D-map-generator render --elevation=-40 --moisture=25 --hs=1'
|
23
23
|
|
24
|
+
example 'Render with roads',
|
25
|
+
' $ ruby-perlin-2D-map-generator render --roads=2'
|
26
|
+
|
24
27
|
example 'Describe tile [1, 1]',
|
25
28
|
' $ ruby-perlin-2D-map-generator describe coordinates=1,1'
|
26
29
|
end
|
@@ -41,6 +44,15 @@ module CLI
|
|
41
44
|
desc 'Used with the describe command, only returns the given coordinate tile details'
|
42
45
|
end
|
43
46
|
|
47
|
+
option :roads_to_make do
|
48
|
+
arity one
|
49
|
+
long '--roads_to_make ints'
|
50
|
+
convert :int_list
|
51
|
+
validate ->(v) { v >= 0 }
|
52
|
+
desc 'Attempt to create a road from a start and end point (4 integers), can be supplied multiple paths'
|
53
|
+
default MapConfig::DEFAULT_ROADS_TO_MAKE
|
54
|
+
end
|
55
|
+
|
44
56
|
option :height_seed do
|
45
57
|
long '--hs int'
|
46
58
|
# or
|
@@ -210,7 +222,7 @@ module CLI
|
|
210
222
|
long '--temp float'
|
211
223
|
long '--temp=float'
|
212
224
|
|
213
|
-
desc 'Adjust each generated temperature by this percent (
|
225
|
+
desc 'Adjust each generated temperature by this percent (-100 - 100)'
|
214
226
|
convert ->(val) { val.to_f / 100.0 }
|
215
227
|
validate ->(val) { val >= -1.0 && val <= 1.0 }
|
216
228
|
default MapConfig::DEFAULT_TEMP_ADJUSTMENT
|
@@ -220,7 +232,7 @@ module CLI
|
|
220
232
|
long '--elevation float'
|
221
233
|
long '--elevation=float'
|
222
234
|
|
223
|
-
desc 'Adjust each generated elevation by this percent (
|
235
|
+
desc 'Adjust each generated elevation by this percent (-100 - 100)'
|
224
236
|
convert ->(val) { val.to_f / 100.0 }
|
225
237
|
validate ->(val) { val >= -1.0 && val <= 1.0 }
|
226
238
|
default MapConfig::DEFAULT_HEIGHT_ADJUSTMENT
|
@@ -230,12 +242,58 @@ module CLI
|
|
230
242
|
long '--moisture float'
|
231
243
|
long '--moisture=float'
|
232
244
|
|
233
|
-
desc 'Adjust each generated moisture by this percent (
|
245
|
+
desc 'Adjust each generated moisture by this percent (-100 - 100)'
|
234
246
|
convert ->(val) { val.to_f / 100.0 }
|
235
247
|
validate ->(val) { val >= -1.0 && val <= 1.0 }
|
236
248
|
default MapConfig::DEFAULT_MOIST_ADJUSTMENT
|
237
249
|
end
|
238
250
|
|
251
|
+
option :roads do
|
252
|
+
long '--roads int'
|
253
|
+
long '--roads=int'
|
254
|
+
|
255
|
+
desc 'Add this many roads through the map, starting and ending at edges'
|
256
|
+
convert Integer
|
257
|
+
validate ->(val) { val >= 0 }
|
258
|
+
default MapConfig::DEFAULT_NUM_OF_ROADS
|
259
|
+
end
|
260
|
+
|
261
|
+
option :road_seed do
|
262
|
+
long '--rs int'
|
263
|
+
long '--rs=int'
|
264
|
+
|
265
|
+
desc 'The seed for generating roads'
|
266
|
+
convert Integer
|
267
|
+
default MapConfig::DEFAULT_ROAD_SEED
|
268
|
+
end
|
269
|
+
|
270
|
+
option :road_exclude_water_path do
|
271
|
+
long '--road_exclude_water_path bool'
|
272
|
+
long '--road_exclude_water_path=bool'
|
273
|
+
|
274
|
+
desc 'Controls if roads will run through water'
|
275
|
+
convert :bool
|
276
|
+
default MapConfig::DEFAULT_ROAD_EXCLUDE_WATER_PATH
|
277
|
+
end
|
278
|
+
|
279
|
+
option :road_exclude_mountain_path do
|
280
|
+
long '--road_exclude_mountain_path bool'
|
281
|
+
long '--road_exclude_mountain_path=bool'
|
282
|
+
|
283
|
+
desc 'Controls if roads will run through high mountains'
|
284
|
+
convert :bool
|
285
|
+
default MapConfig::DEFAULT_ROAD_EXCLUDE_MOUNTAIN_PATH
|
286
|
+
end
|
287
|
+
|
288
|
+
option :road_exclude_flora_path do
|
289
|
+
long '--road_exclude_flora_path bool'
|
290
|
+
long '--road_exclude_flora_path=bool'
|
291
|
+
|
292
|
+
desc 'Controls if roads will run tiles containing flora'
|
293
|
+
convert :bool
|
294
|
+
default MapConfig::DEFAULT_ROAD_EXCLUDE_FLORA_PATH
|
295
|
+
end
|
296
|
+
|
239
297
|
flag :help do
|
240
298
|
short '-h'
|
241
299
|
long '--help'
|
@@ -259,10 +317,9 @@ module CLI
|
|
259
317
|
map = Map.new(map_config: MapConfig.new(
|
260
318
|
width: params[:width],
|
261
319
|
height: params[:height],
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
generate_flora: params[:generate_flora]
|
320
|
+
all_perlin_configs: MapConfig::AllPerlinConfigs.new(perlin_height_config, perlin_moist_config, perlin_temp_config),
|
321
|
+
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)
|
266
323
|
))
|
267
324
|
case params[:command]
|
268
325
|
when 'render' then map.render
|
data/lib/ansi_colours.rb
CHANGED
data/lib/biome.rb
CHANGED
@@ -32,6 +32,10 @@ class Biome
|
|
32
32
|
TAIGA_TERRAIN.include?(self)
|
33
33
|
end
|
34
34
|
|
35
|
+
def high_mountain?
|
36
|
+
HIGH_MOUNTAIN.include?(self)
|
37
|
+
end
|
38
|
+
|
35
39
|
def flora_available
|
36
40
|
!flora_range.nil?
|
37
41
|
end
|
@@ -132,6 +136,12 @@ class Biome
|
|
132
136
|
TAIGA_COAST
|
133
137
|
].freeze
|
134
138
|
|
139
|
+
HIGH_MOUNTAIN = [
|
140
|
+
SNOW,
|
141
|
+
ROCKS,
|
142
|
+
MOUNTAIN
|
143
|
+
].freeze
|
144
|
+
|
135
145
|
LAND_TERRAIN = (ALL_TERRAIN - WATER_TERRAIN).freeze
|
136
146
|
|
137
147
|
class << self
|
data/lib/map.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'map_tile_generator'
|
4
4
|
require 'map_config'
|
5
|
+
require 'road_generator'
|
5
6
|
|
6
7
|
class Map
|
7
8
|
attr_reader :config
|
@@ -30,6 +31,18 @@ class Map
|
|
30
31
|
# rubocop:enable Naming/MethodParameterName:
|
31
32
|
|
32
33
|
def tiles
|
33
|
-
@tiles
|
34
|
+
return @tiles if @tiles
|
35
|
+
|
36
|
+
@tiles = generate_tiles
|
37
|
+
road_generator = RoadGenerator.new(@tiles)
|
38
|
+
road_generator.generate_num_of_random_roads(config.road_config)
|
39
|
+
road_generator.generate_roads_from_coordinate_list(config.road_config.roads_to_make)
|
40
|
+
@tiles
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def generate_tiles
|
46
|
+
MapTileGenerator.new(map: self).generate
|
34
47
|
end
|
35
48
|
end
|
data/lib/map_config.rb
CHANGED
@@ -25,25 +25,43 @@ class MapConfig
|
|
25
25
|
DEFAULT_TEMP_X_FREQUENCY = 2.5
|
26
26
|
DEFAULT_TEMP_ADJUSTMENT = 0.0
|
27
27
|
|
28
|
+
DEFAULT_ROAD_SEED = 100
|
29
|
+
DEFAULT_NUM_OF_ROADS = 0
|
30
|
+
DEFAULT_ROAD_EXCLUDE_WATER_PATH = true
|
31
|
+
DEFAULT_ROAD_EXCLUDE_MOUNTAIN_PATH = true
|
32
|
+
DEFAULT_ROAD_EXCLUDE_FLORA_PATH = true
|
33
|
+
DEFAULT_ROADS_TO_MAKE = [].freeze
|
34
|
+
|
28
35
|
PERLIN_CONFIG_OPTIONS = %i[width height noise_seed octaves x_frequency y_frequency persistance adjustment].freeze
|
29
|
-
|
36
|
+
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
|
30
38
|
|
31
|
-
|
39
|
+
PerlinConfig = Struct.new(*PERLIN_CONFIG_OPTIONS)
|
40
|
+
AllPerlinConfigs = Struct.new(*ALL_PERLIN_CONFIGS)
|
41
|
+
RoadConfig = Struct.new(*ROAD_CONFIG_OPTIONS)
|
32
42
|
|
33
|
-
|
34
|
-
height: DEFAULT_TILE_COUNT, generate_flora: DEFAULT_GENERATE_FLORA)
|
35
|
-
raise ArgumentError unless perlin_height_config.is_a?(PerlinConfig) && perlin_moist_config.is_a?(PerlinConfig)
|
43
|
+
attr_reader :generate_flora, :perlin_height_config, :perlin_moist_config, :perlin_temp_config, :width, :height, :road_config
|
36
44
|
|
45
|
+
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)
|
47
|
+
validate(all_perlin_configs)
|
37
48
|
@generate_flora = generate_flora
|
38
|
-
@perlin_height_config = perlin_height_config
|
39
|
-
@perlin_moist_config = perlin_moist_config
|
40
|
-
@perlin_temp_config = perlin_temp_config
|
49
|
+
@perlin_height_config = all_perlin_configs.perlin_height_config
|
50
|
+
@perlin_moist_config = all_perlin_configs.perlin_moist_config
|
51
|
+
@perlin_temp_config = all_perlin_configs.perlin_temp_config
|
41
52
|
@width = width
|
42
53
|
@height = height
|
54
|
+
@road_config = road_config
|
43
55
|
end
|
44
56
|
|
45
57
|
private
|
46
58
|
|
59
|
+
def validate(all_perlin_configs)
|
60
|
+
unless all_perlin_configs.perlin_height_config.is_a?(PerlinConfig) && all_perlin_configs.perlin_moist_config.is_a?(PerlinConfig) && all_perlin_configs.perlin_temp_config.is_a?(PerlinConfig)
|
61
|
+
raise ArgumentError
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
47
65
|
def default_perlin_height_config
|
48
66
|
PerlinConfig.new(DEFAULT_TILE_COUNT, DEFAULT_TILE_COUNT, DEFAULT_HEIGHT_SEED, DEFAULT_HEIGHT_OCTAVES,
|
49
67
|
DEFAULT_HEIGHT_X_FREQUENCY, DEFAULT_HEIGHT_Y_FREQUENCY, DEFAULT_HEIGHT_PERSISTANCE, DEFAULT_HEIGHT_ADJUSTMENT)
|
@@ -58,4 +76,12 @@ class MapConfig
|
|
58
76
|
PerlinConfig.new(DEFAULT_TILE_COUNT, DEFAULT_TILE_COUNT, DEFAULT_TEMP_SEED, DEFAULT_TEMP_OCTAVES,
|
59
77
|
DEFAULT_TEMP_X_FREQUENCY, DEFAULT_TEMP_Y_FREQUENCY, DEFAULT_TEMP_PERSISTANCE, DEFAULT_TEMP_ADJUSTMENT)
|
60
78
|
end
|
79
|
+
|
80
|
+
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)
|
82
|
+
end
|
83
|
+
|
84
|
+
def default_perlin_configs
|
85
|
+
AllPerlinConfigs.new(default_perlin_height_config, default_perlin_moist_config, default_perlin_temp_config)
|
86
|
+
end
|
61
87
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pathfinding
|
4
|
+
#
|
5
|
+
# An A* Pathfinder to build roads/paths between two coordinates containing
|
6
|
+
# different path costs, the heuristic behaviour that can be altered via configuration
|
7
|
+
#
|
8
|
+
class AStarFinder
|
9
|
+
def find_path(start_node, end_node, grid)
|
10
|
+
open_set = [start_node]
|
11
|
+
came_from = {}
|
12
|
+
g_score = { start_node => 0 }
|
13
|
+
f_score = { start_node => heuristic_cost_estimate(start_node, end_node) }
|
14
|
+
|
15
|
+
until open_set.empty?
|
16
|
+
current_node = open_set.min_by { |node| f_score[node] }
|
17
|
+
|
18
|
+
return reconstruct_path(came_from, current_node) if current_node == end_node
|
19
|
+
|
20
|
+
open_set.delete(current_node)
|
21
|
+
|
22
|
+
grid.neighbors(current_node).each do |neighbor|
|
23
|
+
tentative_g_score = g_score[current_node] + 1
|
24
|
+
|
25
|
+
next unless !g_score[neighbor] || tentative_g_score < g_score[neighbor]
|
26
|
+
|
27
|
+
came_from[neighbor] = current_node
|
28
|
+
g_score[neighbor] = tentative_g_score
|
29
|
+
f_score[neighbor] = g_score[neighbor] + heuristic_cost_estimate(neighbor, end_node)
|
30
|
+
|
31
|
+
open_set << neighbor unless open_set.include?(neighbor)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# No path found
|
36
|
+
[]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def heuristic_cost_estimate(node, end_node)
|
42
|
+
(node.x - end_node.x).abs +
|
43
|
+
(node.y - end_node.y).abs +
|
44
|
+
(node.path_heuristic - end_node.path_heuristic) + # elevation for natural roads
|
45
|
+
(node.road? ? 0 : 5) # share existing roads
|
46
|
+
end
|
47
|
+
|
48
|
+
def reconstruct_path(came_from, current_node)
|
49
|
+
path = [current_node]
|
50
|
+
while came_from[current_node]
|
51
|
+
current_node = came_from[current_node]
|
52
|
+
path.unshift(current_node)
|
53
|
+
end
|
54
|
+
path
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pathfinding
|
4
|
+
#
|
5
|
+
# Responsible for manipulating and encapsulating behaviour of tiles related
|
6
|
+
# to pathfinding
|
7
|
+
#
|
8
|
+
class Grid
|
9
|
+
attr_reader :nodes
|
10
|
+
|
11
|
+
def initialize(nodes)
|
12
|
+
@nodes = nodes
|
13
|
+
end
|
14
|
+
|
15
|
+
# rubocop:disable Naming/MethodParameterName:
|
16
|
+
def node(x, y)
|
17
|
+
nodes[y][x]
|
18
|
+
end
|
19
|
+
# rubocop:enable Naming/MethodParameterName:
|
20
|
+
|
21
|
+
def neighbors(node)
|
22
|
+
neighbors = []
|
23
|
+
return neighbors unless node.can_contain_road?
|
24
|
+
|
25
|
+
x = node.x
|
26
|
+
y = node.y
|
27
|
+
|
28
|
+
node_lookup = node(x - 1, y) if x.positive?
|
29
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_contain_road?
|
30
|
+
node_lookup = node(x + 1, y) if x < @nodes[0].size - 1
|
31
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_contain_road?
|
32
|
+
node_lookup = node(x, y - 1) if y.positive?
|
33
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_contain_road?
|
34
|
+
node_lookup = node(x, y + 1) if y < @nodes.size - 1
|
35
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_contain_road?
|
36
|
+
|
37
|
+
neighbors
|
38
|
+
end
|
39
|
+
|
40
|
+
def min_max_coordinates
|
41
|
+
@min_max_coordinates ||= begin
|
42
|
+
min_x = nil
|
43
|
+
min_y = nil
|
44
|
+
max_x = nil
|
45
|
+
max_y = nil
|
46
|
+
|
47
|
+
@nodes.each do |row|
|
48
|
+
row.each do |object|
|
49
|
+
x = object.x
|
50
|
+
y = object.y
|
51
|
+
|
52
|
+
# Update minimum x and y values
|
53
|
+
min_x = x if min_x.nil? || x < min_x
|
54
|
+
min_y = y if min_y.nil? || y < min_y
|
55
|
+
|
56
|
+
# Update maximum x and y values
|
57
|
+
max_x = x if max_x.nil? || x > max_x
|
58
|
+
max_y = y if max_y.nil? || y > max_y
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
{ min_x: min_x, min_y: min_y, max_x: max_x, max_y: max_y }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def edge_nodes
|
67
|
+
@edge_nodes ||=
|
68
|
+
@nodes.map do |row|
|
69
|
+
row.select do |obj|
|
70
|
+
obj.x == min_max_coordinates[:min_x] ||
|
71
|
+
obj.x == min_max_coordinates[:max_x] ||
|
72
|
+
obj.y == min_max_coordinates[:min_y] ||
|
73
|
+
obj.y == min_max_coordinates[:max_y]
|
74
|
+
end
|
75
|
+
end.flatten
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathfinding/grid'
|
4
|
+
require 'pathfinding/a_star_finder'
|
5
|
+
|
6
|
+
#
|
7
|
+
# Generates roads across map tiles, randomly or given specific coordinates
|
8
|
+
#
|
9
|
+
class RoadGenerator
|
10
|
+
attr_reader :grid, :finder
|
11
|
+
|
12
|
+
def initialize(tiles)
|
13
|
+
@grid = Pathfinding::Grid.new(tiles)
|
14
|
+
@finder = Pathfinding::AStarFinder.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_num_of_random_roads(config)
|
18
|
+
return if config.roads <= 0
|
19
|
+
|
20
|
+
seed = config.road_seed
|
21
|
+
(1..config.roads).each do |n|
|
22
|
+
random_objects_at_edges = random_nodes_not_on_same_edge(seed + n) # add n otherwise each road is the same
|
23
|
+
generate_path(
|
24
|
+
random_objects_at_edges[0].x,
|
25
|
+
random_objects_at_edges[0].y,
|
26
|
+
random_objects_at_edges[1].x,
|
27
|
+
random_objects_at_edges[1].y
|
28
|
+
).each(&:make_road)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_roads_from_coordinate_list(road_paths)
|
33
|
+
road_paths.each_slice(4) do |road_coordinates|
|
34
|
+
generate_path(
|
35
|
+
road_coordinates[0],
|
36
|
+
road_coordinates[1],
|
37
|
+
road_coordinates[2],
|
38
|
+
road_coordinates[3]
|
39
|
+
).each(&:make_road)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_path(start_x, start_y, end_x, end_y)
|
44
|
+
start_node = grid.node(start_x, start_y)
|
45
|
+
end_node = grid.node(end_x, end_y)
|
46
|
+
finder.find_path(start_node, end_node, grid)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def random_nodes_not_on_same_edge(seed)
|
52
|
+
random_generator = Random.new(seed)
|
53
|
+
length = @grid.edge_nodes.length
|
54
|
+
|
55
|
+
loop do
|
56
|
+
index1 = random_generator.rand(length)
|
57
|
+
index2 = random_generator.rand(length)
|
58
|
+
node_one, node_two = @grid.edge_nodes.values_at(index1, index2)
|
59
|
+
|
60
|
+
return [node_one, node_two] if node_one.x != node_two.x && node_one.y != node_two.y
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/tile.rb
CHANGED
@@ -2,17 +2,27 @@
|
|
2
2
|
|
3
3
|
require 'biome'
|
4
4
|
require 'flora'
|
5
|
+
require 'ansi_colours'
|
6
|
+
require 'pry-byebug'
|
5
7
|
|
6
8
|
class Tile
|
7
|
-
attr_reader :x, :y, :height, :moist, :temp, :map
|
9
|
+
attr_reader :x, :y, :height, :moist, :temp, :map, :type
|
8
10
|
|
9
|
-
|
11
|
+
TYPES = %i[
|
12
|
+
terrain
|
13
|
+
road
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
def initialize(map:, x:, y:, height: 0, moist: 0, temp: 0, type: :terrain)
|
10
17
|
@x = x
|
11
18
|
@y = y
|
12
19
|
@height = height
|
13
20
|
@moist = moist
|
14
21
|
@temp = temp
|
15
22
|
@map = map
|
23
|
+
raise ArgumentError, 'invalid tile type' unless TYPES.include?(type)
|
24
|
+
|
25
|
+
@type = type
|
16
26
|
end
|
17
27
|
|
18
28
|
def surrounding_tiles(distance = 1)
|
@@ -36,7 +46,7 @@ class Tile
|
|
36
46
|
end
|
37
47
|
|
38
48
|
def render_to_standard_output
|
39
|
-
print
|
49
|
+
print render_color_by_type + (!items.empty? ? item_with_highest_priority.render_symbol : ' ')
|
40
50
|
print AnsiColours::Background::ANSI_RESET
|
41
51
|
end
|
42
52
|
|
@@ -50,6 +60,10 @@ class Tile
|
|
50
60
|
items.max_by(&:render_priority)
|
51
61
|
end
|
52
62
|
|
63
|
+
def items_contain_flora?
|
64
|
+
items.any? { |i| i.is_a?(Flora) }
|
65
|
+
end
|
66
|
+
|
53
67
|
def to_h
|
54
68
|
{
|
55
69
|
x: x,
|
@@ -58,12 +72,56 @@ class Tile
|
|
58
72
|
moist: moist,
|
59
73
|
temp: temp,
|
60
74
|
biome: biome.to_h,
|
61
|
-
items: items.map(&:to_h)
|
75
|
+
items: items.map(&:to_h),
|
76
|
+
type: type
|
62
77
|
}
|
63
78
|
end
|
64
79
|
|
80
|
+
def make_road
|
81
|
+
@type = :road
|
82
|
+
end
|
83
|
+
|
84
|
+
def road?
|
85
|
+
@type == :road
|
86
|
+
end
|
87
|
+
|
88
|
+
def path_heuristic
|
89
|
+
height
|
90
|
+
end
|
91
|
+
|
92
|
+
def can_contain_road?
|
93
|
+
return true unless biome_is_water_and_is_excluded? || biome_is_high_mountain_and_is_excluded? || tile_contains_flora_and_is_excluded?
|
94
|
+
end
|
95
|
+
|
65
96
|
private
|
66
97
|
|
98
|
+
def biome_is_water_and_is_excluded?
|
99
|
+
biome.water? && map.config.road_config.road_exclude_water_path
|
100
|
+
end
|
101
|
+
|
102
|
+
def biome_is_high_mountain_and_is_excluded?
|
103
|
+
biome.high_mountain? && map.config.road_config.road_exclude_mountain_path
|
104
|
+
end
|
105
|
+
|
106
|
+
def tile_contains_flora_and_is_excluded?
|
107
|
+
items_contain_flora? && map.config.road_config.road_exclude_flora_path
|
108
|
+
end
|
109
|
+
|
110
|
+
def render_color_by_type
|
111
|
+
case type
|
112
|
+
when :terrain then biome.colour
|
113
|
+
when :road
|
114
|
+
case height
|
115
|
+
when 0.66..1
|
116
|
+
AnsiColours::Background::HIGH_ROAD_BLACK
|
117
|
+
when 0.33..0.66
|
118
|
+
AnsiColours::Background::ROAD_BLACK
|
119
|
+
when 0..0.33
|
120
|
+
AnsiColours::Background::LOW_ROAD_BLACK
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
67
125
|
def items_generated_with_flora_if_applicable
|
68
126
|
if map.config.generate_flora && biome.flora_available
|
69
127
|
range_max_value = map.tiles[(y - biome.flora_range)...(y + biome.flora_range)]&.map do |r|
|
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.5
|
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-
|
11
|
+
date: 2023-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: perlin
|
@@ -58,14 +58,28 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.0
|
61
|
+
version: 2.1.0
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 2.0
|
68
|
+
version: 2.1.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.10.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.10.1
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rake
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +100,14 @@ dependencies:
|
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version: 1.
|
103
|
+
version: 1.55.1
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version: 1.
|
110
|
+
version: 1.55.1
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: simplecov
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,10 +122,10 @@ dependencies:
|
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: 0.22.0
|
111
|
-
description: A gem that procedurally generates a seeded and customizable 2D map
|
112
|
-
perlin noise. Map can be rendered in console using ansi colors
|
113
|
-
array of hashes describing each tile and binome.
|
114
|
-
--help option for full usage details.
|
125
|
+
description: A gem that procedurally generates a seeded and customizable 2D map with
|
126
|
+
optional roads using perlin noise. Map can be rendered in console using ansi colors
|
127
|
+
or returned as 2D array of hashes describing each tile and binome. Completely customizable,
|
128
|
+
use the --help option for full usage details.
|
115
129
|
email: matthews.tyl@gmail.com
|
116
130
|
executables:
|
117
131
|
- ruby-perlin-2D-map-generator
|
@@ -128,6 +142,9 @@ files:
|
|
128
142
|
- lib/map.rb
|
129
143
|
- lib/map_config.rb
|
130
144
|
- lib/map_tile_generator.rb
|
145
|
+
- lib/pathfinding/a_star_finder.rb
|
146
|
+
- lib/pathfinding/grid.rb
|
147
|
+
- lib/road_generator.rb
|
131
148
|
- lib/tile.rb
|
132
149
|
- lib/tile_item.rb
|
133
150
|
- lib/tile_perlin_generator.rb
|
@@ -155,6 +172,6 @@ requirements: []
|
|
155
172
|
rubygems_version: 3.2.3
|
156
173
|
signing_key:
|
157
174
|
specification_version: 4
|
158
|
-
summary: Procedurally generate seeded and customizable 2D maps
|
159
|
-
colours or described in a 2D array of hashes
|
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
|
160
177
|
test_files: []
|