aboutsummaryrefslogtreecommitdiff
path: root/lib/path.rb
blob: 365df9c5a173fc0490e8b702c8ade4306a2ade48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
class Node
  O_cost = 1000
  D_cost = 1414

  def initialize(x, y, sx, sy, tx, ty, w, h, g = 0, parent = nil)
    @x = x
    @y = y
    @sx = sx
    @sy = sy
    @tx = tx
    @ty = ty
    @w = w
    @h = h
    @g = g
    @parent = parent
  end

  attr_reader :x, :y
  attr_accessor :g, :parent

  def h
    dx = (@x - @tx).abs
    dy = (@y - @ty).abs

    if dy > dx then
      D_cost * dx + (O_cost * (dy - dx))
    else
      D_cost * dy + (O_cost * (dx - dy))
    end
  end

  def f
    g + h
  end

  def start?
    @x == @sx && @y == @sy
  end

  def target?
    @x == @tx && @y == @ty
  end

  def ==(oth)
    @x == oth.x && @y == oth.y
  end

  def neighbours
    ns = []
    ns << neighbour(@x-1, @y-1, true ) if @x > 0      && @y > 0
    ns << neighbour(@x-1, @y  , false) if @x > 0
    ns << neighbour(@x-1, @y+1, true ) if @x > 0      && @y < (@h-1)
    ns << neighbour(@x  , @y+1, false) if                @y < (@h-1)
    ns << neighbour(@x+1, @y+1, true ) if @x < (@w-1) && @y < (@h-1)
    ns << neighbour(@x+1, @y  , false) if @x < (@w-1)
    ns << neighbour(@x+1, @y-1, true ) if @x < (@w-1) && @y > 0
    ns << neighbour(@x  , @y-1, false) if                @y > 0
    ns
  end

  private
  def neighbour(x, y, d)
    Node.new(x, y, @sx, @sy, @tx, @ty, @w, @h, @g + (d ? D_cost : O_cost), self)
  end
end

class Path
  def initialize(maze)
    @maze = maze
    @path = []
    @pathvalid = false
  end

  def valid?
    @pathvalid
  end

  def generate_astar
    open = []
    closed = []

    open << Node.new(@maze.start[0], @maze.start[1],
                     @maze.start[0], @maze.start[1],
                     @maze.target[0], @maze.target[1],
                     @maze.width, @maze.height)
    loop do
      break if open.empty?

      open.sort! { |a, b| a.f <=> b.f }
      current = open.shift
      closed << current

      if current.target? then
        n = current.parent
        until n.start?
          @path.unshift([n.x, n.y])
          n = n.parent
        end
        @pathvalid = true
        break
      end

      current.neighbours.each do |n|
        next if !traversable(current.x, current.y, n.x, n.y) || closed.include?(n)

        if open.include?(n) then
          ni = open.index(n)
          m = open[ni]
          if n.f < m.f then
            m.g = n.g
            m.parent = current
          end
        else
          open << n unless open.include?(n)
        end
      end
    end

    self
  end

  def traversable(sx, sy, tx, ty)
    if sx == tx || sy == ty then
      @maze.walkable(tx, ty)
    else
      @maze.walkable(tx, ty) &&
      (@maze.walkable(sx, ty) || @maze.walkable(tx, sy))
    end
  end

  def pml
    @path.map{|p|p.join(" ")}.join("\n")
  end
end