Skip to content

Commit 5f1aa1f

Browse files
committed
town road generation w/ tree spanning alg
1 parent e77e64c commit 5f1aa1f

File tree

1 file changed

+53
-27
lines changed

1 file changed

+53
-27
lines changed

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)