scottkit 0.0.0

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.
Files changed (67) hide show
  1. data/.gitignore +4 -0
  2. data/GPL-2 +339 -0
  3. data/Makefile +5 -0
  4. data/README +75 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/bin/scottkit +96 -0
  8. data/bin/scottkit.rb +96 -0
  9. data/data/.gitignore +1 -0
  10. data/data/adams/.gitignore +2 -0
  11. data/data/adams/AdamsGames.zip +0 -0
  12. data/data/adams/Makefile +18 -0
  13. data/data/crystal/crystal.map +112 -0
  14. data/data/crystal/crystal.sck +598 -0
  15. data/data/crystal/crystal.solution +82 -0
  16. data/data/dan-and-matt.sck +180 -0
  17. data/data/dan-and-matt.solution +32 -0
  18. data/data/howarth/.gitignore +2 -0
  19. data/data/howarth/Makefile +14 -0
  20. data/data/howarth/mysterious.tar.gz +0 -0
  21. data/data/test/Makefile +18 -0
  22. data/data/test/adams/Makefile +13 -0
  23. data/data/test/adams/adv01.solution +186 -0
  24. data/data/test/adams/adv01.transcript +869 -0
  25. data/data/test/adams/adv01.transcript.md5 +1 -0
  26. data/data/test/adams/adv02.solution +225 -0
  27. data/data/test/adams/adv02.transcript +970 -0
  28. data/data/test/adams/adv02.transcript.md5 +1 -0
  29. data/data/test/adams/adv04.solution +187 -0
  30. data/data/test/adams/adv04.transcript +876 -0
  31. data/data/test/adams/adv04.transcript.md5 +1 -0
  32. data/data/test/crystal.decompile +628 -0
  33. data/data/test/crystal.sao +373 -0
  34. data/data/test/crystal.save-file +62 -0
  35. data/data/test/crystal.save-script +41 -0
  36. data/data/test/crystal.sck +598 -0
  37. data/data/test/crystal.solution +82 -0
  38. data/data/test/crystal.transcript +413 -0
  39. data/data/test/t6.pretty-print +225 -0
  40. data/data/test/t7.sao +110 -0
  41. data/data/test/t7.solution +28 -0
  42. data/data/test/t7.transcript +147 -0
  43. data/data/tutorial/t1.sck +5 -0
  44. data/data/tutorial/t2.sck +14 -0
  45. data/data/tutorial/t3.sck +38 -0
  46. data/data/tutorial/t4.sck +62 -0
  47. data/data/tutorial/t5.sck +87 -0
  48. data/data/tutorial/t6.sck +119 -0
  49. data/data/tutorial/t7.sck +135 -0
  50. data/lib/scottkit/compile.rb +661 -0
  51. data/lib/scottkit/decompile.rb +175 -0
  52. data/lib/scottkit/game.rb +409 -0
  53. data/lib/scottkit/play.rb +474 -0
  54. data/notes/Definition +147 -0
  55. data/notes/Definition.saved-game +9 -0
  56. data/notes/Definition.scottfree-1.14 +142 -0
  57. data/notes/adventureland-maze +20 -0
  58. data/notes/continue-action +51 -0
  59. data/test/test_canonicalise.rb +94 -0
  60. data/test/test_compile.rb +54 -0
  61. data/test/test_decompile.rb +13 -0
  62. data/test/test_play.rb +20 -0
  63. data/test/test_playadams.rb +36 -0
  64. data/test/test_save.rb +31 -0
  65. data/test/withio.rb +15 -0
  66. data/test/withio_test.rb +31 -0
  67. metadata +118 -0
@@ -0,0 +1,175 @@
1
+ module ScottKit
2
+ class Game
3
+ private
4
+
5
+ def quote(token) #:nodoc:
6
+ if ((token !~ /^([!a-z_0-9-]+)$/i || token =~ /\n/) ||
7
+ Compiler::Lexer::TOKENMAP[token])
8
+ "\"#{token.gsub(/[""]/, '\'')}\""
9
+ else
10
+ token
11
+ end
12
+ end
13
+
14
+ def decompile(f)
15
+ f << "# #{@rooms.size} rooms, "
16
+ f << "#{@items.size} items, "
17
+ f << "#{@actions.size} actions\n"
18
+ f << "# #{@messages.size} messages, "
19
+ f << "#{defined?(@ntreasures) ? @ntreasures : "UNDEFINED"} treasures, "
20
+ f << "#{@verbs.size} verbs/nouns\n"
21
+ f.puts "ident #{@id}" if defined? @id
22
+ f.puts "version #{@version}" if defined? @version
23
+ f.puts "wordlen #{@wordlen}" if defined? @wordlen
24
+ f.puts "maxload #{@maxload}" if defined? @maxload
25
+ f.puts "lighttime #{@lamptime}" if defined? @lamptime
26
+ f.puts "unknown1 #{@unknown1}" if defined? @unknown1
27
+ f.puts "unknown2 #{@unknown2}" if defined? @unknown2
28
+ f.puts "start #{quote roomname @startloc}" if defined? @startloc
29
+ ### Do NOT change the nested if's to a single &&ed one: for
30
+ # reasons that I do not at all understand, doing so results in
31
+ # the protected statement being executed when @treasury is 0
32
+ if defined? @treasury
33
+ if @treasury != 0
34
+ f.puts "treasury #{quote roomname @treasury}"
35
+ end
36
+ end
37
+ f.puts
38
+ decompile_wordgroup(f, @verbs, "verb")
39
+ decompile_wordgroup(f, @nouns, "noun")
40
+
41
+ @rooms.each.with_index do |room, i|
42
+ next if i == 0
43
+ f.puts "room " << quote(roomname(i)) << " \"#{room.desc}\""
44
+ room.exits.each.with_index do |exit, j|
45
+ if exit != 0
46
+ f.puts "\texit #{dirname(j)} #{quote roomname(exit)}"
47
+ end
48
+ end
49
+ f.puts
50
+ end
51
+
52
+ @items.each.with_index do |item, i|
53
+ f.puts "item #{quote itemname(i)} \"#{item.desc}\""
54
+ f.puts "\tcalled #{quote item.name}" if item.name
55
+ f.puts case item.startloc
56
+ when ROOM_CARRIED then "\tcarried"
57
+ when ROOM_NOWHERE then "\tnowhere"
58
+ else "\tat #{quote roomname(item.startloc)}"
59
+ end
60
+ f.puts
61
+ end
62
+
63
+ @actions.each { |action| action.decompile(f) }
64
+ end
65
+
66
+ def decompile_wordgroup(f, list, label)
67
+ canonical = nil
68
+ synonyms = []
69
+ printed = false
70
+
71
+ list.each.with_index do |word, i|
72
+ if (word =~ /^\*/)
73
+ synonyms << word.sub(/^\*/, "")
74
+ end
75
+ if (word !~ /^\*/ || i == list.size-1)
76
+ if synonyms.size > 0
77
+ f.print "#{label}group #{quote canonical} "
78
+ f.puts synonyms.map { |token| quote token }.join(" ")
79
+ printed = true
80
+ end
81
+ canonical = word
82
+ synonyms = []
83
+ end
84
+ end
85
+
86
+ f.puts if printed
87
+ end
88
+
89
+ public :decompile # Must be visible to driver program
90
+ public :quote # Needed for contained classes' decompile()/render() methods
91
+
92
+
93
+ class Action
94
+ def quote(*args); @game.quote(*args); end
95
+
96
+ def decompile(f)
97
+ emitted_noun_or_condition = false
98
+ if self.verb == 0 then
99
+ f << "occur"
100
+ f << " " << self.noun << "%" if self.noun != 100
101
+ else
102
+ f << "action #{quote @game.verbs[self.verb]}"
103
+ if self.noun != 0
104
+ f << " #{quote @game.nouns[self.noun]}"
105
+ emitted_noun_or_condition = true
106
+ end
107
+ end
108
+ self.conds.each.with_index do |cond, i|
109
+ f << (i == 0 ? " when " : " and ") << cond.render
110
+ emitted_noun_or_condition = true
111
+ end
112
+ f << ":" if self.verb != 0 && !emitted_noun_or_condition
113
+ f.puts
114
+ args = @args.clone
115
+ self.instructions.each do |instruction|
116
+ f.puts "\t" + instruction.render(args)
117
+ end
118
+ if (self.comment != "")
119
+ f.puts "\tcomment \"#{self.comment}\""
120
+ end
121
+ f.puts
122
+ end
123
+ end
124
+
125
+ class Condition
126
+ def quote(*args); @game.quote(*args); end
127
+
128
+ def render
129
+ type = OPS[@cond][1]
130
+ res = quote(OPS[@cond][0])
131
+ res += " " +
132
+ quote(type == :room ? @game.roomname(@value) :
133
+ type == :item ? @game.itemname(@value) :
134
+ type == :number ? String(@value) : "ERROR") if
135
+ type != :NONE
136
+ res
137
+ end
138
+ end
139
+
140
+ class Instruction
141
+ def quote(*args); @game.quote(*args); end
142
+
143
+ def render(args)
144
+ if (@op == 0)
145
+ return "NOP" # shouldn't happen
146
+ elsif (@op <= 51)
147
+ return "print #{quote @game.messages[@op]}"
148
+ elsif (@op >= 102)
149
+ return "print #{quote @game.messages[@op-50]}"
150
+ end
151
+
152
+ op = OPS[@op-52]
153
+ return "UNKNOWN_OP" if !op
154
+ op[0] + case op[1]
155
+ when :item
156
+ " #{quote @game.itemname(args.shift)}"
157
+ when :room
158
+ " #{quote @game.roomname(args.shift)}"
159
+ when :number
160
+ " #{@game.quote String(args.shift)}"
161
+ when :item_item
162
+ " #{quote @game.itemname(args.shift)}" +
163
+ " #{quote @game.itemname(args.shift)}"
164
+ when :item_room
165
+ " #{quote @game.itemname(args.shift)}" +
166
+ " #{quote @game.roomname(args.shift)}"
167
+ when :NONE
168
+ "" # Nothing to add
169
+ else
170
+ " UNKNOWN_PARAM"
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,409 @@
1
+ module ScottKit
2
+ class Game
3
+ NFLAGS = 16 #:nodoc: (The most that ScottFree save-game format supports)
4
+ VERB_GO = 1 #:nodoc:
5
+ VERB_GET = 10 #:nodoc:
6
+ VERB_DROP = 18 #:nodoc:
7
+ ITEM_LAMP = 9 #:nodoc:
8
+ FLAG_DARK = 15 #:nodoc:
9
+ FLAG_LAMPDEAD = 16 #:nodoc:
10
+ ROOM_CARRIED = -1 #:nodoc:
11
+ ROOM_OLDCARRIED = 255 #:nodoc: (from when all words were eight bits wide)
12
+ ROOM_NOWHERE = 0 #:nodoc:
13
+
14
+ # Constant once they've been initialised
15
+ attr_reader :options, :nouns, :verbs, :rooms, :items, :messages,
16
+ :maxload, :lamptime #:nodoc:
17
+
18
+ # Variable during run (but mostly set only within this class)
19
+ attr_reader :flags, :counters, :saved_rooms, :noun, :lampleft #:nodoc:
20
+ attr_accessor :loc, :counter, :saved_room #:nodoc:
21
+
22
+ private
23
+
24
+ # Creates a new game, with no room, items or actions -- load must
25
+ # be called to make the game ready for playing, or
26
+ # compile_to_stdout can be called to generate a new game-file.
27
+ # The options hash affects various aspects of how the game will be
28
+ # loaded, played and compiled. The following symbols are
29
+ # recognised as keys in the options hash:
30
+ #
31
+ # [+wizard_mode+] If specified, then the player can use
32
+ # "wizard commands", prefixed with a hash,
33
+ # as well as the usual commands: these
34
+ # include +sg+ (superget, to take any item
35
+ # whose number is specified), +go+ (teleport
36
+ # to the room whose number is specified),
37
+ # +where+ (to find the location of the item
38
+ # whose number is specified), and +set+ and
39
+ # +clear+ (to set and clear verbosity
40
+ # flags).
41
+ #
42
+ # [+restore_file+] If specified, the name of a saved-game
43
+ # file to restore before starting to play.
44
+ #
45
+ # [+read_file+] If specified, the name of a file of game
46
+ # commands to be run after restoring s saved
47
+ # game (if any) and before starting to read
48
+ # commands from the user.
49
+ #
50
+ # [+echo_input+] If true, then game commands are echoed
51
+ # before being executed. This is useful
52
+ # primarily if input is being redirected
53
+ # from a pipe or a file, so that it's
54
+ # possible to see what the game's responses
55
+ # are in response to. (This is not needed
56
+ # when <tt>:read_file</tt> is used.)
57
+ #
58
+ # [+random_seed+] If a number is specified, it is used as
59
+ # the random seed before starting to run the
60
+ # game. This is useful to get random events
61
+ # happening at the same time every time, for
62
+ # example when regression-testing a
63
+ # solution.
64
+ #
65
+ # [+bug_tolerant+] If true, then the game tolerates
66
+ # out-of-range room-numbers as the locations
67
+ # of items, and also compiles such
68
+ # room-named using special names of the form
69
+ # <tt>\_ROOM<i>number</i></tt>. (This is not
70
+ # necessary when dealing with well-formed
71
+ # games, but <i>Buckaroo Banzai</i> is not
72
+ # well-formed.)
73
+ #
74
+ # [+no_wait+] If true, then the game does not pause when
75
+ # running a +pause+ instruction, nor at the
76
+ # end of the game. This is useful to speed
77
+ # up regression tests.
78
+ #
79
+ # [+show_tokens+] The compiler shows the tokens it is
80
+ # encountering as it lexically analyses the
81
+ # text of a game source.
82
+ #
83
+ # [+show_random+] Notes when a random occurrence is tested
84
+ # to see whether it fires or not.
85
+ #
86
+ # [+show_parse+] Shows the parsed verb and noun from each
87
+ # game command. (Note that this does _not_
88
+ # emit information about parsing game
89
+ # source.)
90
+ #
91
+ # [+show_conditions+] Shows each condition that is tested when
92
+ # determining whether to run an action,
93
+ # indicating whether it is true or not.
94
+ #
95
+ # [+show_instructions+] Shows each instruction executed as part of
96
+ # an action.
97
+ #
98
+ # The +show_random+, +show_parse+, +show_conditions+ and
99
+ # +show_conditions+ flags can be set and cleared on the fly if the
100
+ # game is being played in wizard mode, using the +set+ and +clear+
101
+ # wizard commnds with the arguments +r+, +p+, +c+ and +i+
102
+ # respectively.
103
+
104
+ def initialize(options)
105
+ @options = options
106
+ @rooms, @items, @actions, @nouns, @verbs, @messages =
107
+ [], [], [], [], [], []
108
+ end
109
+
110
+ # Virtual accessor
111
+ def dark_flag #:nodoc:
112
+ @flags[FLAG_DARK]
113
+ end
114
+ def dark_flag=(val) #:nodoc:
115
+ @flags[FLAG_DARK] = val
116
+ end
117
+
118
+ # Loads the game-file specified by str. Note that this must be
119
+ # the _content_ of the game-file, not its name.
120
+ #
121
+ def load(str)
122
+ @roombynumber = [ "_ROOM0" ]
123
+ @roomregister = Hash.new(0) # name-stem -> number of registered instances
124
+ @itembynumber = []
125
+ @itemregister = Hash.new(0) # name-stem -> number of registered instances
126
+
127
+ lexer = Fiber.new do
128
+ while str != "" do
129
+ if match = str.match(/^\s*(-?\d+|"(.*?)")\s*/m)
130
+ dputs(:show_tokens, "token " + (match[2] ? "\"#{match[2]}\"" : match[1]))
131
+ Fiber.yield match[2] || Integer(match[1])
132
+ str = match.post_match
133
+ else
134
+ raise "bad token: #{str}"
135
+ end
136
+ end
137
+ end
138
+
139
+ (@unknown1, nitems, nactions, nwords, nrooms, @maxload,
140
+ @startloc, @ntreasures, @wordlen, @lamptime, nmessages, @treasury) =
141
+ 12.times.map { lexer.resume }
142
+ @actions = 0.upto(nactions).map do
143
+ verbnoun = lexer.resume
144
+ conds, args = [], []
145
+ 5.times do
146
+ n = lexer.resume
147
+ cond, value = n%20, n/20
148
+ if cond == 0
149
+ args << value
150
+ else
151
+ conds << Condition.new(self, cond, value)
152
+ end
153
+ end
154
+
155
+ instructions = []
156
+ 2.times do
157
+ n = lexer.resume
158
+ [ n/150, n%150 ].each { |val|
159
+ instructions << Instruction.new(self, val) if val != 0
160
+ }
161
+ end
162
+
163
+ Action.new(self, verbnoun/150, verbnoun%150, conds, instructions, args)
164
+ end
165
+
166
+ @verbs, @nouns = [], []
167
+ 0.upto(nwords) do
168
+ @verbs << lexer.resume
169
+ @nouns << lexer.resume
170
+ end
171
+
172
+ @rooms = 0.upto(nrooms).map do
173
+ exits = 6.times.map { lexer.resume }
174
+ desc = lexer.resume
175
+ Room.new(desc, exits)
176
+ end
177
+
178
+ @messages = 0.upto(nmessages).map { lexer.resume }
179
+
180
+ @items = 0.upto(nitems).map do
181
+ desc, name = lexer.resume, nil
182
+ if match = desc.match(/^(.*)\/(.*)\/$/)
183
+ desc, name = match[1], match[2]
184
+ end
185
+ startloc = lexer.resume
186
+ startloc = ROOM_CARRIED if startloc == ROOM_OLDCARRIED
187
+ Item.new(desc, name, startloc)
188
+ end
189
+
190
+ 0.upto(nactions) do |i|
191
+ @actions[i].comment =lexer.resume
192
+ end
193
+
194
+ @version, @id, @unknown2 = 3.times.map { lexer.resume }
195
+ raise "extra text in adventure file" if lexer.resume
196
+ end
197
+
198
+ def roomname(i) #:nodoc:
199
+ entityname(i, "room", @rooms, @roombynumber, @roomregister)
200
+ end
201
+
202
+ def itemname(i) #:nodoc:
203
+ entityname(i, "item", @items, @itembynumber, @itemregister)
204
+ end
205
+
206
+ def entityname(i, caption, list, index, register)
207
+ if i < 0 || i > list.size-1
208
+ return "_#{caption.upcase}#{i}" if options[:bug_tolerant]
209
+ raise "#{caption} ##{i} out of range 0..#{list.size-1}"
210
+ end
211
+
212
+ if name = index[i]
213
+ return name
214
+ end
215
+ stem = list[i].desc
216
+ stem = "VOID" if stem =~ /^\s*$/
217
+ stem = stem.split.last.sub(/[^a-z]*$/i, "").sub(/.*?([a-z]+)$/i, '\1')
218
+ count = register[stem]
219
+ register[stem] += 1
220
+ index[i] = count == 0 ? stem : "#{stem}#{count}"
221
+ end
222
+
223
+ def dirname(i) #:nodoc:
224
+ %w{north south east west up down}[i]
225
+ end
226
+
227
+ def save(name)
228
+ f = File.new(name, "w") or
229
+ raise "#$0: can't save game to #{name}: #$!"
230
+ f.print(0.upto(NFLAGS-1).map { |i|
231
+ String(@counters[i]) + " " + String(@saved_rooms[i]) + "\n"
232
+ }.join)
233
+ f.print(0.upto(NFLAGS-1).reduce(0) { |acc, i|
234
+ acc | (@flags[i] ? 1 : 0) << i })
235
+ f.print " ", dark_flag ? 1 : 0
236
+ f.print " ", @loc
237
+ f.print " ", @counter
238
+ f.print " ", @saved_room
239
+ f.print " ", @lampleft, "\n"
240
+ f.print @items.map { |item| "#{item.loc}\n" }.join
241
+ f.close
242
+ puts "Saved to #{name}"
243
+ end
244
+
245
+ def restore(name)
246
+ f = File.new(name) or
247
+ raise "#$0: can't restore game from #{name}: #$!"
248
+ 0.upto(NFLAGS-1) do |i|
249
+ @counters[i], @saved_rooms[i] = f.gets.chomp.split.map(&:to_i)
250
+ end
251
+ tmp, dark_flag, @loc, @counter, @saved_room, @lampleft =
252
+ f.gets.chomp.split.map(&:to_i)
253
+ 0.upto(NFLAGS-1) do |i|
254
+ @flags[i] = (tmp & 1 << i) != 0
255
+ end
256
+ @items.each { |item| item.loc = f.gets.to_i }
257
+ end
258
+
259
+ def dputs(level, *args) #:nodoc:
260
+ puts args.map { |x| "##{x}" } if @options[level]
261
+ end
262
+
263
+ # Compiles the specified game-source file, writing the resulting
264
+ # object file to stdout, whence it should be redirected into a
265
+ # file so that it can be played. Yes, this API is sucky: it would
266
+ # be better if we had a simple compile method that builds the game
267
+ # in memory in a form that can by played, and which can then also
268
+ # be saved as an object file by some other method -- but that
269
+ # would have been more work for little gain.
270
+ #
271
+ # The input file may be specified either as a filename or a
272
+ # filehandle, or both. If both are given, then the filename is
273
+ # used only in reporting to help locate errors. _Some_ value must
274
+ # be given for the filename: an empty string is OK.
275
+ #
276
+ # (In case you're wondering, the main reason this has to be an
277
+ # instance method of the Game class rather than a standalone
278
+ # function is that its behaviour is influenced by the game's
279
+ # options.)
280
+ #
281
+ def compile_to_stdout(filename, fh = nil)
282
+ compiler = ScottKit::Game::Compiler.new(self, filename, fh)
283
+ compiler.compile_to_stdout
284
+ end
285
+
286
+ public :load, :compile_to_stdout # Must be visible to driver program
287
+ public :roomname, :itemname # Needed by Condition.render()
288
+ public :dputs # Needed for contained classes' debugging output
289
+ public :dirname # Needed by compiler
290
+ public :dark_flag= # Invoked from Instruction.execute()
291
+
292
+
293
+ class Condition #:nodoc:
294
+ OPS = [# Name, type of corresponding parameter
295
+ [ "param", :NONE ], # 0
296
+ [ "carried", :item ], # 1
297
+ [ "here", :item ], # 2
298
+ [ "present", :item ], # 3
299
+ [ "at", :room ], # 4
300
+ [ "!here", :item ], # 5
301
+ [ "!carried", :item ], # 6
302
+ [ "!at", :room ], # 7
303
+ [ "flag", :number ], # 8
304
+ [ "!flag", :number ], # 9
305
+ [ "loaded", :NONE ], # 10
306
+ [ "!loaded", :NONE ], # 11
307
+ [ "!present", :item ], # 12
308
+ [ "exists", :item ], # 13
309
+ [ "!exists", :item ], # 14
310
+ [ "counter_le", :number ], # 15
311
+ [ "counter_gt", :number ], # 16
312
+ [ "!moved", :item ], # 17
313
+ [ "moved", :item ], # 18
314
+ [ "counter_eq", :number ], # 19
315
+ ]
316
+ OPStoindex = {}; OPS.each.with_index { |x, i| OPStoindex[x[0]] = i }
317
+ OPStotype = {}; OPS.each { |x| OPStotype[x[0]] = x[1] }
318
+
319
+ def initialize(game, cond, value)
320
+ @game, @cond, @value = game, cond, value
321
+ end
322
+ end
323
+
324
+
325
+ class Instruction #:nodoc:
326
+ OPS = [# Name, type of corresponding parameters
327
+ [ "get", :item ], # 52
328
+ [ "drop", :item ], # 53
329
+ [ "goto", :room ], # 54
330
+ [ "destroy", :item ], # 55
331
+ [ "set_dark", :NONE ], # 56
332
+ [ "clear_dark", :NONE ], # 57
333
+ [ "set_flag", :number ], # 58
334
+ [ "destroy2", :item ], # 59
335
+ [ "clear_flag", :number ], # 60
336
+ [ "die", :NONE ], # 61
337
+ [ "put", :item_room ], # 62
338
+ [ "game_over", :NONE ], # 63
339
+ [ "look", :NONE ], # 64
340
+ [ "score", :NONE ], # 65
341
+ [ "inventory", :NONE ], # 66
342
+ [ "set_flag0", :NONE ], # 67
343
+ [ "clear_flag0", :NONE ], # 68
344
+ [ "refill_lamp", :NONE ], # 69
345
+ [ "clear", :NONE ], # 70
346
+ [ "save_game", :NONE ], # 71
347
+ [ "swap", :item_item ], # 72
348
+ [ "continue", :NONE ], # 73
349
+ [ "superget", :item ], # 74
350
+ [ "put_with", :item_item ], # 75
351
+ [ "look2", :NONE ], # 76
352
+ [ "dec_counter", :NONE ], # 77
353
+ [ "print_counter", :NONE ], # 78
354
+ [ "set_counter", :number ], # 79
355
+ [ "swap_room", :NONE ], # 80
356
+ [ "select_counter", :number ], # 81
357
+ [ "add_to_counter", :number ], # 82
358
+ [ "subtract_from_counter", :number ], # 83
359
+ [ "print_noun", :NONE ], # 84
360
+ [ "println_noun", :NONE ], # 85
361
+ [ "println", :NONE ], # 86
362
+ [ "swap_specific_room", :number ], # 87
363
+ [ "pause", :NONE ], # 88
364
+ [ "draw", :number ], # 89
365
+ ]
366
+ OPStoindex = {}; OPS.each.with_index { |x, i| OPStoindex[x[0]] = 52+i }
367
+ OPStotype = {}; OPS.each { |x| OPStotype[x[0]] = x[1] }
368
+
369
+ def initialize(game, op)
370
+ @game, @op = game, op
371
+ end
372
+ end
373
+
374
+
375
+ class Action #:nodoc:
376
+ attr_reader :verb, :noun, :conds, :instructions, :args
377
+ attr_accessor :comment
378
+
379
+ def initialize(game, verb, noun, conds, instructions, args)
380
+ @game, @verb, @noun, @conds, @instructions, @args =
381
+ game, verb, noun, conds, instructions, args
382
+ end
383
+ end
384
+
385
+
386
+ class Room #:nodoc:
387
+ attr_reader :desc, :exits
388
+
389
+ def initialize(desc, exits)
390
+ @desc, @exits = desc, exits
391
+ end
392
+ end
393
+
394
+
395
+ class Item #:nodoc:
396
+ attr_reader :desc, :name, :startloc
397
+ attr_accessor :loc
398
+
399
+ def initialize(desc, name, startloc)
400
+ @desc, @name, @startloc = desc, name, startloc
401
+ end
402
+ end
403
+ end
404
+ end
405
+
406
+
407
+ require_relative 'compile'
408
+ require_relative 'decompile'
409
+ require_relative 'play'