data.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. """Parse the original PDP ``advent.dat`` file.
  2. Copyright 2010-2015 Brandon Rhodes. Licensed as free software under the
  3. Apache License, Version 2.0 as detailed in the accompanying README.txt.
  4. """
  5. from operator import attrgetter
  6. from .model import Hint, Message, Move, Object, Room, Word
  7. # The Adventure data file knows only the first five characters of each
  8. # word in the game, so we have to know the full verion of each word.
  9. long_words = { w[:5]: w for w in """upstream downstream forest
  10. forward continue onward return retreat valley staircase outside building stream
  11. cobble inward inside surface nowhere passage tunnel canyon awkward
  12. upward ascend downward descend outdoors barren across debris broken
  13. examine describe slabroom depression entrance secret bedquilt plover
  14. oriental cavern reservoir office headlamp lantern pillow velvet fissure tablet
  15. oyster magazine spelunker dwarves knives rations bottle mirror beanstalk
  16. stalactite shadow figure drawings pirate dragon message volcano geyser
  17. machine vending batteries carpet nuggets diamonds silver jewelry treasure
  18. trident shards pottery emerald platinum pyramid pearl persian spices capture
  19. release discard mumble unlock nothing extinguish placate travel proceed
  20. continue explore follow attack strike devour inventory detonate ignite
  21. blowup peruse shatter disturb suspend sesame opensesame abracadabra
  22. shazam excavate information""".split() }
  23. class Data(object):
  24. def __init__(self):
  25. self.rooms = {}
  26. self.vocabulary = {}
  27. self.objects = {}
  28. self.messages = {}
  29. self.class_messages = []
  30. self.hints = {}
  31. self.magic_messages = {}
  32. def referent(self, word):
  33. if word.kind == 'noun':
  34. return self.objects[word.n % 1000]
  35. # Helper functions.
  36. def make_object(dictionary, klass, n):
  37. if n not in dictionary:
  38. dictionary[n] = obj = klass()
  39. obj.n = n
  40. return dictionary[n]
  41. def expand_tabs(segments):
  42. it = iter(segments)
  43. line = next(it)
  44. for segment in it:
  45. spaces = 8 - len(line) % 8
  46. line += ' ' * spaces + segment
  47. return line
  48. def accumulate_message(dictionary, n, line):
  49. dictionary[n] = dictionary.get(n, '') + line + '\n'
  50. # Knowledge of what each section contains.
  51. def section1(data, n, *etc):
  52. room = make_object(data.rooms, Room, n)
  53. if not etc[0].startswith('>$<'):
  54. room.long_description += expand_tabs(etc) + '\n'
  55. def section2(data, n, line):
  56. make_object(data.rooms, Room, n).short_description += line + '\n'
  57. def section3(data, x, y, *verbs):
  58. last_travel = data._last_travel
  59. if last_travel[0] == x and last_travel[1][0] == verbs[0]:
  60. verbs = last_travel[1] # same first verb implies use whole list
  61. else:
  62. data._last_travel = [x, verbs]
  63. m, n = divmod(y, 1000)
  64. mh, mm = divmod(m, 100)
  65. if m == 0:
  66. condition = (None,)
  67. elif 0 < m < 100:
  68. condition = ('%', m)
  69. elif m == 100:
  70. condition = ('not_dwarf',)
  71. elif 100 < m <= 200:
  72. condition = ('carrying', mm)
  73. elif 200 < m <= 300:
  74. condition = ('carrying_or_in_room_with', mm)
  75. elif 300 < m:
  76. condition = ('prop!=', mm, mh - 3)
  77. if n <= 300:
  78. action = make_object(data.rooms, Room, n)
  79. elif 300 < n <= 500:
  80. action = n # special computed goto
  81. else:
  82. action = make_object(data.messages, Message, n - 500)
  83. move = Move()
  84. if len(verbs) == 1 and verbs[0] == 1:
  85. move.is_forced = True
  86. else:
  87. move.verbs = [ make_object(data.vocabulary, Word, verb_n)
  88. for verb_n in verbs if verb_n < 100 ] # skip bad "109"
  89. move.condition = condition
  90. move.action = action
  91. data.rooms[x].travel_table.append(move)
  92. def section4(data, n, text, *etc):
  93. text = text.lower()
  94. text = long_words.get(text, text)
  95. word = make_object(data.vocabulary, Word, n)
  96. if word.text is None: # this is the first word with index "n"
  97. word.text = text
  98. else: # there is already a word sitting at "n", so create a synonym
  99. original = word
  100. word = Word()
  101. word.n = n
  102. word.text = text
  103. original.add_synonym(word)
  104. word.kind = ['travel', 'noun', 'verb', 'snappy_comeback'][n // 1000]
  105. if word.kind == 'noun':
  106. n %= 1000
  107. obj = make_object(data.objects, Object, n)
  108. obj.names.append(text)
  109. obj.is_treasure = (n >= 50)
  110. data.objects[text] = obj
  111. if text not in data.vocabulary: # since duplicate names exist
  112. data.vocabulary[text] = word
  113. def section5(data, n, *etc):
  114. if 1 <= n <= 99:
  115. data._object = make_object(data.objects, Object, n)
  116. data._object.inventory_message = expand_tabs(etc)
  117. else:
  118. n /= 100
  119. messages = data._object.messages
  120. if etc[0].startswith('>$<'):
  121. more = ''
  122. else:
  123. more = expand_tabs(etc) + '\n'
  124. messages[n] = messages.get(n, '') + more
  125. def section6(data, n, *etc):
  126. message = make_object(data.messages, Message, n)
  127. message.text += expand_tabs(etc) + '\n'
  128. def section7(data, n, room_n, *etc):
  129. if not room_n:
  130. return
  131. obj = make_object(data.objects, Object, n)
  132. room = make_object(data.rooms, Room, room_n)
  133. obj.drop(room)
  134. if len(etc):
  135. if etc[0] == -1:
  136. obj.is_fixed = True
  137. else:
  138. room2 = make_object(data.rooms, Room, etc[0])
  139. obj.rooms.append(room2) # exists two places, like grate
  140. obj.starting_rooms = list(obj.rooms) # remember where things started
  141. def section8(data, word_n, message_n):
  142. if not message_n:
  143. return
  144. word = make_object(data.vocabulary, Word, word_n + 2000)
  145. message = make_object(data.messages, Message, message_n)
  146. for word2 in word.synonyms:
  147. word2.default_message = message
  148. def section9(data, bit, *nlist):
  149. for n in nlist:
  150. room = make_object(data.rooms, Room, n)
  151. if bit == 0:
  152. room.is_light = True
  153. elif bit == 1:
  154. room.liquid = make_object(data.objects, Object, 22) #oil
  155. elif bit == 2:
  156. room.liquid = make_object(data.objects, Object, 21) #water
  157. elif bit == 3:
  158. room.is_forbidden_to_pirate = True
  159. else:
  160. hint = make_object(data.hints, Hint, bit)
  161. hint.rooms.append(room)
  162. def section10(data, score, line, *etc):
  163. data.class_messages.append((score, line))
  164. def section11(data, n, turns_needed, penalty, question_n, message_n):
  165. hint = make_object(data.hints, Hint, n)
  166. hint.turns_needed = turns_needed
  167. hint.penalty = penalty
  168. hint.question = make_object(data.messages, Message, question_n)
  169. hint.message = make_object(data.messages, Message, message_n)
  170. def section12(data, n, line):
  171. accumulate_message(data.magic_messages, n, line)
  172. # Process every section of the file in turn.
  173. def parse(data, datafile):
  174. """Read the Adventure data file and return a ``Data`` object."""
  175. data._last_travel = [0, [0]] # x and verbs used by section 3
  176. while True:
  177. section_number = int(datafile.readline())
  178. if not section_number: # no further sections
  179. break
  180. store = globals().get('section%d' % section_number)
  181. while True:
  182. fields = [ (int(field) if field.lstrip('-').isdigit() else field)
  183. for field in datafile.readline().strip().split('\t') ]
  184. if fields[0] == -1: # end-of-section marker
  185. break
  186. store(data, *fields)
  187. del data._last_travel # state used by section 3
  188. del data._object # state used by section 5
  189. data.object_list = sorted(set(data.objects.values()), key=attrgetter('n'))
  190. #data.room_list = sorted(set(data.rooms.values()), key=attrgetter('n'))
  191. for obj in data.object_list:
  192. name = obj.names[0]
  193. if hasattr(data, name):
  194. name = name + '2' # create identifiers like ROD2, PLANT2
  195. setattr(data, name, obj)
  196. return data