ifmapper 1.0.0 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. data/HISTORY.txt +648 -627
  2. data/IFMapper.gemspec +29 -28
  3. data/IFMapper.rbw +31 -31
  4. data/TODO.txt +8 -7
  5. data/bin/IFMapper +31 -31
  6. data/docs/en/index.html +0 -0
  7. data/docs/en/start.html +3 -2
  8. data/docs/en/start.html~ +516 -0
  9. data/docs/es/index.html +0 -0
  10. data/docs/es/start.html +13 -14
  11. data/docs/es/start.html~ +1280 -0
  12. data/docs/images/IFMapper_main.gif +0 -0
  13. data/docs/images/automap.gif +0 -0
  14. data/docs/images/complex_connection.gif +0 -0
  15. data/docs/images/connection.gif +0 -0
  16. data/docs/images/connection_menu.gif +0 -0
  17. data/docs/images/room_description.gif +0 -0
  18. data/docs/images/room_small.gif +0 -0
  19. data/icons/copy.png +0 -0
  20. data/icons/cut.png +0 -0
  21. data/icons/filenew.png +0 -0
  22. data/icons/fileopen.png +0 -0
  23. data/icons/filesave.png +0 -0
  24. data/icons/filesaveas.png +0 -0
  25. data/icons/help.png +0 -0
  26. data/icons/kill.png +0 -0
  27. data/icons/nextpage.png +0 -0
  28. data/icons/paste.png +0 -0
  29. data/icons/prevpage.png +0 -0
  30. data/icons/printicon.png +0 -0
  31. data/icons/redo.png +0 -0
  32. data/icons/room_e.gif +0 -0
  33. data/icons/room_e.xpm +0 -0
  34. data/icons/room_n.gif +0 -0
  35. data/icons/room_n.xpm +0 -0
  36. data/icons/room_ne.gif +0 -0
  37. data/icons/room_ne.xpm +0 -0
  38. data/icons/room_nw.gif +0 -0
  39. data/icons/room_nw.xpm +0 -0
  40. data/icons/room_s.gif +0 -0
  41. data/icons/room_s.xpm +0 -0
  42. data/icons/room_se.gif +0 -0
  43. data/icons/room_se.xpm +0 -0
  44. data/icons/room_sw.gif +0 -0
  45. data/icons/room_sw.xpm +0 -0
  46. data/icons/room_w.gif +0 -0
  47. data/icons/room_w.xpm +0 -0
  48. data/icons/saveas.png +0 -0
  49. data/icons/undo.png +0 -0
  50. data/icons/winapp.png +0 -0
  51. data/icons/zoom.png +0 -0
  52. data/lib/IFMapper/AStar.rb +250 -250
  53. data/lib/IFMapper/Connection.rb +202 -202
  54. data/lib/IFMapper/FXAboutDialogBox.rb +32 -32
  55. data/lib/IFMapper/FXConnection.rb +364 -364
  56. data/lib/IFMapper/FXConnectionDialogBox.rb +124 -124
  57. data/lib/IFMapper/FXDCPostscript.rb +404 -404
  58. data/lib/IFMapper/FXDCPrint.rb +15 -15
  59. data/lib/IFMapper/FXItemList.rb +108 -0
  60. data/lib/IFMapper/FXMap.rb +2147 -2116
  61. data/lib/IFMapper/FXMapColorBox.rb +88 -88
  62. data/lib/IFMapper/FXMapDialogBox.rb +127 -127
  63. data/lib/IFMapper/FXMapFileDialog.rb +34 -34
  64. data/lib/IFMapper/FXMapperSettings.rb +206 -205
  65. data/lib/IFMapper/FXMapperWindow.rb +1592 -1571
  66. data/lib/IFMapper/FXPDFMapExporterOptionsDialogBox.rb +46 -0
  67. data/lib/IFMapper/FXRoom.rb +263 -263
  68. data/lib/IFMapper/FXRoomDialogBox.rb +159 -159
  69. data/lib/IFMapper/FXRoomList.rb +95 -95
  70. data/lib/IFMapper/FXSearchDialogBox.rb +51 -51
  71. data/lib/IFMapper/FXSection.rb +33 -33
  72. data/lib/IFMapper/FXSectionDialogBox.rb +38 -38
  73. data/lib/IFMapper/FXSpline.rb +52 -52
  74. data/lib/IFMapper/FXWarningBox.rb +51 -50
  75. data/lib/IFMapper/GUEReader.rb +445 -445
  76. data/lib/IFMapper/IFMReader.rb +584 -584
  77. data/lib/IFMapper/IFMWriter.rb +245 -227
  78. data/lib/IFMapper/Inform7Writer.rb +579 -573
  79. data/lib/IFMapper/InformReader.rb +478 -478
  80. data/lib/IFMapper/InformWriter.rb +364 -359
  81. data/lib/IFMapper/Map.rb +202 -200
  82. data/lib/IFMapper/MapPrinting.rb +162 -162
  83. data/lib/IFMapper/MapReader.rb +900 -900
  84. data/lib/IFMapper/PDFMapExporter.rb +526 -483
  85. data/lib/IFMapper/Room.rb +153 -151
  86. data/lib/IFMapper/Section.rb +234 -234
  87. data/lib/IFMapper/TADSReader.rb +474 -471
  88. data/lib/IFMapper/TADSWriter.rb +375 -370
  89. data/lib/IFMapper/TranscriptDialogBox.rb +0 -0
  90. data/lib/IFMapper/TranscriptReader.rb +1361 -1359
  91. data/lib/IFMapper/locales/en/Messages.rb +446 -435
  92. data/lib/IFMapper/locales/es/Messages.rb +451 -440
  93. data/lib/IFMapper/locales/es/Messages_iso-8859-1.rb +455 -440
  94. data/lib/IFMapper/locales/es/runme.sh +3 -3
  95. data/maps/A New Life.map b/data/maps/A New → Life.map +0 -0
  96. data/maps/AMFV.map +0 -0
  97. data/maps/AllRoads.map +0 -0
  98. data/maps/Aotearoa.map +0 -0
  99. data/maps/Bronze.map +0 -0
  100. data/maps/Bureaucracy.ifm +0 -0
  101. data/maps/Bureaucracy.map +0 -0
  102. data/maps/CityOfSecrets.map +0 -0
  103. data/maps/DDIV.map +0 -0
  104. data/maps/Following_A_Star.map +0 -0
  105. data/maps/Heated.map +0 -0
  106. data/maps/Heroine.map +0 -0
  107. data/maps/History Repeating.map b/data/maps/History → Repeating.map +0 -0
  108. data/maps/Hollywood_Hijinx.ifm +0 -0
  109. data/maps/Janitor.map +0 -0
  110. data/maps/Jigsaw.ifm +0 -0
  111. data/maps/Jigsaw.map +0 -0
  112. data/maps/LGOP.ifm +0 -0
  113. data/maps/Mercy.ifm +0 -0
  114. data/maps/Ninjas_Fate.map +0 -0
  115. data/maps/Pen_and_Paint.map +0 -0
  116. data/maps/Planetfall.ifm +0 -0
  117. data/maps/Planetfall.map +0 -0
  118. data/maps/Plundered_Hearts.ifm +0 -0
  119. data/maps/QuietEvening.map +0 -0
  120. data/maps/Ralph.ifm +0 -0
  121. data/maps/Reliques_of_Tolti_Alph.map +0 -0
  122. data/maps/Revolution.map +0 -0
  123. data/maps/Robots_of_Dawn.ifm +0 -0
  124. data/maps/SavoirFare.map +0 -0
  125. data/maps/Seastalker.ifm +0 -0
  126. data/maps/Seastalker.map +0 -0
  127. data/maps/Sherlock.ifm +0 -0
  128. data/maps/SoFar.ifm +0 -0
  129. data/maps/Starcross.ifm +0 -0
  130. data/maps/Suspended.ifm +0 -0
  131. data/maps/Tangle.map +0 -0
  132. data/maps/The_Lost_Sheep.map +0 -0
  133. data/maps/Unforgotten.map +0 -0
  134. data/maps/Warbler's Nest.map +0 -0
  135. data/maps/Warbler's_Nest.map +0 -0
  136. data/maps/Westminster_Abbey.map +0 -0
  137. data/maps/WinterWonderland.map +0 -0
  138. data/maps/Wishbringer.ifm +0 -0
  139. data/maps/Wishbringer2.ifm +0 -0
  140. data/maps/Zork1.ifm +0 -0
  141. data/maps/Zork2.ifm +0 -0
  142. data/maps/Zork3.ifm +0 -0
  143. data/maps/Zork_Zero.ifm +0 -0
  144. data/maps/anchor.ifm +0 -0
  145. data/maps/anchor.map +0 -0
  146. data/maps/atrox.ifm +0 -0
  147. data/maps/awaken.ifm +0 -0
  148. data/maps/babel.ifm +0 -0
  149. data/maps/balances.map +0 -0
  150. data/maps/ballerina.map +0 -0
  151. data/maps/bear.map +0 -0
  152. data/maps/bluechairs.map +0 -0
  153. data/maps/break_in.map +0 -0
  154. data/maps/bse.ifm +0 -0
  155. data/maps/building.map +0 -0
  156. data/maps/change.ifm +0 -0
  157. data/maps/christminster.map +0 -0
  158. data/maps/curses.ifm +0 -0
  159. data/maps/curves.ifm +0 -0
  160. data/maps/deadline.map +0 -0
  161. data/maps/delusions.map +0 -0
  162. data/maps/devours.map +0 -0
  163. data/maps/distress.map +0 -0
  164. data/maps/djinni.map +0 -0
  165. data/maps/dreamhold.map +0 -0
  166. data/maps/drift3.map +0 -0
  167. data/maps/eas.map +0 -0
  168. data/maps/eas2.map +0 -0
  169. data/maps/eas3.map +0 -0
  170. data/maps/edifice.ifm +0 -0
  171. data/maps/fallacy.map +0 -0
  172. data/maps/frozen.ifm +0 -0
  173. data/maps/gamlet.map +0 -0
  174. data/maps/glow.ifm +0 -0
  175. data/maps/guilty_bastards.map +0 -0
  176. data/maps/heist.map +0 -0
  177. data/maps/heroes.map +0 -0
  178. data/maps/inhumane.map +0 -0
  179. data/maps/kaged.map +0 -0
  180. data/maps/library.ifm +0 -0
  181. data/maps/lurkinghorror.map +0 -0
  182. data/maps/metamorphoses.map +0 -0
  183. data/maps/mindelec.ifm +0 -0
  184. data/maps/minster.ifm +0 -0
  185. data/maps/mite.map +0 -0
  186. data/maps/moonmist.map +0 -0
  187. data/maps/muldoon_legacy.map +0 -0
  188. data/maps/muse.ifm +0 -0
  189. data/maps/paperchase.ifm +0 -0
  190. data/maps/party.map +0 -0
  191. data/maps/pawn.map +0 -0
  192. data/maps/photograph.map +0 -0
  193. data/maps/pkgirl.map +0 -0
  194. data/maps/pytho.map +0 -0
  195. data/maps/risorgimento.map +0 -0
  196. data/maps/sherbet.map +0 -0
  197. data/maps/simple.map +0 -0
  198. data/maps/slouch.map +0 -0
  199. data/maps/space_st.ifm +0 -0
  200. data/maps/splashdown.map +0 -0
  201. data/maps/spring.map +0 -0
  202. data/maps/squarecircle.map +0 -0
  203. data/maps/stationfall.ifm +0 -0
  204. data/maps/theatre.ifm +0 -0
  205. data/maps/toonesia.ifm +0 -0
  206. data/maps/tortoise.ifm +0 -0
  207. data/maps/trinity.map +0 -0
  208. data/maps/vespers.map +0 -0
  209. data/maps/vgame.ifm +0 -0
  210. data/maps/wasp.map +0 -0
  211. data/maps/weather.ifm +0 -0
  212. data/maps/windhall.ifm +0 -0
  213. data/maps/worlds.map +0 -0
  214. data/maps/xtcontest.map +0 -0
  215. data/maps/zdungeon.map +0 -0
  216. data/maps/zebulon.ifm +0 -0
  217. data/maps/zerosum.map +0 -0
  218. metadata +226 -183
@@ -1,573 +1,579 @@
1
-
2
-
3
- class Inform7Writer
4
-
5
-
6
- DIRECTIONS = [
7
- 'North',
8
- 'Northeast',
9
- 'East',
10
- 'Southeast',
11
- 'South',
12
- 'Southwest',
13
- 'West',
14
- 'Northwest',
15
- ]
16
-
17
- OTHERDIRS = [
18
- '',
19
- 'Above',
20
- 'Below',
21
- 'Inside',
22
- 'Outside',
23
- ]
24
-
25
- IGNORE_WORDS = [
26
- 'a', 'the', 'and', 'of', 'your', 'to', 'out', 'in'
27
- ]
28
-
29
- IGNORED_ARTICLES = /^(?:#{IGNORE_WORDS.join('|')})$/
30
-
31
-
32
-
33
- KEYWORDS = [
34
- 'include',
35
- 'has',
36
- 'with',
37
- 'is',
38
- 'in',
39
- 'container',
40
- 'to',
41
- 'and',
42
- 'side',
43
- '\s*,\s*',
44
- '\s*\(\s*',
45
- ] + DIRECTIONS + OTHERDIRS[1..-1]
46
-
47
- INVALID_KEYWORD = /\b(?:#{KEYWORDS.join('|')})\b/i
48
-
49
-
50
- LOCATION_NAMES = [
51
- 'door',
52
- 'include',
53
- 'room',
54
- 'has',
55
- 'with',
56
- 'is',
57
- 'container',
58
- ] + DIRECTIONS
59
- INVALID_LOCATION_NAME = /^(?:#{LOCATION_NAMES.join('|')})$/i
60
-
61
-
62
- #
63
- # Some common animals in adventure games
64
- #
65
- ANIMALS = [
66
- 'horse',
67
- 'donkey',
68
- 'mule',
69
- 'goat',
70
- # lizards
71
- 'lizard',
72
- 'snake',
73
- 'turtle',
74
- # fishes
75
- 'fish',
76
- 'dolphin',
77
- 'whale',
78
- 'tortoise',
79
- # dogs
80
- 'dog',
81
- 'wolf',
82
- # cats
83
- 'cat',
84
- 'leopard',
85
- 'tiger',
86
- 'lion',
87
- # fantastic
88
- 'grue',
89
- # birds
90
- 'bird',
91
- 'pigeon',
92
- 'peacock',
93
- 'eagle',
94
- ]
95
-
96
- IS_ANIMAL = /\b(?:#{ANIMALS.join('|')})\b/i
97
-
98
- MALE_PEOPLE = [
99
- 'boy',
100
- 'man',
101
- 'sir',
102
- ]
103
- IS_MALE_PERSON = /\b(?:#{MALE_PEOPLE.join('|')})\b/i
104
-
105
- FEMALE_PEOPLE = [
106
- 'girl',
107
- 'woman',
108
- 'lady',
109
- 'actress',
110
- ]
111
- IS_FEMALE_PERSON = /\b(?:#{FEMALE_PEOPLE.join('|')})\b/i
112
-
113
- PEOPLE = MALE_PEOPLE + FEMALE_PEOPLE + [
114
- 'child',
115
- 'attendant',
116
- 'doctor',
117
- 'actor',
118
- 'character',
119
- 'engineer',
120
- 'bum',
121
- 'nerd',
122
- 'ghost',
123
- ]
124
-
125
- IS_PERSON = /\b(?:#{PEOPLE.join('|')})\b/i
126
-
127
- #
128
- # Some common container types
129
- #
130
- CONTAINERS = [
131
- 'box',
132
- 'cup',
133
- 'crate',
134
- 'tin',
135
- 'can',
136
- 'chest',
137
- 'wardrobe',
138
- 'jacket',
139
- 'suit',
140
- 'trophy case',
141
- 'coffin',
142
- 'briefcase',
143
- 'suitcase',
144
- 'bag',
145
- 'flask',
146
- ]
147
- IS_CONTAINER = /\b(?:#{CONTAINERS.join('|')})\b/i
148
-
149
- #
150
- # Some common lit types
151
- #
152
- UNLIT_TYPES = [
153
- 'lantern',
154
- 'torch',
155
- ]
156
- IS_LIGHT = /\b(?:#{UNLIT_TYPES.join('|')})\b/i
157
-
158
- #
159
- # Some common wearable types
160
- #
161
- CLOTHES = [
162
- 'cape',
163
- 'glove',
164
- 'jeans',
165
- 'trousers',
166
- 'hat',
167
- 'gown',
168
- 'backpack',
169
- 'shirt',
170
- 'jacket',
171
- 'suit',
172
- 'ring',
173
- 'bracelet',
174
- 'amulet',
175
- 'locket',
176
- ]
177
- IS_WEARABLE = /\b(?:#{CLOTHES.join('|')})\b/i
178
-
179
- #
180
- # Some common supporter types
181
- #
182
- SUPPORTERS = [
183
- 'table',
184
- 'shelf',
185
- ]
186
- IS_SUPPORTER = /\b(?:#{SUPPORTERS.join('|')})\b/i
187
-
188
- #
189
- # Some common edible types
190
- #
191
- FOOD = [
192
- 'bread',
193
- 'cake',
194
- # drinks
195
- 'water',
196
- 'soda',
197
- 'beer',
198
- 'beverage',
199
- 'potion',
200
- # fruits
201
- 'fruit',
202
- 'apple',
203
- 'orange',
204
- 'banana',
205
- 'almond',
206
- 'nut',
207
- ]
208
- IS_EDIBLE = /\b(?:#{FOOD.join('|')})\b/i
209
-
210
- def new_tag(elem, str)
211
- tag = str.dup
212
-
213
- # Take text from Unicode utf-8 to iso-8859-1
214
- utf = Iconv.new( 'iso-8859-1', 'utf-8' )
215
- tag = utf.iconv( tag )
216
-
217
- # Remove redundant spaces
218
- tag.sub!(/^\s/, '')
219
- tag.sub!(/\s$/, '')
220
-
221
- # Invalid tag characters, replaced with _
222
- tag.gsub!(/[\s"\\\#\,\.:;!\?\n\(\)]+/,'_')
223
- tag.sub!(/^([\d]+)_?(.*)/, '\2\1') # No numbers allowed at start of tag
224
-
225
- tag.gsub!(/__/, '_')
226
-
227
- # tag cannot be repeated and cannot be keyword (Doorway, Room, etc)
228
- # In those cases, we add a number to the tag name.
229
- idx = 0
230
- if @tags.values.include?(tag) or tag =~ INVALID_LOCATION_NAME
231
- idx = 1
232
- end
233
-
234
-
235
-
236
- if idx > 0
237
- root = tag.dup
238
- tag = "#{root}#{idx}"
239
- while @tags.values.include?(tag)
240
- tag = "#{root}#{idx}"
241
- idx += 1
242
- end
243
- end
244
-
245
-
246
- if elem.kind_of?(String)
247
- @tags[tag] = tag
248
- else
249
- @tags[elem] = tag
250
- end
251
- return tag
252
- end
253
-
254
- def get_tag(elem, name)
255
- return @tags[elem] if @tags[elem]
256
- tag = name
257
- if tag =~ INVALID_KEYWORD or @tags.values.include?(tag)
258
- return new_tag(elem, name)
259
- else
260
- @tags[elem] = tag
261
- end
262
- return tag
263
- end
264
-
265
- def get_room_tag(elem)
266
- return @tags[elem] if @tags[elem]
267
- tag = elem.name
268
- if tag =~ INVALID_KEYWORD or @tags.values.include?(tag)
269
- tag = new_tag(elem, elem.name)
270
- else
271
- @tags[elem] = tag
272
- end
273
- return tag
274
- end
275
-
276
- def get_door_name(e)
277
- dirA = e.roomA.exits.index(e)
278
- dirB = e.roomB.exits.rindex(e)
279
- name = DIRECTIONS[dirA].downcase + "-" + DIRECTIONS[dirB].downcase
280
- name << " door"
281
- end
282
-
283
- def get_door_tag(e)
284
- name = get_door_name(e)
285
- name.upcase
286
- get_tag(e, name)
287
- end
288
-
289
- def wrap_text(text, width = 75, indent = 78 - width)
290
- return 'UNDER CONSTRUCTION.' if not text or text == ''
291
- str = inform_quote( text )
292
-
293
- if str.size > width
294
- r = ''
295
- while str
296
- if str.size >= width
297
- idx = str.rindex(/[ -]/, width)
298
- idx = str.size unless idx
299
- else
300
- idx = str.size
301
- end
302
- r << str[0..idx]
303
- str = str[idx+1..-1]
304
- r << "\n" << ' ' * indent if str
305
- end
306
- return r
307
- else
308
- return str
309
- end
310
- end
311
-
312
-
313
- #
314
- # Take a text and quote it for inform's double-quote text areas.
315
- #
316
- def inform_quote(text)
317
- str = text.dup
318
-
319
- # Take text from Unicode utf-8 to iso-8859-1
320
- utf = Iconv.new( 'iso-8859-1', 'utf-8' )
321
- str = utf.iconv( str )
322
-
323
- # Quote special characters
324
- # str.gsub!(/@/, '@@64')
325
- str.gsub!(/"/, '\'')
326
- # str.gsub!(/~/, '@@126')
327
- # str.gsub!(/\\/, '@@92')
328
- # str.gsub!(/\^/, '@@94')
329
- return str
330
- end
331
-
332
-
333
- def objects(r)
334
- room = get_room_tag(r)
335
- objs = r.objects.split("\n")
336
- objs.each { |o|
337
-
338
- tag = get_tag(o, o)
339
-
340
- names = o.dup
341
- names.gsub!(/"/, '') # remove any quotes
342
- names.gsub!(/\b\w\b/, '') # remove single letter words
343
-
344
- name = names
345
- names = name.split(' ')
346
-
347
- article = 'a '
348
- if name =~ /ves$/ or name =~ /s$/
349
- article = 'some '
350
- elsif name =~ /^[aeiou]/
351
- article = 'an '
352
- end
353
-
354
-
355
- # If name begins with uppercase, assume it is an NPC
356
- type = 'a thing'
357
-
358
- if name =~ IS_ANIMAL
359
- type = 'an animal'
360
- elsif name =~ IS_PERSON
361
- article = 'a '
362
- type = 'a person'
363
- if name =~ IS_MALE_PERSON
364
- type = 'a male person'
365
- elsif name =~ IS_FEMALE_PERSON
366
- type = 'a female person'
367
- end
368
- elsif name =~ /[A-Z]/
369
- if name !~ /'/ # possesive, like Michael's wallet
370
- # if too many words, probably a book's title
371
- if names.size <= 3
372
- article = ''
373
- if name =~ /^(?:Miss|Mrs)/
374
- type = 'a woman'
375
- else
376
- type = 'a person'
377
- end
378
- end
379
- end
380
- end
381
-
382
-
383
- props = ''
384
-
385
- if tag != name
386
- props << "\n The printed name of #{tag} is \"#{article}#{name}\"."
387
- else
388
- tag = article + tag
389
- end
390
-
391
- if name =~ IS_LIGHT
392
- props << "\n It is not lit."
393
- end
394
-
395
- if name =~ IS_CONTAINER
396
- props << "\n It is a container."
397
- end
398
-
399
- if name =~ IS_SUPPORTER
400
- props << "\n It is a supporter."
401
- end
402
-
403
- if name =~ IS_WEARABLE
404
- props << "\n It is wearable."
405
- end
406
-
407
- if name =~ IS_EDIBLE
408
- props << "\n It is edible."
409
- end
410
-
411
- @f.print <<"EOF"
412
-
413
- In #{room}, there is #{type} called #{tag}. #{props}
414
- The description of #{tag} is \"UNDER CONSTRUCTION.\"
415
- EOF
416
- if names.size > 1
417
- names.each { |n|
418
- next if n =~ IGNORED_ARTICLES
419
- @f.puts " Understand \"#{n}\" as #{tag}."
420
- }
421
- end
422
- }
423
-
424
-
425
- end
426
-
427
- def door(e)
428
- name = get_door_name(e)
429
- tag = get_tag(e, name)
430
- roomA = get_room_tag(e.roomA)
431
- roomB = get_room_tag(e.roomB)
432
- dirA = e.roomA.exits.index(e)
433
- dirB = e.roomB.exits.rindex(e)
434
- dirA = DIRECTIONS[dirA].downcase
435
- dirB = DIRECTIONS[dirB].downcase
436
-
437
- props = ''
438
- if e.type == Connection::LOCKED_DOOR
439
- props = "It is locked."
440
- elsif e.type == Connection::CLOSED_DOOR
441
- props = "It is closed."
442
- end
443
-
444
- found_in = 'It is '
445
- if e.dir == Connection::BOTH
446
- found_in << "#{dirA} of #{roomA} and #{dirB} of #{roomB}"
447
- elsif e.dir == Connection::AtoB
448
- found_in << "#{dirA} of #{roomA}. Through it is #{roomB}"
449
- elsif e.dir == Connection::BtoA
450
- found_in << "#{dirB} of #{roomB}. Through it is #{roomA}"
451
- end
452
-
453
- @f.puts <<"EOF"
454
-
455
- The #{tag} is a door. #{props}
456
- The printed name of #{tag} is "a #{name}".
457
- Understand "#{name}" as #{tag}.
458
- Understand "door" as #{tag}.
459
- #{found_in}.
460
- EOF
461
-
462
- end
463
-
464
-
465
- def room(r)
466
- tag = get_room_tag(r)
467
- name = r.name
468
-
469
- prop = ''
470
- if r.darkness
471
- prop << 'dark '
472
- end
473
-
474
- @f.print "\n#{tag} is a #{prop}room."
475
-
476
- if name != tag
477
- name = inform_quote(name)
478
- @f.print " The printed name of #{tag} is \"#{name}\"."
479
- end
480
-
481
- @f.puts
482
- @f.puts " \"#{wrap_text(r.desc)}\""
483
-
484
- # Now, handle exits...
485
- r.exits.each_with_index { |e, dir|
486
- next if (not e) or e.stub? or e.type == Connection::SPECIAL
487
- if e.roomB == r
488
- next if e.dir == Connection::AtoB
489
- text = e.exitBtext
490
- b = e.roomA
491
- else
492
- next if e.dir == Connection::BtoA
493
- text = e.exitAtext
494
- b = e.roomB
495
- end
496
- @f.print ' '
497
- if text == 0
498
- @f.print "#{DIRECTIONS[dir]} is "
499
- else
500
- @f.print "#{OTHERDIRS[text]} is "
501
- end
502
- if e.type == Connection::CLOSED_DOOR or
503
- e.type == Connection::LOCKED_DOOR
504
- @f.print get_door_tag(e)
505
- else
506
- @f.print get_room_tag(b)
507
- end
508
- @f.puts '.'
509
- }
510
- objects(r)
511
- end
512
-
513
- def section(sect, idx)
514
- name = sect.name
515
- name = 'Unnamed' if name.to_s == ''
516
-
517
- @f.puts
518
- @f.puts "Section #{idx+1} - #{name}"
519
- @f.puts
520
-
521
- @f.puts
522
- @f.puts "Part 1 - Room Descriptions"
523
- @f.puts
524
- sect.rooms.each { |r| room(r) }
525
- @f.puts
526
- @f.puts
527
-
528
- @f.puts
529
- @f.puts "Part 2 - Doors"
530
- @f.puts
531
- sect.connections.each { |e|
532
- next if (e.type != Connection::LOCKED_DOOR and
533
- e.type != Connection::CLOSED_DOOR)
534
- door(e)
535
- }
536
- end
537
-
538
- def start
539
- @f = File.open("#@root.inform", "w")
540
- story = @map.name
541
- story = 'Untitled' if story == ''
542
- today = Date.today
543
- serial = today.strftime("%y%m%d")
544
-
545
- @f.puts
546
- @f.puts "\"#{story}\" by \"#{@map.creator}\""
547
- @f.puts
548
- @f.puts <<"EOF"
549
- The story genre is "Unknown". The release number is 1.
550
- The story headline is "An Interactive Fiction".
551
- The story description is "".
552
- The story creation year is #{today.year}.
553
-
554
-
555
- Use full-length room descriptions.
556
-
557
- EOF
558
-
559
- @map.sections.each_with_index { |sect, idx|
560
- section(sect, idx)
561
- }
562
- @f.close
563
- end
564
-
565
- def initialize(map, fileroot)
566
- @tags = {}
567
- @root = fileroot
568
- @base = File.basename(@root)
569
- @map = map
570
-
571
- start
572
- end
573
- end
1
+
2
+
3
+ class Inform7Writer
4
+
5
+
6
+ DIRECTIONS = [
7
+ 'North',
8
+ 'Northeast',
9
+ 'East',
10
+ 'Southeast',
11
+ 'South',
12
+ 'Southwest',
13
+ 'West',
14
+ 'Northwest',
15
+ ]
16
+
17
+ OTHERDIRS = [
18
+ '',
19
+ 'Above',
20
+ 'Below',
21
+ 'Inside',
22
+ 'Outside',
23
+ ]
24
+
25
+ IGNORE_WORDS = [
26
+ 'a', 'the', 'and', 'of', 'your', 'to', 'out', 'in'
27
+ ]
28
+
29
+ IGNORED_ARTICLES = /^(?:#{IGNORE_WORDS.join('|')})$/
30
+
31
+
32
+
33
+ KEYWORDS = [
34
+ 'include',
35
+ 'has',
36
+ 'with',
37
+ 'is',
38
+ 'in',
39
+ 'container',
40
+ 'to',
41
+ 'and',
42
+ 'side',
43
+ '\s*,\s*',
44
+ '\s*\(\s*',
45
+ ] + DIRECTIONS + OTHERDIRS[1..-1]
46
+
47
+ INVALID_KEYWORD = /\b(?:#{KEYWORDS.join('|')})\b/i
48
+
49
+
50
+ LOCATION_NAMES = [
51
+ 'door',
52
+ 'include',
53
+ 'room',
54
+ 'has',
55
+ 'with',
56
+ 'is',
57
+ 'container',
58
+ ] + DIRECTIONS
59
+ INVALID_LOCATION_NAME = /^(?:#{LOCATION_NAMES.join('|')})$/i
60
+
61
+
62
+ #
63
+ # Some common animals in adventure games
64
+ #
65
+ ANIMALS = [
66
+ 'horse',
67
+ 'donkey',
68
+ 'mule',
69
+ 'goat',
70
+ # lizards
71
+ 'lizard',
72
+ 'snake',
73
+ 'turtle',
74
+ # fishes
75
+ 'fish',
76
+ 'dolphin',
77
+ 'whale',
78
+ 'tortoise',
79
+ # dogs
80
+ 'dog',
81
+ 'wolf',
82
+ # cats
83
+ 'cat',
84
+ 'leopard',
85
+ 'tiger',
86
+ 'lion',
87
+ # fantastic
88
+ 'grue',
89
+ # birds
90
+ 'bird',
91
+ 'pigeon',
92
+ 'peacock',
93
+ 'eagle',
94
+ ]
95
+
96
+ IS_ANIMAL = /\b(?:#{ANIMALS.join('|')})\b/i
97
+
98
+ MALE_PEOPLE = [
99
+ 'boy',
100
+ 'man',
101
+ 'sir',
102
+ ]
103
+ IS_MALE_PERSON = /\b(?:#{MALE_PEOPLE.join('|')})\b/i
104
+
105
+ FEMALE_PEOPLE = [
106
+ 'girl',
107
+ 'woman',
108
+ 'lady',
109
+ 'actress',
110
+ ]
111
+ IS_FEMALE_PERSON = /\b(?:#{FEMALE_PEOPLE.join('|')})\b/i
112
+
113
+ PEOPLE = MALE_PEOPLE + FEMALE_PEOPLE + [
114
+ 'child',
115
+ 'attendant',
116
+ 'doctor',
117
+ 'actor',
118
+ 'character',
119
+ 'engineer',
120
+ 'bum',
121
+ 'nerd',
122
+ 'ghost',
123
+ ]
124
+
125
+ IS_PERSON = /\b(?:#{PEOPLE.join('|')})\b/i
126
+
127
+ #
128
+ # Some common container types
129
+ #
130
+ CONTAINERS = [
131
+ 'box',
132
+ 'cup',
133
+ 'crate',
134
+ 'tin',
135
+ 'can',
136
+ 'chest',
137
+ 'wardrobe',
138
+ 'jacket',
139
+ 'suit',
140
+ 'trophy case',
141
+ 'coffin',
142
+ 'briefcase',
143
+ 'suitcase',
144
+ 'bag',
145
+ 'flask',
146
+ ]
147
+ IS_CONTAINER = /\b(?:#{CONTAINERS.join('|')})\b/i
148
+
149
+ #
150
+ # Some common lit types
151
+ #
152
+ UNLIT_TYPES = [
153
+ 'lantern',
154
+ 'torch',
155
+ ]
156
+ IS_LIGHT = /\b(?:#{UNLIT_TYPES.join('|')})\b/i
157
+
158
+ #
159
+ # Some common wearable types
160
+ #
161
+ CLOTHES = [
162
+ 'cape',
163
+ 'glove',
164
+ 'jeans',
165
+ 'trousers',
166
+ 'hat',
167
+ 'gown',
168
+ 'backpack',
169
+ 'shirt',
170
+ 'jacket',
171
+ 'suit',
172
+ 'ring',
173
+ 'bracelet',
174
+ 'amulet',
175
+ 'locket',
176
+ ]
177
+ IS_WEARABLE = /\b(?:#{CLOTHES.join('|')})\b/i
178
+
179
+ #
180
+ # Some common supporter types
181
+ #
182
+ SUPPORTERS = [
183
+ 'table',
184
+ 'shelf',
185
+ ]
186
+ IS_SUPPORTER = /\b(?:#{SUPPORTERS.join('|')})\b/i
187
+
188
+ #
189
+ # Some common edible types
190
+ #
191
+ FOOD = [
192
+ 'bread',
193
+ 'cake',
194
+ # drinks
195
+ 'water',
196
+ 'soda',
197
+ 'beer',
198
+ 'beverage',
199
+ 'potion',
200
+ # fruits
201
+ 'fruit',
202
+ 'apple',
203
+ 'orange',
204
+ 'banana',
205
+ 'almond',
206
+ 'nut',
207
+ ]
208
+ IS_EDIBLE = /\b(?:#{FOOD.join('|')})\b/i
209
+
210
+ def new_tag(elem, str)
211
+ tag = str.dup
212
+
213
+ # Take text from Unicode utf-8 to iso-8859-1
214
+ utf = Iconv.new( 'iso-8859-1', 'utf-8' )
215
+ tag = utf.iconv( tag )
216
+
217
+ # Remove redundant spaces
218
+ tag.sub!(/^\s/, '')
219
+ tag.sub!(/\s$/, '')
220
+
221
+ # Invalid tag characters, replaced with _
222
+ tag.gsub!(/[\s"\\\#\,\.:;!\?\n\(\)]+/,'_')
223
+ tag.sub!(/^([\d]+)_?(.*)/, '\2\1') # No numbers allowed at start of tag
224
+
225
+ tag.gsub!(/__/, '_')
226
+
227
+ # tag cannot be repeated and cannot be keyword (Doorway, Room, etc)
228
+ # In those cases, we add a number to the tag name.
229
+ idx = 0
230
+ if @tags.values.include?(tag) or tag =~ INVALID_LOCATION_NAME
231
+ idx = 1
232
+ end
233
+
234
+
235
+
236
+ if idx > 0
237
+ root = tag.dup
238
+ tag = "#{root}#{idx}"
239
+ while @tags.values.include?(tag)
240
+ tag = "#{root}#{idx}"
241
+ idx += 1
242
+ end
243
+ end
244
+
245
+
246
+ if elem.kind_of?(String)
247
+ @tags[tag] = tag
248
+ else
249
+ @tags[elem] = tag
250
+ end
251
+ return tag
252
+ end
253
+
254
+ def get_tag(elem, name)
255
+ return @tags[elem] if @tags[elem]
256
+ tag = name
257
+ if tag =~ INVALID_KEYWORD or @tags.values.include?(tag)
258
+ return new_tag(elem, name)
259
+ else
260
+ @tags[elem] = tag
261
+ end
262
+ return tag
263
+ end
264
+
265
+ def get_room_tag(elem)
266
+ return @tags[elem] if @tags[elem]
267
+ tag = elem.name
268
+ if tag =~ INVALID_KEYWORD or @tags.values.include?(tag)
269
+ tag = new_tag(elem, elem.name)
270
+ else
271
+ @tags[elem] = tag
272
+ end
273
+ return tag
274
+ end
275
+
276
+ def get_door_name(e)
277
+ dirA = e.roomA.exits.index(e)
278
+ dirB = e.roomB.exits.rindex(e)
279
+ name = DIRECTIONS[dirA].downcase + "-" + DIRECTIONS[dirB].downcase
280
+ name << " door"
281
+ end
282
+
283
+ def get_door_tag(e)
284
+ name = get_door_name(e)
285
+ name.upcase
286
+ get_tag(e, name)
287
+ end
288
+
289
+ def wrap_text(text, width = 75, indent = 78 - width)
290
+ return 'UNDER CONSTRUCTION.' if not text or text == ''
291
+ str = inform_quote( text )
292
+
293
+ if str.size > width
294
+ r = ''
295
+ while str
296
+ if str.size >= width
297
+ idx = str.rindex(/[ -]/, width)
298
+ idx = str.size unless idx
299
+ else
300
+ idx = str.size
301
+ end
302
+ r << str[0..idx]
303
+ str = str[idx+1..-1]
304
+ r << "\n" << ' ' * indent if str
305
+ end
306
+ return r
307
+ else
308
+ return str
309
+ end
310
+ end
311
+
312
+
313
+ #
314
+ # Take a text and quote it for inform's double-quote text areas.
315
+ #
316
+ def inform_quote(text)
317
+ str = text.dup
318
+
319
+ # Take text from Unicode utf-8 to iso-8859-1
320
+ utf = Iconv.new( 'iso-8859-1', 'utf-8' )
321
+ str = utf.iconv( str )
322
+
323
+ # Quote special characters
324
+ # str.gsub!(/@/, '@@64')
325
+ str.gsub!(/"/, '\'')
326
+ # str.gsub!(/~/, '@@126')
327
+ # str.gsub!(/\\/, '@@92')
328
+ # str.gsub!(/\^/, '@@94')
329
+ return str
330
+ end
331
+
332
+
333
+ def objects(r)
334
+ room = get_room_tag(r)
335
+ objs = r.objects.split("\n")
336
+ objs.each { |o|
337
+
338
+ tag = get_tag(o, o)
339
+
340
+ names = o.dup
341
+ names.gsub!(/"/, '') # remove any quotes
342
+ names.gsub!(/\b\w\b/, '') # remove single letter words
343
+
344
+ name = names
345
+ names = name.split(' ')
346
+
347
+ article = 'a '
348
+ if name =~ /ves$/ or name =~ /s$/
349
+ article = 'some '
350
+ elsif name =~ /^[aeiou]/
351
+ article = 'an '
352
+ end
353
+
354
+
355
+ # If name begins with uppercase, assume it is an NPC
356
+ type = 'a thing'
357
+
358
+ if name =~ IS_ANIMAL
359
+ type = 'an animal'
360
+ elsif name =~ IS_PERSON
361
+ article = 'a '
362
+ type = 'a person'
363
+ if name =~ IS_MALE_PERSON
364
+ type = 'a male person'
365
+ elsif name =~ IS_FEMALE_PERSON
366
+ type = 'a female person'
367
+ end
368
+ elsif name =~ /[A-Z]/
369
+ if name !~ /'/ # possesive, like Michael's wallet
370
+ # if too many words, probably a book's title
371
+ if names.size <= 3
372
+ article = ''
373
+ if name =~ /^(?:Miss|Mrs)/
374
+ type = 'a woman'
375
+ else
376
+ type = 'a person'
377
+ end
378
+ end
379
+ end
380
+ end
381
+
382
+
383
+ props = ''
384
+
385
+ if tag != name
386
+ props << "\n The printed name of #{tag} is \"#{article}#{name}\"."
387
+ else
388
+ tag = article + tag
389
+ end
390
+
391
+ if name =~ IS_LIGHT
392
+ props << "\n It is not lit."
393
+ end
394
+
395
+ if name =~ IS_CONTAINER
396
+ props << "\n It is a container."
397
+ end
398
+
399
+ if name =~ IS_SUPPORTER
400
+ props << "\n It is a supporter."
401
+ end
402
+
403
+ if name =~ IS_WEARABLE
404
+ props << "\n It is wearable."
405
+ end
406
+
407
+ if name =~ IS_EDIBLE
408
+ props << "\n It is edible."
409
+ end
410
+
411
+ @f.print <<"EOF"
412
+
413
+ In #{room}, there is #{type} called #{tag}. #{props}
414
+ The description of #{tag} is \"UNDER CONSTRUCTION.\"
415
+ EOF
416
+ if names.size > 1
417
+ names.each { |n|
418
+ next if n =~ IGNORED_ARTICLES
419
+ @f.puts " Understand \"#{n}\" as #{tag}."
420
+ }
421
+ end
422
+ }
423
+
424
+
425
+ end
426
+
427
+ def door(e)
428
+ name = get_door_name(e)
429
+ tag = get_tag(e, name)
430
+ roomA = get_room_tag(e.roomA)
431
+ roomB = get_room_tag(e.roomB)
432
+ dirA = e.roomA.exits.index(e)
433
+ dirB = e.roomB.exits.rindex(e)
434
+ dirA = DIRECTIONS[dirA].downcase
435
+ dirB = DIRECTIONS[dirB].downcase
436
+
437
+ props = ''
438
+ if e.type == Connection::LOCKED_DOOR
439
+ props = "It is locked."
440
+ elsif e.type == Connection::CLOSED_DOOR
441
+ props = "It is closed."
442
+ end
443
+
444
+ found_in = 'It is '
445
+ if e.dir == Connection::BOTH
446
+ found_in << "#{dirA} of #{roomA} and #{dirB} of #{roomB}"
447
+ elsif e.dir == Connection::AtoB
448
+ found_in << "#{dirA} of #{roomA}. Through it is #{roomB}"
449
+ elsif e.dir == Connection::BtoA
450
+ found_in << "#{dirB} of #{roomB}. Through it is #{roomA}"
451
+ end
452
+
453
+ @f.puts <<"EOF"
454
+
455
+ The #{tag} is a door. #{props}
456
+ The printed name of #{tag} is "a #{name}".
457
+ Understand "#{name}" as #{tag}.
458
+ Understand "door" as #{tag}.
459
+ #{found_in}.
460
+ EOF
461
+
462
+ end
463
+
464
+
465
+ def room(r)
466
+ tag = get_room_tag(r)
467
+ name = r.name
468
+
469
+ prop = ''
470
+ if r.darkness
471
+ prop << 'dark '
472
+ end
473
+
474
+ @f.print "\n#{tag} is a #{prop}room."
475
+
476
+ if name != tag
477
+ name = inform_quote(name)
478
+ @f.print " The printed name of #{tag} is \"#{name}\"."
479
+ end
480
+
481
+ @f.puts
482
+ @f.puts " \"#{wrap_text(r.desc)}\""
483
+
484
+ # Now, handle exits...
485
+ r.exits.each_with_index { |e, dir|
486
+ next if (not e) or e.stub? or e.type == Connection::SPECIAL
487
+ if e.loop?
488
+ text = e.exitAtext
489
+ b = e.roomB
490
+ else
491
+ if e.roomB == r
492
+ next if e.dir == Connection::AtoB
493
+ text = e.exitBtext
494
+ b = e.roomA
495
+ else
496
+ next if e.dir == Connection::BtoA
497
+ text = e.exitAtext
498
+ b = e.roomB
499
+ end
500
+ end
501
+ @f.print ' '
502
+ if text == 0
503
+ @f.print "#{DIRECTIONS[dir]} is "
504
+ else
505
+ @f.print "#{OTHERDIRS[text]} is "
506
+ end
507
+
508
+ if e.type == Connection::CLOSED_DOOR or
509
+ e.type == Connection::LOCKED_DOOR
510
+ @f.print get_door_tag(e)
511
+ else
512
+ @f.print get_room_tag(b)
513
+ end
514
+ @f.puts '.'
515
+ }
516
+ objects(r)
517
+ end
518
+
519
+ def section(sect, idx)
520
+ name = sect.name
521
+ name = 'Unnamed' if name.to_s == ''
522
+
523
+ @f.puts
524
+ @f.puts "Section #{idx+1} - #{name}"
525
+ @f.puts
526
+
527
+ @f.puts
528
+ @f.puts "Part 1 - Room Descriptions"
529
+ @f.puts
530
+ sect.rooms.each { |r| room(r) }
531
+ @f.puts
532
+ @f.puts
533
+
534
+ @f.puts
535
+ @f.puts "Part 2 - Doors"
536
+ @f.puts
537
+ sect.connections.each { |e|
538
+ next if (e.type != Connection::LOCKED_DOOR and
539
+ e.type != Connection::CLOSED_DOOR)
540
+ door(e)
541
+ }
542
+ end
543
+
544
+ def start
545
+ @f = File.open("#@root.inform", "w")
546
+ story = @map.name
547
+ story = 'Untitled' if story == ''
548
+ today = Date.today
549
+ serial = today.strftime("%y%m%d")
550
+
551
+ @f.puts
552
+ @f.puts "\"#{story}\" by \"#{@map.creator}\""
553
+ @f.puts
554
+ @f.puts <<"EOF"
555
+ The story genre is "Unknown". The release number is 1.
556
+ The story headline is "An Interactive Fiction".
557
+ The story description is "".
558
+ The story creation year is #{today.year}.
559
+
560
+
561
+ Use full-length room descriptions.
562
+
563
+ EOF
564
+
565
+ @map.sections.each_with_index { |sect, idx|
566
+ section(sect, idx)
567
+ }
568
+ @f.close
569
+ end
570
+
571
+ def initialize(map, fileroot)
572
+ @tags = {}
573
+ @root = fileroot
574
+ @base = File.basename(@root)
575
+ @map = map
576
+
577
+ start
578
+ end
579
+ end