polaris 0.0.1 → 0.0.2
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.
- data/README.txt +21 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/line_of_site.rb +61 -0
- data/lib/polaris.rb +109 -0
- data/lib/two_d_grid_location.rb +21 -0
- data/lib/two_d_grid_map.rb +81 -0
- data/polaris.gemspec +62 -0
- metadata +16 -6
data/README.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
= polaris
|
2
|
+
|
3
|
+
* http://github.com/shawn42/polaris
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A* pathfinding in Ruby. Pulled out of Gamebox.
|
8
|
+
|
9
|
+
|
10
|
+
== REQUIREMENTS:
|
11
|
+
* algorithms gem
|
12
|
+
|
13
|
+
== INSTALL:
|
14
|
+
|
15
|
+
* gem install poloaris
|
16
|
+
|
17
|
+
== LICENSE:
|
18
|
+
|
19
|
+
(MIT)
|
20
|
+
|
21
|
+
Copyright (c) 2010 Shawn Anderson
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gem|
|
4
|
+
gem.name = "polaris"
|
5
|
+
gem.rubyforge_project = "polaris"
|
6
|
+
gem.summary = %Q{A* pathfinding in ruby.}
|
7
|
+
gem.description = %Q{A* pathfinding in Ruby, using C datastructures to speed things up.}
|
8
|
+
gem.email = "shawn42@gmail.com"
|
9
|
+
gem.homepage = "http://github.com/shawn42/polaris"
|
10
|
+
gem.authors = ["Shawn Anderson"]
|
11
|
+
gem.add_development_dependency "rspec"
|
12
|
+
gem.add_development_dependency "jeweler"
|
13
|
+
gem.add_dependency 'algorithms'
|
14
|
+
gem.test_files = FileList['{spec,test}/**/*.rb']
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
desc "Run all rspecs"
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
24
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
25
|
+
end
|
26
|
+
task :default => :spec
|
27
|
+
|
28
|
+
# vim: syntax=Ruby
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
data/lib/line_of_site.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# LineOfSite is a class for finding neighbors in a map that are visible
|
2
|
+
class LineOfSite
|
3
|
+
def initialize(map)
|
4
|
+
@map = map
|
5
|
+
end
|
6
|
+
|
7
|
+
def losline(x, y, x2, y2)
|
8
|
+
brensenham_line(x, y, x2, y2).each do |i|
|
9
|
+
iterx, itery = *i
|
10
|
+
occ = @map.occupant(loc2(iterx,itery))
|
11
|
+
return unless occ
|
12
|
+
occ.lit = true
|
13
|
+
occ.seen = true
|
14
|
+
|
15
|
+
return if occ.solid?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Brensenham line algorithm
|
20
|
+
def brensenham_line(x,y,x2,y2)
|
21
|
+
steep = false
|
22
|
+
coords = []
|
23
|
+
dx = (x2 - x).abs
|
24
|
+
if (x2 - x) > 0
|
25
|
+
sx = 1
|
26
|
+
else
|
27
|
+
sx = -1
|
28
|
+
end
|
29
|
+
dy = (y2 - y).abs
|
30
|
+
if (y2 - y) > 0
|
31
|
+
sy = 1
|
32
|
+
else
|
33
|
+
sy = -1
|
34
|
+
end
|
35
|
+
if dy > dx
|
36
|
+
steep = true
|
37
|
+
x,y = y,x
|
38
|
+
dx,dy = dy,dx
|
39
|
+
sx,sy = sy,sx
|
40
|
+
end
|
41
|
+
d = (2 * dy) - dx
|
42
|
+
|
43
|
+
dx.times do
|
44
|
+
if steep
|
45
|
+
coords << [y,x]
|
46
|
+
else
|
47
|
+
coords << [x,y]
|
48
|
+
end
|
49
|
+
while d >= 0
|
50
|
+
y = y + sy
|
51
|
+
d = d - (2 * dx)
|
52
|
+
end
|
53
|
+
x = x + sx
|
54
|
+
d = d + (2 * dy)
|
55
|
+
end
|
56
|
+
coords << [x2,y2]
|
57
|
+
|
58
|
+
coords
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/lib/polaris.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'algorithms'
|
2
|
+
include Containers
|
3
|
+
|
4
|
+
# Polaris is a star that guides, aka "The North Star". It implements the A* algorithm.
|
5
|
+
class Polaris
|
6
|
+
attr_reader :nodes_considered
|
7
|
+
|
8
|
+
def initialize(map)
|
9
|
+
@map = map
|
10
|
+
@nodes_considered = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns the path without the from location.
|
14
|
+
# Returns nil if max_depth is hit or if no path exists.
|
15
|
+
def guide(from, to, unit_type=nil, max_depth=400)
|
16
|
+
return nil if @map.blocked?(from, unit_type) || @map.blocked?(to, unit_type)
|
17
|
+
from_element = PathElement.new(from)
|
18
|
+
from_element.dist_from = @map.distance(from,to)
|
19
|
+
open = PriorityQueue.new { |x, y| (x <=> y) == -1 }
|
20
|
+
open.push from_element, from_element.rating
|
21
|
+
closed = SplayTreeMap.new
|
22
|
+
step = 0
|
23
|
+
|
24
|
+
until open.empty? || step > max_depth
|
25
|
+
step += 1
|
26
|
+
|
27
|
+
current_element = open.pop
|
28
|
+
@nodes_considered += 1
|
29
|
+
|
30
|
+
loc = current_element.location
|
31
|
+
if @map.cost(loc,to) == 0
|
32
|
+
path = []
|
33
|
+
until current_element.parent.nil?
|
34
|
+
path.unshift current_element
|
35
|
+
current_element = current_element.parent
|
36
|
+
end
|
37
|
+
|
38
|
+
return path
|
39
|
+
else
|
40
|
+
closed.push current_element.location, current_element
|
41
|
+
@map.neighbors(loc).each do |next_door|
|
42
|
+
el = PathElement.new(next_door,current_element)
|
43
|
+
next if closed.has_key? next_door
|
44
|
+
|
45
|
+
if @map.blocked? next_door, unit_type
|
46
|
+
closed.push el.location, el
|
47
|
+
else
|
48
|
+
current_rating = current_element.cost_to + @map.cost(loc, next_door)
|
49
|
+
|
50
|
+
# add to open
|
51
|
+
el.cost_to = current_rating
|
52
|
+
el.dist_from = @map.distance(next_door,to)
|
53
|
+
|
54
|
+
open.push el, el.rating
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class PathElement
|
64
|
+
include Comparable
|
65
|
+
|
66
|
+
attr_accessor :location, :parent
|
67
|
+
attr_reader :cost_to, :dist_from, :rating
|
68
|
+
def initialize(location=nil,parent=nil)
|
69
|
+
@location = location
|
70
|
+
@parent = parent
|
71
|
+
@cost_to = 0
|
72
|
+
@dist_from = 0
|
73
|
+
@rating = 99_999
|
74
|
+
end
|
75
|
+
|
76
|
+
def cost_to=(new_cost)
|
77
|
+
@cost_to = new_cost
|
78
|
+
reset_rating
|
79
|
+
end
|
80
|
+
|
81
|
+
def dist_from=(new_dist_from)
|
82
|
+
@dist_from = new_dist_from
|
83
|
+
reset_rating
|
84
|
+
end
|
85
|
+
|
86
|
+
def reset_rating
|
87
|
+
@rating = @cost_to + @dist_from
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
"#{@location} at cost of #{@cost_to} and rating of #{@rating}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def <=>(b)
|
95
|
+
a = self
|
96
|
+
if a.rating < b.rating
|
97
|
+
return -1
|
98
|
+
elsif a.rating > b.rating
|
99
|
+
return 1
|
100
|
+
else
|
101
|
+
0
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def ==(other)
|
106
|
+
return false if other.nil?
|
107
|
+
@location == other.location
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# TwoDGridLocation exibits an x,y,cost location
|
2
|
+
class TwoDGridLocation
|
3
|
+
attr_accessor :x,:y
|
4
|
+
def initialize(x,y);@x=x;@y=y;end
|
5
|
+
def ==(other)
|
6
|
+
@x == other.x and @y == other.y
|
7
|
+
end
|
8
|
+
|
9
|
+
def <=>(b)
|
10
|
+
ret = 1
|
11
|
+
if @x == b.x && @y == b.y
|
12
|
+
ret = 0
|
13
|
+
end
|
14
|
+
ret = -1 if @x <= b.x && @y < b.y
|
15
|
+
return ret
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#{@x},#{@y}"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'two_d_grid_location'
|
2
|
+
|
3
|
+
# TwoDGridMap exibits the contract that the map requires.
|
4
|
+
# Works on an X,Y grid that uses Ftors for 2D vectors
|
5
|
+
class TwoDGridMap
|
6
|
+
attr_accessor :w, :h
|
7
|
+
TRAVEL_COST_DIAG = 14
|
8
|
+
TRAVEL_COST_STRAIGHT = 10
|
9
|
+
|
10
|
+
def initialize(w,h)
|
11
|
+
@w = w
|
12
|
+
@h = h
|
13
|
+
@grid = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def size
|
17
|
+
[@w,@h]
|
18
|
+
end
|
19
|
+
|
20
|
+
# place thing at location. If thing is nil, location will be placed in the map
|
21
|
+
def place(location, thing=nil)
|
22
|
+
thing ||= location
|
23
|
+
@grid[location.x] ||= {}
|
24
|
+
@grid[location.x][location.y] = thing
|
25
|
+
end
|
26
|
+
|
27
|
+
def occupant(location)
|
28
|
+
@grid[location.x][location.y] if @grid[location.x]
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear(location)
|
32
|
+
@grid[location.x] ||= {}
|
33
|
+
@grid[location.x][location.y] = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# is the location available for the specified type
|
37
|
+
def blocked?(location, type=nil)
|
38
|
+
return true if type == :blocked
|
39
|
+
return true if location.x >= @w || location.y >= @h || location.x < 0 || location.y < 0
|
40
|
+
if @grid[location.x] and @grid[location.x][location.y]
|
41
|
+
return true
|
42
|
+
else
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns a list of neighbors and their distance
|
48
|
+
def neighbors(location)
|
49
|
+
x = location.x
|
50
|
+
y = location.y
|
51
|
+
[
|
52
|
+
TwoDGridLocation.new(x-1, y-1),
|
53
|
+
TwoDGridLocation.new(x-1, y+1),
|
54
|
+
TwoDGridLocation.new(x+1, y-1),
|
55
|
+
TwoDGridLocation.new(x+1, y+1),
|
56
|
+
TwoDGridLocation.new(x-1, y),
|
57
|
+
TwoDGridLocation.new(x+1, y),
|
58
|
+
TwoDGridLocation.new(x, y-1),
|
59
|
+
TwoDGridLocation.new(x, y+1)
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
def distance(from,to)
|
64
|
+
h_diagonal = [(from.x-to.x).abs, (from.y-to.y).abs].min
|
65
|
+
h_straight = ((from.x-to.x).abs + (from.y-to.y).abs)
|
66
|
+
return TRAVEL_COST_DIAG * h_diagonal + TRAVEL_COST_STRAIGHT * (h_straight - 2*h_diagonal)
|
67
|
+
end
|
68
|
+
|
69
|
+
# return the cost to go from => to (assuming from and to are neighbors)
|
70
|
+
def cost(from, to)
|
71
|
+
if from.x == to.x or from.y == to.y
|
72
|
+
if from.x == to.x and from.y == to.y
|
73
|
+
0
|
74
|
+
else
|
75
|
+
TRAVEL_COST_STRAIGHT
|
76
|
+
end
|
77
|
+
else
|
78
|
+
TRAVEL_COST_DIAG
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/polaris.gemspec
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{polaris}
|
8
|
+
s.version = "0.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Shawn Anderson"]
|
12
|
+
s.date = %q{2010-02-27}
|
13
|
+
s.description = %q{A* pathfinding in Ruby, using C datastructures to speed things up.}
|
14
|
+
s.email = %q{shawn42@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.txt"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"README.txt",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION",
|
22
|
+
"lib/line_of_site.rb",
|
23
|
+
"lib/polaris.rb",
|
24
|
+
"lib/two_d_grid_location.rb",
|
25
|
+
"lib/two_d_grid_map.rb",
|
26
|
+
"polaris.gemspec",
|
27
|
+
"spec/helper.rb",
|
28
|
+
"spec/line_of_site_spec.rb",
|
29
|
+
"spec/polaris_spec.rb"
|
30
|
+
]
|
31
|
+
s.homepage = %q{http://github.com/shawn42/polaris}
|
32
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.rubyforge_project = %q{polaris}
|
35
|
+
s.rubygems_version = %q{1.3.6}
|
36
|
+
s.summary = %q{A* pathfinding in ruby.}
|
37
|
+
s.test_files = [
|
38
|
+
"spec/helper.rb",
|
39
|
+
"spec/line_of_site_spec.rb",
|
40
|
+
"spec/polaris_spec.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
49
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
50
|
+
s.add_runtime_dependency(%q<algorithms>, [">= 0"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
53
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
54
|
+
s.add_dependency(%q<algorithms>, [">= 0"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
58
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
59
|
+
s.add_dependency(%q<algorithms>, [">= 0"])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Shawn Anderson
|
@@ -59,10 +59,20 @@ executables: []
|
|
59
59
|
|
60
60
|
extensions: []
|
61
61
|
|
62
|
-
extra_rdoc_files:
|
63
|
-
|
64
|
-
files:
|
65
|
-
|
62
|
+
extra_rdoc_files:
|
63
|
+
- README.txt
|
64
|
+
files:
|
65
|
+
- README.txt
|
66
|
+
- Rakefile
|
67
|
+
- VERSION
|
68
|
+
- lib/line_of_site.rb
|
69
|
+
- lib/polaris.rb
|
70
|
+
- lib/two_d_grid_location.rb
|
71
|
+
- lib/two_d_grid_map.rb
|
72
|
+
- polaris.gemspec
|
73
|
+
- spec/helper.rb
|
74
|
+
- spec/line_of_site_spec.rb
|
75
|
+
- spec/polaris_spec.rb
|
66
76
|
has_rdoc: true
|
67
77
|
homepage: http://github.com/shawn42/polaris
|
68
78
|
licenses: []
|