pathfinding 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: 10c0aa6e36e9928d826330a689ad3cf9767d35da2cdbd7549d166d39dd773cae
4
+ data.tar.gz: 0ffca62280a953128a6dce12fcf770327c047d0e34643a69bede0b6fadc8aa96
5
+ SHA512:
6
+ metadata.gz: c2f8885e510048fb258f89001c9a00279180d39fd5b088464f45764660e6e25f4ba0646276d37a9e02551800c8742bef6fa44f279d698ad3a0f32c1e24f82c38
7
+ data.tar.gz: 953698ca4662703997497c94977ce312ed16ebe1a38e06955c4c794d36588a70d2e20ab1e345b04f874ba4508de95a38dd2139260021c3029afc273e0a3e8cab
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,105 @@
1
+ # Pathfinding in Ruby
2
+
3
+ A pathfinding library in Ruby based on A* algorithm.
4
+
5
+ Inpired by [python-pathfinding](https://github.com/brean/python-pathfinding)
6
+
7
+ ## Install
8
+ To install the library, use the `gem` command:
9
+ ```
10
+ gem install pathfinding
11
+ ```
12
+
13
+ ## Usage
14
+ This is a simple example to find a path using A*.
15
+
16
+ 1. Import the library:
17
+ ```ruby
18
+ require 'pathfinding'
19
+ ```
20
+ 2. Create a map using a 2D-list. Any value smaller or equal to 0 describes a walkable node. Any number bigger than 0 describes an obstacle. In this example, we added an obstacle in the middle.
21
+ ```ruby
22
+ matrix = [
23
+ [0, 0, 0],
24
+ [0, 1, 0],
25
+ [0, 0, 0]
26
+ ]
27
+ ```
28
+ Note: you can use negative values to describe different types of obstacles.
29
+
30
+ 3. Create a new grid from this map representation. This will create Nodes instances for every element of the map.
31
+ ```ruby
32
+ grid = Grid.new(matrix)
33
+ ```
34
+
35
+ 4. Set the start and end point from the map. In this example, the start point is on top-left and the end point is on bottom-right.
36
+ ```ruby
37
+ start_node = grid.node(0, 0)
38
+ end_node = grid.node(2, 2)
39
+ ```
40
+
41
+ 5. Create a new instance of the finder and run the `find_path` method. If a path from start to the end point exists, this method returns the list of nodes of the path. Else, it returns an empty list.
42
+ ```ruby
43
+ finder = AStarFinder.new()
44
+ path = finder.find_path(start_node, end_node, grid)
45
+ ```
46
+ Note:
47
+
48
+ - You can choose the heuristic function to use using the `heuristic` argument.
49
+ - You can also choose the diagonal movements allowed using the `diagonal_movement` argument.
50
+ ```ruby
51
+ finder = AStarFinder.new(Heuristic::method(:manhattan), DiagonalMovement::NEVER)
52
+ ```
53
+ See the [documentation](https://www.rubydoc.info/gems/pathfinding/0.0.1) for more details.
54
+
55
+ 6. Print the result (or do something else with it).
56
+ ```ruby
57
+ puts grid.to_s(path, start_node, end_node)
58
+ ```
59
+ The result should look like this:
60
+ ```
61
+ +---+
62
+ |sxx|
63
+ | #x|
64
+ | e|
65
+ +---+
66
+ ```
67
+ * +, - and | characters show the border around the map
68
+ * the blank space is a free field
69
+ * 's' marks the start
70
+ * 'e' marks the end
71
+ * '#' is the obstacle
72
+ * the 'x' characters mark the path from start to end
73
+
74
+ This is the whole example:
75
+ ```ruby
76
+ require 'pathfinding'
77
+
78
+ matrix = [
79
+ [0, 0, 0],
80
+ [0, 1, 0],
81
+ [0, 0, 0]
82
+ ]
83
+ grid = Grid.new(matrix)
84
+
85
+ start_node = grid.node(0, 0)
86
+ end_node = grid.node(2, 2)
87
+
88
+ finder = AStarFinder.new()
89
+ path = finder.find_path(start_node, end_node, grid)
90
+
91
+ puts grid.to_s(path, start_node, end_node)
92
+ ```
93
+
94
+ Take a look at the `examples/` folder for more examples.
95
+
96
+ ## Links
97
+ - GitHub: https://github.com/Quentin18/pathfinding.rb
98
+ - RubyGems: https://rubygems.org/gems/pathfinding
99
+ - Documentation: https://www.rubydoc.info/gems/pathfinding/0.0.1
100
+
101
+ ## Author
102
+ [Quentin Deschamps](mailto:quentindeschamps18@gmail.com)
103
+
104
+ ## License
105
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathfinding/core/diagonal_movement'
4
+ require 'pathfinding/core/grid'
5
+ require 'pathfinding/core/heuristic'
6
+ require 'pathfinding/core/node'
7
+ require 'pathfinding/finder/astar'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: diagonal_movement.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+
9
+ #
10
+ # Constants to set diagonal movements.
11
+ #
12
+ module DiagonalMovement
13
+ # Always accept diagonal movements.
14
+ ALWAYS = 1
15
+
16
+ # Never accept diagonal movements.
17
+ NEVER = 2
18
+
19
+ # Accept a diagonal movement if there is at most one obstacle.
20
+ IF_AT_MOST_ONE_OBSTACLE = 3
21
+
22
+ # Accept a diagonal movement only when there is no obstacle.
23
+ ONLY_WHEN_NO_OBSTACLE = 4
24
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: grid.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+
9
+ require 'pathfinding/core/node'
10
+ require 'pathfinding/core/diagonal_movement'
11
+
12
+ #
13
+ # Represents the grid as a 2d-list of nodes.
14
+ #
15
+ class Grid
16
+ include Enumerable
17
+
18
+ #
19
+ # Creates a grid from a matrix:
20
+ # * 0 (or less) represents a walkable node
21
+ # * A number greater than 0 does not represents a walkable node
22
+ # The +width+ represents the number of columns whereas the +height+
23
+ # is the number of rows of the grid. The +node+ attribute
24
+ # is the list of nodes.
25
+ #
26
+ def initialize(matrix)
27
+ @height = matrix.length
28
+ @width = matrix[0].length
29
+ @nodes = Grid.build_nodes(@width, @height, matrix)
30
+ end
31
+
32
+ #
33
+ # Gets the node at position (+x+, +y+).
34
+ #
35
+ def node(x, y)
36
+ @nodes[y][x]
37
+ end
38
+
39
+ #
40
+ # Yields all nodes of the grid.
41
+ #
42
+ def each_node
43
+ @height.times do |y|
44
+ @width.times do |x|
45
+ yield node(x, y)
46
+ end
47
+ end
48
+ end
49
+
50
+ #
51
+ # Creates a printable string from the grid using ASCII characters.
52
+ # Params:
53
+ # +path+:: list of nodes that show the path
54
+ # +start_node+:: start node
55
+ # +end_node+:: end node
56
+ # +border+:: create a border around the grid
57
+ # +start_chr+:: character for the start (default "s")
58
+ # +end_chr+:: character for the end (default "e")
59
+ # +path_chr+:: character for the path (default "x")
60
+ # +empty_chr+:: character for the empty fields (default " ")
61
+ # +block_chr+:: character for the blocking elements (default "#")
62
+ #
63
+ def to_s(
64
+ path = nil, start_node = nil, end_node = nil, border = true,
65
+ start_chr = 's', end_chr = 'e', path_chr = 'x', empty_chr = ' ', block_chr = '#'
66
+ )
67
+ data = []
68
+ data << '+' + '-' * @width + '+' if border
69
+ @height.times do |y|
70
+ line = ''
71
+ line += '|' if border
72
+ @width.times do |x|
73
+ current = node(x, y)
74
+ if current == start_node
75
+ line += start_chr
76
+ elsif current == end_node
77
+ line += end_chr
78
+ elsif path&.include?(current)
79
+ line += path_chr
80
+ elsif current.walkable
81
+ line += empty_chr
82
+ else
83
+ line += block_chr
84
+ end
85
+ end
86
+ line += '|' if border
87
+ data << line
88
+ end
89
+ data << '+' + '-' * @width + '+' if border
90
+ data.join("\n")
91
+ end
92
+
93
+ #
94
+ # Returns if the (+x+, +y+) position is in the grid.
95
+ #
96
+ def inside?(x, y)
97
+ x >= 0 && x < @width && y >= 0 && y < @height
98
+ end
99
+
100
+ #
101
+ # Returns if a node at position (+x+, +y+) is walkable.
102
+ #
103
+ def walkable?(x, y)
104
+ inside?(x, y) && node(x, y).walkable
105
+ end
106
+
107
+ #
108
+ # Get all neighbors of a node.
109
+ #
110
+ def neighbors(node, diagonal_movement=DiagonalMovement::NEVER)
111
+ x = node.x
112
+ y = node.y
113
+ neighbors = []
114
+ s0 = d0 = s1 = d1 = s2 = d2 = s3 = d3 = false
115
+
116
+ # ↑
117
+ if walkable?(x, y - 1)
118
+ neighbors << node(x, y - 1)
119
+ s0 = true
120
+ end
121
+
122
+ # →
123
+ if walkable?(x + 1, y)
124
+ neighbors << node(x + 1, y)
125
+ s1 = true
126
+ end
127
+
128
+ # ↓
129
+ if walkable?(x, y + 1)
130
+ neighbors << node(x, y + 1)
131
+ s2 = true
132
+ end
133
+
134
+ # ←
135
+ if walkable?(x - 1, y)
136
+ neighbors << node(x - 1, y)
137
+ s3 = true
138
+ end
139
+
140
+ return neighbors if diagonal_movement == DiagonalMovement::NEVER
141
+
142
+ if diagonal_movement == DiagonalMovement::ONLY_WHEN_NO_OBSTACLE
143
+ d0 = s3 && s0
144
+ d1 = s0 && s1
145
+ d2 = s1 && s2
146
+ d3 = s2 && s3
147
+ elsif diagonal_movement == DiagonalMovement::IF_AT_MOST_ONE_OBSTACLE
148
+ d0 = s3 || s0
149
+ d1 = s0 || s1
150
+ d2 = s1 || s2
151
+ d3 = s2 || s3
152
+ elsif diagonal_movement == DiagonalMovement::ALWAYS
153
+ d0 = d1 = d2 = d3 = true
154
+ else
155
+ raise 'Incorrect value of diagonal_movement'
156
+ end
157
+
158
+ # ↖
159
+ neighbors << node(x - 1, y - 1) if d0 && walkable?(x - 1, y - 1)
160
+
161
+ # ↗
162
+ neighbors << node(x + 1, y - 1) if d1 && walkable?(x + 1, y - 1)
163
+
164
+ # ↘
165
+ neighbors << node(x + 1, y + 1) if d2 && walkable?(x + 1, y + 1)
166
+
167
+ # ↙
168
+ neighbors << node(x - 1, y + 1) if d3 && walkable?(x - 1, y + 1)
169
+
170
+ neighbors
171
+ end
172
+
173
+ #
174
+ # Builds and returns the nodes.
175
+ #
176
+ def self.build_nodes(width, height, matrix)
177
+ nodes = []
178
+ height.times do |y|
179
+ nodes << []
180
+ width.times do |x|
181
+ walkable = matrix[y][x] <= 0
182
+ nodes[y] << Node.new(x, y, walkable)
183
+ end
184
+ end
185
+ nodes
186
+ end
187
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: heuristic.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+
9
+ #
10
+ # A collection of heuristic functions.
11
+ #
12
+ module Heuristic
13
+ #
14
+ # Manhattan distance.
15
+ #
16
+ def self.manhattan(dx, dy)
17
+ dx + dy
18
+ end
19
+
20
+ #
21
+ # Euclidean distance.
22
+ #
23
+ def self.euclidean(dx, dy)
24
+ Math.sqrt(dx * dx + dy * dy)
25
+ end
26
+
27
+ #
28
+ # Octile distance.
29
+ #
30
+ def self.octile(dx, dy)
31
+ f = Math.sqrt(2) - 1
32
+ dx < dy ? f * dx + dy : f * dy + dx
33
+ end
34
+
35
+ #
36
+ # Chebyshev distance.
37
+ #
38
+ def self.chebyshev(dx, dy)
39
+ [dx, dy].max
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: node.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+
9
+ #
10
+ # Represents a node in the grid.
11
+ #
12
+ class Node
13
+ # Gets the x coordinate in the grid.
14
+ attr_reader :x
15
+
16
+ # Gets the y coordinate in the grid.
17
+ attr_reader :y
18
+
19
+ # Gets whether the node is walkable.
20
+ attr_reader :walkable
21
+
22
+ #
23
+ # Creates a node.
24
+ #
25
+ def initialize(x, y, walkable = true)
26
+ @x = x
27
+ @y = y
28
+ @walkable = walkable
29
+ end
30
+
31
+ #
32
+ # Makes the string format of a node.
33
+ #
34
+ def to_s
35
+ "(#{@x}, #{@y})"
36
+ end
37
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # File: astar.rb
5
+ # Author: Quentin Deschamps
6
+ # Date: August 2020
7
+ #
8
+ require 'pathfinding/core/grid'
9
+ require 'pathfinding/core/diagonal_movement'
10
+ require 'pathfinding/core/heuristic'
11
+
12
+ #
13
+ # A* path-finder.
14
+ #
15
+ class AStarFinder
16
+ #
17
+ # Initializes the A* path-finder. Params:
18
+ # * +heuristic+: heuristic function (see the +Heuristic+ module)
19
+ # * +diagonal_movement+: set diagonal movements (see the +DiagonalMovement+ module)
20
+ #
21
+ def initialize(
22
+ heuristic = Heuristic::method(:manhattan),
23
+ diagonal_movement = DiagonalMovement::NEVER
24
+ )
25
+ @diagonal_movement = diagonal_movement
26
+ if diagonal_movement == DiagonalMovement::NEVER
27
+ @heuristic = heuristic
28
+ else
29
+ @heuristic = Heuristic::method(:octile)
30
+ end
31
+ end
32
+
33
+ #
34
+ # Finds and returns the path as a list of node objects.
35
+ #
36
+ def find_path(start_node, end_node, grid)
37
+ open_set = [start_node]
38
+ came_from = {}
39
+ g_score = {}
40
+ f_score = {}
41
+ grid.each_node do |node|
42
+ g_score[node] = Float::INFINITY
43
+ f_score[node] = Float::INFINITY
44
+ end
45
+ g_score[start_node] = 0
46
+ f_score[start_node] = @heuristic.call(
47
+ (start_node.x - end_node.x).abs, (start_node.y - end_node.y).abs)
48
+
49
+ until open_set.empty?
50
+ current = open_set[0]
51
+ open_set.each do |node|
52
+ current = node if f_score[node] < f_score[current]
53
+ end
54
+
55
+ if current == end_node
56
+ return reconstruct_path(came_from, current)
57
+ end
58
+
59
+ current = open_set.delete_at(open_set.index(current))
60
+
61
+ grid.neighbors(current, @diagonal_movement).each do |neighbor|
62
+ tentative_g_score = g_score[current] + d(current, neighbor)
63
+ next if tentative_g_score >= g_score[neighbor]
64
+
65
+ came_from[neighbor] = current
66
+ g_score[neighbor] = tentative_g_score
67
+ f_score[neighbor] = g_score[neighbor] + @heuristic.call(
68
+ (neighbor.x - end_node.x).abs, (neighbor.y - end_node.y).abs)
69
+ unless open_set.include?(neighbor)
70
+ open_set << neighbor
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ #
77
+ # Returns the distance between two nodes.
78
+ #
79
+ def d(n1, n2)
80
+ (n1.x == n2.x || n1.y == n2.y) ? 1 : Math.sqrt(2)
81
+ end
82
+
83
+ #
84
+ # Reconstructs the path from the current node.
85
+ #
86
+ def reconstruct_path(came_from, current)
87
+ total_path = [current]
88
+ while came_from.include?(current)
89
+ current = came_from[current]
90
+ total_path << current
91
+ end
92
+ total_path.reverse
93
+ end
94
+ end
@@ -0,0 +1,32 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'pathfinding'
3
+ s.version = '0.0.1'
4
+ s.license = 'MIT'
5
+ s.summary = 'A pathfinding library based on A* algorithm.'
6
+ s.description = <<-DESCRIPTION
7
+ A pathfinding library based on A* algorithm.
8
+ Different options about heuristic and diagonal movements can be used.
9
+ The grid can be printed in the command prompt.
10
+ DESCRIPTION
11
+ s.author = 'Quentin Deschamps'
12
+ s.email = 'quentindeschamps18@gmail.com'
13
+ s.homepage = 'https://github.com/Quentin18/pathfinding.rb'
14
+
15
+ s.require_paths = ['lib']
16
+ s.files = [
17
+ 'lib/pathfinding.rb',
18
+ 'lib/pathfinding/core/diagonal_movement.rb',
19
+ 'lib/pathfinding/core/grid.rb',
20
+ 'lib/pathfinding/core/heuristic.rb',
21
+ 'lib/pathfinding/core/node.rb',
22
+ 'lib/pathfinding/finder/astar.rb',
23
+ 'LICENSE',
24
+ 'README.md',
25
+ 'pathfinding.gemspec'
26
+ ]
27
+
28
+ s.rdoc_options = ['--main', 'README.md']
29
+ s.extra_rdoc_files = ['LICENSE', 'README.md']
30
+
31
+ s.post_install_message = 'Thanks for installing!'
32
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pathfinding
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-18 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ A pathfinding library based on A* algorithm.
15
+ Different options about heuristic and diagonal movements can be used.
16
+ The grid can be printed in the command prompt.
17
+ email: quentindeschamps18@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files:
21
+ - LICENSE
22
+ - README.md
23
+ files:
24
+ - LICENSE
25
+ - README.md
26
+ - lib/pathfinding.rb
27
+ - lib/pathfinding/core/diagonal_movement.rb
28
+ - lib/pathfinding/core/grid.rb
29
+ - lib/pathfinding/core/heuristic.rb
30
+ - lib/pathfinding/core/node.rb
31
+ - lib/pathfinding/finder/astar.rb
32
+ - pathfinding.gemspec
33
+ homepage: https://github.com/Quentin18/pathfinding.rb
34
+ licenses:
35
+ - MIT
36
+ metadata: {}
37
+ post_install_message: Thanks for installing!
38
+ rdoc_options:
39
+ - "--main"
40
+ - README.md
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.0.3
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: A pathfinding library based on A* algorithm.
58
+ test_files: []