polaris 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|