ruby-perlin-2D-map-generator 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 59f285c1212afff796f6788a429ce6165cd782527330d9a2130f5bf444cae048
4
+ data.tar.gz: cb54ce932c804c506f26ad66523fa35e8ca37d274b89ab6a345c6a263dff75b0
5
+ SHA512:
6
+ metadata.gz: d9790b03d89b999a94be35a5c0d7aff5a81d844c06a8a52fcfbdd63e4923aee49353e0319d69d1ab108d8b3de0e5a6a8998d396481c7014037a122dd4ed48e6a
7
+ data.tar.gz: f85761e76b1294b5ff318ddfd14d947f34586f1f4241ee61f42972cfa1f0c21649b288c3e48bb200134ba13338f2e3bb26fa1037d0fcf5e07c6ace87a5828592
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Tyler Matthews
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Ruby Perlin 2D Map Generator
2
+ A gem that procedurally generates seeded and customizable 2D map using perlin noise.
3
+
4
+ Include the gem in your project, or use the executable from the command line.
5
+
6
+ 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.
7
+
8
+
9
+ ![2D-maps](https://github.com/matthewstyler/ruby-perlin-2D-map-generator/assets/4560901/89b4f623-53e3-445e-8e5b-96f4fcf67af5)
10
+
11
+ # Customization examples
12
+
13
+ See Command line Usage for full customization, below are some examples. Alter the temperature, moisture or elevation seeds to alter these maps:
14
+
15
+ - Plains with random terrain evens: `ruby-perlin-2D-map-generator render`
16
+ - Desert (increase temperature, decrease moisture): `ruby-perlin-2D-map-generator render --temp=100 --moisture=-100`
17
+ - Mountainous with lakes (increase elevation, increase moisture) `ruby-perlin-2D-map-generator render --elevation=25 --moisture=25`
18
+ - Islands (decreaes elevation, increase moisture): `ruby-perlin-2D-map-generator render --elevation=-40 --moisture=25`
19
+ - Taiga map (decrease temperature, increase moisture): `ruby-perlin-2D-map-generator render --temp=-60 --moisture=30 `
20
+
21
+ ## Common customization
22
+ ```bash
23
+ --width=int The width of the generated map (default 128)
24
+ --height=int The height of the generated map (default 128)
25
+
26
+ --hs=int The seed for a terrains height perlin generation
27
+ (default 10)
28
+ --ms=int The seed for a terrains moist perlin generation
29
+ (default 300)
30
+ --ts=int The seed for a terrains temperature perlin generation
31
+ (default 3000)
32
+
33
+ --elevation=float Adjust each generated elevation by this percent (0 -
34
+ 100) (default 0.0)
35
+ --moisture=float Adjust each generated moisture by this percent (0 -
36
+ 100) (default 0.0)
37
+ --temp=float Adjust each generated temperature by this percent (0
38
+ - 100) (default 0.0)
39
+ ```
40
+
41
+ # Full Command line Usage
42
+ ```bash
43
+ $ ruby-perlin-2D-map-generator --help
44
+ ```
45
+ ```bash
46
+ Usage: ruby-perlin-2D-map-generator [OPTIONS] (DESCRIBE | RENDER)
47
+
48
+ Generate a seeded customizable procedurally generated 2D map.
49
+ Rendered in the console using ansi colours, or described as a 2D array of
50
+ hashes with each tiles information.
51
+
52
+ Arguments:
53
+ (DESCRIBE | RENDER) command to run: render prints the map to standard
54
+ output using ansi colors, while describe prints each
55
+ tiles bionome information in the map.
56
+
57
+ Options:
58
+ --elevation=float Adjust each generated elevation by this percent (0 -
59
+ 100) (default 0.0)
60
+ --fhx=float The frequency for height generation across the x-axis
61
+ (default 2.5)
62
+ --fhy=float The frequency for height generation across the y-axis
63
+ (default 2.5)
64
+ --fmx=float The frequency for moist generation across the x-axis
65
+ (default 2.5)
66
+ --fmy=float The frequency for moist generation across the y-axis
67
+ (default 2.5)
68
+ --ftx=float The frequency for temp generation across the x-axis
69
+ (default 2.5)
70
+ --fty=float The frequency for temp generation across the y-axis
71
+ (default 2.5)
72
+ --gf=bool Generate flora, significantly affects performance
73
+ --height=int The height of the generated map (default 128)
74
+ -h, --help Print usage
75
+ --hs=int The seed for a terrains height perlin generation
76
+ (default 10)
77
+ --moisture=float Adjust each generated moisture by this percent (0 -
78
+ 100) (default 0.0)
79
+ --ms=int The seed for a terrains moist perlin generation
80
+ (default 300)
81
+ --oh=int Octaves for height generation (default 3)
82
+ --om=int Octaves for moist generation (default 3)
83
+ --ot=int Octaves for temp generation (default 3)
84
+ --ph=float Persistance for height generation (default 1.0)
85
+ --pm=float Persistance for moist generation (default 1.0)
86
+ --pt=float Persistance for temp generation (default 1.0)
87
+ --temp=float Adjust each generated temperature by this percent (0
88
+ - 100) (default 0.0)
89
+ --ts=int The seed for a terrains temperature perlin generation
90
+ (default 3000)
91
+ --width=int The width of the generated map (default 128)
92
+
93
+ Examples:
94
+ Render with defaults
95
+ $ ruby-perlin-2D-map-generator render
96
+
97
+ Render with options
98
+ $ ruby-perlin-2D-map-generator render --elevation=-40 --moisture=25 --hs=1
99
+ ```
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
+ require 'CLI/command'
7
+
8
+ Signal.trap('INT') do
9
+ warn("\n#{caller.join("\n")}: interrupted")
10
+ exit(1)
11
+ end
12
+
13
+ CLI::Command.new.parse.run
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty/option'
4
+ require 'map'
5
+ require 'map_config'
6
+ module CLI
7
+ class Command
8
+ include TTY::Option
9
+
10
+ usage do
11
+ program 'ruby-perlin-2D-map-generator'
12
+
13
+ no_command
14
+
15
+ desc 'Generate a seeded customizable procedurally generated 2D map. Rendered in the console ' \
16
+ ' using ansi colours, or described as a 2D array of hashes with each tiles information.'
17
+
18
+ example 'Render with defaults',
19
+ ' $ ruby-perlin-2D-map-generator render'
20
+
21
+ example 'Render with options',
22
+ ' $ ruby-perlin-2D-map-generator render --elevation=-40 --moisture=25 --hs=1'
23
+ end
24
+
25
+ argument :command do
26
+ name '(describe | render)'
27
+ arity one
28
+ validate ->(v) { v.downcase == 'describe' || v.downcase == 'render' }
29
+ desc 'command to run: render prints the map to standard output using ansi colors, ' \
30
+ 'while describe prints each tiles bionome information in the map.'
31
+ end
32
+
33
+ option :height_seed do
34
+ long '--hs int'
35
+ # or
36
+ long '--hs=int'
37
+
38
+ desc 'The seed for a terrains height perlin generation'
39
+ convert Integer
40
+ default MapConfig::DEFAULT_HEIGHT_SEED
41
+ end
42
+
43
+ option :moist_seed do
44
+ long '--ms int'
45
+ # or
46
+ long '--ms=int'
47
+
48
+ desc 'The seed for a terrains moist perlin generation'
49
+ convert Integer
50
+ default MapConfig::DEFAULT_MOIST_SEED
51
+ end
52
+
53
+ option :temp_seed do
54
+ long '--ts int'
55
+ # or
56
+ long '--ts=int'
57
+
58
+ desc 'The seed for a terrains temperature perlin generation'
59
+ convert Integer
60
+ default MapConfig::DEFAULT_TEMP_SEED
61
+ end
62
+
63
+ option :octaves_height do
64
+ long '--oh int'
65
+ long '--oh=int'
66
+
67
+ desc 'Octaves for height generation'
68
+ convert Integer
69
+ default MapConfig::DEFAULT_HEIGHT_OCTAVES
70
+ end
71
+
72
+ option :octaves_moist do
73
+ long '--om int'
74
+ long '--om=int'
75
+
76
+ desc 'Octaves for moist generation'
77
+ convert Integer
78
+ default MapConfig::DEFAULT_MOIST_OCTAVES
79
+ end
80
+
81
+ option :octaves_temp do
82
+ long '--ot int'
83
+ long '--ot=int'
84
+
85
+ desc 'Octaves for temp generation'
86
+ convert Integer
87
+ default MapConfig::DEFAULT_TEMP_OCTAVES
88
+ end
89
+
90
+ option :persistance_moist do
91
+ long '--pm float'
92
+ long '--pm=float'
93
+
94
+ desc 'Persistance for moist generation'
95
+ convert Float
96
+ default MapConfig::DEFAULT_MOIST_PERSISTANCE
97
+ end
98
+
99
+ option :persistance_height do
100
+ long '--ph float'
101
+ long '--ph=float'
102
+
103
+ desc 'Persistance for height generation'
104
+ convert Float
105
+ default MapConfig::DEFAULT_HEIGHT_PERSISTANCE
106
+ end
107
+
108
+ option :persistance_temp do
109
+ long '--pt float'
110
+ long '--pt=float'
111
+
112
+ desc 'Persistance for temp generation'
113
+ convert Float
114
+ default MapConfig::DEFAULT_TEMP_PERSISTANCE
115
+ end
116
+
117
+ option :width do
118
+ long '--width int'
119
+ long '--width=int'
120
+
121
+ desc 'The width of the generated map'
122
+ convert Integer
123
+ default MapConfig::DEFAULT_TILE_COUNT
124
+ end
125
+
126
+ option :height do
127
+ long '--height int'
128
+ long '--height=int'
129
+
130
+ desc 'The height of the generated map'
131
+ convert Integer
132
+ default MapConfig::DEFAULT_TILE_COUNT
133
+ end
134
+
135
+ option :perlin_height_horizontal_frequency do
136
+ long '--fhx float'
137
+ long '--fhx=float'
138
+
139
+ desc 'The frequency for height generation across the x-axis'
140
+ convert Float
141
+ default MapConfig::DEFAULT_HEIGHT_X_FREQUENCY
142
+ end
143
+
144
+ option :perlin_height_vertical_frequency do
145
+ long '--fhy float'
146
+ long '--fhy=float'
147
+
148
+ desc 'The frequency for height generation across the y-axis'
149
+ convert Float
150
+ default MapConfig::DEFAULT_HEIGHT_Y_FREQUENCY
151
+ end
152
+
153
+ option :perlin_temp_horizontal_frequency do
154
+ long '--ftx float'
155
+ long '--ftx=float'
156
+
157
+ desc 'The frequency for temp generation across the x-axis'
158
+ convert Float
159
+ default MapConfig::DEFAULT_TEMP_X_FREQUENCY
160
+ end
161
+
162
+ option :perlin_temp_vertical_frequency do
163
+ long '--fty float'
164
+ long '--fty=float'
165
+
166
+ desc 'The frequency for temp generation across the y-axis'
167
+ convert Float
168
+ default MapConfig::DEFAULT_TEMP_Y_FREQUENCY
169
+ end
170
+
171
+ option :perlin_moist_horizontal_frequency do
172
+ long '--fmx float'
173
+ long '--fmx=float'
174
+
175
+ desc 'The frequency for moist generation across the x-axis'
176
+ convert Float
177
+ default MapConfig::DEFAULT_MOIST_X_FREQUENCY
178
+ end
179
+
180
+ option :perlin_moist_vertical_frequency do
181
+ long '--fmy float'
182
+ long '--fmy=float'
183
+
184
+ desc 'The frequency for moist generation across the y-axis'
185
+ convert Float
186
+ default MapConfig::DEFAULT_MOIST_Y_FREQUENCY
187
+ end
188
+
189
+ option :generate_flora do
190
+ long '--gf bool'
191
+ long '--gf=bool'
192
+
193
+ desc 'Generate flora, significantly affects performance'
194
+ convert :bool
195
+ default MapConfig::DEFAULT_GENERATE_FLORA
196
+ end
197
+
198
+ option :temp do
199
+ long '--temp float'
200
+ long '--temp=float'
201
+
202
+ desc 'Adjust each generated temperature by this percent (0 - 100)'
203
+ convert ->(val) { val.to_f / 100.0 }
204
+ validate ->(val) { val >= -1.0 && val <= 1.0 }
205
+ default MapConfig::DEFAULT_TEMP_ADJUSTMENT
206
+ end
207
+
208
+ option :elevation do
209
+ long '--elevation float'
210
+ long '--elevation=float'
211
+
212
+ desc 'Adjust each generated elevation by this percent (0 - 100)'
213
+ convert ->(val) { val.to_f / 100.0 }
214
+ validate ->(val) { val >= -1.0 && val <= 1.0 }
215
+ default MapConfig::DEFAULT_HEIGHT_ADJUSTMENT
216
+ end
217
+
218
+ option :moisture do
219
+ long '--moisture float'
220
+ long '--moisture=float'
221
+
222
+ desc 'Adjust each generated moisture by this percent (0 - 100)'
223
+ convert ->(val) { val.to_f / 100.0 }
224
+ validate ->(val) { val >= -1.0 && val <= 1.0 }
225
+ default MapConfig::DEFAULT_MOIST_ADJUSTMENT
226
+ end
227
+
228
+ flag :help do
229
+ short '-h'
230
+ long '--help'
231
+ desc 'Print usage'
232
+ end
233
+
234
+ def run
235
+ if params[:help]
236
+ print help
237
+ elsif params.errors.any?
238
+ puts params.errors.summary
239
+ else
240
+ execute_command
241
+ # pp params.to_h
242
+ end
243
+ end
244
+
245
+ private
246
+
247
+ def execute_command
248
+ map = Map.new(map_config: MapConfig.new(
249
+ width: params[:width],
250
+ height: params[:height],
251
+ perlin_height_config: perlin_height_config,
252
+ perlin_moist_config: perlin_moist_config,
253
+ perlin_temp_config: perlin_temp_config,
254
+ generate_flora: params[:generate_flora]
255
+ ))
256
+ case params[:command]
257
+ when 'render' then map.render
258
+ when 'describe' then puts map.describe
259
+ end
260
+ end
261
+
262
+ def perlin_moist_config
263
+ MapConfig::PerlinConfig.new(*params.to_h.slice(:width, :height, :moist_seed, :octaves_moist,
264
+ :perlin_moist_horizontal_frequency, :perlin_moist_vertical_frequency, :persistance_moist, :moisture).values)
265
+ end
266
+
267
+ def perlin_height_config
268
+ MapConfig::PerlinConfig.new(*params.to_h.slice(:width, :height, :height_seed, :octaves_height,
269
+ :perlin_height_horizontal_frequency, :perlin_height_vertical_frequency, :persistance_height, :elevation).values)
270
+ end
271
+
272
+ def perlin_temp_config
273
+ MapConfig::PerlinConfig.new(*params.to_h.slice(:width, :height, :temp_seed, :octaves_temp,
274
+ :perlin_temp_horizontal_frequency, :perlin_temp_vertical_frequency, :persistance_temp, :temp).values)
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnsiColours
4
+ module Background
5
+ WHITE = "\e[48;5;231m"
6
+ BLACK = "\033[40m"
7
+ DEEP_BLUE = "\033[48;5;23m"
8
+ BLUE = "\033[44m"
9
+ LIGHT_BLUE = "\033[48;5;117m"
10
+ SHOAL_BLUE = "\e[48;5;87m"
11
+ GREEN = "\033[102m"
12
+ LIGHT_GREEN = "\e[48;5;47m"
13
+ DESERT_YELLOW = "\e[48;5;220m"
14
+ DESERT_LIGHT_YELLOW = "\e[48;5;223m"
15
+ DESERT_DARK_YELLOW = "\e[48;5;214m"
16
+ GREEN_SWAMP = "\e[48;5;58m"
17
+ DARK_GREEN = "\033[42m"
18
+ BROWN = "\033[48;5;130m"
19
+ GREY = "\e[48;5;7m"
20
+ GREY_MOUNTAIN = "\033[38;5;240m"
21
+ SANDY = "\033[48;5;229m"
22
+ RED_BROWN = "\033[48;5;173m"
23
+ GRASSLAND = "\033[48;2;195;205;103m"
24
+ TAIGA_PLAIN = "\e[48;5;67m"
25
+ TAIGA_VALLEY = "\e[48;5;66m"
26
+ TAIGA_HIGHLAND = "\e[48;5;65m"
27
+ TAIGA_COAST = "\e[48;5;17m"
28
+ ICE = "\e[48;5;159m"
29
+ ANSI_RESET = "\033[0m"
30
+ end
31
+ end
data/lib/biome.rb ADDED
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ansi_colours'
4
+ require 'flora'
5
+
6
+ class Biome
7
+ attr_accessor :name, :flora_range, :colour
8
+
9
+ def initialize(name:, colour:, flora_range: nil)
10
+ @name = name
11
+ @flora_range = flora_range
12
+ @colour = colour
13
+ end
14
+
15
+ def water?
16
+ WATER_TERRAIN.include?(self)
17
+ end
18
+
19
+ def land?
20
+ LAND_TERRAIN.include?(self)
21
+ end
22
+
23
+ def grassland?
24
+ GRASSLAND_TERRAIN.include?(self)
25
+ end
26
+
27
+ def desert?
28
+ DESERT_TERRAIN.include?(self)
29
+ end
30
+
31
+ def taiga?
32
+ TAIGA_TERRAIN.include?(self)
33
+ end
34
+
35
+ def flora_available
36
+ !flora_range.nil?
37
+ end
38
+
39
+ def flora
40
+ @flora ||=
41
+ if flora_available
42
+ if desert?
43
+ Flora.new(Flora::CACTUS)
44
+ elsif grassland?
45
+ Flora.new(Flora::DECIDUOUS_TREE)
46
+ elsif taiga?
47
+ Flora.new(Flora::EVERGREEN_TREE)
48
+ else
49
+ raise StandardError, "Unknown flora #{to_h}"
50
+ end
51
+ end
52
+ end
53
+
54
+ def to_h
55
+ {
56
+ name: name,
57
+ flora_range: flora_range,
58
+ colour: colour
59
+ }
60
+ end
61
+
62
+ def self.from(elevation, moist, temp)
63
+ terrain_selection(elevation, moist, temp)
64
+ end
65
+
66
+ SNOW = Biome.new(name: 'snow', flora_range: nil, colour: AnsiColours::Background::WHITE)
67
+ ROCKS = Biome.new(name: 'rocks', flora_range: nil, colour: AnsiColours::Background::GREY)
68
+ MOUNTAIN = Biome.new(name: 'mountain', flora_range: nil, colour: AnsiColours::Background::BROWN)
69
+ MOUNTAIN_FOOT = Biome.new(name: 'mountain_foot', flora_range: 10, colour: AnsiColours::Background::RED_BROWN)
70
+ GRASSLAND = Biome.new(name: 'grassland', flora_range: 1, colour: AnsiColours::Background::DARK_GREEN)
71
+ VALLEY = Biome.new(name: 'valley', flora_range: 1, colour: AnsiColours::Background::GREEN)
72
+ DEEP_VALLEY = Biome.new(name: 'deep_valley', flora_range: 1, colour: AnsiColours::Background::LIGHT_GREEN)
73
+ DESERT = Biome.new(name: 'desert', flora_range: 1, colour: AnsiColours::Background::DESERT_YELLOW)
74
+ DEEP_DESERT = Biome.new(name: 'deep_desert', flora_range: 1, colour: AnsiColours::Background::DESERT_LIGHT_YELLOW)
75
+ STEPPE_DESERT = Biome.new(name: 'steppe_desert', flora_range: 1, colour: AnsiColours::Background::DESERT_DARK_YELLOW)
76
+ SWAMP = Biome.new(name: 'swamp', flora_range: nil, colour: AnsiColours::Background::GREEN_SWAMP)
77
+ COASTLINE = Biome.new(name: 'coastline', flora_range: nil, colour: AnsiColours::Background::SANDY)
78
+ SHOAL = Biome.new(name: 'shoal', flora_range: nil, colour: AnsiColours::Background::SHOAL_BLUE)
79
+ OCEAN = Biome.new(name: 'ocean', flora_range: nil, colour: AnsiColours::Background::LIGHT_BLUE)
80
+ DEEP_OCEAN = Biome.new(name: 'deep_ocean', flora_range: nil, colour: AnsiColours::Background::BLUE)
81
+ TAIGA_PLAIN = Biome.new(name: 'taiga_plain', flora_range: 1.5, colour: AnsiColours::Background::TAIGA_PLAIN)
82
+ TAIGA_VALLEY = Biome.new(name: 'taiga_valley', flora_range: 1.5, colour: AnsiColours::Background::TAIGA_VALLEY)
83
+ TAIGA_HIGHLAND = Biome.new(name: 'taiga_highland', flora_range: 1.5, colour: AnsiColours::Background::TAIGA_HIGHLAND)
84
+ TAIGA_COAST = Biome.new(name: 'taiga_coast', flora_range: nil, colour: AnsiColours::Background::TAIGA_COAST)
85
+ ICE = Biome.new(name: 'ice', flora_range: nil, colour: AnsiColours::Background::ICE)
86
+
87
+ ALL_TERRAIN = [
88
+ SNOW,
89
+ ROCKS,
90
+ MOUNTAIN,
91
+ MOUNTAIN_FOOT,
92
+ GRASSLAND,
93
+ VALLEY,
94
+ COASTLINE,
95
+ SHOAL,
96
+ OCEAN,
97
+ DEEP_OCEAN,
98
+ DESERT,
99
+ DEEP_DESERT,
100
+ STEPPE_DESERT,
101
+ SWAMP,
102
+ TAIGA_PLAIN,
103
+ TAIGA_HIGHLAND,
104
+ TAIGA_VALLEY,
105
+ TAIGA_COAST,
106
+ ICE
107
+ ].freeze
108
+
109
+ WATER_TERRAIN = [
110
+ SHOAL,
111
+ OCEAN,
112
+ DEEP_OCEAN
113
+ ].freeze
114
+
115
+ DESERT_TERRAIN = [
116
+ DESERT,
117
+ DEEP_DESERT,
118
+ STEPPE_DESERT
119
+ ].freeze
120
+
121
+ GRASSLAND_TERRAIN = [
122
+ GRASSLAND,
123
+ VALLEY,
124
+ DEEP_VALLEY,
125
+ MOUNTAIN_FOOT
126
+ ].freeze
127
+
128
+ TAIGA_TERRAIN = [
129
+ TAIGA_PLAIN,
130
+ TAIGA_HIGHLAND,
131
+ TAIGA_VALLEY,
132
+ TAIGA_COAST
133
+ ].freeze
134
+
135
+ LAND_TERRAIN = (ALL_TERRAIN - WATER_TERRAIN).freeze
136
+
137
+ class << self
138
+ private
139
+
140
+ def terrain_selection(elevation, moist, temp)
141
+ case elevation
142
+ when 0.95..1
143
+ SNOW
144
+ when 0.9..0.95
145
+ ROCKS
146
+ when 0.8..0.9
147
+ if moist < 0.9
148
+ MOUNTAIN
149
+ else
150
+ SHOAL
151
+ end
152
+ when 0.7..0.8
153
+ if moist < 0.9
154
+ MOUNTAIN_FOOT
155
+ else
156
+ SHOAL
157
+ end
158
+ when 0.6..0.7
159
+ if moist < 0.8
160
+ if desert_condition?(moist, temp)
161
+ STEPPE_DESERT
162
+ elsif taiga_condition?(moist, temp)
163
+ TAIGA_HIGHLAND
164
+ elsif moist > 0.75
165
+ SWAMP
166
+ else
167
+ GRASSLAND
168
+ end
169
+ else
170
+ SHOAL
171
+ end
172
+ when 0.3..0.6
173
+ if desert_condition?(moist, temp)
174
+ DESERT
175
+ elsif taiga_condition?(moist, temp)
176
+ TAIGA_PLAIN
177
+ else
178
+ VALLEY
179
+ end
180
+ when 0.2..0.3
181
+ if desert_condition?(moist, temp)
182
+ DEEP_DESERT
183
+ elsif taiga_condition?(moist, temp)
184
+ TAIGA_VALLEY
185
+ else
186
+ DEEP_VALLEY
187
+ end
188
+ when 0.15..0.2
189
+ if taiga_condition?(moist, temp)
190
+ TAIGA_COAST
191
+ else
192
+ COASTLINE
193
+ end
194
+ when 0.05..0.15
195
+ if taiga_condition?(moist, temp)
196
+ ICE
197
+ else
198
+ SHOAL
199
+ end
200
+ when 0.025..0.05
201
+ if taiga_condition?(moist, temp)
202
+ ICE
203
+ else
204
+ OCEAN
205
+ end
206
+ when 0.0..0.025
207
+ if taiga_condition?(moist, temp)
208
+ ICE
209
+ else
210
+ DEEP_OCEAN
211
+ end
212
+ else
213
+ raise ArgumentError, "unknown biome elevation: #{elevation} moisture: #{moist} temp: #{temp}"
214
+ end
215
+ end
216
+
217
+ def desert_condition?(moist, temp)
218
+ moist < 0.1 && temp > 0.5
219
+ end
220
+
221
+ def taiga_condition?(moist, temp)
222
+ moist > 0.6 && temp < 0.2
223
+ end
224
+ end
225
+ end
data/lib/flora.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tile_item'
4
+
5
+ class Flora < TileItem
6
+ CACTUS = "\u{1F335}"
7
+ EVERGREEN_TREE = "\u{1F332}"
8
+ DECIDUOUS_TREE = "\u{1F333}"
9
+
10
+ def initialize(render_symbol)
11
+ super self, render_symbol: render_symbol
12
+ end
13
+ end
data/lib/map.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'map_tile_generator'
4
+ require 'map_config'
5
+
6
+ class Map
7
+ attr_reader :config
8
+
9
+ def initialize(map_config: MapConfig.new)
10
+ @config = map_config
11
+ end
12
+
13
+ def describe
14
+ tiles.map { |row| row.map(&:to_h) }
15
+ end
16
+
17
+ def render
18
+ tiles.each do |row|
19
+ row.each(&:render_to_standard_output)
20
+ puts
21
+ end
22
+ end
23
+
24
+ def tiles
25
+ @tiles ||= MapTileGenerator.new(map: self).generate
26
+ end
27
+ end
data/lib/map_config.rb ADDED
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MapConfig
4
+ DEFAULT_TILE_COUNT = 128
5
+ DEFAULT_GENERATE_FLORA = true
6
+
7
+ DEFAULT_HEIGHT_SEED = 10
8
+ DEFAULT_HEIGHT_OCTAVES = 3
9
+ DEFAULT_HEIGHT_X_FREQUENCY = 2.5
10
+ DEFAULT_HEIGHT_Y_FREQUENCY = 2.5
11
+ DEFAULT_HEIGHT_PERSISTANCE = 1.0
12
+ DEFAULT_HEIGHT_ADJUSTMENT = 0.0
13
+
14
+ DEFAULT_MOIST_SEED = 300
15
+ DEFAULT_MOIST_OCTAVES = 3
16
+ DEFAULT_MOIST_X_FREQUENCY = 2.5
17
+ DEFAULT_MOIST_Y_FREQUENCY = 2.5
18
+ DEFAULT_MOIST_PERSISTANCE = 1.0
19
+ DEFAULT_MOIST_ADJUSTMENT = 0.0
20
+
21
+ DEFAULT_TEMP_SEED = 3000
22
+ DEFAULT_TEMP_OCTAVES = 3
23
+ DEFAULT_TEMP_PERSISTANCE = 1.0
24
+ DEFAULT_TEMP_Y_FREQUENCY = 2.5
25
+ DEFAULT_TEMP_X_FREQUENCY = 2.5
26
+ DEFAULT_TEMP_ADJUSTMENT = 0.0
27
+
28
+ PERLIN_CONFIG_OPTIONS = %i[width height noise_seed octaves x_frequency y_frequency persistance adjustment].freeze
29
+ PerlinConfig = Struct.new(*PERLIN_CONFIG_OPTIONS)
30
+
31
+ attr_reader :generate_flora, :perlin_height_config, :perlin_moist_config, :perlin_temp_config, :width, :height
32
+
33
+ def initialize(perlin_height_config: default_perlin_height_config, perlin_moist_config: default_perlin_moist_config, perlin_temp_config: default_perlin_temp_config, width: DEFAULT_TILE_COUNT,
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)
36
+
37
+ @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
41
+ @width = width
42
+ @height = height
43
+ end
44
+
45
+ private
46
+
47
+ def default_perlin_height_config
48
+ PerlinConfig.new(DEFAULT_TILE_COUNT, DEFAULT_TILE_COUNT, DEFAULT_HEIGHT_SEED, DEFAULT_HEIGHT_OCTAVES,
49
+ DEFAULT_HEIGHT_X_FREQUENCY, DEFAULT_HEIGHT_Y_FREQUENCY, DEFAULT_HEIGHT_PERSISTANCE, DEFAULT_HEIGHT_ADJUSTMENT)
50
+ end
51
+
52
+ def default_perlin_moist_config
53
+ PerlinConfig.new(DEFAULT_TILE_COUNT, DEFAULT_TILE_COUNT, DEFAULT_MOIST_SEED, DEFAULT_MOIST_OCTAVES,
54
+ DEFAULT_MOIST_X_FREQUENCY, DEFAULT_MOIST_Y_FREQUENCY, DEFAULT_MOIST_PERSISTANCE, DEFAULT_MOIST_ADJUSTMENT)
55
+ end
56
+
57
+ def default_perlin_temp_config
58
+ PerlinConfig.new(DEFAULT_TILE_COUNT, DEFAULT_TILE_COUNT, DEFAULT_TEMP_SEED, DEFAULT_TEMP_OCTAVES,
59
+ DEFAULT_TEMP_X_FREQUENCY, DEFAULT_TEMP_Y_FREQUENCY, DEFAULT_TEMP_PERSISTANCE, DEFAULT_TEMP_ADJUSTMENT)
60
+ end
61
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tile'
4
+ require 'tile_perlin_generator'
5
+
6
+ class MapTileGenerator
7
+ attr_reader :map_config, :map, :height_perlin_generator, :moist_perlin_generator, :temp_perlin_generator
8
+
9
+ def initialize(map:, height_perlin_generator: nil, moist_perlin_generator: nil, temp_perlin_generator: nil)
10
+ @map = map
11
+ @map_config = map.config
12
+ @height_perlin_generator = height_perlin_generator || default_perlin_height_generator
13
+ @moist_perlin_generator = moist_perlin_generator || default_perlin_moist_generator
14
+ @temp_perlin_generator = temp_perlin_generator || default_perlin_temp_generator
15
+ end
16
+
17
+ def generate
18
+ positive_quadrant_cartesian_plane
19
+ end
20
+
21
+ private
22
+
23
+ def positive_quadrant_cartesian_plane
24
+ y_axis_array do |y|
25
+ x_axis_array do |x|
26
+ Tile.new(
27
+ map: map,
28
+ x: x,
29
+ y: y,
30
+ height: heights[y][x],
31
+ moist: moists[y][x],
32
+ temp: temps[y][x]
33
+ )
34
+ end
35
+ end
36
+ end
37
+
38
+ def y_axis_array(&block)
39
+ Array.new(map_config.height, &block)
40
+ end
41
+
42
+ def x_axis_array(&block)
43
+ Array.new(map_config.width, &block)
44
+ end
45
+
46
+ def default_perlin_height_generator
47
+ TilePerlinGenerator.new(map_config.perlin_height_config)
48
+ end
49
+
50
+ def default_perlin_moist_generator
51
+ TilePerlinGenerator.new(map_config.perlin_moist_config)
52
+ end
53
+
54
+ def default_perlin_temp_generator
55
+ TilePerlinGenerator.new(map_config.perlin_temp_config)
56
+ end
57
+
58
+ def heights
59
+ @heights ||= height_perlin_generator.generate
60
+ end
61
+
62
+ def moists
63
+ @moists ||= moist_perlin_generator.generate
64
+ end
65
+
66
+ def temps
67
+ @temps ||= temp_perlin_generator.generate
68
+ end
69
+ end
data/lib/tile.rb ADDED
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'biome'
4
+ require 'flora'
5
+
6
+ class Tile
7
+ attr_reader :x, :y, :height, :moist, :temp, :map
8
+
9
+ def initialize(map:, x:, y:, height: 0, moist: 0, temp: 0)
10
+ @x = x
11
+ @y = y
12
+ @height = height
13
+ @moist = moist
14
+ @temp = temp
15
+ @map = map
16
+ end
17
+
18
+ def surrounding_tiles(distance = 1)
19
+ @surround_cache ||= {}
20
+ @surround_cache[distance] ||= begin
21
+ left_limit = [0, x - distance].max
22
+ top_limit = [0, y - distance].max
23
+
24
+ map.tiles[left_limit..(x + distance)].map do |r|
25
+ r[top_limit..(y + distance)]
26
+ end.flatten
27
+ end
28
+ end
29
+
30
+ def biome
31
+ @biome ||= Biome.from(height, moist, temp)
32
+ end
33
+
34
+ def items
35
+ @items ||= items_generated_with_flora_if_applicable
36
+ end
37
+
38
+ def render_to_standard_output
39
+ print biome.colour + (!items.empty? ? item_with_highest_priority.render_symbol : ' ')
40
+ print AnsiColours::Background::ANSI_RESET
41
+ end
42
+
43
+ def add_item(tile_item)
44
+ raise ArgumentError, 'item should be a tile' unless tile_item.is_a?(TileItem)
45
+
46
+ items.push(tile_item)
47
+ end
48
+
49
+ def item_with_highest_priority
50
+ items.max_by(&:render_priority)
51
+ end
52
+
53
+ def to_h
54
+ {
55
+ x: x,
56
+ y: y,
57
+ height: height,
58
+ moist: moist,
59
+ temp: temp,
60
+ biome: biome.to_h,
61
+ items: items.map(&:to_h)
62
+ }
63
+ end
64
+
65
+ private
66
+
67
+ def items_generated_with_flora_if_applicable
68
+ if map.config.generate_flora && biome.flora_available
69
+ range_max_value = map.tiles[(y - biome.flora_range)...(y + biome.flora_range)]&.map do |r|
70
+ r[(x - biome.flora_range)...(x + biome.flora_range)]
71
+ end&.flatten&.map(&:height)&.max
72
+ if range_max_value == height
73
+ [biome.flora]
74
+ else
75
+ []
76
+ end
77
+ else
78
+ []
79
+ end
80
+ end
81
+ end
data/lib/tile_item.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TileItem
4
+ DEFAULT_RENDER_PRIORITY = 0
5
+ attr_reader :obj, :id, :render_symbol, :colour, :render_priority
6
+
7
+ def initialize(obj, render_symbol:, id: object_id, render_priority: DEFAULT_RENDER_PRIORITY)
8
+ @obj = obj
9
+ @id = id
10
+ @render_symbol = render_symbol
11
+ @render_priority = render_priority
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ id: id,
17
+ type: obj.class.name.downcase,
18
+ render_symbol: render_symbol,
19
+ render_priority: render_priority
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'perlin'
4
+
5
+ class TilePerlinGenerator
6
+ attr_reader :perlin_config
7
+
8
+ def initialize(perlin_config)
9
+ @perlin_config = perlin_config
10
+ end
11
+
12
+ def generate
13
+ Array.new(perlin_config.height) do |y|
14
+ Array.new(perlin_config.width) do |x|
15
+ nx = perlin_config.x_frequency * (x.to_f / perlin_config.width - 0.5)
16
+ ny = perlin_config.y_frequency * (y.to_f / perlin_config.height - 0.5)
17
+ with_adjustment((Math.cos(noise(nx, ny))**6))
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def with_adjustment(result)
25
+ if perlin_config.adjustment != 0.0
26
+ result += perlin_config.adjustment
27
+ result = [result, 1.0].min
28
+ [result, 0.0].max
29
+ else
30
+ result
31
+ end
32
+ end
33
+
34
+ def noise(x, y)
35
+ (noise_generator[x, y] / 2) + 0.5
36
+ end
37
+
38
+ def noise_generator
39
+ @noise_generator ||=
40
+ Perlin::Generator.new(
41
+ perlin_config.noise_seed,
42
+ perlin_config.persistance,
43
+ perlin_config.octaves
44
+ )
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-perlin-2D-map-generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Tyler Matthews (matthewstyler)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-07-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: perlin
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-option
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A gem that procedurally generates a seeded and customizable 2D map using
84
+ perlin noise. Map can be rendered in console using ansi colors or returned as 2D
85
+ array of hashes describing each tile and binome. Completelycustomizable, use the
86
+ --help option for full usage details.
87
+ email: matthews.tyl@gmail.com
88
+ executables:
89
+ - ruby-perlin-2D-map-generator
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - LICENSE
94
+ - README.md
95
+ - bin/ruby-perlin-2D-map-generator
96
+ - lib/CLI/command.rb
97
+ - lib/ansi_colours.rb
98
+ - lib/biome.rb
99
+ - lib/flora.rb
100
+ - lib/map.rb
101
+ - lib/map_config.rb
102
+ - lib/map_tile_generator.rb
103
+ - lib/tile.rb
104
+ - lib/tile_item.rb
105
+ - lib/tile_perlin_generator.rb
106
+ homepage: https://github.com/matthewstyler/ruby-perlin-2D-map-generator
107
+ licenses:
108
+ - MIT
109
+ metadata:
110
+ source_code_uri: https://github.com/matthewstyler/ruby-perlin-2D-map-generator
111
+ bug_tracker_uri: https://github.com/matthewstyler/ruby-perlin-2D-map-generator/issues
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '3.0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.2.3
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Procedurally generate seeded and customizable 2D maps, rendered with ansi
131
+ colours or described in a 2D array of hashes
132
+ test_files: []