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