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
|
|
@@ -1,1359 +1,1361 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
# Class used to parse an Infocom-style transcript and automatically
|
|
5
|
-
# generate a map. This can be used to automatically map a game as
|
|
6
|
-
# you play it.
|
|
7
|
-
#
|
|
8
|
-
# This code is based on Perl code by Dave Chapeskie, used in IFM,
|
|
9
|
-
# albeit it has been enhanced to handle multi-line commands, room stub
|
|
10
|
-
# exits and oneway exits, objects parsing and the ability to parse
|
|
11
|
-
# transcripts as they are being spit out.
|
|
12
|
-
#
|
|
13
|
-
class TranscriptReader
|
|
14
|
-
|
|
15
|
-
TRANSCRIPT = /^(?:Start of a transcript of|Here begins a transcript of interaction with (.*)|Here begins a transcript of interaction with)/
|
|
16
|
-
|
|
17
|
-
# PROMPT = /^>\s*/
|
|
18
|
-
LOOK = /^l(ook)?/i
|
|
19
|
-
UNDO = /^undo$/i
|
|
20
|
-
RESTART = /^restart$/i
|
|
21
|
-
RESTORE = /^restore$/i
|
|
22
|
-
IGNORE = /transcript/i
|
|
23
|
-
OTHERS = /^(read|inventory$|i$)/i
|
|
24
|
-
UNSCRIPT = /^unscript$/i
|
|
25
|
-
BLANK = /^\s*$/
|
|
26
|
-
TAKE = /^(take|get)\s+(a\s+|the\s+)?(.*)/i
|
|
27
|
-
DROP = /^(drop|leave)\s+()/i
|
|
28
|
-
STARTUP = /(^[A-Z]+$|Copyright|\([cC]\)\s*\d|Trademark|Release|Version|[Ss]erial [Nn]umber|Written by)/
|
|
29
|
-
DARKNESS = /^dark(ness)?$|^in the dark$|^a dark place$|^It is pitch black|^It is too dark to see anything|^You stumble around in the dark/i
|
|
30
|
-
DEAD = /(You die|You have died|You are dead)/i
|
|
31
|
-
YES = /^y(es)/i
|
|
32
|
-
|
|
33
|
-
THEN = /\bthen\b/i
|
|
34
|
-
|
|
35
|
-
# Compass direction command -> direction mapping.
|
|
36
|
-
DIRMAP = {
|
|
37
|
-
"n" => 0, "north" => 0, "ne" => 1, "northeast" => 1,
|
|
38
|
-
"e" => 2, "east" => 2, "southeast" => 3, "se" => 3,
|
|
39
|
-
"south" => 4, "s" => 4, "southwest" => 5, "sw" => 5,
|
|
40
|
-
"west" => 6, "w" => 6, "northwest" => 7, "nw" => 7
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
ODIRMAP = {"up" => 1, "u" => 1, "down" => 2, "d" => 2,
|
|
44
|
-
"in" => 3, "out" => 4, 'enter' => 3, 'exit' => 4 }
|
|
45
|
-
|
|
46
|
-
DIR_REX = '(' + DIRMAP.keys.join('|') + '|' + ODIRMAP.keys.join('|') + ')'
|
|
47
|
-
GO = /^((walk|run|go|drive|jump)\s+)?#{DIR_REX}[.,\s]*\b/i
|
|
48
|
-
|
|
49
|
-
# Direction list in order of positioning preference.
|
|
50
|
-
DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
|
|
51
|
-
|
|
52
|
-
# No exit replies
|
|
53
|
-
NO_EXIT = [
|
|
54
|
-
/\byou\scan't\sgo\sthat\sway\b/i,
|
|
55
|
-
/\byou\scan't\sgo\sin\sthat\sdirection\b/i,
|
|
56
|
-
/\bdoorways\slead\s/i,
|
|
57
|
-
/\bthat's\sa\swall\b/i,
|
|
58
|
-
/\bno\sexit\b/i,
|
|
59
|
-
/\bthere's\s+nothing\sin\sthat\sdirection\b/i,
|
|
60
|
-
/\bblock\sthe\sway/i,
|
|
61
|
-
/\byou\scan\sonly\sgo\s/i,
|
|
62
|
-
/\byou\scan\sgo\sonly\s/i,
|
|
63
|
-
/\bimpenetrable\b/i,
|
|
64
|
-
/\bbars\syour\sway/i,
|
|
65
|
-
/\bthere's\s.*in\s(?:your|the)\sway/i
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
# Closed exit replies
|
|
69
|
-
CLOSED_EXIT = [
|
|
70
|
-
/door\s+is\s+closed/i,
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
# remove things like (on the bed) from short room name
|
|
74
|
-
PREPOSITION = "(?:in|on|under|behind|inside|\w+ing)"
|
|
75
|
-
NAME_REMOVE = /(\s+\(#{PREPOSITION}\s+.+\)|,\s+#{PREPOSITION}\s+[^\,\.]+)/
|
|
76
|
-
|
|
77
|
-
# not a room name if it has any of this.
|
|
78
|
-
NAME_INVALID = /--|[:;\*\[\]\|\+\=!\.\?\000<>]/
|
|
79
|
-
|
|
80
|
-
# Salutations and other words with periods that can appear as name of room
|
|
81
|
-
SALUT = '(Mr|Mr?s|Miss|Jr|Sr|St|Dr|Ave|Inc)'
|
|
82
|
-
SALUTATIONS = /\b#{SALUT}\./
|
|
83
|
-
|
|
84
|
-
# Maximum length of room name. Otherwise, a sentence we ignore.
|
|
85
|
-
NAME_MAXWORDS = 20
|
|
86
|
-
|
|
87
|
-
# word list that may be uncapitalized in room name
|
|
88
|
-
NAME_UNCAP = /^(?:of|on|to|with|by|a|in|the|at|under|through|near)$/i
|
|
89
|
-
|
|
90
|
-
# Default room description recognition parameters.
|
|
91
|
-
DESC_MINWORDS = 20
|
|
92
|
-
|
|
93
|
-
# Ignore these words when matching words in description
|
|
94
|
-
# To avoid cases, like: An open gate vs. A closed gate
|
|
95
|
-
DESC_IGNORE = /(?:an|a|open(ed)?|closed?)/i
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
RESTORE_OK = /\b(Okay|Ok|completed?|Restored)\b/
|
|
99
|
-
|
|
100
|
-
##
|
|
101
|
-
THE = '(?:the|an|a|your|some of the|some)\s+'
|
|
102
|
-
|
|
103
|
-
## Regex to eliminate articles from get/take replies.
|
|
104
|
-
ARTICLE = /^#{THE}/i
|
|
105
|
-
|
|
106
|
-
## Possible nessages indicating get/take succeeded
|
|
107
|
-
TAKE_OBJ = '([\w\d\-\'\s]+)'
|
|
108
|
-
TAKE_FROM = '(?:\s+(?:up|from|out)\s+.*)$'
|
|
109
|
-
TAKE_ALIAS = '(?:grab|pick\s+up|pick|pilfer|take(?:\s+up)?)'
|
|
110
|
-
TAKE_OK = [
|
|
111
|
-
/\btaken\b/i,
|
|
112
|
-
/you\s+have\s+now\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}#{TAKE_FROM}/i,
|
|
113
|
-
/you\s+have\s+now\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}/i,
|
|
114
|
-
/you\s+#{TAKE_ALIAS}\s+(?:#{THE})?#{TAKE_OBJ}#{TAKE_FROM}/i,
|
|
115
|
-
/you\s+#{TAKE_ALIAS}\s+(?:#{THE})?#{TAKE_OBJ}/i,
|
|
116
|
-
/you\s+now\s+have\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}/i,
|
|
117
|
-
/you\s+pick\s+up\s+(?:#{THE})?#{TAKE_OBJ}/i,
|
|
118
|
-
/you\s+are\s+now\s+holding\s+(?:#{THE})?#{TAKE_OBJ}/i,
|
|
119
|
-
]
|
|
120
|
-
|
|
121
|
-
IT = /^(it|them)$/i
|
|
122
|
-
|
|
123
|
-
@@win = nil
|
|
124
|
-
|
|
125
|
-
## Change this to non-nil to print out debugging info
|
|
126
|
-
@@debug = nil
|
|
127
|
-
|
|
128
|
-
def debug(*msg)
|
|
129
|
-
if @@debug
|
|
130
|
-
$stdout.puts msg
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
IDENTIFY_BY_DESCRIPTION = 0
|
|
135
|
-
IDENTIFY_BY_SHORTNAME = 1
|
|
136
|
-
|
|
137
|
-
SHORTNAME_CLASSIC = 0
|
|
138
|
-
SHORTNAME_CAPITALIZED = 1
|
|
139
|
-
SHORTNAME_MOONMIST = 2
|
|
140
|
-
SHORTNAME_WITNESS = 3
|
|
141
|
-
SHORTNAME_ADRIFT = 4
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
o
|
|
173
|
-
|
|
174
|
-
if
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
obj =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
@objects
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
next if
|
|
210
|
-
o.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
@objects
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
'
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
#
|
|
258
|
-
# so
|
|
259
|
-
#
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
#
|
|
270
|
-
|
|
271
|
-
#
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
#
|
|
276
|
-
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
/(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
/four\s+
|
|
291
|
-
/
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
matches.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
c
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
@
|
|
346
|
-
@
|
|
347
|
-
@map.
|
|
348
|
-
@map.
|
|
349
|
-
@map.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
line.
|
|
362
|
-
line.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
line
|
|
370
|
-
line.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
move[:
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if cmd =~
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
#
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
startup
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
desc.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
:
|
|
495
|
-
:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
:
|
|
512
|
-
:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
move[:
|
|
519
|
-
move[:
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
debug "
|
|
569
|
-
debug "
|
|
570
|
-
debug "
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
desc
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
#
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
@map.
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
#
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
@here
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
#
|
|
633
|
-
#
|
|
634
|
-
#
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
dir
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
@
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
score
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
#
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
# we
|
|
725
|
-
#
|
|
726
|
-
#
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
0
|
|
736
|
-
|
|
737
|
-
# some
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
@map.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
#
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
# we
|
|
791
|
-
#
|
|
792
|
-
#
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
0
|
|
802
|
-
|
|
803
|
-
# some
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
@map.
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
words
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
line
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
return $1
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if line =~ /^\(You
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
#
|
|
895
|
-
#
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
line
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
#
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
#
|
|
934
|
-
#
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if w
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
#
|
|
955
|
-
#
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
@
|
|
964
|
-
|
|
965
|
-
r
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
#
|
|
980
|
-
|
|
981
|
-
@map.
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
r
|
|
999
|
-
r.
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
c.
|
|
1023
|
-
c.
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
#
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
c
|
|
1037
|
-
c.
|
|
1038
|
-
c.
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
idx =
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
idx
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
room[
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
x
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
c.
|
|
1092
|
-
c.
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
#
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
debug "\
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
#
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
#
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
from[dir]
|
|
1161
|
-
|
|
1162
|
-
c.
|
|
1163
|
-
c.
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
c.
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
# that
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
#
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
c.
|
|
1195
|
-
c.
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
c
|
|
1204
|
-
c.
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
#
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
#
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
#
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
#
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
score +=
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
#
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
#
|
|
1269
|
-
|
|
1270
|
-
dx =
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
bestscore
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
@
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
@
|
|
1312
|
-
@
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
@
|
|
1316
|
-
@
|
|
1317
|
-
@
|
|
1318
|
-
@
|
|
1319
|
-
@
|
|
1320
|
-
@
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
@map.name =
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Class used to parse an Infocom-style transcript and automatically
|
|
5
|
+
# generate a map. This can be used to automatically map a game as
|
|
6
|
+
# you play it.
|
|
7
|
+
#
|
|
8
|
+
# This code is based on Perl code by Dave Chapeskie, used in IFM,
|
|
9
|
+
# albeit it has been enhanced to handle multi-line commands, room stub
|
|
10
|
+
# exits and oneway exits, objects parsing and the ability to parse
|
|
11
|
+
# transcripts as they are being spit out.
|
|
12
|
+
#
|
|
13
|
+
class TranscriptReader
|
|
14
|
+
|
|
15
|
+
TRANSCRIPT = /^(?:Start of a transcript of|Here begins a transcript of interaction with (.*)|Here begins a transcript of interaction with)/
|
|
16
|
+
|
|
17
|
+
# PROMPT = /^>\s*/
|
|
18
|
+
LOOK = /^l(ook)?/i
|
|
19
|
+
UNDO = /^undo$/i
|
|
20
|
+
RESTART = /^restart$/i
|
|
21
|
+
RESTORE = /^restore$/i
|
|
22
|
+
IGNORE = /transcript/i
|
|
23
|
+
OTHERS = /^(read|inventory$|i$)/i
|
|
24
|
+
UNSCRIPT = /^unscript$/i
|
|
25
|
+
BLANK = /^\s*$/
|
|
26
|
+
TAKE = /^(take|get)\s+(a\s+|the\s+)?(.*)/i
|
|
27
|
+
DROP = /^(drop|leave)\s+()/i
|
|
28
|
+
STARTUP = /(^[A-Z]+$|Copyright|\([cC]\)\s*\d|Trademark|Release|Version|[Ss]erial [Nn]umber|Written by)/
|
|
29
|
+
DARKNESS = /^dark(ness)?$|^in the dark$|^a dark place$|^It is pitch black|^It is too dark to see anything|^You stumble around in the dark/i
|
|
30
|
+
DEAD = /(You die|You have died|You are dead)/i
|
|
31
|
+
YES = /^y(es)/i
|
|
32
|
+
|
|
33
|
+
THEN = /\bthen\b/i
|
|
34
|
+
|
|
35
|
+
# Compass direction command -> direction mapping.
|
|
36
|
+
DIRMAP = {
|
|
37
|
+
"n" => 0, "north" => 0, "ne" => 1, "northeast" => 1,
|
|
38
|
+
"e" => 2, "east" => 2, "southeast" => 3, "se" => 3,
|
|
39
|
+
"south" => 4, "s" => 4, "southwest" => 5, "sw" => 5,
|
|
40
|
+
"west" => 6, "w" => 6, "northwest" => 7, "nw" => 7
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ODIRMAP = {"up" => 1, "u" => 1, "down" => 2, "d" => 2,
|
|
44
|
+
"in" => 3, "out" => 4, 'enter' => 3, 'exit' => 4 }
|
|
45
|
+
|
|
46
|
+
DIR_REX = '(' + DIRMAP.keys.join('|') + '|' + ODIRMAP.keys.join('|') + ')'
|
|
47
|
+
GO = /^((walk|run|go|drive|jump)\s+)?#{DIR_REX}[.,\s]*\b/i
|
|
48
|
+
|
|
49
|
+
# Direction list in order of positioning preference.
|
|
50
|
+
DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
|
|
51
|
+
|
|
52
|
+
# No exit replies
|
|
53
|
+
NO_EXIT = [
|
|
54
|
+
/\byou\scan't\sgo\sthat\sway\b/i,
|
|
55
|
+
/\byou\scan't\sgo\sin\sthat\sdirection\b/i,
|
|
56
|
+
/\bdoorways\slead\s/i,
|
|
57
|
+
/\bthat's\sa\swall\b/i,
|
|
58
|
+
/\bno\sexit\b/i,
|
|
59
|
+
/\bthere's\s+nothing\sin\sthat\sdirection\b/i,
|
|
60
|
+
/\bblock\sthe\sway/i,
|
|
61
|
+
/\byou\scan\sonly\sgo\s/i,
|
|
62
|
+
/\byou\scan\sgo\sonly\s/i,
|
|
63
|
+
/\bimpenetrable\b/i,
|
|
64
|
+
/\bbars\syour\sway/i,
|
|
65
|
+
/\bthere's\s.*in\s(?:your|the)\sway/i
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
# Closed exit replies
|
|
69
|
+
CLOSED_EXIT = [
|
|
70
|
+
/door\s+is\s+closed/i,
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# remove things like (on the bed) from short room name
|
|
74
|
+
PREPOSITION = "(?:in|on|under|behind|inside|along|\w+ing)"
|
|
75
|
+
NAME_REMOVE = /(\s+\(#{PREPOSITION}\s+.+\)|,\s+#{PREPOSITION}\s+[^\,\.]+)/
|
|
76
|
+
|
|
77
|
+
# not a room name if it has any of this.
|
|
78
|
+
NAME_INVALID = /--|[:;\*\[\]\|\+\=!\.\?\000<>]/
|
|
79
|
+
|
|
80
|
+
# Salutations and other words with periods that can appear as name of room
|
|
81
|
+
SALUT = '(Mr|Mr?s|Miss|Jr|Sr|St|Dr|Ave|Inc)'
|
|
82
|
+
SALUTATIONS = /\b#{SALUT}\./
|
|
83
|
+
|
|
84
|
+
# Maximum length of room name. Otherwise, a sentence we ignore.
|
|
85
|
+
NAME_MAXWORDS = 20
|
|
86
|
+
|
|
87
|
+
# word list that may be uncapitalized in room name
|
|
88
|
+
NAME_UNCAP = /^(?:of|on|to|with|by|a|in|the|at|under|through|near)$/i
|
|
89
|
+
|
|
90
|
+
# Default room description recognition parameters.
|
|
91
|
+
DESC_MINWORDS = 20
|
|
92
|
+
|
|
93
|
+
# Ignore these words when matching words in description
|
|
94
|
+
# To avoid cases, like: An open gate vs. A closed gate
|
|
95
|
+
DESC_IGNORE = /(?:an|a|open(ed)?|closed?)/i
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
RESTORE_OK = /\b(Okay|Ok|completed?|Restored)\b/
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
THE = '(?:the|an|a|your|some of the|some)\s+'
|
|
102
|
+
|
|
103
|
+
## Regex to eliminate articles from get/take replies.
|
|
104
|
+
ARTICLE = /^#{THE}/i
|
|
105
|
+
|
|
106
|
+
## Possible nessages indicating get/take succeeded
|
|
107
|
+
TAKE_OBJ = '([\w\d\-\'\s]+)'
|
|
108
|
+
TAKE_FROM = '(?:\s+(?:up|from|out)\s+.*)$'
|
|
109
|
+
TAKE_ALIAS = '(?:grab|pick\s+up|pick|pilfer|take(?:\s+up)?)'
|
|
110
|
+
TAKE_OK = [
|
|
111
|
+
/\btaken\b/i,
|
|
112
|
+
/you\s+have\s+now\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}#{TAKE_FROM}/i,
|
|
113
|
+
/you\s+have\s+now\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}/i,
|
|
114
|
+
/you\s+#{TAKE_ALIAS}\s+(?:#{THE})?#{TAKE_OBJ}#{TAKE_FROM}/i,
|
|
115
|
+
/you\s+#{TAKE_ALIAS}\s+(?:#{THE})?#{TAKE_OBJ}/i,
|
|
116
|
+
/you\s+now\s+have\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}/i,
|
|
117
|
+
/you\s+pick\s+up\s+(?:#{THE})?#{TAKE_OBJ}/i,
|
|
118
|
+
/you\s+are\s+now\s+holding\s+(?:#{THE})?#{TAKE_OBJ}/i,
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
IT = /^(it|them)$/i
|
|
122
|
+
|
|
123
|
+
@@win = nil
|
|
124
|
+
|
|
125
|
+
## Change this to non-nil to print out debugging info
|
|
126
|
+
@@debug = nil
|
|
127
|
+
|
|
128
|
+
def debug(*msg)
|
|
129
|
+
if @@debug
|
|
130
|
+
$stdout.puts msg
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
IDENTIFY_BY_DESCRIPTION = 0
|
|
135
|
+
IDENTIFY_BY_SHORTNAME = 1
|
|
136
|
+
|
|
137
|
+
SHORTNAME_CLASSIC = 0
|
|
138
|
+
SHORTNAME_CAPITALIZED = 1
|
|
139
|
+
SHORTNAME_MOONMIST = 2
|
|
140
|
+
SHORTNAME_WITNESS = 3
|
|
141
|
+
SHORTNAME_ADRIFT = 4
|
|
142
|
+
|
|
143
|
+
attr_reader :shortName
|
|
144
|
+
attr_accessor :identify, :map
|
|
145
|
+
|
|
146
|
+
def shortName=(x)
|
|
147
|
+
@shortName = x
|
|
148
|
+
if x == SHORTNAME_ADRIFT
|
|
149
|
+
@prompt = /^[a-z]/
|
|
150
|
+
else
|
|
151
|
+
@prompt = /^>\s*/
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
#
|
|
157
|
+
# Handle a take action
|
|
158
|
+
#
|
|
159
|
+
def take(move, objs)
|
|
160
|
+
return unless @here
|
|
161
|
+
if @here.objects != '' and @here.objects[-1,1] != "\n"
|
|
162
|
+
@here.objects << "\n"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
objs.each { |cmd, dummy, obj|
|
|
166
|
+
next if not obj or not move[:reply]
|
|
167
|
+
|
|
168
|
+
objlist = obj.split(',')
|
|
169
|
+
o = objlist[0]
|
|
170
|
+
if objlist.size == 1 and o != 'all'
|
|
171
|
+
# ignore 'get up'
|
|
172
|
+
next if cmd == 'get' and (o == 'up' or o == 'off')
|
|
173
|
+
o = @last_obj if o =~ IT
|
|
174
|
+
next if not o
|
|
175
|
+
if move[:reply][0] =~ /^\((.*)\)$/
|
|
176
|
+
nobj = $1
|
|
177
|
+
words = nobj.split(/\s+/)
|
|
178
|
+
if words.size < 7
|
|
179
|
+
# Acclaration, like:
|
|
180
|
+
# > get mask
|
|
181
|
+
# (the brown mask)
|
|
182
|
+
# Taken.
|
|
183
|
+
o = nobj
|
|
184
|
+
else
|
|
185
|
+
# too big of an aclaration... probably something like
|
|
186
|
+
# (putting the xxx in your bag to make room)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
o.sub!(ARTICLE, '')
|
|
190
|
+
status = move[:reply].to_s
|
|
191
|
+
TAKE_OK.each { |re|
|
|
192
|
+
if status =~ re then
|
|
193
|
+
obj = $1 || o
|
|
194
|
+
obj = o if obj =~ /\b(it|them)\b/
|
|
195
|
+
@last_obj = obj
|
|
196
|
+
# Take succeeded, change object o
|
|
197
|
+
if not @objects.has_key?(obj) and
|
|
198
|
+
not @here.objects =~ /\b#{obj}\b/
|
|
199
|
+
@here.objects << obj << "\n"
|
|
200
|
+
@objects[obj] = 1
|
|
201
|
+
end
|
|
202
|
+
break
|
|
203
|
+
end
|
|
204
|
+
}
|
|
205
|
+
else
|
|
206
|
+
# Handle multiple objects
|
|
207
|
+
move[:reply].each { |reply|
|
|
208
|
+
o, status = reply.split(':')
|
|
209
|
+
next if not status
|
|
210
|
+
next if o.split(' ').size > 6 # ignore if too many words
|
|
211
|
+
o.sub!(ARTICLE, '')
|
|
212
|
+
TAKE_OK.each { |re|
|
|
213
|
+
if status =~ re then
|
|
214
|
+
if o and not @objects.has_key?(o) and
|
|
215
|
+
not @here.objects =~ /\b#{o}\b/
|
|
216
|
+
@here.objects << o << "\n"
|
|
217
|
+
@objects[o] = 1
|
|
218
|
+
end
|
|
219
|
+
@last_obj = o
|
|
220
|
+
break
|
|
221
|
+
end
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
end
|
|
225
|
+
}
|
|
226
|
+
@here.objects.squeeze("\n")
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
#
|
|
230
|
+
# Add a new room to the automapper
|
|
231
|
+
#
|
|
232
|
+
def add(room)
|
|
233
|
+
@rooms << room
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
#
|
|
237
|
+
# Remove some rooms from the automap knowledge (user removed these manually)
|
|
238
|
+
#
|
|
239
|
+
def remove(rooms)
|
|
240
|
+
rooms.each { |r| @rooms.delete(r) }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
#
|
|
245
|
+
# Given a room description, parse it to try to find out all exits
|
|
246
|
+
# from room.
|
|
247
|
+
#
|
|
248
|
+
|
|
249
|
+
DIRS = {
|
|
250
|
+
# SWD added variants used in e.g. YAGWAD
|
|
251
|
+
'north-east' => 1, 'south-east' => 3, 'south-west' => 5, 'north-west' => 7,
|
|
252
|
+
# Usual directions
|
|
253
|
+
'north' => 0, 'northeast' => 1, 'east' => 2, 'southeast' => 3,
|
|
254
|
+
'south' => 4, 'southwest' => 5, 'west' => 6, 'northwest' => 7,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# SWD: hack: the variants with dashes must come first
|
|
258
|
+
# (so that regexes matches 'north-east' in preference to 'north'),
|
|
259
|
+
# so we manually prepend their names. The duplication is harmless,
|
|
260
|
+
# it just makes the regexes slightly slower in the non-match case.
|
|
261
|
+
DIR = '(' + DIRS.keys.join('\b|') + ')'
|
|
262
|
+
OR = '(?:and|or)'
|
|
263
|
+
|
|
264
|
+
EXITS_REGEX =
|
|
265
|
+
[
|
|
266
|
+
# paths lead west and north
|
|
267
|
+
/(run|bears|lead|wander|winds|go|continues)\s+#{DIR}\s+#{OR}\s+#{DIR}\b/i,
|
|
268
|
+
# to the east or west
|
|
269
|
+
/to\s+the#{DIR}\s+#{OR}\s+(to\s+the\s+)?#{DIR}\b/i,
|
|
270
|
+
# You can go south
|
|
271
|
+
/you\s+can\s+go#{DIR}\b/i,
|
|
272
|
+
# to the east
|
|
273
|
+
/to\s+the\s+#{DIR}\b/i,
|
|
274
|
+
# east-west corridor
|
|
275
|
+
/[\.\s+]#{DIR}[\/-]#{DIR}\s+(passage|corridor)/i,
|
|
276
|
+
# East is the postoffice
|
|
277
|
+
/\b#{DIR}\s+is/i,
|
|
278
|
+
# Directly north
|
|
279
|
+
/\bDirectly\s+#{DIR}/i,
|
|
280
|
+
# continues|lies|etc... east
|
|
281
|
+
/(runs|bears|leads|heads|opens|winds|continues\s+on|continues|branches|lies|wanders|bends|curves)\s+#{DIR}\b/i,
|
|
282
|
+
/(running|leading|heading|opening|branching|lying|wandering|looking|bending)\s+#{DIR}\b/i,
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
# passage westwards
|
|
286
|
+
# gap in the northeast corner
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
EXITS_SPECIAL = {
|
|
290
|
+
/four\s+directions/i => [0, 2, 4, 6],
|
|
291
|
+
/four\s+compass\s+points/i => [0, 2, 4, 6],
|
|
292
|
+
/all\s+directions/i => [0, 1, 2, 3, 4, 5, 6, 7],
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
def parse_exits(r, desc)
|
|
296
|
+
return if not desc
|
|
297
|
+
exits = []
|
|
298
|
+
|
|
299
|
+
# Now, start searching for stuff
|
|
300
|
+
|
|
301
|
+
# First try the special directions...
|
|
302
|
+
EXITS_SPECIAL.each { |re, dirs|
|
|
303
|
+
if desc =~ re
|
|
304
|
+
exits += dirs
|
|
305
|
+
break
|
|
306
|
+
end
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# If that failed, start searching for exits
|
|
310
|
+
if exits.empty?
|
|
311
|
+
EXITS_REGEX.each { |re|
|
|
312
|
+
matches = desc.scan(re)
|
|
313
|
+
next if matches.empty?
|
|
314
|
+
matches.each { |arr|
|
|
315
|
+
arr.each { |m|
|
|
316
|
+
next unless DIRS[m]
|
|
317
|
+
exits << DIRS[m]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
exits.uniq!
|
|
324
|
+
|
|
325
|
+
# Add a 'stub' for the new connection
|
|
326
|
+
exits.each { |exit|
|
|
327
|
+
next if r[exit]
|
|
328
|
+
begin
|
|
329
|
+
c = @map.new_connection( r, exit, nil )
|
|
330
|
+
c.dir = Connection::AtoB
|
|
331
|
+
debug "\tADDED STUB #{c}"
|
|
332
|
+
rescue ConnectionError
|
|
333
|
+
end
|
|
334
|
+
}
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def parse_line(line)
|
|
339
|
+
return unless line
|
|
340
|
+
@map.mutex.synchronize do
|
|
341
|
+
_parse_line(line)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
if @map.kind_of?(FXMap)
|
|
345
|
+
@map.clear_selection
|
|
346
|
+
@here.selected = true if @here
|
|
347
|
+
@map.create_pathmap
|
|
348
|
+
@map.zoom = @map.zoom
|
|
349
|
+
@map.center_view_on_room(@here) if @here
|
|
350
|
+
@map.draw
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def _parse_line(line)
|
|
355
|
+
@moves.clear
|
|
356
|
+
|
|
357
|
+
#
|
|
358
|
+
# Read all commands
|
|
359
|
+
#
|
|
360
|
+
loop do
|
|
361
|
+
line.sub!(@prompt, '') if @prompt.to_s =~ />/
|
|
362
|
+
line.chop!
|
|
363
|
+
line.sub!(/\s+$/, '')
|
|
364
|
+
cmd = line
|
|
365
|
+
|
|
366
|
+
# Read reply
|
|
367
|
+
reply = []
|
|
368
|
+
while line = @f.gets
|
|
369
|
+
break if line =~ @prompt
|
|
370
|
+
line.chop!
|
|
371
|
+
line.sub!(/\s+$/,'')
|
|
372
|
+
reply << line
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
if cmd =~ UNDO
|
|
376
|
+
if @moves.size < 2
|
|
377
|
+
@here = nil # out of moves, no clue where we are
|
|
378
|
+
else
|
|
379
|
+
@moves.pop
|
|
380
|
+
end
|
|
381
|
+
else
|
|
382
|
+
move = { }
|
|
383
|
+
|
|
384
|
+
# Replace all 'THEN' in command for periods
|
|
385
|
+
cmd.sub!(THEN, '.')
|
|
386
|
+
# and all ANDs for commas
|
|
387
|
+
cmd.sub!(/\band\b/i,',')
|
|
388
|
+
# and multiple periods for just one
|
|
389
|
+
cmd.sub!(/\s*\.+\s*/, '.')
|
|
390
|
+
|
|
391
|
+
move[:cmd] = cmd
|
|
392
|
+
move[:reply] = reply
|
|
393
|
+
@moves << move
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
break if not line
|
|
397
|
+
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# Step 2
|
|
402
|
+
@restart = false
|
|
403
|
+
tele = nil
|
|
404
|
+
@moves.each { |move|
|
|
405
|
+
cmd = move[:cmd]
|
|
406
|
+
next if cmd =~ IGNORE or cmd =~ OTHERS
|
|
407
|
+
if cmd =~ UNSCRIPT
|
|
408
|
+
tele = 1
|
|
409
|
+
@here = nil
|
|
410
|
+
next
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
if @restart and cmd =~ YES
|
|
414
|
+
@here = nil
|
|
415
|
+
else
|
|
416
|
+
@restart = false
|
|
417
|
+
tele = nil
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
if cmd =~ RESTART
|
|
421
|
+
@restart = true
|
|
422
|
+
tele = 1
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
if cmd =~ RESTORE
|
|
426
|
+
if move[:reply].join() =~ RESTORE_OK
|
|
427
|
+
@here = nil
|
|
428
|
+
tele = 1
|
|
429
|
+
else
|
|
430
|
+
next
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
name = nil
|
|
435
|
+
desc = ''
|
|
436
|
+
roomflag = false
|
|
437
|
+
desc_gap = false
|
|
438
|
+
startup = false
|
|
439
|
+
rooms = []
|
|
440
|
+
move[:reply].each { |r|
|
|
441
|
+
tele = 1 if r =~ DEAD
|
|
442
|
+
|
|
443
|
+
if r =~ STARTUP
|
|
444
|
+
# Dealing with a startup message, such as:
|
|
445
|
+
# MY GAME
|
|
446
|
+
# Copyright (C) 1984 ComputerQuest
|
|
447
|
+
# We skip the whole thing until the next blank line
|
|
448
|
+
debug "#{r} skipped due to startup"
|
|
449
|
+
startup = true
|
|
450
|
+
desc = ''
|
|
451
|
+
name = nil
|
|
452
|
+
roomflag = false
|
|
453
|
+
desc_gap = false
|
|
454
|
+
@here = nil
|
|
455
|
+
tele = 1
|
|
456
|
+
next
|
|
457
|
+
end
|
|
458
|
+
next if startup and r !~ BLANK
|
|
459
|
+
startup = false
|
|
460
|
+
|
|
461
|
+
if not roomflag and r !~ BLANK and n = room_name(r)
|
|
462
|
+
debug "Found room #{n}"
|
|
463
|
+
roomflag = true
|
|
464
|
+
desc_gap = false
|
|
465
|
+
name = n
|
|
466
|
+
desc = ''
|
|
467
|
+
next
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
if not desc_gap and roomflag and r =~ BLANK and desc == ''
|
|
471
|
+
desc_gap = true
|
|
472
|
+
next
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
if roomflag and r !~ BLANK
|
|
477
|
+
desc << r << "\n"
|
|
478
|
+
next
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
if r =~ BLANK and roomflag
|
|
482
|
+
if desc.count("\n") == 1 and desc =~ /\?$/
|
|
483
|
+
# A "What next?" type of prompt, not a room description
|
|
484
|
+
desc = ''
|
|
485
|
+
end
|
|
486
|
+
if desc == ''
|
|
487
|
+
desc = nil
|
|
488
|
+
else
|
|
489
|
+
desc.gsub!(/\n/, ' ')
|
|
490
|
+
desc.strip!
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
rooms << {
|
|
494
|
+
:name => name,
|
|
495
|
+
:desc => desc,
|
|
496
|
+
:tele => tele
|
|
497
|
+
}
|
|
498
|
+
roomflag = false
|
|
499
|
+
desc_gap = false
|
|
500
|
+
name = nil
|
|
501
|
+
desc = ''
|
|
502
|
+
tele = nil
|
|
503
|
+
end
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
# Oops, there was no newline between room description and prompt and
|
|
507
|
+
# we missed the last room.
|
|
508
|
+
# This happens for example with Magnetic Scrolls transcripts.
|
|
509
|
+
if name
|
|
510
|
+
rooms << {
|
|
511
|
+
:name => name,
|
|
512
|
+
:desc => desc,
|
|
513
|
+
:tele => tele
|
|
514
|
+
}
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
if not rooms.empty?
|
|
518
|
+
move[:rooms] = rooms
|
|
519
|
+
move[:look] = true if cmd =~ LOOK
|
|
520
|
+
move[:reply] = nil
|
|
521
|
+
end
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@moves.each { |move|
|
|
526
|
+
cmd = move[:cmd]
|
|
527
|
+
if objs = cmd.scan(TAKE)
|
|
528
|
+
take(move, objs)
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
if not move[:rooms]
|
|
532
|
+
# Check if a moving command failed
|
|
533
|
+
if cmd =~ GO
|
|
534
|
+
dir = $3
|
|
535
|
+
# See if a stub direction exits in that direction
|
|
536
|
+
if @here and DIRMAP[dir] then
|
|
537
|
+
dir = DIRMAP[dir]
|
|
538
|
+
next unless @here[dir]
|
|
539
|
+
|
|
540
|
+
if @here[dir].stub?
|
|
541
|
+
# Check if reply was that there's no exit there.
|
|
542
|
+
NO_EXIT.each { |re|
|
|
543
|
+
if move[:reply][0] =~ re
|
|
544
|
+
# If so, remove it... automapper got it wrong. Not an exit.
|
|
545
|
+
@map.delete_connection(@here[dir])
|
|
546
|
+
break
|
|
547
|
+
end
|
|
548
|
+
}
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# Check if there is a closed door
|
|
552
|
+
if @here[dir] and @here[dir].type == Connection::FREE
|
|
553
|
+
CLOSED_EXIT.each { |re|
|
|
554
|
+
if move[:reply][0] =~ re
|
|
555
|
+
# If so, flag it
|
|
556
|
+
@here[dir].type = Connection::CLOSED_DOOR
|
|
557
|
+
break
|
|
558
|
+
end
|
|
559
|
+
}
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
next
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
move[:rooms].each { |room|
|
|
567
|
+
name = room[:name]
|
|
568
|
+
debug "SECTION: #{@map.section}"
|
|
569
|
+
debug "HERE IS: #{@here}"
|
|
570
|
+
debug "CMD: #{cmd}"
|
|
571
|
+
debug "ENDS AT: #{name}"
|
|
572
|
+
|
|
573
|
+
desc = room[:desc]
|
|
574
|
+
desc.gsub!(/(\w)\s*\n/, '\1 ')
|
|
575
|
+
|
|
576
|
+
line = move[:line]
|
|
577
|
+
|
|
578
|
+
# If we teleported, try to find room
|
|
579
|
+
if room[:tele]
|
|
580
|
+
debug "\t ****TELEPORT TO #{name}****"
|
|
581
|
+
@here = find_room(name, desc)
|
|
582
|
+
debug "\t TO: #{@here}"
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# Make sure the user has not deleted the current room from map
|
|
586
|
+
# and that we are in the proper section.
|
|
587
|
+
if @here
|
|
588
|
+
found = false
|
|
589
|
+
@map.sections.each_with_index { |sect, idx|
|
|
590
|
+
if sect.rooms.include?(@here)
|
|
591
|
+
found = true
|
|
592
|
+
if idx != @map.section
|
|
593
|
+
@map.fit
|
|
594
|
+
@map.section = idx
|
|
595
|
+
end
|
|
596
|
+
break
|
|
597
|
+
end
|
|
598
|
+
}
|
|
599
|
+
@here = nil if not found
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# If it is a look command or we don't know where we are yet,
|
|
603
|
+
# set current room.
|
|
604
|
+
if move[:look] or not @here
|
|
605
|
+
if move[:look] and name =~ DARKNESS
|
|
606
|
+
@here = find_room(name, desc) if @here and not @here.darkness
|
|
607
|
+
else
|
|
608
|
+
@here = find_room(name, desc)
|
|
609
|
+
end
|
|
610
|
+
@here = new_room(move, name, desc) unless @here
|
|
611
|
+
@here.selected = true
|
|
612
|
+
next
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
cmd.sub!(GO, '')
|
|
616
|
+
dir = $3
|
|
617
|
+
|
|
618
|
+
# Swallow everything until next command (separated by .)
|
|
619
|
+
cmd.sub!(/.*\./, '')
|
|
620
|
+
|
|
621
|
+
debug "MOVED IN DIRECTION: #{dir} CMD LEFT:#{cmd}"
|
|
622
|
+
if not cmd
|
|
623
|
+
debug "Oops... run out of commands."
|
|
624
|
+
break
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
sect = @map.section
|
|
628
|
+
|
|
629
|
+
# Otherwise, assume we moved in some way. Try to find the new room.
|
|
630
|
+
if name =~ DARKNESS
|
|
631
|
+
idx = DIRMAP[dir] || ODIRMAP[dir]
|
|
632
|
+
# Moving to a dark room is special. We verify the exit to see
|
|
633
|
+
# if it was already moving to a dark room (in which case it
|
|
634
|
+
# becomes "there") or if it was moving to some other room (in
|
|
635
|
+
# which case we flag it as dark and make it there)
|
|
636
|
+
if idx and @here[idx]
|
|
637
|
+
c = @here[idx]
|
|
638
|
+
|
|
639
|
+
if c.roomA == @here
|
|
640
|
+
b = c.roomB
|
|
641
|
+
else
|
|
642
|
+
b = c.roomA
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
if not b
|
|
646
|
+
there = nil
|
|
647
|
+
else
|
|
648
|
+
if b.name !~ DARKNESS
|
|
649
|
+
b.darkness = true
|
|
650
|
+
end
|
|
651
|
+
there = b
|
|
652
|
+
end
|
|
653
|
+
else
|
|
654
|
+
# No connection yet, create dark room
|
|
655
|
+
there = nil
|
|
656
|
+
end
|
|
657
|
+
else
|
|
658
|
+
there = find_room(name, desc)
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
next if there == @here
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
go = nil
|
|
665
|
+
if DIRMAP[dir]
|
|
666
|
+
dir = DIRMAP[dir]
|
|
667
|
+
elsif ODIRMAP[dir]
|
|
668
|
+
go = ODIRMAP[dir]
|
|
669
|
+
debug "#{dir} move #{go}"
|
|
670
|
+
dir = choose_dir(@here, there, go)
|
|
671
|
+
else
|
|
672
|
+
# special move --- teleport/death/etc.
|
|
673
|
+
go = 0
|
|
674
|
+
dir = choose_dir(@here, there, go)
|
|
675
|
+
end
|
|
676
|
+
debug "MOVED IN DIR INDEX: #{dir}"
|
|
677
|
+
|
|
678
|
+
@here.selected = false
|
|
679
|
+
if not there
|
|
680
|
+
# Unvisited -- new room
|
|
681
|
+
@here = new_room(move, name, desc, dir, @here, go)
|
|
682
|
+
else
|
|
683
|
+
# Visited before -- new link
|
|
684
|
+
if sect == @map.section # don't conn
|
|
685
|
+
new_link(move, @here, there, dir, go)
|
|
686
|
+
end
|
|
687
|
+
@here = there
|
|
688
|
+
end
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
@moves.clear
|
|
693
|
+
@map.fit
|
|
694
|
+
if @map.kind_of?(FXMap)
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def find_room(name, desc)
|
|
699
|
+
case @identify
|
|
700
|
+
when IDENTIFY_BY_DESCRIPTION
|
|
701
|
+
return find_room_by_desc(name, desc)
|
|
702
|
+
when IDENTIFY_BY_SHORTNAME
|
|
703
|
+
return find_room_by_name(name, desc)
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def find_room_by_name(name, desc)
|
|
708
|
+
bestscore = 0
|
|
709
|
+
best = nil
|
|
710
|
+
|
|
711
|
+
@map.sections.each_with_index { |sect, idx|
|
|
712
|
+
sect.rooms.each { |room|
|
|
713
|
+
score = 0
|
|
714
|
+
score += 1 if room.name == name
|
|
715
|
+
|
|
716
|
+
if score == 1 and desc and room.desc
|
|
717
|
+
# We have a description...
|
|
718
|
+
# Try exact description match first
|
|
719
|
+
score += 100 if room.desc == desc
|
|
720
|
+
|
|
721
|
+
# Try substring match
|
|
722
|
+
score += 50 if room.desc.index(desc)
|
|
723
|
+
|
|
724
|
+
# If we have a room where both name and desc match,
|
|
725
|
+
# we get a better score than just description only.
|
|
726
|
+
# This is to help, for example, Trinity, where two rooms have
|
|
727
|
+
# the exact description but different name.
|
|
728
|
+
score += 1 if room.name == name and score > 0
|
|
729
|
+
|
|
730
|
+
# If still no luck, try first N words
|
|
731
|
+
if score == 1
|
|
732
|
+
dwords = room.desc.split(' ')
|
|
733
|
+
words = desc.split(' ')
|
|
734
|
+
match = true
|
|
735
|
+
count = 0
|
|
736
|
+
0.upto(desc.size) { |i|
|
|
737
|
+
# Ignore some words (like open/close, which may just mean
|
|
738
|
+
# some doors changed state)
|
|
739
|
+
next if words[i] =~ DESC_IGNORE
|
|
740
|
+
|
|
741
|
+
if words[i] != dwords[i]
|
|
742
|
+
if count < DESC_MINWORDS
|
|
743
|
+
match = false
|
|
744
|
+
break
|
|
745
|
+
else
|
|
746
|
+
next
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
count += 1
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
score += count if match
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
next if score <= bestscore
|
|
756
|
+
bestscore = score
|
|
757
|
+
best = [room, idx]
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return nil if not best
|
|
762
|
+
|
|
763
|
+
# Make sure we are in the right section
|
|
764
|
+
if best[1] != @map.section
|
|
765
|
+
@map.fit
|
|
766
|
+
@map.section = best[1]
|
|
767
|
+
end
|
|
768
|
+
if desc and (not best[0].desc or best[0].desc == '')
|
|
769
|
+
best[0].desc = desc
|
|
770
|
+
end
|
|
771
|
+
return best[0]
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def find_room_by_desc(name, desc)
|
|
775
|
+
bestscore = 0
|
|
776
|
+
best = nil
|
|
777
|
+
|
|
778
|
+
@map.sections.each_with_index { |sect, idx|
|
|
779
|
+
sect.rooms.each { |room|
|
|
780
|
+
score = 0
|
|
781
|
+
|
|
782
|
+
if desc and room.desc
|
|
783
|
+
# We have a description...
|
|
784
|
+
# Try exact description match first
|
|
785
|
+
score += 100 if room.desc == desc
|
|
786
|
+
|
|
787
|
+
# Try substring match
|
|
788
|
+
score += 50 if room.desc.index(desc)
|
|
789
|
+
|
|
790
|
+
# If we have a room where both name and desc match,
|
|
791
|
+
# we get a better score than just description only.
|
|
792
|
+
# This is to help, for example, Trinity, where two rooms have
|
|
793
|
+
# the exact description but different name.
|
|
794
|
+
score += 1 if room.name == name and score > 0
|
|
795
|
+
|
|
796
|
+
# If still no luck, try first N words
|
|
797
|
+
if score == 0
|
|
798
|
+
dwords = room.desc.split(' ')
|
|
799
|
+
words = desc.split(' ')
|
|
800
|
+
match = true
|
|
801
|
+
count = 0
|
|
802
|
+
0.upto(desc.size) { |i|
|
|
803
|
+
# Ignore some words (like open/close, which may just mean
|
|
804
|
+
# some doors changed state)
|
|
805
|
+
next if words[i] =~ DESC_IGNORE
|
|
806
|
+
|
|
807
|
+
if words[i] != dwords[i]
|
|
808
|
+
if count < DESC_MINWORDS
|
|
809
|
+
match = false
|
|
810
|
+
break
|
|
811
|
+
else
|
|
812
|
+
next
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
count += 1
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
score += count if match and room.name == name
|
|
819
|
+
end
|
|
820
|
+
else
|
|
821
|
+
# Just the name, not so good
|
|
822
|
+
score += 1 if room.name == name
|
|
823
|
+
end
|
|
824
|
+
next if score <= bestscore
|
|
825
|
+
bestscore = score
|
|
826
|
+
best = [room, idx]
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return nil if not best
|
|
831
|
+
|
|
832
|
+
# Make sure we are in the right section
|
|
833
|
+
if best[1] != @map.section
|
|
834
|
+
@map.fit
|
|
835
|
+
@map.section = best[1]
|
|
836
|
+
end
|
|
837
|
+
if desc and (not best[0].desc or best[0].desc == '')
|
|
838
|
+
best[0].desc = desc
|
|
839
|
+
end
|
|
840
|
+
return best[0]
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
def room_name(line)
|
|
844
|
+
case @shortName
|
|
845
|
+
when SHORTNAME_CAPITALIZED
|
|
846
|
+
return room_name_classic(line, false)
|
|
847
|
+
when SHORTNAME_MOONMIST
|
|
848
|
+
return room_name_moonmist(line)
|
|
849
|
+
when SHORTNAME_WITNESS
|
|
850
|
+
return room_name_witness(line)
|
|
851
|
+
else
|
|
852
|
+
return room_name_classic(line, true)
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
def capitalize_room(line)
|
|
857
|
+
words = line.split(' ')
|
|
858
|
+
words.each_with_index { |w, idx|
|
|
859
|
+
if idx > 0 and w =~ NAME_UNCAP
|
|
860
|
+
w.downcase!
|
|
861
|
+
else
|
|
862
|
+
w.capitalize!
|
|
863
|
+
end
|
|
864
|
+
}
|
|
865
|
+
return words.join(' ')
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
def room_name_alan(line)
|
|
869
|
+
return false if line !~ /^[^.]+\.$/
|
|
870
|
+
line.sub!(/\.$/, '')
|
|
871
|
+
return room_name_classic(line, true)
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
def room_name_witness(line)
|
|
875
|
+
if line =~ /^You\sare\s(?:now\s)?(?:[io]n\s)?(?:#{THE})?([\w'\d\s\-_]+)\.$/
|
|
876
|
+
return false if $1 =~ /own feet/
|
|
877
|
+
return $1
|
|
878
|
+
elsif line =~ /^\(([\w\d\s_]+)\)$/
|
|
879
|
+
return $1
|
|
880
|
+
else
|
|
881
|
+
return false
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
def room_name_moonmist(line)
|
|
886
|
+
return false if line =~ /^\(You are not holding it.\)$/
|
|
887
|
+
if line =~ /^\(You\sare\s(?:now\s)?(?:[io]n\s)?(?:#{THE})?([\w'\d\s\-_]+)\.\)$/
|
|
888
|
+
return capitalize_room( $1 )
|
|
889
|
+
else
|
|
890
|
+
return false
|
|
891
|
+
end
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
#
|
|
895
|
+
# Determine if line corresponds to a room name
|
|
896
|
+
#
|
|
897
|
+
def room_name_classic(line, all_capitals = true)
|
|
898
|
+
# Check if user/game has created a room with that name already
|
|
899
|
+
return line if find_room(line, nil)
|
|
900
|
+
|
|
901
|
+
# We have a room if we match darkness
|
|
902
|
+
return line if line =~ DARKNESS
|
|
903
|
+
|
|
904
|
+
# Remove unwanted stuff line (on the bed)
|
|
905
|
+
line.sub!(NAME_REMOVE, '')
|
|
906
|
+
|
|
907
|
+
# Check if user/game has created a room with that name already
|
|
908
|
+
return line if find_room(line, nil)
|
|
909
|
+
|
|
910
|
+
# Remove periods from salutations
|
|
911
|
+
line.sub!(SALUTATIONS, '\1')
|
|
912
|
+
|
|
913
|
+
# quick check for invalid format
|
|
914
|
+
return false if line =~ NAME_INVALID
|
|
915
|
+
|
|
916
|
+
# Qucik check for word characters
|
|
917
|
+
return false unless line =~ /\w/
|
|
918
|
+
|
|
919
|
+
# Check if we start line with uncapitalized words or symbols
|
|
920
|
+
return false if line =~ /^[ a-z\/\\\-\(\)']/
|
|
921
|
+
|
|
922
|
+
# Check if line holds only capitalized words or several spaces together
|
|
923
|
+
# or a quote or a 1) line. If so, not a room.
|
|
924
|
+
return false if line =~ /^[A-Z\d,\.\/\-"'\s]+$/ or line =~ /\s\s/ or
|
|
925
|
+
line =~ /^".*"$/ or line =~ /^"[^"]+$/ or line =~ /^\d+\)/
|
|
926
|
+
|
|
927
|
+
# Check word count (if too many, not a room)
|
|
928
|
+
words = line.split(' ')
|
|
929
|
+
return false if words.size > NAME_MAXWORDS
|
|
930
|
+
|
|
931
|
+
return false if not all_capitals and words.size > 6
|
|
932
|
+
|
|
933
|
+
# If not, check all words of 4 chars or more are capitalized
|
|
934
|
+
# and that there are no 3 or more short letter words together
|
|
935
|
+
# (which means a diagram)
|
|
936
|
+
num = 0
|
|
937
|
+
words.each { |w|
|
|
938
|
+
return false if all_capitals and w =~ /^[a-z]/ and w !~ NAME_UNCAP
|
|
939
|
+
if w.size <= 2
|
|
940
|
+
num += 1
|
|
941
|
+
return false if num > 2
|
|
942
|
+
else
|
|
943
|
+
num = 0
|
|
944
|
+
end
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
# Restore period to salutations
|
|
948
|
+
line.sub!(/\b#{SALUT}\b/, '\1.')
|
|
949
|
+
|
|
950
|
+
# Okay, it is a room.
|
|
951
|
+
return line
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
#
|
|
955
|
+
# Create a new room
|
|
956
|
+
#
|
|
957
|
+
def new_room( move, name, desc, dir = nil, from = nil, go = nil )
|
|
958
|
+
b = nil
|
|
959
|
+
dark = false
|
|
960
|
+
|
|
961
|
+
if not from
|
|
962
|
+
debug "FROM undefined. Increase section #{name}"
|
|
963
|
+
@section += 1
|
|
964
|
+
@map.new_section if @section >= @map.sections.size
|
|
965
|
+
r = @map.new_room(0, 0)
|
|
966
|
+
r.name = name
|
|
967
|
+
else
|
|
968
|
+
|
|
969
|
+
darkexits = []
|
|
970
|
+
if name =~ DARKNESS
|
|
971
|
+
dark = true
|
|
972
|
+
else
|
|
973
|
+
dark = false
|
|
974
|
+
if from[dir]
|
|
975
|
+
# oops, we had a connection there.
|
|
976
|
+
c = from[dir]
|
|
977
|
+
b = c.opposite(from)
|
|
978
|
+
if b and b.name =~ DARKNESS
|
|
979
|
+
# Was it a dark room that is now lit?
|
|
980
|
+
# if so, set dark flag and delete dark room
|
|
981
|
+
@map.delete_connection(c)
|
|
982
|
+
@map.delete_room_only(b)
|
|
983
|
+
dark = true
|
|
984
|
+
c = nil
|
|
985
|
+
end
|
|
986
|
+
end
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
x = from.x
|
|
990
|
+
y = from.y
|
|
991
|
+
dx, dy = Room::DIR_TO_VECTOR[dir]
|
|
992
|
+
x += dx
|
|
993
|
+
y += dy
|
|
994
|
+
@map.shift(x, y, dx, dy) if not @map.free?(x, y)
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
debug "+++ New Room #{name} from #{from}"
|
|
998
|
+
r = @map.new_room(x, y)
|
|
999
|
+
r.name = name
|
|
1000
|
+
r.darkness = dark
|
|
1001
|
+
if b and dark
|
|
1002
|
+
b.exits.each_with_index { |de, didx|
|
|
1003
|
+
next unless de
|
|
1004
|
+
@map.sections[@map.section].connections << de
|
|
1005
|
+
r[didx] = de
|
|
1006
|
+
if de.roomA == b
|
|
1007
|
+
de.roomA = r
|
|
1008
|
+
else
|
|
1009
|
+
de.roomB = r
|
|
1010
|
+
end
|
|
1011
|
+
}
|
|
1012
|
+
end
|
|
1013
|
+
c = nil
|
|
1014
|
+
if from[dir]
|
|
1015
|
+
# oops, we had a connection there.
|
|
1016
|
+
c = from[dir]
|
|
1017
|
+
b = c.roomB
|
|
1018
|
+
if c.stub?
|
|
1019
|
+
# Stub connection. Update it.
|
|
1020
|
+
debug "\tUPDATE #{c}"
|
|
1021
|
+
odir = (dir + 4) % 8
|
|
1022
|
+
c.exitAtext = go if go
|
|
1023
|
+
c.type = Connection::SPECIAL if go == 0
|
|
1024
|
+
c.roomB = r
|
|
1025
|
+
r[odir] = c
|
|
1026
|
+
debug "\tNOW IT IS #{c}"
|
|
1027
|
+
else
|
|
1028
|
+
# Probably a link that is complex
|
|
1029
|
+
# or oneway. Shift it to some other location
|
|
1030
|
+
shift_link(from, dir)
|
|
1031
|
+
c = nil
|
|
1032
|
+
end
|
|
1033
|
+
end
|
|
1034
|
+
if c == nil
|
|
1035
|
+
begin
|
|
1036
|
+
c = @map.new_connection( from, dir, r )
|
|
1037
|
+
c.exitAtext = go if go
|
|
1038
|
+
c.type = Connection::SPECIAL if go == 0
|
|
1039
|
+
c.dir = Connection::AtoB
|
|
1040
|
+
rescue Section::ConnectionError
|
|
1041
|
+
end
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
parse_exits(r, desc)
|
|
1046
|
+
|
|
1047
|
+
# Update room description
|
|
1048
|
+
r.desc = desc
|
|
1049
|
+
return r
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
def shift_link(room, dir)
|
|
1053
|
+
idx = dir + 1
|
|
1054
|
+
idx = 0 if idx > 7
|
|
1055
|
+
while idx != dir
|
|
1056
|
+
break if not room[idx]
|
|
1057
|
+
idx += 1
|
|
1058
|
+
idx = 0 if idx > 7
|
|
1059
|
+
end
|
|
1060
|
+
if idx != dir
|
|
1061
|
+
room[idx] = room[dir]
|
|
1062
|
+
room[dir] = nil
|
|
1063
|
+
# get position of other room
|
|
1064
|
+
ox, oy = Room::DIR_TO_VECTOR[dir]
|
|
1065
|
+
c = room[idx]
|
|
1066
|
+
if c.roomA == room
|
|
1067
|
+
b = c.roomB
|
|
1068
|
+
else
|
|
1069
|
+
b = c.roomA
|
|
1070
|
+
end
|
|
1071
|
+
x, y = [b.x, b.y]
|
|
1072
|
+
x -= ox
|
|
1073
|
+
y -= oy
|
|
1074
|
+
dx, dy = Room::DIR_TO_VECTOR[idx]
|
|
1075
|
+
@map.shift(x, y, -dx, -dy)
|
|
1076
|
+
else
|
|
1077
|
+
debug "Warning. Cannot shift connection."
|
|
1078
|
+
end
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def new_link(move, from, to, dir, go)
|
|
1083
|
+
odir = (dir + 4) % 8
|
|
1084
|
+
c = nil
|
|
1085
|
+
# If we have something in the from direction
|
|
1086
|
+
if from[dir]
|
|
1087
|
+
c = from[dir]
|
|
1088
|
+
debug "\tMOVE #{c} DIR: #{dir}"
|
|
1089
|
+
if c.stub?
|
|
1090
|
+
# Stub connection, fill it
|
|
1091
|
+
c.roomB = to
|
|
1092
|
+
c.exitAtext = go if go
|
|
1093
|
+
c.type = Connection::SPECIAL if go == 0
|
|
1094
|
+
# we still need to check the destination to[odir]...
|
|
1095
|
+
if not to[odir]
|
|
1096
|
+
# nothing to do
|
|
1097
|
+
elsif to[odir].stub?
|
|
1098
|
+
@map.delete_connection(to[odir])
|
|
1099
|
+
debug "\tREMOVE #{to[odir]}"
|
|
1100
|
+
else
|
|
1101
|
+
# this end cannot be deleted. we need to shift odir
|
|
1102
|
+
debug "\tCHOOSE NEW DIR for #{odir}"
|
|
1103
|
+
rgo = nil
|
|
1104
|
+
if go and go > 0
|
|
1105
|
+
rgo = rgo % 2 == 0? go - 1 : go + 1
|
|
1106
|
+
end
|
|
1107
|
+
odir = choose_dir(to, from, rgo, dir)
|
|
1108
|
+
@map.delete_connection(to[odir]) if to[odir]
|
|
1109
|
+
debug "\tSHIFTED DESTINATION TO EXIT #{odir}"
|
|
1110
|
+
end
|
|
1111
|
+
to[odir] = c
|
|
1112
|
+
elsif c.roomB == to
|
|
1113
|
+
# We already went this way. Nothing to do.
|
|
1114
|
+
debug "\tWE ALREADY PASSED THRU HERE"
|
|
1115
|
+
elsif c.roomA == to
|
|
1116
|
+
# We verified we can travel thru this connection in both
|
|
1117
|
+
# directions. Change its status to both.
|
|
1118
|
+
c.dir = Connection::BOTH
|
|
1119
|
+
debug "\tSECTION: #{@map.section}"
|
|
1120
|
+
debug "\tVERIFIED EXIT BOTH WAYS"
|
|
1121
|
+
else
|
|
1122
|
+
debug "\tOTHER"
|
|
1123
|
+
if c.roomA == from
|
|
1124
|
+
b = c.roomB
|
|
1125
|
+
if b.name =~ DARKNESS
|
|
1126
|
+
debug "*** REPLACING DARK ROOM ***"
|
|
1127
|
+
@map.delete_connection(c)
|
|
1128
|
+
to.darkness = true
|
|
1129
|
+
@map.delete_room(b)
|
|
1130
|
+
c = nil
|
|
1131
|
+
else
|
|
1132
|
+
if c.exitAtext != 0
|
|
1133
|
+
# if it was an up/down/in/out dir, we shift it
|
|
1134
|
+
shift_link(from, dir)
|
|
1135
|
+
c = nil
|
|
1136
|
+
else
|
|
1137
|
+
# else, we really have a problem in the map
|
|
1138
|
+
debug "*** CANNOT AUTOMAP --- MAZE ***"
|
|
1139
|
+
dir = Room::DIRECTIONS[dir]
|
|
1140
|
+
@map.cannot_automap "Maze detected.\n'#{from}' #{dir} leads to '#{c.roomB}',\nnot to this '#{to}'."
|
|
1141
|
+
# self.stop
|
|
1142
|
+
return nil
|
|
1143
|
+
end
|
|
1144
|
+
end
|
|
1145
|
+
else
|
|
1146
|
+
debug "SHIFT LINK #{from} #{dir}"
|
|
1147
|
+
# We have a connection that turns. Move the link around
|
|
1148
|
+
shift_link(from, dir)
|
|
1149
|
+
c = nil
|
|
1150
|
+
end
|
|
1151
|
+
end
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
if not c and to[odir]
|
|
1155
|
+
c = to[odir]
|
|
1156
|
+
if c.stub?
|
|
1157
|
+
debug "\tREMOVE #{to[odir]} and REPLACE with #{c}"
|
|
1158
|
+
# Stub connection, fill it
|
|
1159
|
+
c.roomB = from
|
|
1160
|
+
# @map.delete_connection(from[dir]) if from[dir].stub?
|
|
1161
|
+
from[dir] = c
|
|
1162
|
+
c.dir = Connection::BtoA
|
|
1163
|
+
c.exitBtext = go if go
|
|
1164
|
+
c.type = Connection::SPECIAL if go == 0
|
|
1165
|
+
elsif c.roomB == from
|
|
1166
|
+
c.exitBtext = go if go
|
|
1167
|
+
c.type = Connection::SPECIAL if go == 0
|
|
1168
|
+
else
|
|
1169
|
+
# We need to change odir to something else
|
|
1170
|
+
rgo = nil
|
|
1171
|
+
if go and go > 0
|
|
1172
|
+
rgo = go % 2 == 0? go - 1 : go + 1
|
|
1173
|
+
end
|
|
1174
|
+
odir = choose_dir(to, from, rgo, dir)
|
|
1175
|
+
@map.delete_connection(to[odir]) if to[odir] and to[odir].stub?
|
|
1176
|
+
c = nil
|
|
1177
|
+
end
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
if not c
|
|
1181
|
+
# First, check all from exits that are AtoB to see if we have one
|
|
1182
|
+
# that goes to the room we want.
|
|
1183
|
+
from.exits.each_with_index { |e, idx|
|
|
1184
|
+
next unless e
|
|
1185
|
+
if e.roomA == to and e.dir == Connection::AtoB
|
|
1186
|
+
c = e
|
|
1187
|
+
from[idx] = nil
|
|
1188
|
+
end
|
|
1189
|
+
}
|
|
1190
|
+
if c
|
|
1191
|
+
# If so, make that connection go both ways and attach it to
|
|
1192
|
+
# current direction.
|
|
1193
|
+
from[dir] = c
|
|
1194
|
+
c.dir = Connection::BOTH
|
|
1195
|
+
c.exitAtext = go if go
|
|
1196
|
+
c.type = Connection::SPECIAL if go == 0
|
|
1197
|
+
end
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1200
|
+
if not c
|
|
1201
|
+
# No link exists -- create new one.
|
|
1202
|
+
begin
|
|
1203
|
+
c = @map.new_connection( from, dir, to, odir )
|
|
1204
|
+
c.exitAtext = go if go
|
|
1205
|
+
c.dir = Connection::AtoB
|
|
1206
|
+
rescue Section::ConnectionError
|
|
1207
|
+
end
|
|
1208
|
+
end
|
|
1209
|
+
|
|
1210
|
+
return c
|
|
1211
|
+
end
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
# Choose a direction to represent up/down/in/out.
|
|
1215
|
+
def choose_dir(a, b, go = nil, exitB = nil)
|
|
1216
|
+
# Don't add a new connection if we already have a normal connection
|
|
1217
|
+
# to the room and we are moving up/down/etc.
|
|
1218
|
+
if go
|
|
1219
|
+
rgo = go % 2 == 0? go - 1 : go + 1
|
|
1220
|
+
debug "#{Connection::EXIT_TEXT[go]} <=> #{Connection::EXIT_TEXT[rgo]}"
|
|
1221
|
+
# First, check if room already has exit moving towards other room
|
|
1222
|
+
a.exits.each_with_index { |e, idx|
|
|
1223
|
+
next if not e
|
|
1224
|
+
roomA = e.roomA
|
|
1225
|
+
roomB = e.roomB
|
|
1226
|
+
if roomA == a and roomB == b
|
|
1227
|
+
e.exitAtext = go if e.exitBtext == rgo
|
|
1228
|
+
return idx
|
|
1229
|
+
elsif roomB == a and roomA == b
|
|
1230
|
+
e.exitBtext = go if e.exitAtext == rgo
|
|
1231
|
+
return idx
|
|
1232
|
+
end
|
|
1233
|
+
}
|
|
1234
|
+
end
|
|
1235
|
+
|
|
1236
|
+
# We prefer directions that travel less... so we need to figure
|
|
1237
|
+
# out where we start from...
|
|
1238
|
+
if b
|
|
1239
|
+
x = b.x
|
|
1240
|
+
y = b.y
|
|
1241
|
+
else
|
|
1242
|
+
x = a.x
|
|
1243
|
+
y = a.y
|
|
1244
|
+
end
|
|
1245
|
+
if exitB
|
|
1246
|
+
dx, dy = Room::DIR_TO_VECTOR[exitB]
|
|
1247
|
+
x += dx
|
|
1248
|
+
y += dy
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
# No such luck... Pick a direction.
|
|
1252
|
+
best = nil
|
|
1253
|
+
bestscore = nil
|
|
1254
|
+
|
|
1255
|
+
DIRLIST.each { |dir|
|
|
1256
|
+
# We prefer straight directions to diagonal ones
|
|
1257
|
+
inc = dir % 2 == 1 ? 10 : 14
|
|
1258
|
+
score = 1000
|
|
1259
|
+
# We prefer directions where both that dir and the opposite side
|
|
1260
|
+
# are empty.
|
|
1261
|
+
if (not a[dir]) or a[dir].stub?
|
|
1262
|
+
score += inc
|
|
1263
|
+
score += 4 if a[dir] #attaching to stubs is better
|
|
1264
|
+
end
|
|
1265
|
+
# rdir = (dir + 4) % 8
|
|
1266
|
+
# score += 1 unless a[rdir]
|
|
1267
|
+
|
|
1268
|
+
# Measure distance for that exit, we prefer shorter
|
|
1269
|
+
# paths
|
|
1270
|
+
dx, dy = Room::DIR_TO_VECTOR[dir]
|
|
1271
|
+
dx = (a.x + dx) - x
|
|
1272
|
+
dy = (a.y + dy) - y
|
|
1273
|
+
d = dx * dx + dy * dy
|
|
1274
|
+
score -= d
|
|
1275
|
+
next if bestscore and score <= bestscore
|
|
1276
|
+
bestscore = score
|
|
1277
|
+
best = dir
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if not bestscore
|
|
1281
|
+
raise "No free exit for choose_dir"
|
|
1282
|
+
end
|
|
1283
|
+
|
|
1284
|
+
return best
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
def stop
|
|
1288
|
+
@t.kill if @t
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
def destroy
|
|
1292
|
+
@t.kill if @t
|
|
1293
|
+
@f.close if @f
|
|
1294
|
+
GC.start
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
def properties(modal = false)
|
|
1299
|
+
require 'IFMapper/TranscriptDialogBox'
|
|
1300
|
+
if not @@win
|
|
1301
|
+
@@win = TranscriptDialogBox.new(self)
|
|
1302
|
+
else
|
|
1303
|
+
@@win.copy_from(self)
|
|
1304
|
+
end
|
|
1305
|
+
if modal
|
|
1306
|
+
@@win.execute
|
|
1307
|
+
end
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
def initialize(map, file)
|
|
1311
|
+
@shortName = 0
|
|
1312
|
+
@prompt = /^>\s*/
|
|
1313
|
+
@identify = 0
|
|
1314
|
+
|
|
1315
|
+
@f = nil
|
|
1316
|
+
@file = file
|
|
1317
|
+
@map = map
|
|
1318
|
+
@objects = {}
|
|
1319
|
+
@moves = []
|
|
1320
|
+
@here = nil
|
|
1321
|
+
@section = -1
|
|
1322
|
+
@last_obj = nil
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1325
|
+
# Step one user command at a time
|
|
1326
|
+
def step
|
|
1327
|
+
begin
|
|
1328
|
+
parse_line(@f.gets)
|
|
1329
|
+
rescue => e
|
|
1330
|
+
$stderr.puts e
|
|
1331
|
+
$stderr.puts e.backtrace
|
|
1332
|
+
end
|
|
1333
|
+
end
|
|
1334
|
+
|
|
1335
|
+
def start
|
|
1336
|
+
if not @f
|
|
1337
|
+
@f = File.open(@file, 'r')
|
|
1338
|
+
while line = @f.gets
|
|
1339
|
+
if @map.name =~ /^Empty Map/ and line =~ TRANSCRIPT
|
|
1340
|
+
if $1
|
|
1341
|
+
@map.name = $1
|
|
1342
|
+
else
|
|
1343
|
+
@map.name = @f.gets.strip
|
|
1344
|
+
end
|
|
1345
|
+
@map.name = capitalize_room(@map.name)
|
|
1346
|
+
end
|
|
1347
|
+
break if @prompt =~ line
|
|
1348
|
+
end
|
|
1349
|
+
parse_line(line)
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1352
|
+
@t = Thread.new {
|
|
1353
|
+
loop do
|
|
1354
|
+
self.step
|
|
1355
|
+
Thread.pass
|
|
1356
|
+
sleep 3
|
|
1357
|
+
end
|
|
1358
|
+
}
|
|
1359
|
+
@t.run
|
|
1360
|
+
end
|
|
1361
|
+
end
|