ruby-perlin-2D-map-generator 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +104 -40
- data/lib/CLI/command.rb +101 -8
- data/lib/ansi_colours.rb +3 -0
- data/lib/biome.rb +20 -10
- data/lib/building.rb +20 -0
- data/lib/flora_generator.rb +28 -0
- data/lib/map.rb +33 -1
- data/lib/map_config.rb +53 -8
- data/lib/map_tile_generator.rb +1 -0
- data/lib/pathfinding/a_star_finder.rb +70 -0
- data/lib/pathfinding/grid.rb +78 -0
- data/lib/pathfinding/priority_queue.rb +73 -0
- data/lib/poisson_disk_sampling/sample_area.rb +43 -0
- data/lib/poisson_disk_sampling/sampler.rb +108 -0
- data/lib/road_generator.rb +70 -0
- data/lib/tile.rb +80 -16
- data/lib/town_generator.rb +154 -0
- metadata +37 -13
data/lib/map_tile_generator.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'pathfinding/priority_queue'
|
5
|
+
|
6
|
+
module Pathfinding
|
7
|
+
#
|
8
|
+
# An A* Pathfinder to build roads/paths between two coordinates containing
|
9
|
+
# different path costs, the heuristic behaviour that can be altered via configuration
|
10
|
+
#
|
11
|
+
class AStarFinder
|
12
|
+
def find_path(start_node, end_node, grid)
|
13
|
+
came_from = {}
|
14
|
+
g_score = { start_node => 0 }
|
15
|
+
f_score = { start_node => manhattan_distance(start_node, end_node) }
|
16
|
+
|
17
|
+
open_set = Pathfinding::PriorityQueue.new
|
18
|
+
open_set.push(start_node, f_score[start_node])
|
19
|
+
|
20
|
+
closed_set = Set.new
|
21
|
+
until open_set.empty?
|
22
|
+
current = open_set.pop
|
23
|
+
|
24
|
+
# Early exit if the current node is in the closed set
|
25
|
+
next if closed_set.include?(current)
|
26
|
+
|
27
|
+
# Mark the current node as visited
|
28
|
+
closed_set.add(current)
|
29
|
+
|
30
|
+
return reconstruct_path(came_from, current) if current == end_node
|
31
|
+
|
32
|
+
grid.neighbors(current).each do |neighbor|
|
33
|
+
tentative_g_score = g_score[current] + 1
|
34
|
+
|
35
|
+
next if closed_set.include?(neighbor) || (g_score[neighbor] && tentative_g_score >= g_score[neighbor])
|
36
|
+
|
37
|
+
came_from[neighbor] = current
|
38
|
+
g_score[neighbor] = tentative_g_score
|
39
|
+
f_score[neighbor] = g_score[neighbor] + heuristic_cost_estimate(neighbor, end_node)
|
40
|
+
|
41
|
+
open_set.push(neighbor, f_score[neighbor])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# No path found
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def heuristic_cost_estimate(node, end_node)
|
52
|
+
manhattan_distance(node, end_node) +
|
53
|
+
(node.path_heuristic - end_node.path_heuristic) + # elevation for natural roads
|
54
|
+
(node.road? ? 0 : 1000) # share existing roads
|
55
|
+
end
|
56
|
+
|
57
|
+
def manhattan_distance(node, end_node)
|
58
|
+
(node.x - end_node.x).abs + (node.y - end_node.y).abs
|
59
|
+
end
|
60
|
+
|
61
|
+
def reconstruct_path(came_from, current_node)
|
62
|
+
path = [current_node]
|
63
|
+
while came_from[current_node]
|
64
|
+
current_node = came_from[current_node]
|
65
|
+
path.unshift(current_node)
|
66
|
+
end
|
67
|
+
path
|
68
|
+
end
|
69
|
+
end
|
70
|
+
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_haz_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_haz_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_haz_road?
|
32
|
+
node_lookup = node(x, y - 1) if y.positive?
|
33
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_haz_road?
|
34
|
+
node_lookup = node(x, y + 1) if y < @nodes.size - 1
|
35
|
+
neighbors << node_lookup if !node_lookup.nil? && node_lookup.can_haz_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,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pathfinding
|
4
|
+
#
|
5
|
+
# A Priority Queue implementation for managing elements with associated priorities.
|
6
|
+
# Elements are stored and retrieved based on their priority values.
|
7
|
+
#
|
8
|
+
# This Priority Queue is implemented using a binary heap, which provides efficient
|
9
|
+
# insertion, extraction of the minimum element, and deletion operations.
|
10
|
+
#
|
11
|
+
class PriorityQueue
|
12
|
+
def initialize(&block)
|
13
|
+
@heap = []
|
14
|
+
@compare = block || proc { |a, b| @priority_hash[a] < @priority_hash[b] }
|
15
|
+
@priority_hash = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def push(item, priority)
|
19
|
+
@heap << item
|
20
|
+
@priority_hash[item] = priority
|
21
|
+
heapify_up(@heap.length - 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pop
|
25
|
+
return nil if @heap.empty?
|
26
|
+
|
27
|
+
swap(0, @heap.length - 1)
|
28
|
+
popped = @heap.pop
|
29
|
+
heapify_down(0)
|
30
|
+
popped
|
31
|
+
end
|
32
|
+
|
33
|
+
def peek
|
34
|
+
@heap[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty?
|
38
|
+
@heap.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def heapify_up(index)
|
44
|
+
parent_index = (index - 1) / 2
|
45
|
+
|
46
|
+
return unless index.positive? && @compare.call(@heap[index], @heap[parent_index])
|
47
|
+
|
48
|
+
swap(index, parent_index)
|
49
|
+
heapify_up(parent_index)
|
50
|
+
end
|
51
|
+
|
52
|
+
def heapify_down(index)
|
53
|
+
left_child_index = 2 * index + 1
|
54
|
+
right_child_index = 2 * index + 2
|
55
|
+
smallest = index
|
56
|
+
|
57
|
+
smallest = left_child_index if left_child_index < @heap.length && @compare.call(@heap[left_child_index], @heap[smallest])
|
58
|
+
|
59
|
+
smallest = right_child_index if right_child_index < @heap.length && @compare.call(@heap[right_child_index], @heap[smallest])
|
60
|
+
|
61
|
+
return unless smallest != index
|
62
|
+
|
63
|
+
swap(index, smallest)
|
64
|
+
heapify_down(smallest)
|
65
|
+
end
|
66
|
+
|
67
|
+
# rubocop:disable Naming/MethodParameterName:
|
68
|
+
def swap(i, j)
|
69
|
+
@heap[i], @heap[j] = @heap[j], @heap[i]
|
70
|
+
end
|
71
|
+
# rubocop:enable Naming/MethodParameterName:
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PoissonDiskSampling
|
4
|
+
#
|
5
|
+
# Encapsulates area to be sampled by poisson disk sampling
|
6
|
+
#
|
7
|
+
class SampleArea
|
8
|
+
def initialize(grid:)
|
9
|
+
@grid = grid.dup
|
10
|
+
@points = Array.new(height) { Array.new(width) { false } }
|
11
|
+
end
|
12
|
+
|
13
|
+
def height
|
14
|
+
@grid.length
|
15
|
+
end
|
16
|
+
|
17
|
+
def width
|
18
|
+
@grid[0].length
|
19
|
+
end
|
20
|
+
|
21
|
+
# rubocop:disable Naming/MethodParameterName:
|
22
|
+
def set_sampled_point(x, y)
|
23
|
+
@points[y][x] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](x, y)
|
27
|
+
@grid[y][x]
|
28
|
+
end
|
29
|
+
|
30
|
+
def sampled_point?(x, y)
|
31
|
+
@points[y][x]
|
32
|
+
end
|
33
|
+
|
34
|
+
def point_within_bounds?(x, y)
|
35
|
+
y >= 0 && y < height && x >= 0 && x < width
|
36
|
+
end
|
37
|
+
|
38
|
+
def point_within_bounds_and_can_have_road?(x, y)
|
39
|
+
point_within_bounds?(x, y) && @grid[y][x].can_haz_road?
|
40
|
+
end
|
41
|
+
# rubocop:enable Naming/MethodParameterName:
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry-byebug'
|
4
|
+
module PoissonDiskSampling
|
5
|
+
#
|
6
|
+
# Generates X randomly distributed points within given 2D space
|
7
|
+
# using possion disk sampling
|
8
|
+
#
|
9
|
+
class Sampler
|
10
|
+
attr_reader :sample_area, :num_attempts, :seed
|
11
|
+
|
12
|
+
def initialize(sample_area:, num_attempts: 20, seed: rand)
|
13
|
+
@sample_area = sample_area
|
14
|
+
@num_attempts = num_attempts
|
15
|
+
@seed = seed
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_points(num_of_points, radius, intial_start_point = nil)
|
19
|
+
raise ArgumentError, "invalid start argument #{intial_start_point}" if !intial_start_point.nil? && !intial_start_point.is_a?(Array) && intial_start_point.length != 1
|
20
|
+
|
21
|
+
retreive_points_until_active_list_empty_or_num_points_reached(intial_start_point || generate_and_assign_initial_point, num_of_points, radius)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def retreive_points_until_active_list_empty_or_num_points_reached(active_list, num_of_points, radius)
|
27
|
+
points = []
|
28
|
+
retrieve_points(active_list, points, num_of_points, radius) until active_list.empty? || points.length == num_of_points
|
29
|
+
points
|
30
|
+
end
|
31
|
+
|
32
|
+
def retrieve_points(active_list, points, num_of_points, radius)
|
33
|
+
return if active_list.empty?
|
34
|
+
|
35
|
+
current_point, active_index = retreive_current_point(active_list)
|
36
|
+
found = false
|
37
|
+
|
38
|
+
num_attempts.times do
|
39
|
+
new_point = generate_random_point_around(current_point, radius)
|
40
|
+
next if new_point.nil?
|
41
|
+
next unless new_point.can_haz_town? && neighbours_empty?(new_point, radius)
|
42
|
+
|
43
|
+
sample_area.set_sampled_point(new_point.x, new_point.y)
|
44
|
+
active_list << new_point
|
45
|
+
points << new_point
|
46
|
+
return points if points.length == num_of_points
|
47
|
+
|
48
|
+
found = true
|
49
|
+
break
|
50
|
+
end
|
51
|
+
|
52
|
+
active_list.delete_at(active_index) unless found
|
53
|
+
end
|
54
|
+
|
55
|
+
def random_value_and_increment_seed(max)
|
56
|
+
val = Random.new(seed).rand(max)
|
57
|
+
@seed += 1
|
58
|
+
val
|
59
|
+
end
|
60
|
+
|
61
|
+
def retreive_current_point(active_list)
|
62
|
+
active_index = random_value_and_increment_seed(active_list.length)
|
63
|
+
current_point = active_list[active_index]
|
64
|
+
[current_point, active_index]
|
65
|
+
end
|
66
|
+
|
67
|
+
def neighbours_empty?(new_point, radius)
|
68
|
+
cell_empty = true
|
69
|
+
(-radius..radius).each do |dy|
|
70
|
+
(-radius..radius).each do |dx|
|
71
|
+
x = new_point.x + dx
|
72
|
+
y = new_point.y + dy
|
73
|
+
next unless sample_area.point_within_bounds?(x, y) && sample_area[x, y]
|
74
|
+
|
75
|
+
if sample_area.sampled_point?(x, y) && distance(new_point, sample_area[x, y]) < radius
|
76
|
+
cell_empty = false
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
break unless cell_empty
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_and_assign_initial_point
|
85
|
+
num_attempts.times do
|
86
|
+
initial_point_coords = [random_value_and_increment_seed(sample_area.width), random_value_and_increment_seed(sample_area.height)]
|
87
|
+
|
88
|
+
if sample_area[initial_point_coords[0], initial_point_coords[1]].can_haz_town?
|
89
|
+
sample_area.set_sampled_point(initial_point_coords[0], initial_point_coords[1])
|
90
|
+
return [sample_area[initial_point_coords[0], initial_point_coords[1]]]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_random_point_around(point, radius)
|
97
|
+
distance = radius * (random_value_and_increment_seed(1.0) + 1)
|
98
|
+
angle = 2 * Math::PI * random_value_and_increment_seed(1.0)
|
99
|
+
generated = [point.y + distance * Math.cos(angle), point.x + distance * Math.sin(angle)].map(&:round)
|
100
|
+
|
101
|
+
sample_area.point_within_bounds?(generated[1], generated[0]) ? sample_area[generated[1], generated[0]] : nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def distance(point1, point2)
|
105
|
+
Math.sqrt((point1.y - point2.y)**2 + (point1.x - point2.x)**2)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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
|
+
puts "generating #{config.roads} random roads..." if config.verbose
|
21
|
+
|
22
|
+
seed = config.road_seed
|
23
|
+
(1..config.roads).each do |n|
|
24
|
+
puts "generating road #{n}..." if config.verbose
|
25
|
+
random_objects_at_edges = random_nodes_not_on_same_edge(seed + n) # add n otherwise each road is the same
|
26
|
+
generate_path(
|
27
|
+
random_objects_at_edges[0].x,
|
28
|
+
random_objects_at_edges[0].y,
|
29
|
+
random_objects_at_edges[1].x,
|
30
|
+
random_objects_at_edges[1].y
|
31
|
+
).each(&:make_road)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_roads_from_coordinate_list(road_paths, verbose)
|
36
|
+
return unless (road_paths.length % 4).zero?
|
37
|
+
|
38
|
+
puts "generating #{road_paths.length / 4} coordinate roads..." if verbose
|
39
|
+
|
40
|
+
road_paths.each_slice(4) do |road_coordinates|
|
41
|
+
generate_path(
|
42
|
+
road_coordinates[0],
|
43
|
+
road_coordinates[1],
|
44
|
+
road_coordinates[2],
|
45
|
+
road_coordinates[3]
|
46
|
+
).each(&:make_road)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_path(start_x, start_y, end_x, end_y)
|
51
|
+
start_node = grid.node(start_x, start_y)
|
52
|
+
end_node = grid.node(end_x, end_y)
|
53
|
+
finder.find_path(start_node, end_node, grid)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def random_nodes_not_on_same_edge(seed)
|
59
|
+
random_generator = Random.new(seed)
|
60
|
+
length = @grid.edge_nodes.length
|
61
|
+
|
62
|
+
loop do
|
63
|
+
index1 = random_generator.rand(length)
|
64
|
+
index2 = random_generator.rand(length)
|
65
|
+
node_one, node_two = @grid.edge_nodes.values_at(index1, index2)
|
66
|
+
|
67
|
+
return [node_one, node_two] if node_one.x != node_two.x && node_one.y != node_two.y
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/tile.rb
CHANGED
@@ -2,17 +2,28 @@
|
|
2
2
|
|
3
3
|
require 'biome'
|
4
4
|
require 'flora'
|
5
|
+
require 'ansi_colours'
|
6
|
+
require 'pry-byebug'
|
7
|
+
require 'building'
|
5
8
|
|
6
9
|
class Tile
|
7
|
-
attr_reader :x, :y, :height, :moist, :temp, :map
|
10
|
+
attr_reader :x, :y, :height, :moist, :temp, :map, :type
|
8
11
|
|
9
|
-
|
12
|
+
TYPES = %i[
|
13
|
+
terrain
|
14
|
+
road
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
def initialize(map:, x:, y:, height: 0, moist: 0, temp: 0, type: :terrain)
|
10
18
|
@x = x
|
11
19
|
@y = y
|
12
20
|
@height = height
|
13
21
|
@moist = moist
|
14
22
|
@temp = temp
|
15
23
|
@map = map
|
24
|
+
raise ArgumentError, 'invalid tile type' unless TYPES.include?(type)
|
25
|
+
|
26
|
+
@type = type
|
16
27
|
end
|
17
28
|
|
18
29
|
def surrounding_tiles(distance = 1)
|
@@ -32,11 +43,11 @@ class Tile
|
|
32
43
|
end
|
33
44
|
|
34
45
|
def items
|
35
|
-
@items ||=
|
46
|
+
@items ||= []
|
36
47
|
end
|
37
48
|
|
38
49
|
def render_to_standard_output
|
39
|
-
print
|
50
|
+
print render_color_by_type + (!items.empty? ? item_with_highest_priority.render_symbol : ' ')
|
40
51
|
print AnsiColours::Background::ANSI_RESET
|
41
52
|
end
|
42
53
|
|
@@ -50,6 +61,18 @@ class Tile
|
|
50
61
|
items.max_by(&:render_priority)
|
51
62
|
end
|
52
63
|
|
64
|
+
def items_contain_flora?
|
65
|
+
items_contain?(Flora)
|
66
|
+
end
|
67
|
+
|
68
|
+
def items_contain_building?
|
69
|
+
items_contain?(Building)
|
70
|
+
end
|
71
|
+
|
72
|
+
def items_contain?(item_class)
|
73
|
+
items.any? { |i| i.is_a?(item_class) }
|
74
|
+
end
|
75
|
+
|
53
76
|
def to_h
|
54
77
|
{
|
55
78
|
x: x,
|
@@ -58,24 +81,65 @@ class Tile
|
|
58
81
|
moist: moist,
|
59
82
|
temp: temp,
|
60
83
|
biome: biome.to_h,
|
61
|
-
items: items.map(&:to_h)
|
84
|
+
items: items.map(&:to_h),
|
85
|
+
type: type
|
62
86
|
}
|
63
87
|
end
|
64
88
|
|
89
|
+
def add_town_item(seed)
|
90
|
+
add_item(Building.random_town_building(seed))
|
91
|
+
end
|
92
|
+
|
93
|
+
def make_road
|
94
|
+
@type = :road
|
95
|
+
end
|
96
|
+
|
97
|
+
def road?
|
98
|
+
@type == :road
|
99
|
+
end
|
100
|
+
|
101
|
+
def path_heuristic
|
102
|
+
height
|
103
|
+
end
|
104
|
+
|
105
|
+
def can_haz_town?
|
106
|
+
!road? && !biome.water? && !biome.high_mountain? && !items_contain_flora?
|
107
|
+
end
|
108
|
+
|
109
|
+
def can_haz_road?
|
110
|
+
true unless biome_is_water_and_is_excluded? || biome_is_high_mountain_and_is_excluded? || tile_contains_flora_and_is_excluded? || items_contain_building?
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_flora
|
114
|
+
add_item(biome.flora)
|
115
|
+
end
|
116
|
+
|
65
117
|
private
|
66
118
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
119
|
+
def biome_is_water_and_is_excluded?
|
120
|
+
biome.water? && map.config.road_config.road_exclude_water_path
|
121
|
+
end
|
122
|
+
|
123
|
+
def biome_is_high_mountain_and_is_excluded?
|
124
|
+
biome.high_mountain? && map.config.road_config.road_exclude_mountain_path
|
125
|
+
end
|
126
|
+
|
127
|
+
def tile_contains_flora_and_is_excluded?
|
128
|
+
items_contain_flora? && map.config.road_config.road_exclude_flora_path
|
129
|
+
end
|
130
|
+
|
131
|
+
def render_color_by_type
|
132
|
+
case type
|
133
|
+
when :terrain then biome.colour
|
134
|
+
when :road
|
135
|
+
case height
|
136
|
+
when 0.66..1
|
137
|
+
AnsiColours::Background::HIGH_ROAD_BLACK
|
138
|
+
when 0.33..0.66
|
139
|
+
AnsiColours::Background::ROAD_BLACK
|
140
|
+
when 0..0.33
|
141
|
+
AnsiColours::Background::LOW_ROAD_BLACK
|
76
142
|
end
|
77
|
-
else
|
78
|
-
[]
|
79
143
|
end
|
80
144
|
end
|
81
145
|
end
|