astar_visualizer 0.0.1

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.
@@ -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: []