tiny_astar 1.0.0

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/tiny_astar.rb +194 -0
  3. metadata +68 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 45461643b6c4fcc65ae63da7423e0c5b7c0fdd47af0955615927ad7b6743a8e1
4
+ data.tar.gz: 45ef38b717733eaef15384c08e70d021a1c292e5cf1546234172e9ad8665999a
5
+ SHA512:
6
+ metadata.gz: c14338375413d8edd1fd3ac5f49a689546b75b5a3175a9716eb53fd2f53ee12a63db081c813a1872edaecf63b0e7cd11b0411dbe4c9c7c6ef8e7a0357f89b89d
7
+ data.tar.gz: 06f6fc592bbb8864c311b9b47c8d3c1e5a058f0154aefa56c848c11ebc3751dfabfd248b490f391bf5c2a7b7c0b95b827b94316c43b23baa6c482905e05c200f
data/lib/tiny_astar.rb ADDED
@@ -0,0 +1,194 @@
1
+ require 'time'
2
+ require 'pry'
3
+ require 'pry-nav'
4
+
5
+ # ex:
6
+ # 's' is the starting node
7
+ # 'f' is the finishing node
8
+ # 'X' are nodes that are impassable
9
+ # map = [
10
+ # [s ],
11
+ # [ X ],
12
+ # [ XX ],
13
+ # [ ],
14
+ # [ f]
15
+ # ]
16
+ # start = [0, 0]
17
+ # finish = [4, 4]
18
+ #
19
+ # path = TinyAstar.path_for(map: map, start: start, finish: finish)
20
+ #
21
+ # => ... and '.' are the steps from 's' to 'f'
22
+ # map = [
23
+ # [s ],
24
+ # [. X ],
25
+ # [.XX ],
26
+ # [. ],
27
+ # [ ...f]
28
+ # ]
29
+ module TinyAstar
30
+ Node = Struct.new(:parent, :x, :y, :f, :g)
31
+
32
+ attr_reader :path # when pathfinding is complete, this attribute is either:
33
+ # - empty, meaning no path was found
34
+ # - non-empty, meaning a path was found
35
+
36
+
37
+ # map: a 2d boolean array representing the map with passable and impassable
38
+ # nodes
39
+ # - passable nodes are `true`
40
+ # - impassable nodes are `false`
41
+ # start: the coordinates (x, y) of the starting node
42
+ # finish: the coordinates (x, y) of the finishing node
43
+ def self.path_for(map:, start:, finish:)
44
+ width = map.first.length
45
+ height = map.length
46
+
47
+ visited = map.map{|row| row.map{|cell| !cell } }
48
+
49
+ pending_nodes = [
50
+ Node.new(
51
+ parent: nil,
52
+ x: start[0],
53
+ y: start[1],
54
+ f: _pythag_distance(start, finish),
55
+ g: 0
56
+ )
57
+ ]
58
+
59
+ path = []
60
+ solution_node = loop do
61
+ curr_node = pending_nodes.shift
62
+ break unless curr_node
63
+ break curr_node if [curr_node.x, curr_node.y] == finish
64
+
65
+ visited[curr_node.y][curr_node.x] = true
66
+
67
+ [
68
+ # N node
69
+ Node.new(
70
+ parent: curr_node,
71
+ x: curr_node.x,
72
+ y: curr_node.y - 1,
73
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
74
+ g: curr_node.g + 1
75
+ ),
76
+ # NE node
77
+ Node.new(
78
+ parent: curr_node,
79
+ x: curr_node.x + 1,
80
+ y: curr_node.y - 1,
81
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
82
+ g: curr_node.g + 1.414
83
+ ),
84
+ # E node
85
+ Node.new(
86
+ parent: curr_node,
87
+ x: curr_node.x + 1,
88
+ y: curr_node.y,
89
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
90
+ g: curr_node.g + 1
91
+ ),
92
+ # SE node
93
+ Node.new(
94
+ parent: curr_node,
95
+ x: curr_node.x + 1,
96
+ y: curr_node.y + 1,
97
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
98
+ g: curr_node.g + 1.414
99
+ ),
100
+ # S node
101
+ Node.new(
102
+ parent: curr_node,
103
+ x: curr_node.x,
104
+ y: curr_node.y + 1,
105
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
106
+ g: curr_node.g + 1
107
+ ),
108
+ # SW node
109
+ Node.new(
110
+ parent: curr_node,
111
+ x: curr_node.x - 1,
112
+ y: curr_node.y + 1,
113
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
114
+ g: curr_node.g + 1.414
115
+ ),
116
+ # W node
117
+ Node.new(
118
+ parent: curr_node,
119
+ x: curr_node.x - 1,
120
+ y: curr_node.y,
121
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
122
+ g: curr_node.g + 1
123
+ ),
124
+ # NW node
125
+ Node.new(
126
+ parent: curr_node,
127
+ x: curr_node.x - 1,
128
+ y: curr_node.y - 1,
129
+ f: _pythag_distance([curr_node.x, curr_node.y], finish) + curr_node.g,
130
+ g: curr_node.g + 1.414
131
+ )
132
+ ].each do |candidate_node|
133
+ # out-of-bounds checks
134
+ next if candidate_node.x < 0
135
+ next if candidate_node.y < 0
136
+ next if candidate_node.x >= width
137
+ next if candidate_node.y >= height
138
+
139
+ next if visited[candidate_node.y][candidate_node.x] # skip if we've already been there
140
+ next unless map[candidate_node.y][candidate_node.x] # skip if impassable
141
+
142
+ insert_index = pending_nodes.index(pending_nodes.detect{|n| n.f > candidate_node.f }) || pending_nodes.length
143
+ pending_nodes.insert(insert_index, candidate_node)
144
+ end
145
+ end
146
+
147
+ loop do
148
+ break unless solution_node
149
+ path.unshift(solution_node)
150
+ solution_node = solution_node.parent
151
+ end
152
+
153
+ path
154
+ end
155
+
156
+ # calculates a^2 + b^2, where a and b are (x, y) coordinates
157
+ def self._pythag_distance(a, b)
158
+ (
159
+ (b[0] - a[0]).abs**2
160
+ ) + (
161
+ (b[1] - a[1]).abs**2
162
+ )
163
+ end
164
+
165
+ def self._to_s(map, start, finish, path)
166
+ s = ''
167
+
168
+ s << '-' * map[0].length * 3
169
+ s << "\n"
170
+ map.each_with_index do |row, y|
171
+ row.each_with_index do |cell, x|
172
+ if start == [x, y]
173
+ s << 'SSS'
174
+ elsif finish == [x, y]
175
+ s << 'FFF'
176
+ elsif (path.last || start) == ([x, y])
177
+ s << 'xxx'
178
+ elsif map[y][x]
179
+ s << ' '
180
+ else
181
+ s << '███'
182
+ end
183
+ end
184
+ s << "\n"
185
+ end
186
+ s << '-' * map[0].length * 3
187
+ s << "\n"
188
+
189
+ s << "w, h: #{@map[0].length}, #{@map.length}\n"
190
+ s << "heur: #{_pythagorean_square_distance(@path_stack.last || @start, @finish)}"
191
+
192
+ s
193
+ end
194
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tiny_astar
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Lunt
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: pry
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: pry-nav
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ description: a tiny a* pathfinder
41
+ email: jefflunt@gmail.com
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - lib/tiny_astar.rb
47
+ homepage: https://github.com/jefflunt/tiny_astar
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.6.9
66
+ specification_version: 4
67
+ summary: a tiny a* pathfinder
68
+ test_files: []