Skip to content

Commit 6a98d7a

Browse files
Merge pull request #12 from matthewstyler/tm/min-tree-spanning-alg
Prim's Algorithm with MST for road generation
2 parents e77e64c + 71551ad commit 6a98d7a

File tree

2 files changed

+54
-28
lines changed

2 files changed

+54
-28
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ See Command line Usage for full customization, below are some examples. Alter th
7373
Roads can be generated by providing a positive integer to the `roads=` argument. Roads are randomly seeded to begin
7474
and start at an axis (but not the same axis).
7575

76-
A* pathfinding with a priority queue is used to generate the roads. The heuristic uses manhattan distance, and favours existing roads and similar elevations in adjacent tiles.
76+
A* pathfinding with a priority queue, along with prim's algorithn and a minimum spanning tree is used to generate the roads. The heuristic uses manhattan distance, and favours existing roads and similar elevations in adjacent tiles.
7777

7878
Roads can be configured to include/exclude generating paths thorugh water, mountains and flora.
7979

lib/town_generator.rb

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#
99
# Generates building tile items using Poisson Disk Sampling for the given tiles
1010
# Roads are generated between the buildings and between towns using A* pathfinding
11+
# and a minimum tree spanning algorithm
1112
#
1213
class TownGenerator
1314
attr_reader :sample_area, :road_generator
@@ -81,25 +82,66 @@ def generate_points_for_town(num_of_points, radius, intial_coordinates)
8182
end
8283

8384
def generate_town_roads(points, town_num, verbose)
84-
# TODO: slow, bad (complete graph) will update to use minimum tree spanning algorithm instead
8585
puts "generating town #{town_num} roads..." if verbose
8686

87+
generate_roads_from_connected_pairs(build_minimum_spanning_tree(points, populate_distances_between_each_point(points)))
88+
end
89+
90+
def generate_roads_from_connected_pairs(connected_pairs)
91+
connected_pairs.each do |edge|
92+
road_to_building_one = place_in_front_or_behind(edge.first)
93+
road_to_building_two = place_in_front_or_behind(edge.last)
94+
95+
next if road_to_building_one.nil? || road_to_building_two.nil?
96+
97+
road_generator.generate_roads_from_coordinate_list(road_to_building_one.concat(road_to_building_two), false)
98+
end
99+
end
100+
101+
def build_minimum_spanning_tree(points, distances)
87102
connected_pairs = Set.new
103+
visited = Set.new([points.first]) # Create a set to keep track of visited nodes
104+
until visited.size == points.size
105+
edge = find_minimum_edge(distances, visited)
106+
connected_pairs.add(edge)
107+
visited.add(edge.last)
108+
end
109+
connected_pairs
110+
end
111+
112+
def populate_distances_between_each_point(points)
113+
distances = {}
88114
points.each_with_index do |point_one, idx_one|
89115
points[idx_one + 1..].each do |point_two|
90-
next if connected_pairs.include?([point_one, point_two]) || connected_pairs.include?([point_two, point_one])
116+
distance = calculate_distance(point_one, point_two)
117+
distances[[point_one, point_two]] = distance
118+
distances[[point_two, point_one]] = distance
119+
end
120+
end
121+
distances
122+
end
91123

92-
road_to_building_one = place_in_front_or_behind(point_one)
93-
road_to_building_two = place_in_front_or_behind(point_two)
124+
def calculate_distance(point1, point2)
125+
Math.sqrt((point1.y - point2.y)**2 + (point1.x - point2.x)**2)
126+
end
94127

95-
connected_pairs.add([point_one, point_two])
96-
connected_pairs.add([point_two, point_one])
128+
def find_minimum_edge(distances, visited)
129+
# method to find the minimum edge connecting visited and unvisited nodes
130+
min_edge = nil
131+
min_distance = Float::INFINITY
97132

98-
next if road_to_building_one.nil? || road_to_building_two.nil?
133+
visited.each do |visited_node|
134+
distances.each do |edge, distance|
135+
next if visited.include?(edge.last) || edge.first != visited_node
99136

100-
road_generator.generate_roads_from_coordinate_list(road_to_building_one.concat(road_to_building_two), false)
137+
if distance < min_distance
138+
min_distance = distance
139+
min_edge = edge
140+
end
101141
end
102142
end
143+
144+
min_edge
103145
end
104146

105147
def place_in_front_or_behind(point)
@@ -116,24 +158,8 @@ def generate_roads_between_towns(verbose)
116158

117159
puts 'generating roads between towns...' if verbose
118160

119-
connected_pairs = Set.new
120-
town_centroids = {}
121-
122-
@all_town_points.each_with_index do |town_one, idx_one|
123-
find_town_centroid(town_one)
124-
125-
@all_town_points[idx_one + 1..].each do |town_two|
126-
next if connected_pairs.include?([town_one, town_two]) || connected_pairs.include?([town_two, town_one])
127-
128-
town_one_center_x, town_one_center_y = (town_centroids[town_one] ||= find_town_centroid(town_one))
129-
town_two_center_x, town_two_center_y = (town_centroids[town_two] ||= find_town_centroid(town_two))
130-
131-
road_generator.generate_roads_from_coordinate_list([town_one_center_x, town_one_center_y, town_two_center_x, town_two_center_y], false)
132-
133-
connected_pairs.add([town_one, town_two])
134-
connected_pairs.add([town_two, town_one])
135-
end
136-
end
161+
centroids = @all_town_points.map { |town_points| find_town_centroid(town_points) }
162+
generate_roads_from_connected_pairs(build_minimum_spanning_tree(centroids, populate_distances_between_each_point(centroids)))
137163
end
138164

139165
def find_town_centroid(points)
@@ -149,6 +175,6 @@ def find_town_centroid(points)
149175
average_x = total_x / num_coordinates.to_f
150176
average_y = total_y / num_coordinates.to_f
151177

152-
[average_x, average_y]
178+
OpenStruct.new(x: average_x, y: average_y)
153179
end
154180
end

0 commit comments

Comments
 (0)