a-star 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/a-star +15 -0
- data/lib/A_Star.rb +214 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eabf290e90562cb865fd597d9c7a446ccf6a10ad
|
4
|
+
data.tar.gz: 61f5081fdde910f354b26d53dfc0fa7b0479810d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6542dba427725e0a7f01d8c1ff7f7da16a31d38c340ae6ba3fc767714bbc8f8c8f1a76aa744f42e233c906f1853b482da9dd0a65fde093cb164c403dbc8cc75e
|
7
|
+
data.tar.gz: eb8e7625a42a6e7a9e0ce58dd8378d0c19b89d25b59b0edec2180772c1104aff77388853ebe89f427d1529480380baa8ec61e4cc8c67cb308107eaf52720ab6c
|
data/bin/a-star
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'A_Star'
|
3
|
+
|
4
|
+
image = nil
|
5
|
+
begin
|
6
|
+
image = ChunkyPNG::Image.from_file(ARGV[ARGV.length - 1])
|
7
|
+
rescue
|
8
|
+
puts "Please supply correct commandline arguments.\nNo image given or wrong image format, PNG only!"
|
9
|
+
end
|
10
|
+
|
11
|
+
unless image.nil?
|
12
|
+
loc = FromTo.new image
|
13
|
+
init = AStar.new image, loc.findStart, loc.findEnd
|
14
|
+
init.draw
|
15
|
+
end
|
data/lib/A_Star.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
|
3
|
+
class FromTo
|
4
|
+
def initialize image
|
5
|
+
@image = image
|
6
|
+
end
|
7
|
+
|
8
|
+
def findEnd
|
9
|
+
0.upto @image.dimension.width - 1 do |i|
|
10
|
+
red = ChunkyPNG::Color.r(@image[i, @image.dimension.height - 1])
|
11
|
+
green = ChunkyPNG::Color.g(@image[i, @image.dimension.height - 1])
|
12
|
+
blue = ChunkyPNG::Color.b(@image[i, @image.dimension.height - 1])
|
13
|
+
|
14
|
+
if red > 255/2 && green > 255/2 && blue > 255/2
|
15
|
+
return [i, @image.dimension.height - 1]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
return Array.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def findStart
|
22
|
+
0.upto @image.dimension.width - 1 do |i|
|
23
|
+
red = ChunkyPNG::Color.r(@image[i, 0])
|
24
|
+
green = ChunkyPNG::Color.g(@image[i, 0])
|
25
|
+
blue = ChunkyPNG::Color.b(@image[i, 0])
|
26
|
+
|
27
|
+
if red > 255/2 && green > 255/2 && blue > 255/2
|
28
|
+
return [i, 0]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
return Array.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def node x, y, index, cost, destCost, totalCost
|
36
|
+
return [x, y, index, cost, destCost, totalCost]
|
37
|
+
end
|
38
|
+
|
39
|
+
class AStar
|
40
|
+
def initialize maze, start, dest
|
41
|
+
raise "Either start, end or both locations not found!" if start.empty? || dest.empty?
|
42
|
+
@maze = maze
|
43
|
+
@start = start
|
44
|
+
@dest = dest
|
45
|
+
@solvedMaze = @maze
|
46
|
+
mazeName = ARGV[ARGV.length - 1]
|
47
|
+
@mazeLabel = mazeName.split( /()\s|\./)[0]
|
48
|
+
@mazeFileType = "." + mazeName.split(/\s|\./)[1]
|
49
|
+
|
50
|
+
@firstNode = node start[0], start[1], -1, -1, -1, -1 # [x, y, index, startCost, destcost, heuristic]
|
51
|
+
@destNode = node dest[0], dest[1], -1, -1, -1, -1
|
52
|
+
|
53
|
+
@height = maze.dimension.height
|
54
|
+
@width = maze.dimension.width
|
55
|
+
@perimiter = (2 * @width) + (2 * @height)
|
56
|
+
@area = @width * @height
|
57
|
+
@visited = []
|
58
|
+
@unvisited = []
|
59
|
+
@visited << @firstNode
|
60
|
+
end
|
61
|
+
|
62
|
+
def solve
|
63
|
+
|
64
|
+
while not @visited.empty? do
|
65
|
+
minIndex = 0
|
66
|
+
0.upto @visited.length - 1 do |i|
|
67
|
+
if @visited[i][5] < @visited[minIndex][5]
|
68
|
+
minIndex = i
|
69
|
+
end
|
70
|
+
end
|
71
|
+
chosenNode = minIndex
|
72
|
+
|
73
|
+
here = @visited[chosenNode]
|
74
|
+
|
75
|
+
if here[0] == @destNode[0] && here[1] == @destNode[1]
|
76
|
+
path = [@destNode]
|
77
|
+
puts "We're here! Final node at: (x: #{here[0]}, y: #{here[1]})"
|
78
|
+
while here[2] != -1 do
|
79
|
+
here = @unvisited[here[2]]
|
80
|
+
path.unshift here
|
81
|
+
end
|
82
|
+
puts "The entire path from #{@start} to #{@dest} is: \n#{path}"
|
83
|
+
path.each do |arr|
|
84
|
+
@solvedMaze[arr[0], arr[1]] = ChunkyPNG::Color.from_hex "#752bff"
|
85
|
+
end
|
86
|
+
return path
|
87
|
+
end
|
88
|
+
|
89
|
+
@visited.delete_at chosenNode
|
90
|
+
@unvisited << here
|
91
|
+
|
92
|
+
friendNodes = lookAround here
|
93
|
+
0.upto friendNodes.length - 1 do |j|
|
94
|
+
horizontalFriend = friendNodes[j][0]
|
95
|
+
verticalFriend = friendNodes[j][1]
|
96
|
+
|
97
|
+
if passable? horizontalFriend, verticalFriend || (horizontalFriend = @destNode[0] && verticalFriend == @destNode[1])
|
98
|
+
onUnvisited = false
|
99
|
+
0.upto @unvisited.length - 1 do |k|
|
100
|
+
unvisitedNode = @unvisited[k]
|
101
|
+
if horizontalFriend == unvisitedNode[0] && verticalFriend == unvisitedNode[1]
|
102
|
+
onUnvisited = true
|
103
|
+
break
|
104
|
+
end
|
105
|
+
end
|
106
|
+
next if onUnvisited
|
107
|
+
|
108
|
+
onVisited = false
|
109
|
+
0.upto @visited.length - 1 do |k|
|
110
|
+
visitedNode = @visited[k]
|
111
|
+
if horizontalFriend == visitedNode[0] && verticalFriend == visitedNode[1]
|
112
|
+
onVisited = true
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
friendHeuristics = Array.new
|
117
|
+
for k in 0..friendNodes.length - 1 do
|
118
|
+
friendHeuristics << heuristic(friendNodes[k], @dest)
|
119
|
+
end
|
120
|
+
lowestHeuristic = friendHeuristics.min
|
121
|
+
if not onVisited && heuristic(friendNodes[j], @dest) == lowestHeuristic # If you're somwhere new and is fastest
|
122
|
+
newNode = node horizontalFriend, verticalFriend, @unvisited.length - 1, -1, -1, -1
|
123
|
+
newNode[3] = here[3] + cost(here, newNode)
|
124
|
+
newNode[4] = heuristic newNode, @destNode
|
125
|
+
newNode[5] = newNode[3] + newNode[4]
|
126
|
+
|
127
|
+
@visited << newNode
|
128
|
+
#puts "!! New Node at\n(x: " + horizontalFriend.to_s + ", y: " + verticalFriend.to_s + ")"
|
129
|
+
#puts "Destination = " + @destNode[0].to_s + ", " + @destNode[1].to_s
|
130
|
+
# Uncoment below to see unvisited nodes!
|
131
|
+
#@solvedMaze[horizontalFriend, verticalFriend] = ChunkyPNG::Color.from_hex "#999"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
return []
|
137
|
+
end
|
138
|
+
|
139
|
+
def heuristic here, destination
|
140
|
+
return ( Math.sqrt( ((here[0] - destination[0]) ** 2) + ((here[1] - destination[1]) ** 2) ) ).floor
|
141
|
+
end
|
142
|
+
|
143
|
+
def cost here, destination
|
144
|
+
direction = direction here, destination
|
145
|
+
if [2, 4, 6, 8].include? direction
|
146
|
+
return 10
|
147
|
+
end
|
148
|
+
return 14
|
149
|
+
end
|
150
|
+
|
151
|
+
def passable? x, y
|
152
|
+
if (x < 0 || y < 0) || (x > @width - 1 || y > @height - 1)
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
red = ChunkyPNG::Color.r(@maze[x, y])
|
156
|
+
green = ChunkyPNG::Color.g(@maze[x, y])
|
157
|
+
blue = ChunkyPNG::Color.b(@maze[x, y])
|
158
|
+
if red > 255/2 && green > 255/2 && blue > 255/2
|
159
|
+
return true
|
160
|
+
end
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
|
164
|
+
def direction here, destination
|
165
|
+
direction = [ destination[1] - here[1], destination[0] - here[0] ]
|
166
|
+
return case
|
167
|
+
when direction[0] > 0 && direction[1] == 0
|
168
|
+
2 # y-negative, down
|
169
|
+
when direction[1] < 0 && direction[0] == 0
|
170
|
+
4 # x-negative, left
|
171
|
+
when direction[0] < 0 && direction[1] == 0
|
172
|
+
8 # y-positive, up
|
173
|
+
when direction[1] > 0 && direction[0] == 0
|
174
|
+
6 # x-positive, right
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def lookAround here
|
179
|
+
return [
|
180
|
+
[here[0], (here[1] + 1)], # y-positive, up
|
181
|
+
[here[0], (here[1] - 1)], # y-negative, down
|
182
|
+
[(here[0] + 1), here[1]], # x-positive, right
|
183
|
+
[(here[0] - 1), here[1]] # x-negative, left
|
184
|
+
]
|
185
|
+
end
|
186
|
+
|
187
|
+
def draw
|
188
|
+
puts "Solving..."
|
189
|
+
go = Time.new
|
190
|
+
|
191
|
+
path = solve # Here we go
|
192
|
+
unless path.empty?
|
193
|
+
finish = Time.new
|
194
|
+
puts "\n\nTime taken to solve: " + (finish - go).to_s + " seconds!"
|
195
|
+
minutes = ((finish - go) / 60).round
|
196
|
+
if minutes > 0
|
197
|
+
if minutes > 1
|
198
|
+
puts "Circa " + minutes.to_s + " Minutes."
|
199
|
+
else
|
200
|
+
puts "Circa " + minutes.to_s + " Minute."
|
201
|
+
end
|
202
|
+
end
|
203
|
+
else
|
204
|
+
puts "No solution found, solve function returned empty array for path!\nPlease make sure your maze is solvable!"
|
205
|
+
end
|
206
|
+
|
207
|
+
startColour = "#ff3c5e"
|
208
|
+
destColour = "#68ff9f"
|
209
|
+
@solvedMaze[@start[0], @start[1]] = ChunkyPNG::Color.from_hex startColour
|
210
|
+
@solvedMaze[@dest[0], @dest[1]] = ChunkyPNG::Color.from_hex destColour
|
211
|
+
|
212
|
+
@solvedMaze.save(@mazeLabel + "-solved" + @mazeFileType)
|
213
|
+
end
|
214
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: a-star
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Demonstrandum
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A* Search Algorithm for PNG images
|
14
|
+
email: knutsen@jetspace.co
|
15
|
+
executables:
|
16
|
+
- a-star
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- bin/a-star
|
21
|
+
- lib/A_Star.rb
|
22
|
+
homepage: https://github.com/Demonstrandum/A-Star
|
23
|
+
licenses:
|
24
|
+
- GPL-3.0
|
25
|
+
metadata: {}
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 2.6.11
|
43
|
+
signing_key:
|
44
|
+
specification_version: 4
|
45
|
+
summary: A* Path finding
|
46
|
+
test_files: []
|