ruby-perlin-2D-map-generator 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +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
|