ifmapper 0.9.6 → 0.9.7
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 +57 -0
- data/IFMapper.gemspec +1 -1
- data/lib/IFMapper/AStar.rb +1 -1
- data/lib/IFMapper/Connection.rb +10 -0
- data/lib/IFMapper/FXDCPostscript.rb +2 -2
- data/lib/IFMapper/FXMap.rb +1 -2
- data/lib/IFMapper/FXMapFileDialog.rb +1 -1
- data/lib/IFMapper/FXMapperWindow.rb +19 -4
- data/lib/IFMapper/FXSpline.rb +3 -1
- data/lib/IFMapper/FXWarningBox.rb +6 -1
- data/lib/IFMapper/GUEReader.rb +445 -0
- data/lib/IFMapper/IFMWriter.rb +11 -3
- data/lib/IFMapper/InformReader.rb +190 -529
- data/lib/IFMapper/MapReader.rb +900 -0
- data/lib/IFMapper/Section.rb +7 -6
- data/lib/IFMapper/TADSReader.rb +103 -531
- data/lib/IFMapper/TranscriptReader.rb +11 -9
- data/maps/A New Life.map +0 -0
- data/maps/History Repeating.map +0 -0
- data/maps/Unforgotten.map +0 -0
- data/maps/devours.map +0 -0
- data/maps/djinni.map +0 -0
- data/maps/party.map +0 -0
- data/maps/pkgirl.map +0 -0
- data/maps/splashdown.map +0 -0
- metadata +17 -9
- data/lib/IFMapper/InformReaderOld.rb +0 -778
@@ -26,7 +26,7 @@ class TranscriptReader
|
|
26
26
|
TAKE = /^(take|get)\s+(a\s+|the\s+)?(.*)/i
|
27
27
|
DROP = /^(drop|leave)\s+()/i
|
28
28
|
STARTUP = /(^[A-Z]+$|Copyright|\([cC]\)\s*\d|Trademark|Release|Version|[Ss]erial [Nn]umber|Written by)/
|
29
|
-
DARKNESS = /^dark(ness)?$|^in the dark$|^It is pitch black|^It is too dark to see anything|^You stumble around in the dark/i
|
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
30
|
DEAD = /(You die|You have died|You are dead)/i
|
31
31
|
YES = /^y(es)/i
|
32
32
|
|
@@ -68,12 +68,14 @@ class TranscriptReader
|
|
68
68
|
]
|
69
69
|
|
70
70
|
# remove things like (on the bed)
|
71
|
-
|
72
|
-
|
71
|
+
PREPOSITION = "(?:in|on|under|behind|inside|\w+ing)"
|
72
|
+
NAME_REMOVE = /(\s+\(#{PREPOSITION}\s+.+\)|,\s+#{PREPOSITION}\s+[^\,\.]+)/
|
73
|
+
NAME_INVALID = /--|[:;\*\[\]\|\+\=!\.\?\000<>]/
|
73
74
|
SALUT = '(Mr|Mr?s|Miss|Jr|Sr|St|Dr|Ave|Inc)'
|
74
75
|
SALUTATIONS = /\b#{SALUT}\./
|
75
76
|
NAME_MAXWORDS = 20
|
76
|
-
|
77
|
+
# word list that may be uncapitalized
|
78
|
+
NAME_UNCAP = /^(?:of|on|to|with|by|a|in|the|under)$/
|
77
79
|
|
78
80
|
# Default room description recognition parameters.
|
79
81
|
DESC_MINWORDS = 20
|
@@ -890,10 +892,6 @@ class TranscriptReader
|
|
890
892
|
# Qucik check for word characters
|
891
893
|
return false unless line =~ /\w/
|
892
894
|
|
893
|
-
# Check word count (if too many, not a room)
|
894
|
-
words = line.split(' ')
|
895
|
-
return false if words.size > NAME_MAXWORDS
|
896
|
-
|
897
895
|
# Check if we start line with uncapitalized words or symbols
|
898
896
|
return false if line =~ /^[ a-z\/\\\-\(\)']/
|
899
897
|
|
@@ -902,6 +900,10 @@ class TranscriptReader
|
|
902
900
|
return false if line =~ /^[A-Z\d,\.\/\-"'\s]+$/ or line =~ /\s\s/ or
|
903
901
|
line =~ /^".*"$/ or line =~ /^"[^"]+$/ or line =~ /^\d+\)/
|
904
902
|
|
903
|
+
# Check word count (if too many, not a room)
|
904
|
+
words = line.split(' ')
|
905
|
+
return false if words.size > NAME_MAXWORDS
|
906
|
+
|
905
907
|
return false if not all_capitals and words.size > 6
|
906
908
|
|
907
909
|
# If not, check all words of 4 chars or more are capitalized
|
@@ -909,7 +911,7 @@ class TranscriptReader
|
|
909
911
|
# (which means a diagram)
|
910
912
|
num = 0
|
911
913
|
words.each { |w|
|
912
|
-
return false if all_capitals and w =~ /^[a-z]/ and w
|
914
|
+
return false if all_capitals and w =~ /^[a-z]/ and w !~ NAME_UNCAP
|
913
915
|
if w.size <= 2
|
914
916
|
num += 1
|
915
917
|
return false if num > 2
|
data/maps/A New Life.map
ADDED
Binary file
|
Binary file
|
Binary file
|
data/maps/devours.map
ADDED
Binary file
|
data/maps/djinni.map
ADDED
Binary file
|
data/maps/party.map
ADDED
Binary file
|
data/maps/pkgirl.map
CHANGED
Binary file
|
data/maps/splashdown.map
ADDED
Binary file
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.10
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ifmapper
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.9.
|
7
|
-
date: 2005-
|
6
|
+
version: 0.9.7
|
7
|
+
date: 2005-10-08
|
8
8
|
summary: Interactive Fiction Mapping Tool.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -30,35 +30,36 @@ files:
|
|
30
30
|
- IFMapper.rbw
|
31
31
|
- lib/IFMapper/FXMap.rb
|
32
32
|
- lib/IFMapper/Map.rb
|
33
|
+
- lib/IFMapper/FXSpline.rb
|
33
34
|
- lib/IFMapper/FXRoom.rb
|
34
35
|
- lib/IFMapper/FXSectionDialogBox.rb
|
35
|
-
- lib/IFMapper/FXMapperWindow.rb
|
36
36
|
- lib/IFMapper/Section.rb
|
37
37
|
- lib/IFMapper/FXConnection.rb
|
38
38
|
- lib/IFMapper/FXMapColorBox.rb
|
39
39
|
- lib/IFMapper/IFMReader.rb
|
40
|
-
- lib/IFMapper/IFMWriter.rb
|
41
40
|
- lib/IFMapper/FXWarningBox.rb
|
41
|
+
- lib/IFMapper/IFMWriter.rb
|
42
42
|
- lib/IFMapper/FXMapperSettings.rb
|
43
|
+
- lib/IFMapper/AStar.rb
|
44
|
+
- lib/IFMapper/FXMapperWindow.rb
|
43
45
|
- lib/IFMapper/Connection.rb
|
44
|
-
- lib/IFMapper/MapPrinting.rb
|
45
46
|
- lib/IFMapper/FXDCPostscript.rb
|
47
|
+
- lib/IFMapper/MapPrinting.rb
|
46
48
|
- lib/IFMapper/FXDCPrint.rb
|
47
49
|
- lib/IFMapper/Room.rb
|
48
50
|
- lib/IFMapper/PDFMapExporter.rb
|
49
51
|
- lib/IFMapper/TranscriptReader.rb
|
50
52
|
- lib/IFMapper/FXMapDialogBox.rb
|
51
|
-
- lib/IFMapper/FXSpline.rb
|
52
53
|
- lib/IFMapper/InformReader.rb
|
53
|
-
- lib/IFMapper/FXMapFileDialog.rb
|
54
54
|
- lib/IFMapper/InformWriter.rb
|
55
55
|
- lib/IFMapper/FXSection.rb
|
56
56
|
- lib/IFMapper/TADSReader.rb
|
57
|
-
- lib/IFMapper/
|
57
|
+
- lib/IFMapper/GUEReader.rb
|
58
|
+
- lib/IFMapper/FXMapFileDialog.rb
|
58
59
|
- lib/IFMapper/TADSWriter.rb
|
59
60
|
- lib/IFMapper/FXRoomDialogBox.rb
|
60
61
|
- lib/IFMapper/FXConnectionDialogBox.rb
|
61
|
-
- lib/IFMapper/
|
62
|
+
- lib/IFMapper/MapReader.rb
|
62
63
|
- lib/IFMapper/TranscriptDialogBox.rb
|
63
64
|
- lib/IFMapper/FXRoomList.rb
|
64
65
|
- lib/IFMapper/FXAboutDialogBox.rb
|
@@ -152,6 +153,13 @@ files:
|
|
152
153
|
- maps/heroes.map
|
153
154
|
- maps/photograph.map
|
154
155
|
- maps/drift3.map
|
156
|
+
- maps/party.map
|
157
|
+
- maps/devours.map
|
158
|
+
- maps/djinni.map
|
159
|
+
- maps/A New Life.map
|
160
|
+
- maps/History Repeating.map
|
161
|
+
- maps/Unforgotten.map
|
162
|
+
- maps/splashdown.map
|
155
163
|
- icons/filenew.png
|
156
164
|
- icons/fileopen.png
|
157
165
|
- icons/filesave.png
|
@@ -1,778 +0,0 @@
|
|
1
|
-
|
2
|
-
require "IFMapper/Map"
|
3
|
-
|
4
|
-
class FXMap; end
|
5
|
-
|
6
|
-
#
|
7
|
-
# Class that allows creating a map from an Inform source file.
|
8
|
-
#
|
9
|
-
class InformReader
|
10
|
-
|
11
|
-
class ParseError < StandardError; end
|
12
|
-
class MapError < StandardError; end
|
13
|
-
|
14
|
-
# Take a quoted Inform string and return a valid ASCII one, replacing
|
15
|
-
# Inform's special characters.
|
16
|
-
def self.inform_unquote(text)
|
17
|
-
return '' unless text
|
18
|
-
text.gsub!(/\~/, '"')
|
19
|
-
text.gsub!(/\^/, "\n")
|
20
|
-
while text =~ /(@@(\d+))/
|
21
|
-
text.sub!($1, $2.to_i.chr)
|
22
|
-
end
|
23
|
-
return text
|
24
|
-
end
|
25
|
-
|
26
|
-
# Temporary classes used to store inform room information
|
27
|
-
class InformObject
|
28
|
-
attr_reader :name
|
29
|
-
attr_accessor :tag, :location, :enterable
|
30
|
-
|
31
|
-
def name=(x)
|
32
|
-
@name = InformReader::inform_unquote(x)
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_s
|
36
|
-
"#@name tag:#@tag"
|
37
|
-
end
|
38
|
-
def initialize()
|
39
|
-
@location = []
|
40
|
-
@enterable = []
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class InformDoor
|
45
|
-
attr_accessor :location
|
46
|
-
attr_accessor :locked
|
47
|
-
attr_accessor :tag
|
48
|
-
def method_missing(*x)
|
49
|
-
end
|
50
|
-
def initialize
|
51
|
-
@location = []
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class InformRoom
|
56
|
-
attr_reader :name
|
57
|
-
attr_accessor :exits, :tag, :darkness
|
58
|
-
def to_s
|
59
|
-
"#@name tag:#@tag"
|
60
|
-
end
|
61
|
-
def name=(x)
|
62
|
-
@name = InformReader::inform_unquote(x)
|
63
|
-
end
|
64
|
-
def initialize
|
65
|
-
@exits = Array.new(12, nil)
|
66
|
-
@darkness = true
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
|
71
|
-
DIRECTIONS = {
|
72
|
-
'n_to' => 0,
|
73
|
-
'ne_to' => 1,
|
74
|
-
'e_to' => 2,
|
75
|
-
'se_to' => 3,
|
76
|
-
's_to' => 4,
|
77
|
-
'sw_to' => 5,
|
78
|
-
'w_to' => 6,
|
79
|
-
'nw_to' => 7,
|
80
|
-
'u_to' => 8,
|
81
|
-
'd_to' => 9,
|
82
|
-
'in_to' => 10,
|
83
|
-
'out_to' => 11
|
84
|
-
}
|
85
|
-
|
86
|
-
FUNCTION = /^\[ (\w+);/
|
87
|
-
|
88
|
-
GO_OBJ = /\b(#{DIRECTIONS.keys.join('|').gsub(/_to/, '_obj')})\s*:/i
|
89
|
-
|
90
|
-
|
91
|
-
# Direction list in order of positioning preference.
|
92
|
-
DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
|
93
|
-
|
94
|
-
NAME = /(?:^|\s+)p?name\s+/i
|
95
|
-
DIR_TO = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s+/i
|
96
|
-
|
97
|
-
DIR = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s+(\w+)/i
|
98
|
-
ENTER_DIR = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s+\[;\s*<<\s*Enter\s+(\w+)\s*>>/i
|
99
|
-
|
100
|
-
attr_reader :map
|
101
|
-
|
102
|
-
@@debug = 1
|
103
|
-
def debug(*x)
|
104
|
-
return unless @@debug
|
105
|
-
$stdout.puts x
|
106
|
-
$stdout.flush
|
107
|
-
end
|
108
|
-
|
109
|
-
#
|
110
|
-
# Main parsing loop. We basically parse the file twice to
|
111
|
-
# solve dependencies. Yes, this is inefficient, but the alternative
|
112
|
-
# was to build a full parser that understands forward dependencies.
|
113
|
-
#
|
114
|
-
def parse(file)
|
115
|
-
# We start map at 0, 0
|
116
|
-
@x, @y = [0, 0]
|
117
|
-
@room = nil
|
118
|
-
|
119
|
-
if @map.kind_of?(FXMap)
|
120
|
-
@map.options['Edit on Creation'] = false
|
121
|
-
@map.window.hide
|
122
|
-
end
|
123
|
-
@map.section = 0
|
124
|
-
|
125
|
-
@parsing = nil
|
126
|
-
@last_section = 0
|
127
|
-
@ignore_first_section = true
|
128
|
-
@room_idx = 0
|
129
|
-
line_number = 0
|
130
|
-
|
131
|
-
debug "...Parse... #{file.path}"
|
132
|
-
while not file.eof?
|
133
|
-
@line = ''
|
134
|
-
while not file.eof? and @line == ''
|
135
|
-
@line << file.readline()
|
136
|
-
@line.sub!( /^\s*!.*$/, '')
|
137
|
-
line_number += 1
|
138
|
-
end
|
139
|
-
# Remove comments at end of line
|
140
|
-
@line.sub!( /\s+![^"]*$/, '')
|
141
|
-
# Remove starting spaces (if any)
|
142
|
-
@line.sub! /^\s+/, ''
|
143
|
-
# Replace \n with simple space
|
144
|
-
@line.gsub! /\n/, ' '
|
145
|
-
next if @line == ''
|
146
|
-
full_line = @line.dup
|
147
|
-
begin
|
148
|
-
parse_line
|
149
|
-
rescue ParseError => e
|
150
|
-
$stderr.puts
|
151
|
-
$stderr.puts "#{e} at #{file.path}, line #{line_number}:"
|
152
|
-
$stderr.puts ">>>> #{full_line};"
|
153
|
-
$stderr.puts
|
154
|
-
end
|
155
|
-
end
|
156
|
-
debug "...End Parse..."
|
157
|
-
end
|
158
|
-
|
159
|
-
|
160
|
-
CLASS = /^class\s+(\w+)/i
|
161
|
-
DOOR = /(?:^|\s+)door_to(?:\s+([^,;]*)|$)/i
|
162
|
-
INCLUDE = /^#?include\s+"([^"]+)"/i
|
163
|
-
PLAYER_TO = /\bplayerto\((\w+)/i
|
164
|
-
|
165
|
-
|
166
|
-
STD_LIB = [
|
167
|
-
'Parser',
|
168
|
-
'VerbLib',
|
169
|
-
'Grammar'
|
170
|
-
]
|
171
|
-
|
172
|
-
|
173
|
-
def find_file(file)
|
174
|
-
return file if File.exists?(file)
|
175
|
-
@include_dirs.each { |d|
|
176
|
-
[ "#{d}/#{file}",
|
177
|
-
"#{d}/#{file}.h",
|
178
|
-
"#{d}/#{file}.inf", ].each { |full|
|
179
|
-
return full if File.exists?(full)
|
180
|
-
}
|
181
|
-
}
|
182
|
-
return nil
|
183
|
-
end
|
184
|
-
|
185
|
-
#
|
186
|
-
# Parse a line of file
|
187
|
-
#
|
188
|
-
def parse_line
|
189
|
-
if @line =~ INCLUDE
|
190
|
-
name = $1
|
191
|
-
unless STD_LIB.include?(name)
|
192
|
-
file = find_file(name)
|
193
|
-
if file
|
194
|
-
File.open(file, 'r') { |f| parse(f) }
|
195
|
-
else
|
196
|
-
raise ParseError, "Include file #{name} not found"
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
if @line =~ CLASS
|
202
|
-
@clas = $1
|
203
|
-
debug "CLASS: #@clas"
|
204
|
-
if @classes.has_key?(@clas)
|
205
|
-
if @obj
|
206
|
-
else
|
207
|
-
end
|
208
|
-
else
|
209
|
-
@classes[@clas] = {}
|
210
|
-
@tag = @name = nil
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
re = /^(#{@classes.keys.join('|')})(\s+->)?(\s+(\w+))?(\s+"([^"]+)")?(\s+(\w+))?/
|
215
|
-
if @line =~ re
|
216
|
-
@clas = $1
|
217
|
-
prev = $2
|
218
|
-
@tag = $4 || $6
|
219
|
-
@name = $6
|
220
|
-
|
221
|
-
loc = $8
|
222
|
-
if prev and @room
|
223
|
-
loc = @room.tag
|
224
|
-
end
|
225
|
-
|
226
|
-
debug <<"EOF"
|
227
|
-
CURRENT ROOM:#@room
|
228
|
-
Class : #@clas
|
229
|
-
Tag : #@tag
|
230
|
-
Name : #@name
|
231
|
-
Location: #{loc} #{loc.class}
|
232
|
-
EOF
|
233
|
-
|
234
|
-
@obj = nil
|
235
|
-
|
236
|
-
c = @classes[@clas]
|
237
|
-
if c[:door]
|
238
|
-
@obj = InformDoor.new
|
239
|
-
@obj.tag = @tag
|
240
|
-
@tags[@tag] = @obj
|
241
|
-
@doors << @obj
|
242
|
-
debug "+++ DOOR"
|
243
|
-
elsif loc or c[:scenery] or c[:static]
|
244
|
-
debug "+++ OBJECT"
|
245
|
-
if @tag
|
246
|
-
@obj = InformObject.new()
|
247
|
-
@obj.tag = @tag
|
248
|
-
@obj.name = @name
|
249
|
-
@obj.location << loc
|
250
|
-
@tags[@tag] = @obj
|
251
|
-
@objects << @obj
|
252
|
-
end
|
253
|
-
else
|
254
|
-
debug "+++ ROOM?"
|
255
|
-
@obj = @room = nil
|
256
|
-
if @tag and @name
|
257
|
-
# We assume we are a room (albeit we could be an obj)
|
258
|
-
@room = InformRoom.new
|
259
|
-
@room.tag = @tag
|
260
|
-
@room.name = @name
|
261
|
-
@room.darkness = !c[:light]
|
262
|
-
@tags[@tag] = @room
|
263
|
-
@rooms << @room
|
264
|
-
end
|
265
|
-
end
|
266
|
-
@before = false
|
267
|
-
@go = false
|
268
|
-
|
269
|
-
end
|
270
|
-
|
271
|
-
if @line =~ NAME
|
272
|
-
# We have an object. Delete last room we created, as it is not one.
|
273
|
-
@rooms.delete_if { |r| r.tag == @tag }
|
274
|
-
@obj = InformObject.new()
|
275
|
-
@obj.tag = @tag
|
276
|
-
@obj.name = @name
|
277
|
-
@tags[@tag] = @obj
|
278
|
-
@objects << @obj
|
279
|
-
end
|
280
|
-
|
281
|
-
if @line =~ FUNCTION
|
282
|
-
@functions << $1
|
283
|
-
end
|
284
|
-
|
285
|
-
dirs = @line.scan(DIR_TO) + @line.scan(DIR) + @line.scan(ENTER_DIR)
|
286
|
-
if dirs.size > 0
|
287
|
-
dirs.each { |d, room|
|
288
|
-
dir = DIRECTIONS[d]
|
289
|
-
@room.exits[dir] = room
|
290
|
-
}
|
291
|
-
end
|
292
|
-
|
293
|
-
if @line =~ /\bbefore\b/i
|
294
|
-
@before = true
|
295
|
-
end
|
296
|
-
|
297
|
-
if @line =~ /\bgo\s*:/i and @room and @before
|
298
|
-
if @line =~ GO_OBJ
|
299
|
-
dir = DIRECTIONS[$1]
|
300
|
-
if @line =~ PLAYER_TO
|
301
|
-
@room.exits[dir] = $1
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
if @obj.kind_of?(InformObject) and @before
|
307
|
-
if @line =~ PLAYER_TO
|
308
|
-
@obj.enterable << $1
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
if @tag and @line =~ DOOR
|
313
|
-
door = InformDoor.new
|
314
|
-
door.location = $1.split(' ')
|
315
|
-
door.location += @obj.location if @obj
|
316
|
-
door.tag = @tag
|
317
|
-
@obj = door
|
318
|
-
@tags[@tag] = door
|
319
|
-
@doors << door
|
320
|
-
@objects.delete(@obj) if @obj and @obj.tag == @tag
|
321
|
-
end
|
322
|
-
|
323
|
-
if @line =~ /\bhas\s+([^,;]+)[,;]/i
|
324
|
-
props = $1.split
|
325
|
-
props.each { |p|
|
326
|
-
if not @tag
|
327
|
-
if p[0,1] == '~'
|
328
|
-
@classes[@clas][p[1,-1].to_sym] = false
|
329
|
-
else
|
330
|
-
@classes[@clas][p.to_sym] = true
|
331
|
-
end
|
332
|
-
else
|
333
|
-
if p =~ /locked/ and @doors.size > 0
|
334
|
-
@doors[-1].locked = true
|
335
|
-
end
|
336
|
-
if p =~ /(static|scenery)/ and @obj
|
337
|
-
@objects.delete(@obj)
|
338
|
-
end
|
339
|
-
if @room and p =~ /(\~)?light/
|
340
|
-
dark = ($1 == '~')
|
341
|
-
@room.darkness = dark
|
342
|
-
end
|
343
|
-
end
|
344
|
-
}
|
345
|
-
end
|
346
|
-
|
347
|
-
if @line =~ /\bfound_in\s+(.*)[,;]?/
|
348
|
-
if not @obj
|
349
|
-
puts '-' * 78
|
350
|
-
puts "#@name -> obj not defined "
|
351
|
-
puts '-' * 78
|
352
|
-
else
|
353
|
-
locs = $1.split
|
354
|
-
@obj.location = locs
|
355
|
-
debug "#{@obj} #{@obj.location} FOUND_IN: #{locs.join(' ')}"
|
356
|
-
end
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
|
361
|
-
def shift_link(room, dir)
|
362
|
-
idx = dir + 1
|
363
|
-
idx = 0 if idx > 7
|
364
|
-
while idx != dir
|
365
|
-
break if not room[idx]
|
366
|
-
idx += 1
|
367
|
-
idx = 0 if idx > 7
|
368
|
-
end
|
369
|
-
if idx != dir
|
370
|
-
room[idx] = room[dir]
|
371
|
-
room[dir] = nil
|
372
|
-
# get position of other room
|
373
|
-
ox, oy = Room::DIR_TO_VECTOR[dir]
|
374
|
-
c = room[idx]
|
375
|
-
if c.roomA == room
|
376
|
-
b = c.roomB
|
377
|
-
else
|
378
|
-
b = c.roomA
|
379
|
-
end
|
380
|
-
x, y = [b.x, b.y]
|
381
|
-
x -= ox
|
382
|
-
y -= oy
|
383
|
-
dx, dy = Room::DIR_TO_VECTOR[idx]
|
384
|
-
@map.shift(x, y, -dx, -dy)
|
385
|
-
else
|
386
|
-
# raise "Warning. Cannot shift connection for #{room}."
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
|
-
|
391
|
-
def oneway_link?(a, b)
|
392
|
-
# First, check if room already has exit moving towards other room
|
393
|
-
a.exits.each_with_index { |e, idx|
|
394
|
-
next if not e or e.dir != Connection::AtoB
|
395
|
-
roomA = e.roomA
|
396
|
-
roomB = e.roomB
|
397
|
-
if roomA == a and roomB == b
|
398
|
-
return e
|
399
|
-
end
|
400
|
-
}
|
401
|
-
return nil
|
402
|
-
end
|
403
|
-
|
404
|
-
|
405
|
-
# Choose a direction to represent up/down/in/out.
|
406
|
-
def choose_dir(a, b, go = nil, exitB = nil)
|
407
|
-
if go
|
408
|
-
rgo = go % 2 == 0? go - 1 : go + 1
|
409
|
-
# First, check if room already has exit moving towards other room
|
410
|
-
a.exits.each_with_index { |e, idx|
|
411
|
-
next if not e or e.stub?
|
412
|
-
roomA = e.roomA
|
413
|
-
roomB = e.roomB
|
414
|
-
if roomA == a and roomB == b
|
415
|
-
e.exitAtext = go
|
416
|
-
return idx
|
417
|
-
elsif roomB == a and roomA == b
|
418
|
-
e.exitBtext = go
|
419
|
-
return idx
|
420
|
-
end
|
421
|
-
}
|
422
|
-
end
|
423
|
-
|
424
|
-
# We prefer directions that travel less... so we need to figure
|
425
|
-
# out where we start from...
|
426
|
-
if b
|
427
|
-
x = b.x
|
428
|
-
y = b.y
|
429
|
-
else
|
430
|
-
x = a.x
|
431
|
-
y = a.y
|
432
|
-
end
|
433
|
-
if exitB
|
434
|
-
dx, dy = Room::DIR_TO_VECTOR[exitB]
|
435
|
-
x += dx
|
436
|
-
y += dy
|
437
|
-
end
|
438
|
-
|
439
|
-
# No such luck... Pick a direction.
|
440
|
-
best = nil
|
441
|
-
bestscore = nil
|
442
|
-
|
443
|
-
DIRLIST.each { |dir|
|
444
|
-
# We prefer straight directions to diagonal ones
|
445
|
-
inc = dir % 2 == 1 ? 100 : 140
|
446
|
-
score = 1000
|
447
|
-
# We prefer directions where both that dir and the opposite side
|
448
|
-
# are empty.
|
449
|
-
if (not a[dir]) or a[dir].stub?
|
450
|
-
score += inc
|
451
|
-
score += 4 if a[dir] #attaching to stubs is better
|
452
|
-
end
|
453
|
-
# rdir = (dir + 4) % 8
|
454
|
-
# score += 1 unless a[rdir]
|
455
|
-
|
456
|
-
# Measure distance for that exit, we prefer shorter
|
457
|
-
# paths
|
458
|
-
dx, dy = Room::DIR_TO_VECTOR[dir]
|
459
|
-
dx = (a.x + dx) - x
|
460
|
-
dy = (a.y + dy) - y
|
461
|
-
d = dx * dx + dy * dy
|
462
|
-
score -= d
|
463
|
-
next if bestscore and score <= bestscore
|
464
|
-
bestscore = score
|
465
|
-
best = dir
|
466
|
-
}
|
467
|
-
|
468
|
-
if not bestscore
|
469
|
-
raise "No free exit for choose_dir"
|
470
|
-
end
|
471
|
-
|
472
|
-
return best
|
473
|
-
end
|
474
|
-
|
475
|
-
def new_room(from, to, x, y, dx = 1, dy = 0 )
|
476
|
-
elem = @tags[to.tag]
|
477
|
-
if elem.kind_of?(InformRoom)
|
478
|
-
if not @map.free?(x, y)
|
479
|
-
@map.shift(x, y, dx, dy)
|
480
|
-
end
|
481
|
-
room = @map.new_room(x, y)
|
482
|
-
room.name = to.name
|
483
|
-
room.darkness = to.darkness
|
484
|
-
@tags[to.tag] = room
|
485
|
-
return [room, Connection::FREE]
|
486
|
-
elsif elem.kind_of?(InformDoor)
|
487
|
-
if elem.locked
|
488
|
-
type = Connection::LOCKED_DOOR
|
489
|
-
else
|
490
|
-
type = Connection::CLOSED_DOOR
|
491
|
-
end
|
492
|
-
|
493
|
-
@rooms.each { |o|
|
494
|
-
next if @tags[o.tag] == from
|
495
|
-
o.exits.each { |e|
|
496
|
-
next unless e
|
497
|
-
if @tags[e] == elem
|
498
|
-
res = new_room( o, o, x, y, dx, dy )
|
499
|
-
return [ res[0], type ]
|
500
|
-
end
|
501
|
-
}
|
502
|
-
}
|
503
|
-
|
504
|
-
# Okay, connecting room is missing. Check door's locations property
|
505
|
-
p elem.location
|
506
|
-
elem.location.each { |tag|
|
507
|
-
next if @tags[tag] == from
|
508
|
-
@rooms.each { |o|
|
509
|
-
next if o.tag != tag
|
510
|
-
res = new_room( o, o, x, y, dx, dy )
|
511
|
-
return [ res[0], type ]
|
512
|
-
}
|
513
|
-
}
|
514
|
-
|
515
|
-
#raise "error: no room with door #{to.name} #{elem.name}"
|
516
|
-
return [nil, nil]
|
517
|
-
else
|
518
|
-
return [elem, Connection::FREE]
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
|
-
def create_room(r, x, y, dx = 1, dy = 0)
|
523
|
-
from, = new_room(r, r, x, y)
|
524
|
-
debug "CREATE ROOM #{r.name} SET FROM TO: #{from}"
|
525
|
-
|
526
|
-
r.exits.each_with_index { |e, exit|
|
527
|
-
next unless e
|
528
|
-
next if e == 'nothing'
|
529
|
-
debug "#{r.name} EXIT:#{exit} points to #{e}"
|
530
|
-
|
531
|
-
to = @tags[e]
|
532
|
-
if not to
|
533
|
-
next if @functions.include?(e)
|
534
|
-
raise "Room #{e} #{e.class} not found." if not to
|
535
|
-
end
|
536
|
-
|
537
|
-
go = c = nil
|
538
|
-
|
539
|
-
dir = exit
|
540
|
-
type = 0
|
541
|
-
|
542
|
-
# If exit leads to an enterable object, find out where does that
|
543
|
-
# enterable object lead to.
|
544
|
-
if to.kind_of?(InformObject)
|
545
|
-
rooms = to.enterable
|
546
|
-
rooms.each { |room|
|
547
|
-
next if room == r
|
548
|
-
to = @tags[room]
|
549
|
-
break
|
550
|
-
}
|
551
|
-
# Skip it if we are still an object. This means we are just
|
552
|
-
# a container, like the phone booth in the Fate game demo.
|
553
|
-
next if to.kind_of?(InformObject)
|
554
|
-
end
|
555
|
-
|
556
|
-
if to.kind_of?(InformRoom) or to.kind_of?(InformDoor)
|
557
|
-
if dir > 7
|
558
|
-
# choose a dir for up/down/in/out
|
559
|
-
go = dir - 7
|
560
|
-
dir = choose_dir(from, nil, go)
|
561
|
-
end
|
562
|
-
|
563
|
-
dx, dy = Room::DIR_TO_VECTOR[dir]
|
564
|
-
x = from.x + dx
|
565
|
-
y = from.y + dy
|
566
|
-
debug "#{exit} CREATE TO #{from} -> #{to.tag}"
|
567
|
-
to, type = new_room(from, to, x, y, dx, dy)
|
568
|
-
next if not to
|
569
|
-
puts "---- back: #{to.name} #{to.class}"
|
570
|
-
else
|
571
|
-
if dir > 7
|
572
|
-
# choose a dir for up/down/in/out
|
573
|
-
go = dir - 7
|
574
|
-
dir = choose_dir(from, to, go)
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
odir = (dir + 4) % 8
|
579
|
-
|
580
|
-
if from[dir]
|
581
|
-
c = from[dir]
|
582
|
-
if to[odir] == c and c.roomB == from
|
583
|
-
debug "LINK TRAVELLED BOTH"
|
584
|
-
c.dir = Connection::BOTH
|
585
|
-
c.exitBtext = go if go
|
586
|
-
next
|
587
|
-
else
|
588
|
-
debug "#{exit} FROM #{from}->#{to} BLOCKED DIR: #{dir}"
|
589
|
-
shift_link(from, dir)
|
590
|
-
end
|
591
|
-
end
|
592
|
-
|
593
|
-
# Check we don't have a connection already
|
594
|
-
if to[odir]
|
595
|
-
c = to[odir]
|
596
|
-
debug "#{from} #{dir} -> #{to} dir:#{odir} filled. Swap..."
|
597
|
-
|
598
|
-
# We need to change odir to something else
|
599
|
-
rgo = 0
|
600
|
-
if go
|
601
|
-
rgo = go % 2 == 0? go - 1 : go + 1
|
602
|
-
end
|
603
|
-
|
604
|
-
# First, check if we have a dangling one-way link going to->from
|
605
|
-
# If we do, we use it.
|
606
|
-
c = oneway_link?(from, to)
|
607
|
-
if not c
|
608
|
-
odir = choose_dir(to, from, rgo, dir)
|
609
|
-
debug "Swapped to #{odir}"
|
610
|
-
else
|
611
|
-
debug "FOUND LINK #{c} -- filling it."
|
612
|
-
idx = from.exits.index(c)
|
613
|
-
from[idx] = nil
|
614
|
-
from[dir] = c
|
615
|
-
c.dir = Connection::BOTH
|
616
|
-
c.exitBtext = go
|
617
|
-
end
|
618
|
-
else
|
619
|
-
debug "to[odir] empty."
|
620
|
-
# First, check if we have a dangling one-way link going to->from
|
621
|
-
# If we do, we use it.
|
622
|
-
c = oneway_link?(to, from)
|
623
|
-
if c
|
624
|
-
debug "FOUND LINK #{c} -- filling it."
|
625
|
-
idx = from.exits.index(c)
|
626
|
-
from[idx] = nil
|
627
|
-
from[dir] = c
|
628
|
-
c.dir = Connection::BOTH
|
629
|
-
c.exitBtext = go if go
|
630
|
-
end
|
631
|
-
end
|
632
|
-
|
633
|
-
if not c
|
634
|
-
debug "NEW LINK #{from} #{dir} to #{to} #{odir}"
|
635
|
-
begin
|
636
|
-
c = @map.new_connection(from, dir, to, odir)
|
637
|
-
c.exitAtext = go if go
|
638
|
-
c.dir = Connection::AtoB
|
639
|
-
c.type = type
|
640
|
-
rescue Section::ConnectionError
|
641
|
-
end
|
642
|
-
end
|
643
|
-
}
|
644
|
-
|
645
|
-
return r
|
646
|
-
end
|
647
|
-
|
648
|
-
#
|
649
|
-
# Create all the stuff we found
|
650
|
-
#
|
651
|
-
def create
|
652
|
-
@rooms.each { |r| create_room(r, 0, 0) }
|
653
|
-
@rooms = []
|
654
|
-
|
655
|
-
# Add objects to rooms
|
656
|
-
@objects.each { |obj|
|
657
|
-
obj.location.each { |loc|
|
658
|
-
r = @tags[loc]
|
659
|
-
next unless r and r.kind_of?(Room)
|
660
|
-
r.objects << obj.name + "\n"
|
661
|
-
}
|
662
|
-
}
|
663
|
-
end
|
664
|
-
|
665
|
-
|
666
|
-
if RUBY_PLATFORM =~ /win/
|
667
|
-
SEP = ';'
|
668
|
-
else
|
669
|
-
SEP = ':'
|
670
|
-
end
|
671
|
-
|
672
|
-
#
|
673
|
-
# Bring up the Inform properties window, to allow user to change
|
674
|
-
# settings
|
675
|
-
#
|
676
|
-
def properties
|
677
|
-
decor = DECOR_TITLE|DECOR_BORDER
|
678
|
-
|
679
|
-
dlg = FXDialogBox.new( @map.window.parent, "Inform Settings", decor )
|
680
|
-
mainFrame = FXVerticalFrame.new(dlg,
|
681
|
-
FRAME_SUNKEN|FRAME_THICK|
|
682
|
-
LAYOUT_FILL_X|LAYOUT_FILL_Y)
|
683
|
-
|
684
|
-
frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
|
685
|
-
|
686
|
-
FXLabel.new(frame, "Include Dirs: ", nil, 0, LAYOUT_FILL_X)
|
687
|
-
inc = FXTextField.new(frame, 80, nil, 0, LAYOUT_FILL_ROW)
|
688
|
-
inc.text = @include_dirs.join(SEP)
|
689
|
-
|
690
|
-
buttons = FXHorizontalFrame.new(mainFrame,
|
691
|
-
LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|
|
692
|
-
PACK_UNIFORM_WIDTH)
|
693
|
-
# Accept
|
694
|
-
FXButton.new(buttons, "&Accept", nil, dlg, FXDialogBox::ID_ACCEPT,
|
695
|
-
FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
696
|
-
|
697
|
-
# Cancel
|
698
|
-
FXButton.new(buttons, "&Cancel", nil, dlg, FXDialogBox::ID_CANCEL,
|
699
|
-
FRAME_RAISED|FRAME_THICK|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
700
|
-
if dlg.execute != 0
|
701
|
-
@include_dirs = inc.text.split(SEP)
|
702
|
-
return true
|
703
|
-
end
|
704
|
-
return false
|
705
|
-
end
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
def set_include_dirs
|
710
|
-
# Try to find inform(.exe) in path.
|
711
|
-
paths = ENV['PATH'].split(SEP)
|
712
|
-
paths.each { |p|
|
713
|
-
next if not File.directory?(p)
|
714
|
-
Dir.foreach(p) { |x|
|
715
|
-
if x =~ /^inform(.exe)?$/i
|
716
|
-
@include_dirs << p
|
717
|
-
@include_dirs << p + "/Base"
|
718
|
-
@include_dirs << p + "/Contrib"
|
719
|
-
@include_dirs << p + "/../Contrib"
|
720
|
-
break
|
721
|
-
end
|
722
|
-
}
|
723
|
-
}
|
724
|
-
end
|
725
|
-
|
726
|
-
def initialize(file, map = Map.new('Inform Map'))
|
727
|
-
debug "Initialize"
|
728
|
-
@classes = { 'Object' => {} }
|
729
|
-
@tags = {}
|
730
|
-
@map = map
|
731
|
-
@objects = []
|
732
|
-
@doors = []
|
733
|
-
@functions = []
|
734
|
-
@rooms = []
|
735
|
-
|
736
|
-
@include_dirs = [File.dirname(file)]
|
737
|
-
set_include_dirs
|
738
|
-
|
739
|
-
|
740
|
-
debug "Get properties"
|
741
|
-
if @map.kind_of?(FXMap)
|
742
|
-
return unless properties
|
743
|
-
end
|
744
|
-
|
745
|
-
debug "Start parsing #{file}"
|
746
|
-
File.open(file) { |f|
|
747
|
-
parse(f)
|
748
|
-
}
|
749
|
-
debug "Done parsing #{file}"
|
750
|
-
debug "Rooms: #{@rooms.size}"
|
751
|
-
debug "Doors: #{@doors.size}"
|
752
|
-
debug "Objects: #{@objects.size}"
|
753
|
-
|
754
|
-
create
|
755
|
-
debug "Done creating #{file}"
|
756
|
-
|
757
|
-
if @map.kind_of?(FXMap)
|
758
|
-
@map.filename = file.sub(/\.inf$/i, '.map')
|
759
|
-
# @map.navigation = true
|
760
|
-
@map.modified = false
|
761
|
-
@map.window.show
|
762
|
-
end
|
763
|
-
@objects = nil
|
764
|
-
@tags = nil # save some memory by clearing the tag list
|
765
|
-
@rooms = nil # and room list
|
766
|
-
end
|
767
|
-
end
|
768
|
-
|
769
|
-
|
770
|
-
if $0 == __FILE__
|
771
|
-
p "Opening file '#{ARGV[0]}'"
|
772
|
-
BEGIN {
|
773
|
-
$LOAD_PATH << 'C:\Windows\Escritorio\IFMapper\lib'
|
774
|
-
}
|
775
|
-
|
776
|
-
require "IFMapper/Map"
|
777
|
-
InformReader.new(ARGV[0])
|
778
|
-
end
|