ifmapper 1.0.0 → 1.0.6
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/HISTORY.txt +648 -627
- data/IFMapper.gemspec +29 -28
- data/IFMapper.rbw +31 -31
- data/TODO.txt +8 -7
- data/bin/IFMapper +31 -31
- data/docs/en/index.html +0 -0
- data/docs/en/start.html +3 -2
- data/docs/en/start.html~ +516 -0
- data/docs/es/index.html +0 -0
- data/docs/es/start.html +13 -14
- data/docs/es/start.html~ +1280 -0
- data/docs/images/IFMapper_main.gif +0 -0
- data/docs/images/automap.gif +0 -0
- data/docs/images/complex_connection.gif +0 -0
- data/docs/images/connection.gif +0 -0
- data/docs/images/connection_menu.gif +0 -0
- data/docs/images/room_description.gif +0 -0
- data/docs/images/room_small.gif +0 -0
- data/icons/copy.png +0 -0
- data/icons/cut.png +0 -0
- data/icons/filenew.png +0 -0
- data/icons/fileopen.png +0 -0
- data/icons/filesave.png +0 -0
- data/icons/filesaveas.png +0 -0
- data/icons/help.png +0 -0
- data/icons/kill.png +0 -0
- data/icons/nextpage.png +0 -0
- data/icons/paste.png +0 -0
- data/icons/prevpage.png +0 -0
- data/icons/printicon.png +0 -0
- data/icons/redo.png +0 -0
- data/icons/room_e.gif +0 -0
- data/icons/room_e.xpm +0 -0
- data/icons/room_n.gif +0 -0
- data/icons/room_n.xpm +0 -0
- data/icons/room_ne.gif +0 -0
- data/icons/room_ne.xpm +0 -0
- data/icons/room_nw.gif +0 -0
- data/icons/room_nw.xpm +0 -0
- data/icons/room_s.gif +0 -0
- data/icons/room_s.xpm +0 -0
- data/icons/room_se.gif +0 -0
- data/icons/room_se.xpm +0 -0
- data/icons/room_sw.gif +0 -0
- data/icons/room_sw.xpm +0 -0
- data/icons/room_w.gif +0 -0
- data/icons/room_w.xpm +0 -0
- data/icons/saveas.png +0 -0
- data/icons/undo.png +0 -0
- data/icons/winapp.png +0 -0
- data/icons/zoom.png +0 -0
- data/lib/IFMapper/AStar.rb +250 -250
- data/lib/IFMapper/Connection.rb +202 -202
- data/lib/IFMapper/FXAboutDialogBox.rb +32 -32
- data/lib/IFMapper/FXConnection.rb +364 -364
- data/lib/IFMapper/FXConnectionDialogBox.rb +124 -124
- data/lib/IFMapper/FXDCPostscript.rb +404 -404
- data/lib/IFMapper/FXDCPrint.rb +15 -15
- data/lib/IFMapper/FXItemList.rb +108 -0
- data/lib/IFMapper/FXMap.rb +2147 -2116
- data/lib/IFMapper/FXMapColorBox.rb +88 -88
- data/lib/IFMapper/FXMapDialogBox.rb +127 -127
- data/lib/IFMapper/FXMapFileDialog.rb +34 -34
- data/lib/IFMapper/FXMapperSettings.rb +206 -205
- data/lib/IFMapper/FXMapperWindow.rb +1592 -1571
- data/lib/IFMapper/FXPDFMapExporterOptionsDialogBox.rb +46 -0
- data/lib/IFMapper/FXRoom.rb +263 -263
- data/lib/IFMapper/FXRoomDialogBox.rb +159 -159
- data/lib/IFMapper/FXRoomList.rb +95 -95
- data/lib/IFMapper/FXSearchDialogBox.rb +51 -51
- data/lib/IFMapper/FXSection.rb +33 -33
- data/lib/IFMapper/FXSectionDialogBox.rb +38 -38
- data/lib/IFMapper/FXSpline.rb +52 -52
- data/lib/IFMapper/FXWarningBox.rb +51 -50
- data/lib/IFMapper/GUEReader.rb +445 -445
- data/lib/IFMapper/IFMReader.rb +584 -584
- data/lib/IFMapper/IFMWriter.rb +245 -227
- data/lib/IFMapper/Inform7Writer.rb +579 -573
- data/lib/IFMapper/InformReader.rb +478 -478
- data/lib/IFMapper/InformWriter.rb +364 -359
- data/lib/IFMapper/Map.rb +202 -200
- data/lib/IFMapper/MapPrinting.rb +162 -162
- data/lib/IFMapper/MapReader.rb +900 -900
- data/lib/IFMapper/PDFMapExporter.rb +526 -483
- data/lib/IFMapper/Room.rb +153 -151
- data/lib/IFMapper/Section.rb +234 -234
- data/lib/IFMapper/TADSReader.rb +474 -471
- data/lib/IFMapper/TADSWriter.rb +375 -370
- data/lib/IFMapper/TranscriptDialogBox.rb +0 -0
- data/lib/IFMapper/TranscriptReader.rb +1361 -1359
- data/lib/IFMapper/locales/en/Messages.rb +446 -435
- data/lib/IFMapper/locales/es/Messages.rb +451 -440
- data/lib/IFMapper/locales/es/Messages_iso-8859-1.rb +455 -440
- data/lib/IFMapper/locales/es/runme.sh +3 -3
- data/maps/A New Life.map b/data/maps/A New → Life.map +0 -0
- data/maps/AMFV.map +0 -0
- data/maps/AllRoads.map +0 -0
- data/maps/Aotearoa.map +0 -0
- data/maps/Bronze.map +0 -0
- data/maps/Bureaucracy.ifm +0 -0
- data/maps/Bureaucracy.map +0 -0
- data/maps/CityOfSecrets.map +0 -0
- data/maps/DDIV.map +0 -0
- data/maps/Following_A_Star.map +0 -0
- data/maps/Heated.map +0 -0
- data/maps/Heroine.map +0 -0
- data/maps/History Repeating.map b/data/maps/History → Repeating.map +0 -0
- data/maps/Hollywood_Hijinx.ifm +0 -0
- data/maps/Janitor.map +0 -0
- data/maps/Jigsaw.ifm +0 -0
- data/maps/Jigsaw.map +0 -0
- data/maps/LGOP.ifm +0 -0
- data/maps/Mercy.ifm +0 -0
- data/maps/Ninjas_Fate.map +0 -0
- data/maps/Pen_and_Paint.map +0 -0
- data/maps/Planetfall.ifm +0 -0
- data/maps/Planetfall.map +0 -0
- data/maps/Plundered_Hearts.ifm +0 -0
- data/maps/QuietEvening.map +0 -0
- data/maps/Ralph.ifm +0 -0
- data/maps/Reliques_of_Tolti_Alph.map +0 -0
- data/maps/Revolution.map +0 -0
- data/maps/Robots_of_Dawn.ifm +0 -0
- data/maps/SavoirFare.map +0 -0
- data/maps/Seastalker.ifm +0 -0
- data/maps/Seastalker.map +0 -0
- data/maps/Sherlock.ifm +0 -0
- data/maps/SoFar.ifm +0 -0
- data/maps/Starcross.ifm +0 -0
- data/maps/Suspended.ifm +0 -0
- data/maps/Tangle.map +0 -0
- data/maps/The_Lost_Sheep.map +0 -0
- data/maps/Unforgotten.map +0 -0
- data/maps/Warbler's Nest.map +0 -0
- data/maps/Warbler's_Nest.map +0 -0
- data/maps/Westminster_Abbey.map +0 -0
- data/maps/WinterWonderland.map +0 -0
- data/maps/Wishbringer.ifm +0 -0
- data/maps/Wishbringer2.ifm +0 -0
- data/maps/Zork1.ifm +0 -0
- data/maps/Zork2.ifm +0 -0
- data/maps/Zork3.ifm +0 -0
- data/maps/Zork_Zero.ifm +0 -0
- data/maps/anchor.ifm +0 -0
- data/maps/anchor.map +0 -0
- data/maps/atrox.ifm +0 -0
- data/maps/awaken.ifm +0 -0
- data/maps/babel.ifm +0 -0
- data/maps/balances.map +0 -0
- data/maps/ballerina.map +0 -0
- data/maps/bear.map +0 -0
- data/maps/bluechairs.map +0 -0
- data/maps/break_in.map +0 -0
- data/maps/bse.ifm +0 -0
- data/maps/building.map +0 -0
- data/maps/change.ifm +0 -0
- data/maps/christminster.map +0 -0
- data/maps/curses.ifm +0 -0
- data/maps/curves.ifm +0 -0
- data/maps/deadline.map +0 -0
- data/maps/delusions.map +0 -0
- data/maps/devours.map +0 -0
- data/maps/distress.map +0 -0
- data/maps/djinni.map +0 -0
- data/maps/dreamhold.map +0 -0
- data/maps/drift3.map +0 -0
- data/maps/eas.map +0 -0
- data/maps/eas2.map +0 -0
- data/maps/eas3.map +0 -0
- data/maps/edifice.ifm +0 -0
- data/maps/fallacy.map +0 -0
- data/maps/frozen.ifm +0 -0
- data/maps/gamlet.map +0 -0
- data/maps/glow.ifm +0 -0
- data/maps/guilty_bastards.map +0 -0
- data/maps/heist.map +0 -0
- data/maps/heroes.map +0 -0
- data/maps/inhumane.map +0 -0
- data/maps/kaged.map +0 -0
- data/maps/library.ifm +0 -0
- data/maps/lurkinghorror.map +0 -0
- data/maps/metamorphoses.map +0 -0
- data/maps/mindelec.ifm +0 -0
- data/maps/minster.ifm +0 -0
- data/maps/mite.map +0 -0
- data/maps/moonmist.map +0 -0
- data/maps/muldoon_legacy.map +0 -0
- data/maps/muse.ifm +0 -0
- data/maps/paperchase.ifm +0 -0
- data/maps/party.map +0 -0
- data/maps/pawn.map +0 -0
- data/maps/photograph.map +0 -0
- data/maps/pkgirl.map +0 -0
- data/maps/pytho.map +0 -0
- data/maps/risorgimento.map +0 -0
- data/maps/sherbet.map +0 -0
- data/maps/simple.map +0 -0
- data/maps/slouch.map +0 -0
- data/maps/space_st.ifm +0 -0
- data/maps/splashdown.map +0 -0
- data/maps/spring.map +0 -0
- data/maps/squarecircle.map +0 -0
- data/maps/stationfall.ifm +0 -0
- data/maps/theatre.ifm +0 -0
- data/maps/toonesia.ifm +0 -0
- data/maps/tortoise.ifm +0 -0
- data/maps/trinity.map +0 -0
- data/maps/vespers.map +0 -0
- data/maps/vgame.ifm +0 -0
- data/maps/wasp.map +0 -0
- data/maps/weather.ifm +0 -0
- data/maps/windhall.ifm +0 -0
- data/maps/worlds.map +0 -0
- data/maps/xtcontest.map +0 -0
- data/maps/zdungeon.map +0 -0
- data/maps/zebulon.ifm +0 -0
- data/maps/zerosum.map +0 -0
- metadata +226 -183
|
File without changes
|
data/docs/images/automap.gif
CHANGED
|
File without changes
|
|
File without changes
|
data/docs/images/connection.gif
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/docs/images/room_small.gif
CHANGED
|
File without changes
|
data/icons/copy.png
CHANGED
|
File without changes
|
data/icons/cut.png
CHANGED
|
File without changes
|
data/icons/filenew.png
CHANGED
|
File without changes
|
data/icons/fileopen.png
CHANGED
|
File without changes
|
data/icons/filesave.png
CHANGED
|
File without changes
|
data/icons/filesaveas.png
CHANGED
|
File without changes
|
data/icons/help.png
CHANGED
|
File without changes
|
data/icons/kill.png
CHANGED
|
File without changes
|
data/icons/nextpage.png
CHANGED
|
File without changes
|
data/icons/paste.png
CHANGED
|
File without changes
|
data/icons/prevpage.png
CHANGED
|
File without changes
|
data/icons/printicon.png
CHANGED
|
File without changes
|
data/icons/redo.png
CHANGED
|
File without changes
|
data/icons/room_e.gif
CHANGED
|
File without changes
|
data/icons/room_e.xpm
CHANGED
|
File without changes
|
data/icons/room_n.gif
CHANGED
|
File without changes
|
data/icons/room_n.xpm
CHANGED
|
File without changes
|
data/icons/room_ne.gif
CHANGED
|
File without changes
|
data/icons/room_ne.xpm
CHANGED
|
File without changes
|
data/icons/room_nw.gif
CHANGED
|
File without changes
|
data/icons/room_nw.xpm
CHANGED
|
File without changes
|
data/icons/room_s.gif
CHANGED
|
File without changes
|
data/icons/room_s.xpm
CHANGED
|
File without changes
|
data/icons/room_se.gif
CHANGED
|
File without changes
|
data/icons/room_se.xpm
CHANGED
|
File without changes
|
data/icons/room_sw.gif
CHANGED
|
File without changes
|
data/icons/room_sw.xpm
CHANGED
|
File without changes
|
data/icons/room_w.gif
CHANGED
|
File without changes
|
data/icons/room_w.xpm
CHANGED
|
File without changes
|
data/icons/saveas.png
CHANGED
|
File without changes
|
data/icons/undo.png
CHANGED
|
File without changes
|
data/icons/winapp.png
CHANGED
|
File without changes
|
data/icons/zoom.png
CHANGED
|
File without changes
|
data/lib/IFMapper/AStar.rb
CHANGED
|
@@ -1,250 +1,250 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
# A class used for path finding.
|
|
6
|
-
# This is largely based on C++ code by Justin Heyes-Jones, albeit simplified
|
|
7
|
-
#
|
|
8
|
-
class AStar
|
|
9
|
-
|
|
10
|
-
attr :start, :goal
|
|
11
|
-
attr_reader :state
|
|
12
|
-
attr :successors
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
SEARCH_STATE_NOT_INITIALIZED = 0
|
|
17
|
-
SEARCH_STATE_SEARCHING = 1
|
|
18
|
-
SEARCH_STATE_SUCCEEDED = 2
|
|
19
|
-
SEARCH_STATE_FAILED = 3
|
|
20
|
-
|
|
21
|
-
def open_list
|
|
22
|
-
puts "Open List:"
|
|
23
|
-
@open_list.each { |n|
|
|
24
|
-
p n
|
|
25
|
-
}
|
|
26
|
-
puts "Closed List # of nodes: #{@open_list.size}"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def closed_list
|
|
30
|
-
puts "Closed List:"
|
|
31
|
-
@closed_list.each { |n|
|
|
32
|
-
p n
|
|
33
|
-
}
|
|
34
|
-
puts "Closed List # of nodes: #{@closed_list.size}"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class Node
|
|
39
|
-
attr_accessor :parent, :child
|
|
40
|
-
attr_accessor :h, :g, :f
|
|
41
|
-
|
|
42
|
-
attr_reader :info
|
|
43
|
-
|
|
44
|
-
def <=>(b)
|
|
45
|
-
return -1 if @f > b.f
|
|
46
|
-
return 0 if @f == b.f
|
|
47
|
-
return 1
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def inspect
|
|
51
|
-
"AStarNode: #{@f} = #{@g} + #{@h} #{@info.inspect}"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def initialize( info = nil )
|
|
55
|
-
@h = @g = @f = 0.0
|
|
56
|
-
@info = info
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# sets start and end goals and initializes A* search
|
|
61
|
-
def goals( start, goal )
|
|
62
|
-
@start = Node.new( start )
|
|
63
|
-
@start.g = 0.0
|
|
64
|
-
@start.h = start.distance_estimate( goal )
|
|
65
|
-
@start.f = @start.h
|
|
66
|
-
|
|
67
|
-
@open_list = []
|
|
68
|
-
@open_list.push( @start )
|
|
69
|
-
@closed_list = []
|
|
70
|
-
|
|
71
|
-
@goal = Node.new( goal )
|
|
72
|
-
|
|
73
|
-
@state = SEARCH_STATE_SEARCHING
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Advances one search step
|
|
77
|
-
def search_step
|
|
78
|
-
if @open_list.empty?
|
|
79
|
-
@state = SEARCH_STATE_FAILED
|
|
80
|
-
return @state
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Pop the best node (the one with the lowest f)
|
|
84
|
-
n = @open_list.pop
|
|
85
|
-
|
|
86
|
-
# Check for the goal. Once we pop that, we are done
|
|
87
|
-
if n.info.is_goal?( @goal.info )
|
|
88
|
-
# The user is going to use the Goal Node he passed in
|
|
89
|
-
# so copy the parent pointer of n
|
|
90
|
-
@goal.parent = n.parent
|
|
91
|
-
# Special case is that the goal was passed in as the start state
|
|
92
|
-
# so handle that here
|
|
93
|
-
if n != @start
|
|
94
|
-
child = @goal
|
|
95
|
-
parent = @goal.parent
|
|
96
|
-
|
|
97
|
-
while child != @start
|
|
98
|
-
parent.child = child
|
|
99
|
-
child = parent
|
|
100
|
-
parent = parent.parent
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
@state = SEARCH_STATE_SUCCEEDED
|
|
104
|
-
return @state
|
|
105
|
-
else
|
|
106
|
-
# Not goal, get successors
|
|
107
|
-
n.info.successors( self, n.parent )
|
|
108
|
-
@successors.each do |s|
|
|
109
|
-
newg = n.g + n.info.cost( s.info )
|
|
110
|
-
|
|
111
|
-
# Now, we need to find out if this node is on the open or close lists
|
|
112
|
-
# If it is, but the node that is already on them is better (lower g)
|
|
113
|
-
# then we can forget about this successor
|
|
114
|
-
open = @open_list.find { |e| e.info.is_same?( s.info ) }
|
|
115
|
-
# State in open list is cheaper than this successor
|
|
116
|
-
next if open and open.g <= newg
|
|
117
|
-
|
|
118
|
-
closed = @closed_list.find { |e| e.info.is_same?( s.info ) }
|
|
119
|
-
# We found a cheaper state in closed list
|
|
120
|
-
next if closed and closed.g <= newg
|
|
121
|
-
|
|
122
|
-
# This node is the best node so far so let's keep it and set up
|
|
123
|
-
# its A* specific data
|
|
124
|
-
s.parent = n
|
|
125
|
-
s.g = newg
|
|
126
|
-
s.h = s.info.distance_estimate( @goal.info )
|
|
127
|
-
s.f = s.g + s.h
|
|
128
|
-
|
|
129
|
-
# Remove succesor from closed list if it was on it
|
|
130
|
-
@closed_list.delete(closed) if closed
|
|
131
|
-
# Change succesor from open list if it was on it
|
|
132
|
-
if open
|
|
133
|
-
open.parent = s.parent
|
|
134
|
-
open.g = s.g
|
|
135
|
-
open.h = s.h
|
|
136
|
-
open.f = s.f
|
|
137
|
-
else
|
|
138
|
-
@open_list.push(s)
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
# Make sure open list stays sorted based on f
|
|
142
|
-
@open_list.sort!
|
|
143
|
-
|
|
144
|
-
@closed_list.push(n)
|
|
145
|
-
@successors.clear
|
|
146
|
-
end
|
|
147
|
-
return @state
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def add_successor( info )
|
|
151
|
-
@successors << Node.new(info)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# return solution as an path (ie. an array of [x,y] coordinates)
|
|
155
|
-
def path
|
|
156
|
-
p = []
|
|
157
|
-
curr = @start
|
|
158
|
-
while curr
|
|
159
|
-
p << [curr.info.x, curr.info.y]
|
|
160
|
-
curr = curr.child
|
|
161
|
-
end
|
|
162
|
-
return p
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def initialize
|
|
166
|
-
@state = SEARCH_STATE_NOT_INITIALIZED
|
|
167
|
-
@successors = []
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
#
|
|
173
|
-
# Simple class used as an adapter for our FXMap<->AStar
|
|
174
|
-
#
|
|
175
|
-
class MapNode
|
|
176
|
-
attr_reader :x, :y
|
|
177
|
-
@@pmap = nil
|
|
178
|
-
|
|
179
|
-
def self.map(pmap)
|
|
180
|
-
@@pmap = pmap
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def initialize(x, y)
|
|
184
|
-
@x = x
|
|
185
|
-
@y = y
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def is_same?(node)
|
|
189
|
-
return true if node and @x == node.x and @y == node.y
|
|
190
|
-
return false
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def is_goal?( goal )
|
|
194
|
-
return true if @x == goal.x and @y == goal.y
|
|
195
|
-
return false
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def distance_estimate( goal )
|
|
199
|
-
dx = @x - goal.x
|
|
200
|
-
dy = @y - goal.y
|
|
201
|
-
return dx*dx + dy*dy
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
MAX_SCALE = 9
|
|
205
|
-
|
|
206
|
-
def get_map(x, y)
|
|
207
|
-
if x < 0 or y < 0 or x >= @@pmap.size or y >= @@pmap[0].size
|
|
208
|
-
return MAX_SCALE
|
|
209
|
-
else
|
|
210
|
-
t = @@pmap.at(x).at(y)
|
|
211
|
-
return MAX_SCALE if t.kind_of?(Room)
|
|
212
|
-
return MAX_SCALE-1 if t.kind_of?(Connection)
|
|
213
|
-
return 1
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def successors( astar, parent = nil )
|
|
218
|
-
info = nil
|
|
219
|
-
info = parent.info if parent
|
|
220
|
-
Room::DIR_TO_VECTOR.each_value { |x, y|
|
|
221
|
-
new_node = MapNode.new(@x + x, @y + y)
|
|
222
|
-
if get_map( new_node.x, new_node.y ) < MAX_SCALE and
|
|
223
|
-
not new_node.is_same?(info)
|
|
224
|
-
astar.add_successor(new_node)
|
|
225
|
-
end
|
|
226
|
-
}
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def inspect
|
|
230
|
-
"pos: #{@x}, #{@y}"
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
def to_s
|
|
234
|
-
inspect
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def cost( successor )
|
|
238
|
-
g = get_map(@x,@y)
|
|
239
|
-
|
|
240
|
-
dx = (@x - successor.x).abs
|
|
241
|
-
dy = (@y - successor.y).abs
|
|
242
|
-
if dx + dy == 1
|
|
243
|
-
g += 10 # straight move
|
|
244
|
-
else
|
|
245
|
-
g += 14 # diagonal move
|
|
246
|
-
end
|
|
247
|
-
return g
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
end
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# A class used for path finding.
|
|
6
|
+
# This is largely based on C++ code by Justin Heyes-Jones, albeit simplified
|
|
7
|
+
#
|
|
8
|
+
class AStar
|
|
9
|
+
|
|
10
|
+
attr :start, :goal
|
|
11
|
+
attr_reader :state
|
|
12
|
+
attr :successors
|
|
13
|
+
attr_writer :open_list
|
|
14
|
+
attr_writer :closed_list
|
|
15
|
+
|
|
16
|
+
SEARCH_STATE_NOT_INITIALIZED = 0
|
|
17
|
+
SEARCH_STATE_SEARCHING = 1
|
|
18
|
+
SEARCH_STATE_SUCCEEDED = 2
|
|
19
|
+
SEARCH_STATE_FAILED = 3
|
|
20
|
+
|
|
21
|
+
def open_list
|
|
22
|
+
puts "Open List:"
|
|
23
|
+
@open_list.each { |n|
|
|
24
|
+
p n
|
|
25
|
+
}
|
|
26
|
+
puts "Closed List # of nodes: #{@open_list.size}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def closed_list
|
|
30
|
+
puts "Closed List:"
|
|
31
|
+
@closed_list.each { |n|
|
|
32
|
+
p n
|
|
33
|
+
}
|
|
34
|
+
puts "Closed List # of nodes: #{@closed_list.size}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Node
|
|
39
|
+
attr_accessor :parent, :child
|
|
40
|
+
attr_accessor :h, :g, :f
|
|
41
|
+
|
|
42
|
+
attr_reader :info
|
|
43
|
+
|
|
44
|
+
def <=>(b)
|
|
45
|
+
return -1 if @f > b.f
|
|
46
|
+
return 0 if @f == b.f
|
|
47
|
+
return 1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def inspect
|
|
51
|
+
"AStarNode: #{@f} = #{@g} + #{@h} #{@info.inspect}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def initialize( info = nil )
|
|
55
|
+
@h = @g = @f = 0.0
|
|
56
|
+
@info = info
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# sets start and end goals and initializes A* search
|
|
61
|
+
def goals( start, goal )
|
|
62
|
+
@start = Node.new( start )
|
|
63
|
+
@start.g = 0.0
|
|
64
|
+
@start.h = start.distance_estimate( goal )
|
|
65
|
+
@start.f = @start.h
|
|
66
|
+
|
|
67
|
+
@open_list = []
|
|
68
|
+
@open_list.push( @start )
|
|
69
|
+
@closed_list = []
|
|
70
|
+
|
|
71
|
+
@goal = Node.new( goal )
|
|
72
|
+
|
|
73
|
+
@state = SEARCH_STATE_SEARCHING
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Advances one search step
|
|
77
|
+
def search_step
|
|
78
|
+
if @open_list.empty?
|
|
79
|
+
@state = SEARCH_STATE_FAILED
|
|
80
|
+
return @state
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Pop the best node (the one with the lowest f)
|
|
84
|
+
n = @open_list.pop
|
|
85
|
+
|
|
86
|
+
# Check for the goal. Once we pop that, we are done
|
|
87
|
+
if n.info.is_goal?( @goal.info )
|
|
88
|
+
# The user is going to use the Goal Node he passed in
|
|
89
|
+
# so copy the parent pointer of n
|
|
90
|
+
@goal.parent = n.parent
|
|
91
|
+
# Special case is that the goal was passed in as the start state
|
|
92
|
+
# so handle that here
|
|
93
|
+
if n != @start
|
|
94
|
+
child = @goal
|
|
95
|
+
parent = @goal.parent
|
|
96
|
+
|
|
97
|
+
while child != @start
|
|
98
|
+
parent.child = child
|
|
99
|
+
child = parent
|
|
100
|
+
parent = parent.parent
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
@state = SEARCH_STATE_SUCCEEDED
|
|
104
|
+
return @state
|
|
105
|
+
else
|
|
106
|
+
# Not goal, get successors
|
|
107
|
+
n.info.successors( self, n.parent )
|
|
108
|
+
@successors.each do |s|
|
|
109
|
+
newg = n.g + n.info.cost( s.info )
|
|
110
|
+
|
|
111
|
+
# Now, we need to find out if this node is on the open or close lists
|
|
112
|
+
# If it is, but the node that is already on them is better (lower g)
|
|
113
|
+
# then we can forget about this successor
|
|
114
|
+
open = @open_list.find { |e| e.info.is_same?( s.info ) }
|
|
115
|
+
# State in open list is cheaper than this successor
|
|
116
|
+
next if open and open.g <= newg
|
|
117
|
+
|
|
118
|
+
closed = @closed_list.find { |e| e.info.is_same?( s.info ) }
|
|
119
|
+
# We found a cheaper state in closed list
|
|
120
|
+
next if closed and closed.g <= newg
|
|
121
|
+
|
|
122
|
+
# This node is the best node so far so let's keep it and set up
|
|
123
|
+
# its A* specific data
|
|
124
|
+
s.parent = n
|
|
125
|
+
s.g = newg
|
|
126
|
+
s.h = s.info.distance_estimate( @goal.info )
|
|
127
|
+
s.f = s.g + s.h
|
|
128
|
+
|
|
129
|
+
# Remove succesor from closed list if it was on it
|
|
130
|
+
@closed_list.delete(closed) if closed
|
|
131
|
+
# Change succesor from open list if it was on it
|
|
132
|
+
if open
|
|
133
|
+
open.parent = s.parent
|
|
134
|
+
open.g = s.g
|
|
135
|
+
open.h = s.h
|
|
136
|
+
open.f = s.f
|
|
137
|
+
else
|
|
138
|
+
@open_list.push(s)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
# Make sure open list stays sorted based on f
|
|
142
|
+
@open_list.sort!
|
|
143
|
+
|
|
144
|
+
@closed_list.push(n)
|
|
145
|
+
@successors.clear
|
|
146
|
+
end
|
|
147
|
+
return @state
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def add_successor( info )
|
|
151
|
+
@successors << Node.new(info)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# return solution as an path (ie. an array of [x,y] coordinates)
|
|
155
|
+
def path
|
|
156
|
+
p = []
|
|
157
|
+
curr = @start
|
|
158
|
+
while curr
|
|
159
|
+
p << [curr.info.x, curr.info.y]
|
|
160
|
+
curr = curr.child
|
|
161
|
+
end
|
|
162
|
+
return p
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def initialize
|
|
166
|
+
@state = SEARCH_STATE_NOT_INITIALIZED
|
|
167
|
+
@successors = []
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
#
|
|
173
|
+
# Simple class used as an adapter for our FXMap<->AStar
|
|
174
|
+
#
|
|
175
|
+
class MapNode
|
|
176
|
+
attr_reader :x, :y
|
|
177
|
+
@@pmap = nil
|
|
178
|
+
|
|
179
|
+
def self.map(pmap)
|
|
180
|
+
@@pmap = pmap
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def initialize(x, y)
|
|
184
|
+
@x = x
|
|
185
|
+
@y = y
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def is_same?(node)
|
|
189
|
+
return true if node and @x == node.x and @y == node.y
|
|
190
|
+
return false
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def is_goal?( goal )
|
|
194
|
+
return true if @x == goal.x and @y == goal.y
|
|
195
|
+
return false
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def distance_estimate( goal )
|
|
199
|
+
dx = @x - goal.x
|
|
200
|
+
dy = @y - goal.y
|
|
201
|
+
return dx*dx + dy*dy
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
MAX_SCALE = 9
|
|
205
|
+
|
|
206
|
+
def get_map(x, y)
|
|
207
|
+
if x < 0 or y < 0 or x >= @@pmap.size or y >= @@pmap[0].size
|
|
208
|
+
return MAX_SCALE
|
|
209
|
+
else
|
|
210
|
+
t = @@pmap.at(x).at(y)
|
|
211
|
+
return MAX_SCALE if t.kind_of?(Room)
|
|
212
|
+
return MAX_SCALE-1 if t.kind_of?(Connection)
|
|
213
|
+
return 1
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def successors( astar, parent = nil )
|
|
218
|
+
info = nil
|
|
219
|
+
info = parent.info if parent
|
|
220
|
+
Room::DIR_TO_VECTOR.each_value { |x, y|
|
|
221
|
+
new_node = MapNode.new(@x + x, @y + y)
|
|
222
|
+
if get_map( new_node.x, new_node.y ) < MAX_SCALE and
|
|
223
|
+
not new_node.is_same?(info)
|
|
224
|
+
astar.add_successor(new_node)
|
|
225
|
+
end
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def inspect
|
|
230
|
+
"pos: #{@x}, #{@y}"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def to_s
|
|
234
|
+
inspect
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def cost( successor )
|
|
238
|
+
g = get_map(@x,@y)
|
|
239
|
+
|
|
240
|
+
dx = (@x - successor.x).abs
|
|
241
|
+
dy = (@y - successor.y).abs
|
|
242
|
+
if dx + dy == 1
|
|
243
|
+
g += 10 # straight move
|
|
244
|
+
else
|
|
245
|
+
g += 14 # diagonal move
|
|
246
|
+
end
|
|
247
|
+
return g
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
end
|