|
| 1 | +"""Parse the original PDP ``advent.dat`` file.""" |
| 2 | + |
| 3 | +from operator import attrgetter |
| 4 | +from model import Hint, Message, Move, Object, Room, Word |
| 5 | + |
| 6 | +# The Adventure data file knows only the first five characters of each |
| 7 | +# word in the game, so we have to know the full verion of each word. |
| 8 | + |
| 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 | + |
| 24 | +class Data(object): |
| 25 | + def __init__(self): |
| 26 | + self.rooms = {} |
| 27 | + self.vocabulary = {} |
| 28 | + self.objects = {} |
| 29 | + self.messages = {} |
| 30 | + self.class_messages = [] |
| 31 | + self.hints = {} |
| 32 | + self.magic_messages = {} |
| 33 | + |
| 34 | + def referent(self, word): |
| 35 | + if word.kind == 'noun': |
| 36 | + return self.objects[word.n % 1000] |
| 37 | + |
| 38 | +# Helper functions. |
| 39 | + |
| 40 | +def make_object(dictionary, klass, n): |
| 41 | + if n not in dictionary: |
| 42 | + dictionary[n] = obj = klass() |
| 43 | + obj.n = n |
| 44 | + return dictionary[n] |
| 45 | + |
| 46 | +def expand_tabs(segments): |
| 47 | + it = iter(segments) |
| 48 | + line = next(it) |
| 49 | + for segment in it: |
| 50 | + spaces = 8 - len(line) % 8 |
| 51 | + line += ' ' * spaces + segment |
| 52 | + return line |
| 53 | + |
| 54 | +def accumulate_message(dictionary, n, line): |
| 55 | + dictionary[n] = dictionary.get(n, '') + line + '\n' |
| 56 | + |
| 57 | +# Knowledge of what each section contains. |
| 58 | + |
| 59 | +def section1(data, n, *etc): |
| 60 | + room = make_object(data.rooms, Room, n) |
| 61 | + if not etc[0].startswith('>$<'): |
| 62 | + room.long_description += expand_tabs(etc) + '\n' |
| 63 | + |
| 64 | +def section2(data, n, line): |
| 65 | + make_object(data.rooms, Room, n).short_description += line + '\n' |
| 66 | + |
| 67 | +def section3(data, x, y, *verbs): |
| 68 | + last_travel = data._last_travel |
| 69 | + if last_travel[0] == x and last_travel[1][0] == verbs[0]: |
| 70 | + verbs = last_travel[1] # same first verb implies use whole list |
| 71 | + else: |
| 72 | + data._last_travel = [x, verbs] |
| 73 | + |
| 74 | + m, n = divmod(y, 1000) |
| 75 | + mh, mm = divmod(m, 100) |
| 76 | + |
| 77 | + if m == 0: |
| 78 | + condition = (None,) |
| 79 | + elif 0 < m < 100: |
| 80 | + condition = ('%', m) |
| 81 | + elif m == 100: |
| 82 | + condition = ('not_dwarf',) |
| 83 | + elif 100 < m <= 200: |
| 84 | + condition = ('carrying', mm) |
| 85 | + elif 200 < m <= 300: |
| 86 | + condition = ('carrying_or_in_room_with', mm) |
| 87 | + elif 300 < m: |
| 88 | + condition = ('prop!=', mm, mh - 3) |
| 89 | + |
| 90 | + if n <= 300: |
| 91 | + action = make_object(data.rooms, Room, n) |
| 92 | + elif 300 < n <= 500: |
| 93 | + action = n # special computed goto |
| 94 | + else: |
| 95 | + action = make_object(data.messages, Message, n - 500) |
| 96 | + |
| 97 | + move = Move() |
| 98 | + if len(verbs) == 1 and verbs[0] == 1: |
| 99 | + move.is_forced = True |
| 100 | + else: |
| 101 | + move.verbs = [ make_object(data.vocabulary, Word, verb_n) |
| 102 | + for verb_n in verbs if verb_n < 100 ] # skip bad "109" |
| 103 | + move.condition = condition |
| 104 | + move.action = action |
| 105 | + data.rooms[x].travel_table.append(move) |
| 106 | + |
| 107 | +def section4(data, n, text, *etc): |
| 108 | + text = text.lower() |
| 109 | + text = long_words.get(text, text) |
| 110 | + word = make_object(data.vocabulary, Word, n) |
| 111 | + if word.text is None: # this is the first word with index "n" |
| 112 | + word.text = text |
| 113 | + else: # there is already a word sitting at "n", so create a synonym |
| 114 | + original = word |
| 115 | + word = Word() |
| 116 | + word.n = n |
| 117 | + word.text = text |
| 118 | + original.add_synonym(word) |
| 119 | + word.kind = ['travel', 'noun', 'verb', 'snappy_comeback'][n // 1000] |
| 120 | + if word.kind == 'noun': |
| 121 | + n %= 1000 |
| 122 | + obj = make_object(data.objects, Object, n) |
| 123 | + obj.names.append(text) |
| 124 | + obj.is_treasure = (n >= 50) |
| 125 | + data.objects[text] = obj |
| 126 | + if text not in data.vocabulary: # since duplicate names exist |
| 127 | + data.vocabulary[text] = word |
| 128 | + |
| 129 | +def section5(data, n, *etc): |
| 130 | + if 1 <= n <= 99: |
| 131 | + data._object = make_object(data.objects, Object, n) |
| 132 | + data._object.inventory_message = expand_tabs(etc) |
| 133 | + else: |
| 134 | + n /= 100 |
| 135 | + messages = data._object.messages |
| 136 | + if etc[0].startswith('>$<'): |
| 137 | + more = '' |
| 138 | + else: |
| 139 | + more = expand_tabs(etc) + '\n' |
| 140 | + messages[n] = messages.get(n, '') + more |
| 141 | + |
| 142 | +def section6(data, n, *etc): |
| 143 | + message = make_object(data.messages, Message, n) |
| 144 | + message.text += expand_tabs(etc) + '\n' |
| 145 | + |
| 146 | +def section7(data, n, room_n, *etc): |
| 147 | + if not room_n: |
| 148 | + return |
| 149 | + obj = make_object(data.objects, Object, n) |
| 150 | + room = make_object(data.rooms, Room, room_n) |
| 151 | + obj.drop(room) |
| 152 | + if len(etc): |
| 153 | + if etc[0] == -1: |
| 154 | + obj.is_fixed = True |
| 155 | + else: |
| 156 | + room2 = make_object(data.rooms, Room, etc[0]) |
| 157 | + obj.rooms.append(room2) # exists two places, like grate |
| 158 | + obj.starting_rooms = list(obj.rooms) # remember where things started |
| 159 | + |
| 160 | +def section8(data, word_n, message_n): |
| 161 | + if not message_n: |
| 162 | + return |
| 163 | + word = make_object(data.vocabulary, Word, word_n + 2000) |
| 164 | + message = make_object(data.messages, Message, message_n) |
| 165 | + for word2 in word.synonyms: |
| 166 | + word2.default_message = message |
| 167 | + |
| 168 | +def section9(data, bit, *nlist): |
| 169 | + for n in nlist: |
| 170 | + room = make_object(data.rooms, Room, n) |
| 171 | + if bit == 0: |
| 172 | + room.is_light = True |
| 173 | + elif bit == 1: |
| 174 | + room.liquid = make_object(data.objects, Object, 22) #oil |
| 175 | + elif bit == 2: |
| 176 | + room.liquid = make_object(data.objects, Object, 21) #water |
| 177 | + elif bit == 3: |
| 178 | + room.is_forbidden_to_pirate = True |
| 179 | + else: |
| 180 | + hint = make_object(data.hints, Hint, bit) |
| 181 | + hint.rooms.append(room) |
| 182 | + |
| 183 | +def section10(data, score, line, *etc): |
| 184 | + data.class_messages.append((score, line)) |
| 185 | + |
| 186 | +def section11(data, n, turns_needed, penalty, question_n, message_n): |
| 187 | + hint = make_object(data.hints, Hint, n) |
| 188 | + hint.turns_needed = turns_needed |
| 189 | + hint.penalty = penalty |
| 190 | + hint.question = make_object(data.messages, Message, question_n) |
| 191 | + hint.message = make_object(data.messages, Message, message_n) |
| 192 | + |
| 193 | +def section12(data, n, line): |
| 194 | + accumulate_message(data.magic_messages, n, line) |
| 195 | + |
| 196 | +# Process every section of the file in turn. |
| 197 | + |
| 198 | +def parse(data, datafile): |
| 199 | + """Read the Adventure data file and return a ``Data`` object.""" |
| 200 | + data._last_travel = [0, [0]] # x and verbs used by section 3 |
| 201 | + |
| 202 | + while True: |
| 203 | + section_number = int(datafile.readline()) |
| 204 | + if not section_number: # no further sections |
| 205 | + break |
| 206 | + store = globals().get('section%d' % section_number) |
| 207 | + while True: |
| 208 | + fields = [ (int(field) if field.lstrip('-').isdigit() else field) |
| 209 | + for field in datafile.readline().strip().split('\t') ] |
| 210 | + if fields[0] == -1: # end-of-section marker |
| 211 | + break |
| 212 | + store(data, *fields) |
| 213 | + |
| 214 | + del data._last_travel # state used by section 3 |
| 215 | + del data._object # state used by section 5 |
| 216 | + |
| 217 | + data.object_list = sorted(set(data.objects.values()), key=attrgetter('n')) |
| 218 | + #data.room_list = sorted(set(data.rooms.values()), key=attrgetter('n')) |
| 219 | + for obj in data.object_list: |
| 220 | + name = obj.names[0] |
| 221 | + if hasattr(data, name): |
| 222 | + name = name + '2' # create identifiers like ROD2, PLANT2 |
| 223 | + setattr(data, name, obj) |
| 224 | + |
| 225 | + return data |
0 commit comments