ruby-perlin-2D-map-generator 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []