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.
- checksums.yaml +7 -0
- data/lib/tiny_astar.rb +194 -0
- 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: []
|