astar_visualizer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 39837dbd735e9048db8a9e1f5a919722b8e6c8acadeee1dd5c40f6bfca756e86
4
+ data.tar.gz: eca57add44720b859d36dcfd56c632ccf1599286b421306fadacbe3271389ea1
5
+ SHA512:
6
+ metadata.gz: ce8f2847926c6776c061a2092e661af8f715fc7ac4c14403b9db1cdd609a4878735254f44c98087eb82f5d940bf4a843ea45225a8b34bc75842e90e03cee5c01
7
+ data.tar.gz: b1ec1e53196d19fe2cb57c9528d9ab9c4f9cd8aff9bbfb29aa4cd18ec5185f8b69f9b2ada4c272e37fcd45e68812f595866a4f2795584d4092a99229a3caaa69
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Quentin DESCHAMPS
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.
@@ -0,0 +1,52 @@
1
+ # A* Visualizer
2
+
3
+ **A* Visualizer** is an interactive application to visualize the
4
+ [A* pathfinding algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm)
5
+ in a grid with obstacles. The heuristic function used is the
6
+ [Manhattan distance](https://en.wikipedia.org/wiki/Taxicab_geometry).
7
+
8
+ It uses the [Gosu](https://github.com/gosu/gosu) game development library.
9
+
10
+ ![Demo](https://github.com/Quentin18/astar-visualizer/blob/master/img/demo.gif)
11
+
12
+ ## Install
13
+ To install this ruby gem, use the `gem` command:
14
+ ```
15
+ gem install astar_visualizer
16
+ ```
17
+
18
+ ## Usage
19
+ To launch the A* Visualizer, use this command:
20
+ ```
21
+ astar-visualizer
22
+ ```
23
+
24
+ You can also use the `irb` environment:
25
+ ```ruby
26
+ require 'astar_visualizer'
27
+ AStar.new.show
28
+ ```
29
+
30
+ You can also choose the size of the grid:
31
+ ```
32
+ astar-visualizer SIZE
33
+ ```
34
+ SIZE must be a number between 10 and 100 (default: 50).
35
+
36
+ It will open a window with the grid. Then:
37
+
38
+ 1. Left click on a node to choose the start node.
39
+ 2. Left click on another node to choose the end node.
40
+ 3. Left click on nodes to put obstacles. Right click on them if you want to remove them.
41
+ 4. Press *ENTER* to launch the A* algorithm. If a path is found, the path is colored in yellow and the visited nodes in cyan.
42
+ 5. Press *SUPPR* to clear the window.
43
+
44
+ ## Links
45
+ - GitHub: https://github.com/Quentin18/astar-visualizer
46
+ - RubyGems: https://rubygems.org/gems/astar_visualizer
47
+
48
+ ## Author
49
+ [Quentin Deschamps](mailto:quentindeschamps18@gmail.com)
50
+
51
+ ## License
52
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -0,0 +1,35 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'astar_visualizer'
3
+ s.version = '0.0.1'
4
+ s.license = 'MIT'
5
+ s.summary = 'A* pathfinding visualizer using Gosu'
6
+ s.description = <<-DESCRIPTION
7
+ A* Visualizer is an interactive application to visualize
8
+ the A* pathfinding algorithm in a grid with obstacles.
9
+ It uses the Gosu game development library.
10
+ DESCRIPTION
11
+ s.author = 'Quentin Deschamps'
12
+ s.email = 'quentindeschamps18@gmail.com'
13
+ s.homepage = 'https://github.com/Quentin18/astar-visualizer'
14
+
15
+ s.add_runtime_dependency 'gosu', '~> 0.15.2'
16
+ s.require_paths = ['lib']
17
+ s.files = [
18
+ 'bin/astar-visualizer',
19
+ 'lib/astar_visualizer.rb',
20
+ 'lib/astar_visualizer/astar.rb',
21
+ 'lib/astar_visualizer/grid.rb',
22
+ 'lib/astar_visualizer/node.rb',
23
+ 'LICENSE',
24
+ 'README.md',
25
+ 'astar_visualizer.gemspec'
26
+ ]
27
+
28
+ s.bindir = 'bin'
29
+ s.executables << 'astar-visualizer'
30
+
31
+ s.rdoc_options = ['--main', 'README.md']
32
+ s.extra_rdoc_files = ['LICENSE', 'README.md']
33
+
34
+ s.post_install_message = 'Thanks for installing! Run this command: astar-visualizer'
35
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'astar_visualizer'
4
+ MIN_SIZE = 10
5
+ MAX_SIZE = 100
6
+
7
+ if ARGV.empty?
8
+ window = AStar.new
9
+ else
10
+ size = ARGV.first.to_i
11
+ if size < MIN_SIZE || size > MAX_SIZE
12
+ puts "Please choose a size between #{MIN_SIZE} and #{MAX_SIZE}."
13
+ return
14
+ end
15
+ window = AStar.new(size)
16
+ end
17
+
18
+ window.show
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'astar_visualizer/astar'
4
+ require 'astar_visualizer/grid'
5
+ require 'astar_visualizer/node'
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: astar.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+
9
+ require 'gosu'
10
+ require 'astar_visualizer/grid'
11
+
12
+ #
13
+ # The AStar class manages the window with the grid and can launch
14
+ # the A* algorithm.
15
+ #
16
+ class AStar < Gosu::Window
17
+ # Size of the grid in pixels.
18
+ SIZE_GRID = 700
19
+
20
+ # Height of the informations block in pixels.
21
+ HEIGHT_INFO_BLOCK = 40
22
+
23
+ # Number of squares by lines and columns in the grid.
24
+ DEFAULT_SIZE = 50
25
+
26
+ #
27
+ # Creates the window with its grid.
28
+ #
29
+ def initialize(size=DEFAULT_SIZE)
30
+ super(SIZE_GRID, SIZE_GRID + HEIGHT_INFO_BLOCK, false)
31
+ self.caption = 'A* Pathfinding Visualizer'
32
+ @font = Gosu::Font.new(28)
33
+ @message = 'Choose the start node.'
34
+ @grid = Grid.new(self, size, size, SIZE_GRID)
35
+ @start = @end = nil
36
+ @needs_reset = false
37
+ end
38
+
39
+ #
40
+ # To show the mouse cursor on the window.
41
+ #
42
+ def needs_cursor?
43
+ true
44
+ end
45
+
46
+ #
47
+ # Returns if the A* algorithm can be launched.
48
+ #
49
+ def ready?
50
+ !@needs_reset && @start && @end
51
+ end
52
+
53
+ #
54
+ # Resets the start node.
55
+ #
56
+ def reset_start!
57
+ @start = nil
58
+ end
59
+
60
+ #
61
+ # Resets the end node.
62
+ #
63
+ def reset_end!
64
+ @end = nil
65
+ end
66
+
67
+ #
68
+ # Resets the window.
69
+ #
70
+ def reset!
71
+ reset_start!
72
+ reset_end!
73
+ @grid.reset!
74
+ @needs_reset = false
75
+ end
76
+
77
+ #
78
+ # Gets the button down. Two different actions:
79
+ #
80
+ # * *ENTER*: launch A* algorithm
81
+ # * *SUPPR*: clear window
82
+ #
83
+ def button_down(id)
84
+ # ENTER: launch A* algorithm
85
+ if id == Gosu::KbReturn && ready?
86
+ @grid.update_neighbors
87
+ a_star
88
+ @needs_reset = true
89
+ end
90
+
91
+ # SUPPR: clear window
92
+ reset! if id == Gosu::KbDelete
93
+ end
94
+
95
+ #
96
+ # Finds the node in the grid corresponding to the mouse position.
97
+ #
98
+ def find_node
99
+ @grid.find_node(self.mouse_x, self.mouse_y)
100
+ end
101
+
102
+ #
103
+ # Updates the window. If the mouse buttons are used, it updates nodes and
104
+ # the message in the informations block.
105
+ #
106
+ def update
107
+ return if @needs_reset
108
+
109
+ # Message
110
+ if !@start
111
+ @message = 'Choose the start node.'
112
+ elsif !@end
113
+ @message = 'Choose the end node.'
114
+ else
115
+ @message = 'Add obstacles and press ENTER.'
116
+ end
117
+
118
+ if Gosu.button_down? Gosu::MsLeft
119
+ node = find_node
120
+ if node
121
+ # Make start node
122
+ if !@start && node != @end
123
+ node.start!
124
+ @start = node
125
+ # Make end node
126
+ elsif !@end && node != @start
127
+ node.end!
128
+ @end = node
129
+ # Make obstacle
130
+ elsif node != @start && node != @end
131
+ node.obstacle!
132
+ end
133
+ end
134
+ end
135
+
136
+ # Clear a node
137
+ if Gosu.button_down? Gosu::MsRight
138
+ node = find_node
139
+ if node
140
+ node.reset!
141
+ if node == @start
142
+ reset_start!
143
+ elsif node == @end
144
+ reset_end!
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ #
151
+ # Draws the grid and the informations block.
152
+ #
153
+ def draw
154
+ @grid.draw
155
+ show_info
156
+ end
157
+
158
+ #
159
+ # Shows the informations block.
160
+ #
161
+ def show_info
162
+ @font.draw_text(@message, 10, SIZE_GRID + 8, 5)
163
+ end
164
+
165
+ #
166
+ # Reconstructs the path found by coloring the nodes.
167
+ #
168
+ def reconstruct_path(came_from, current)
169
+ while came_from.include?(current)
170
+ current = came_from[current]
171
+ current.path!
172
+ end
173
+ @start.start!
174
+ @end.end!
175
+ end
176
+
177
+ #
178
+ # Launchs the A* algorithm.
179
+ #
180
+ def a_star
181
+ open_set = [@start]
182
+ came_from = {}
183
+ g_score = {}
184
+ f_score = {}
185
+ @grid.each_node do |node|
186
+ g_score[node] = Float::INFINITY
187
+ f_score[node] = Float::INFINITY
188
+ end
189
+ g_score[@start] = 0
190
+ f_score[@start] = h(@start)
191
+
192
+ until open_set.empty?
193
+ current = open_set[0]
194
+ open_set.each do |node|
195
+ current = node if f_score[node] < f_score[current]
196
+ end
197
+
198
+ if current == @end
199
+ reconstruct_path(came_from, current)
200
+ @message = 'Path found! Press SUPPR to clear the window.'
201
+ return true
202
+ end
203
+
204
+ current = open_set.delete_at(open_set.index(current))
205
+
206
+ current.neighbors.each do |neighbor|
207
+ tentative_g_score = g_score[current] + 1
208
+ next if tentative_g_score >= g_score[neighbor]
209
+
210
+ came_from[neighbor] = current
211
+ g_score[neighbor] = tentative_g_score
212
+ f_score[neighbor] = g_score[neighbor] + h(neighbor)
213
+ unless open_set.include?(neighbor)
214
+ open_set << neighbor
215
+ neighbor.open!
216
+ end
217
+ end
218
+
219
+ current.closed! if current != @start
220
+ end
221
+ @message = 'No path found! Press SUPPR to clear the window.'
222
+ false
223
+ end
224
+
225
+ #
226
+ # Heuristic function used : Manhattan distance.
227
+ #
228
+ def h(node)
229
+ (node.x - @end.x).abs + (node.y - @end.y).abs
230
+ end
231
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: grid.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+
9
+ require 'gosu'
10
+ require 'astar_visualizer/node'
11
+
12
+ #
13
+ # The Grid class represents the grid. It is composed of nodes.
14
+ #
15
+ class Grid
16
+ #
17
+ # Builds the nodes of the grid. It returns the list of nodes.
18
+ #
19
+ def self.build_nodes(window, cols, rows, width, height)
20
+ nodes = []
21
+ rows.times do |y|
22
+ nodes << []
23
+ cols.times do |x|
24
+ nodes[y] << Node.new(window, x, y, width, height)
25
+ end
26
+ end
27
+ nodes
28
+ end
29
+
30
+ #
31
+ # Creates the grid with the nodes.
32
+ #
33
+ def initialize(window, cols, rows, size)
34
+ @window = window
35
+ @cols = cols
36
+ @rows = rows
37
+ @width = size / cols # width of a node's square
38
+ @height = size / rows # height of a node's square
39
+ @nodes = Grid.build_nodes(window, cols, rows, @width, @height)
40
+ @color = Gosu::Color.argb(0xaad3d3d3)
41
+ end
42
+
43
+ #
44
+ # Returns a node from the grid.
45
+ #
46
+ def node(x, y)
47
+ @nodes[y][x]
48
+ end
49
+
50
+ #
51
+ # Yields all nodes of the grid.
52
+ #
53
+ def each_node
54
+ @rows.times do |y|
55
+ @cols.times do |x|
56
+ yield node(x, y)
57
+ end
58
+ end
59
+ end
60
+
61
+ #
62
+ # Returns if the (x, y) position is in the grid.
63
+ #
64
+ def inside?(x, y)
65
+ x >= 0 && x < @cols && y >= 0 && y < @rows
66
+ end
67
+
68
+ #
69
+ # Returns if a node is walkable.
70
+ #
71
+ def walkable?(x, y)
72
+ inside?(x, y) && !node(x, y).obstacle?
73
+ end
74
+
75
+ #
76
+ # Updates the neighbors of all nodes.
77
+ #
78
+ def update_neighbors
79
+ each_node do |node|
80
+ x = node.x
81
+ y = node.y
82
+ node.neighbors.clear
83
+ node.add_to_neighbors(node(x, y - 1)) if walkable?(x, y - 1) # ↑
84
+ node.add_to_neighbors(node(x + 1, y)) if walkable?(x + 1, y) # →
85
+ node.add_to_neighbors(node(x, y + 1)) if walkable?(x, y + 1) # ↓
86
+ node.add_to_neighbors(node(x - 1, y)) if walkable?(x - 1, y) # ←
87
+ end
88
+ end
89
+
90
+ #
91
+ # Resets all the nodes.
92
+ #
93
+ def reset!
94
+ each_node(&:reset!)
95
+ end
96
+
97
+ #
98
+ # Draws the lines of the grid.
99
+ #
100
+ def draw_grid
101
+ @rows.times do |i| # horizontal
102
+ x1 = 0
103
+ y1 = @height * i
104
+ x2 = @width * @cols
105
+ y2 = @height * i
106
+ @window.draw_line(x1, y1, @color, x2, y2, @color)
107
+ end
108
+ @cols.times do |i| # vertical
109
+ x1 = @width * i
110
+ y1 = 0
111
+ x2 = @width * i
112
+ y2 = @height * @rows
113
+ @window.draw_line(x1, y1, @color, x2, y2, @color)
114
+ end
115
+ end
116
+
117
+ #
118
+ # Draws the nodes and the grid.
119
+ #
120
+ def draw
121
+ each_node(&:draw)
122
+ draw_grid
123
+ end
124
+
125
+ #
126
+ # Finds a node in the grid using mouse position.
127
+ #
128
+ def find_node(mouse_x, mouse_y)
129
+ each_node do |node|
130
+ return node if node.inside?(mouse_x, mouse_y)
131
+ end
132
+ nil
133
+ end
134
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: node.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+
9
+ require 'gosu'
10
+
11
+ #
12
+ # The Node class represents the squares of the grid.
13
+ #
14
+ class Node
15
+ # Gets the x position in the grid.
16
+ attr_reader :x
17
+
18
+ # Gets the y position in the grid.
19
+ attr_reader :y
20
+
21
+ # Gets the neighbors (list of nodes).
22
+ attr_reader :neighbors
23
+
24
+ #
25
+ # Creates a node. It is composed of the (x, y) position in the grid and the
26
+ # neighbors (list of nodes).
27
+ #
28
+ def initialize(window, x, y, width, height)
29
+ @@colors ||= {
30
+ green: Gosu::Color.argb(0xff00ff00),
31
+ red: Gosu::Color.argb(0xffff0000),
32
+ grey: Gosu::Color.argb(0xff808080),
33
+ lightcyan: Gosu::Color.argb(0xffe0ffff),
34
+ cyan: Gosu::Color.argb(0xff00ffff),
35
+ white: Gosu::Color.argb(0xffffffff),
36
+ yellow: Gosu::Color.argb(0xffffff00),
37
+ }
38
+ @@window ||= window
39
+ @x = x
40
+ @y = y
41
+ @width = width
42
+ @height = height
43
+ @color = @@colors[:white]
44
+ @neighbors = []
45
+ end
46
+
47
+ #
48
+ # Returns if the node is the start point (green color).
49
+ #
50
+ def start?
51
+ @color == @@colors[:green]
52
+ end
53
+
54
+ #
55
+ # Makes a node the start point (green color).
56
+ #
57
+ def start!
58
+ @color = @@colors[:green]
59
+ end
60
+
61
+ #
62
+ # Returns if the node is the end point (red color).
63
+ #
64
+ def end?
65
+ @color == @@colors[:red]
66
+ end
67
+
68
+ #
69
+ # Makes a node the end point (red color).
70
+ #
71
+ def end!
72
+ @color = @@colors[:red]
73
+ end
74
+
75
+ #
76
+ # Returns if the node is an obstacle (grey color).
77
+ #
78
+ def obstacle?
79
+ @color == @@colors[:grey]
80
+ end
81
+
82
+ #
83
+ # Makes a node an obstacle (grey color).
84
+ #
85
+ def obstacle!
86
+ @color = @@colors[:grey]
87
+ end
88
+
89
+ #
90
+ # Returns if the node is in the open list (cyan color).
91
+ #
92
+ def open?
93
+ @color == @@colors[:cyan]
94
+ end
95
+
96
+ #
97
+ # Makes a node like in the open list (cyan color).
98
+ #
99
+ def open!
100
+ @color = @@colors[:cyan]
101
+ end
102
+
103
+ #
104
+ # Returns if the node is in the closed list (lightcyan color).
105
+ #
106
+ def closed?
107
+ @color == @@colors[:lightcyan]
108
+ end
109
+
110
+ #
111
+ # Makes a node like in the closed list (lightcyan color).
112
+ #
113
+ def closed!
114
+ @color = @@colors[:lightcyan]
115
+ end
116
+
117
+ #
118
+ # Resets a node (white color).
119
+ #
120
+ def reset!
121
+ @color = @@colors[:white]
122
+ end
123
+
124
+ #
125
+ # Makes a node in the found path (yellow color).
126
+ #
127
+ def path!
128
+ @color = @@colors[:yellow]
129
+ end
130
+
131
+ #
132
+ # Draws the square.
133
+ #
134
+ def draw
135
+ @@window.draw_rect(@x * @width, @y * @height, @width, @height, @color)
136
+ end
137
+
138
+ #
139
+ # Returns if the mouse position is in the square.
140
+ #
141
+ def inside?(mouse_x, mouse_y)
142
+ pos_x = @x * @width
143
+ pos_y = @y * @height
144
+ mouse_x >= pos_x && mouse_x <= pos_x + @width && \
145
+ mouse_y >= pos_y && mouse_y <= pos_y + @height
146
+ end
147
+
148
+ #
149
+ # Adds a node to the neighbors list.
150
+ #
151
+ def add_to_neighbors(node)
152
+ @neighbors << node
153
+ end
154
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: astar_visualizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Quentin Deschamps
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gosu
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.15.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.15.2
27
+ description: |2
28
+ A* Visualizer is an interactive application to visualize
29
+ the A* pathfinding algorithm in a grid with obstacles.
30
+ It uses the Gosu game development library.
31
+ email: quentindeschamps18@gmail.com
32
+ executables:
33
+ - astar-visualizer
34
+ extensions: []
35
+ extra_rdoc_files:
36
+ - LICENSE
37
+ - README.md
38
+ files:
39
+ - LICENSE
40
+ - README.md
41
+ - astar_visualizer.gemspec
42
+ - bin/astar-visualizer
43
+ - lib/astar_visualizer.rb
44
+ - lib/astar_visualizer/astar.rb
45
+ - lib/astar_visualizer/grid.rb
46
+ - lib/astar_visualizer/node.rb
47
+ homepage: https://github.com/Quentin18/astar-visualizer
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message: 'Thanks for installing! Run this command: astar-visualizer'
52
+ rdoc_options:
53
+ - "--main"
54
+ - README.md
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 3.0.3
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: A* pathfinding visualizer using Gosu
72
+ test_files: []