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.
- data/.gitignore +4 -0
- data/GPL-2 +339 -0
- data/Makefile +5 -0
- data/README +75 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/bin/scottkit +96 -0
- data/bin/scottkit.rb +96 -0
- data/data/.gitignore +1 -0
- data/data/adams/.gitignore +2 -0
- data/data/adams/AdamsGames.zip +0 -0
- data/data/adams/Makefile +18 -0
- data/data/crystal/crystal.map +112 -0
- data/data/crystal/crystal.sck +598 -0
- data/data/crystal/crystal.solution +82 -0
- data/data/dan-and-matt.sck +180 -0
- data/data/dan-and-matt.solution +32 -0
- data/data/howarth/.gitignore +2 -0
- data/data/howarth/Makefile +14 -0
- data/data/howarth/mysterious.tar.gz +0 -0
- data/data/test/Makefile +18 -0
- data/data/test/adams/Makefile +13 -0
- data/data/test/adams/adv01.solution +186 -0
- data/data/test/adams/adv01.transcript +869 -0
- data/data/test/adams/adv01.transcript.md5 +1 -0
- data/data/test/adams/adv02.solution +225 -0
- data/data/test/adams/adv02.transcript +970 -0
- data/data/test/adams/adv02.transcript.md5 +1 -0
- data/data/test/adams/adv04.solution +187 -0
- data/data/test/adams/adv04.transcript +876 -0
- data/data/test/adams/adv04.transcript.md5 +1 -0
- data/data/test/crystal.decompile +628 -0
- data/data/test/crystal.sao +373 -0
- data/data/test/crystal.save-file +62 -0
- data/data/test/crystal.save-script +41 -0
- data/data/test/crystal.sck +598 -0
- data/data/test/crystal.solution +82 -0
- data/data/test/crystal.transcript +413 -0
- data/data/test/t6.pretty-print +225 -0
- data/data/test/t7.sao +110 -0
- data/data/test/t7.solution +28 -0
- data/data/test/t7.transcript +147 -0
- data/data/tutorial/t1.sck +5 -0
- data/data/tutorial/t2.sck +14 -0
- data/data/tutorial/t3.sck +38 -0
- data/data/tutorial/t4.sck +62 -0
- data/data/tutorial/t5.sck +87 -0
- data/data/tutorial/t6.sck +119 -0
- data/data/tutorial/t7.sck +135 -0
- data/lib/scottkit/compile.rb +661 -0
- data/lib/scottkit/decompile.rb +175 -0
- data/lib/scottkit/game.rb +409 -0
- data/lib/scottkit/play.rb +474 -0
- data/notes/Definition +147 -0
- data/notes/Definition.saved-game +9 -0
- data/notes/Definition.scottfree-1.14 +142 -0
- data/notes/adventureland-maze +20 -0
- data/notes/continue-action +51 -0
- data/test/test_canonicalise.rb +94 -0
- data/test/test_compile.rb +54 -0
- data/test/test_decompile.rb +13 -0
- data/test/test_play.rb +20 -0
- data/test/test_playadams.rb +36 -0
- data/test/test_save.rb +31 -0
- data/test/withio.rb +15 -0
- data/test/withio_test.rb +31 -0
- 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'
|