rubylabs 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +15 -6
- data/Rakefile +3 -0
- data/VERSION +1 -1
- data/lib/bitlab.rb +593 -328
- data/lib/demos.rb +20 -9
- data/lib/elizalab.rb +660 -507
- data/lib/hashlab.rb +289 -192
- data/lib/introlab.rb +33 -38
- data/lib/iterationlab.rb +117 -61
- data/lib/marslab.rb +608 -475
- data/lib/randomlab.rb +227 -121
- data/lib/recursionlab.rb +197 -140
- data/lib/rubylabs.rb +936 -390
- data/lib/sievelab.rb +32 -24
- data/lib/spherelab.rb +308 -220
- data/lib/tsplab.rb +634 -312
- data/test/bit_test.rb +4 -4
- data/test/tsp_test.rb +18 -0
- metadata +2 -2
data/lib/marslab.rb
CHANGED
@@ -1,94 +1,151 @@
|
|
1
|
+
module RubyLabs
|
1
2
|
|
2
3
|
=begin rdoc
|
3
4
|
|
4
|
-
==
|
5
|
+
== MarsLab
|
5
6
|
|
6
|
-
|
7
|
+
The MARSLab module has definitions of classes and methods used in the projects for Chapter 8
|
8
|
+
of <em>Explorations in Computing</em>.
|
7
9
|
|
10
|
+
The module has a Ruby implementation of the MARS
|
11
|
+
virtual machine used in the game of Core War.
|
8
12
|
The machine implemented here conforms as close as possible to the ICWS'88 standard,
|
9
13
|
as described by Mark Durham in his Introduction to Redcode (http://corewar.co.uk/guides.htm).
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
field is DAT. Other Word objects have executable machine instructions for opcodes.
|
15
|
+
Instructions and data in a MARS program are represented by Word objects.
|
16
|
+
Each Word has an opcode and two operands (A and B). Integer data is represented by a Word where the opcode
|
17
|
+
field is DAT. Other Word objects have names of executable machine instructions for opcodes.
|
15
18
|
|
16
|
-
To test a single program
|
19
|
+
To test a single program users can make an instance of a class named MiniMARS, passing
|
17
20
|
it the name of a test program. The test machine will have a small memory, only big enough
|
18
21
|
to hold the test machine, and special versions of the step, dump and other methods.
|
19
22
|
|
20
23
|
The main machine that runs one, two, or three programs simultaneously is in the module
|
21
24
|
named MARS. Methods that assemble, load, and run programs are module methods, e.g.
|
22
25
|
to assemble a program call MARS.assemble(foo).
|
23
|
-
|
26
|
+
#--
|
27
|
+
TODO add [] and []= methods to Word, use it to extract/assign bits or ranges of bits
|
28
|
+
TODO define ranges for fields, e.g. @@opcode = 0..3; use to get opcode of Word, e.g. instr[@@opcode]
|
29
|
+
TODO make sure all code uses [] interface (e.g. no more instr.amode[0] == ?#)
|
30
|
+
TODO pack words into binary -- will kill several birds at once -- speed, trim values to 0..4095, ..
|
31
|
+
TODO color a cell black when a thread halts after executing an instruction in that cell
|
32
|
+
TODO MARS.dump
|
33
|
+
TODO pass load address to contest (which passes it on to Warrior.load)?
|
34
|
+
#++
|
24
35
|
=end
|
36
|
+
|
37
|
+
module MARSLab
|
38
|
+
|
39
|
+
# -- Initializations -- These are "global" vars in the outer MARSLab scope that are
|
40
|
+
# accessible to all the classes and modules defined inside MARSLab
|
41
|
+
|
42
|
+
@@marsDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'mars')
|
43
|
+
|
44
|
+
@@opcodes = ["DAT", "MOV", "ADD", "SUB", "JMP", "JMZ", "JMN", "DJN", "CMP", "SPL", "END", "SLT", "EQU"]
|
45
|
+
@@modes = "@<#"
|
25
46
|
|
26
|
-
|
27
|
-
|
28
|
-
# TODO make sure all code uses [] interface (e.g. no more instr.amode[0] == ?#)
|
29
|
-
# TODO pack words into binary -- will kill several birds at once -- speed, trim values to 0..4095, ..
|
30
|
-
# TODO color a cell black when a thread halts after executing an instruction in that cell
|
31
|
-
# TODO MARS.dump
|
32
|
-
# TODO pass load address to contest (which passes it on to Warrior.load)?
|
47
|
+
@@maxEntries = 3
|
48
|
+
@@displayMemSize = 4096
|
33
49
|
|
34
|
-
|
35
|
-
|
36
|
-
|
50
|
+
@@params = {
|
51
|
+
:maxRounds => 1000,
|
52
|
+
:memSize => @@displayMemSize,
|
53
|
+
:buffer => 100,
|
54
|
+
:tracing => false,
|
55
|
+
:pause => 0.01,
|
56
|
+
}
|
37
57
|
|
38
58
|
MARSView = Struct.new(:cells, :palettes, :options)
|
39
59
|
|
60
|
+
@@viewOptions = {
|
61
|
+
:cellSize => 8,
|
62
|
+
:cellRows => 32,
|
63
|
+
:cellCols => 128,
|
64
|
+
:padding => 20,
|
65
|
+
:traceSize => 10,
|
66
|
+
:cellColor => '#DDDDDD',
|
67
|
+
}
|
68
|
+
|
69
|
+
@@drawing = nil
|
70
|
+
|
71
|
+
=begin rdoc
|
72
|
+
A MARSRuntimeException is raised when MARS tries to execute an illegal instruction,
|
73
|
+
e.g. when a program contains an instruction with the wrong type of addressing mode for
|
74
|
+
its opcode.
|
75
|
+
=end
|
76
|
+
|
40
77
|
class MARSRuntimeException < StandardError
|
41
78
|
end
|
79
|
+
|
80
|
+
=begin rdoc
|
81
|
+
A RedcodeSyntaxError is raised when a source program contains an instruction that cannot
|
82
|
+
be translated into MARS machine language, e.g. it has an unknown opcode or a missing operand.
|
83
|
+
=end
|
42
84
|
|
43
85
|
class RedcodeSyntaxError < StandardError
|
44
86
|
end
|
45
87
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
88
|
+
=begin rdoc
|
89
|
+
|
90
|
+
== Word
|
91
|
+
|
92
|
+
An object of the Word class represents a single item from memory, either a machine
|
93
|
+
instruction or a piece of data. Attributes are the opcode, the A operand mode, and
|
94
|
+
the B operand (all strings). Instruction execution proceeds according to the description
|
95
|
+
in Durham's spec.
|
96
|
+
|
97
|
+
=end
|
98
|
+
|
99
|
+
class Word
|
100
|
+
|
101
|
+
attr_reader :op, :lineno
|
102
|
+
attr_accessor :a, :b
|
103
|
+
|
104
|
+
# Create a new Word from +op+, +a+, +b+, and +n+.
|
105
|
+
# :call-seq:
|
106
|
+
# Word.new(op, a, b, n) => Word
|
107
|
+
#
|
108
|
+
|
109
|
+
def initialize(*args)
|
110
|
+
@op, @a, @b, @lineno = args
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return a string representation of this word.
|
114
|
+
|
115
|
+
def inspect
|
116
|
+
sprintf "%s %s %s", @op, @a, @b
|
117
|
+
end
|
118
|
+
|
119
|
+
alias to_s inspect
|
120
|
+
|
121
|
+
# Two Words are the same if the strings representing the opcode and
|
122
|
+
# both operands are the same.
|
123
|
+
|
124
|
+
def ==(other)
|
125
|
+
return (op == other.op && a == other.a && b == other.b)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Make a new Word object with all the same attributes as this object.
|
129
|
+
|
130
|
+
def clone
|
131
|
+
Word.new(@op.clone, @a.clone, @b.clone, @lineno)
|
132
|
+
end
|
133
|
+
|
77
134
|
# Convert a field specification into an integer value, stripping off a leading
|
78
135
|
# addressing mode character if it's there
|
79
136
|
|
80
137
|
def field_value(field)
|
81
138
|
return @@modes.include?(field[0]) ? field[1..-1].to_i : field.to_i
|
82
139
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
140
|
+
|
141
|
+
# Return the address of an operand; note that for immediate operands the
|
142
|
+
# address is the address of the current instruction.
|
143
|
+
|
144
|
+
def dereference(field, pc, mem)
|
145
|
+
case field[0]
|
146
|
+
when ?#
|
147
|
+
return pc.current[:addr]
|
148
|
+
when ?@
|
92
149
|
ptrloc = (field_value(field) + pc.current[:addr]) % mem.size
|
93
150
|
ptr = mem.fetch(ptrloc)
|
94
151
|
return (field_value(ptr.b) + ptrloc) % mem.size
|
@@ -99,36 +156,40 @@ module MARSLab
|
|
99
156
|
mem.store_field(ptrloc, (newb % mem.size), :b)
|
100
157
|
return (newb + ptrloc) % mem.size
|
101
158
|
else
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
159
|
+
return (field_value(field) + pc.current[:addr]) % mem.size
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Execute the instruction represented by this word. The first parameter is the program counter used
|
164
|
+
# to fetch the instruction, the second is the machine's memory array.
|
165
|
+
#
|
166
|
+
# The semantics for each type of instruction are defined by private methods that have the same
|
167
|
+
# name as the opcode, e.g. then the opcode is "ADD" execute calls +ADD+, sending it the +pc+ and
|
168
|
+
# +mem+ arguments so the instruction can dereference its arguments and carry out the specified operation.
|
169
|
+
|
170
|
+
def execute(pc, mem)
|
171
|
+
self.send(@op, pc, mem)
|
172
|
+
return (@op == "DAT") ? (:halt) : (:continue)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
# The DAT instruction is effectively a "halt", but we still need to dereference
|
178
|
+
# both its operands to generate the side effects in auto-decrement modes.
|
179
|
+
|
180
|
+
def DAT(pc, mem) # :doc:
|
120
181
|
dereference(@a, pc, mem)
|
121
182
|
dereference(@b, pc, mem)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
183
|
+
end
|
184
|
+
|
185
|
+
# Durham isn't clear on how to handle immediate moves -- does the immediate value
|
186
|
+
# go in the A or B field of the destination? Guess: B, in case the destination
|
187
|
+
# is a DAT.
|
188
|
+
|
189
|
+
def MOV(pc, mem) # :doc:
|
190
|
+
raise MARSRuntimeException, "MOV: immediate B-field not allowed" if @b[0] == ?#
|
191
|
+
src = dereference(@a, pc, mem)
|
192
|
+
val = mem.fetch(src)
|
132
193
|
dest = dereference(@b, pc, mem)
|
133
194
|
if @a[0] == ?#
|
134
195
|
mem.store_field(dest, field_value(val.a), :b)
|
@@ -137,15 +198,15 @@ module MARSLab
|
|
137
198
|
end
|
138
199
|
pc.log(src)
|
139
200
|
pc.log(dest)
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
201
|
+
end
|
202
|
+
|
203
|
+
# Ambiguity on how to handle immediate A values: add operand to the A or B
|
204
|
+
# field of the destination? Guess: B (for same reasons given for MOV)
|
144
205
|
|
145
|
-
|
146
|
-
|
206
|
+
def ADD(pc, mem) # :doc:
|
207
|
+
raise MARSRuntimeException, "ADD: immediate B-field not allowed" if @b[0] == ?#
|
147
208
|
src = dereference(@a, pc, mem)
|
148
|
-
|
209
|
+
left_operand = mem.fetch(src)
|
149
210
|
dest = dereference(@b, pc, mem)
|
150
211
|
right_operand = mem.fetch(dest)
|
151
212
|
if @a[0] == ?#
|
@@ -156,14 +217,14 @@ module MARSLab
|
|
156
217
|
end
|
157
218
|
pc.log(src)
|
158
219
|
pc.log(dest)
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
220
|
+
end
|
221
|
+
|
222
|
+
# See note for ADD, re immediate A operand.
|
223
|
+
|
224
|
+
def SUB(pc, mem) # :doc:
|
225
|
+
raise MARSRuntimeException, "SUB: immediate B-field not allowed" if @b[0] == ?#
|
165
226
|
src = dereference(@a, pc, mem)
|
166
|
-
|
227
|
+
right_operand = mem.fetch(src)
|
167
228
|
dest = dereference(@b, pc, mem)
|
168
229
|
left_operand = mem.fetch(dest)
|
169
230
|
if @a[0] == ?#
|
@@ -174,56 +235,56 @@ module MARSLab
|
|
174
235
|
end
|
175
236
|
pc.log(src)
|
176
237
|
pc.log(dest)
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
238
|
+
end
|
239
|
+
|
240
|
+
# Durham doesn't mention this explicitly, but since a B operand is allowed it implies
|
241
|
+
# we have to dereference it in case it has a side effect.
|
242
|
+
|
243
|
+
def JMP(pc, mem) # :doc:
|
244
|
+
raise MARSRuntimeException, "JMP: immediate A-field not allowed" if @a[0] == ?#
|
245
|
+
target = dereference(@a, pc, mem) % mem.size
|
185
246
|
dereference(@b, pc, mem)
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
247
|
+
pc.branch(target)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Branch to address specified by A if the B-field of the B operand is zero.
|
251
|
+
|
252
|
+
def JMZ(pc, mem) # :doc:
|
253
|
+
raise MARSRuntimeException, "JMZ: immediate A-field not allowed" if @a[0] == ?#
|
254
|
+
target = dereference(@a, pc, mem) % mem.size
|
194
255
|
operand = mem.fetch(dereference(@b, pc, mem))
|
195
256
|
if field_value(operand.b) == 0
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
257
|
+
pc.branch(target)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# As in JMZ, but branch if operand is non-zero
|
262
|
+
|
263
|
+
def JMN(pc, mem) # :doc:
|
264
|
+
raise MARSRuntimeException, "JMN: immediate A-field not allowed" if @a[0] == ?#
|
265
|
+
target = dereference(@a, pc, mem) % mem.size
|
205
266
|
operand = mem.fetch(dereference(@b, pc, mem))
|
206
267
|
if field_value(operand.b) != 0
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
268
|
+
pc.branch(target)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# DJN combines the auto-decrement mode dereference logic with a branch -- take
|
273
|
+
# the branch if the new value of the B field of the pointer is non-zero.
|
274
|
+
|
275
|
+
def DJN(pc, mem) # :doc:
|
276
|
+
raise MARSRuntimeException, "DJN: immediate A-field not allowed" if @a[0] == ?#
|
277
|
+
target = dereference(@a, pc, mem)
|
217
278
|
operand_addr = dereference(@b, pc, mem)
|
218
279
|
operand = mem.fetch(operand_addr)
|
219
280
|
newb = field_value(operand.b) - 1
|
220
281
|
mem.store_field(operand_addr, (newb % mem.size), :b)
|
221
282
|
if newb != 0
|
222
|
-
|
223
|
-
|
283
|
+
pc.branch(target)
|
284
|
+
end
|
224
285
|
pc.log(operand_addr)
|
225
|
-
|
226
|
-
|
286
|
+
end
|
287
|
+
|
227
288
|
# Durham just says "compare two fields" if the operand is immediate. Since
|
228
289
|
# B can't be immediate, we just need to look at the A operand and, presumably,
|
229
290
|
# compare it to the A operand of the dereferenced operand fetched by the B field.
|
@@ -231,67 +292,73 @@ module MARSLab
|
|
231
292
|
# The call to pc.next increments the program counter for this thread, which causes
|
232
293
|
# the skip.
|
233
294
|
|
234
|
-
|
235
|
-
|
295
|
+
def CMP(pc, mem) # :doc:
|
296
|
+
raise MARSRuntimeException, "CMP: immediate B-field not allowed" if @b[0] == ?#
|
236
297
|
right = mem.fetch(dereference(@b, pc, mem))
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
298
|
+
if @a[0] == ?#
|
299
|
+
left = field_value(@a)
|
300
|
+
right = field_value(right.a)
|
301
|
+
else
|
302
|
+
left = mem.fetch(dereference(@a, pc, mem))
|
242
303
|
end
|
243
304
|
if left == right
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
305
|
+
pc.next
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# More ambiguity here -- what does it mean for a word A to be "less than"
|
310
|
+
# word B? First assumption, don't compare opcodes. Second, we're just going
|
311
|
+
# to implement one-field comparisons of B fields. Otherwise for full-word operands
|
312
|
+
# we'd need to do a lexicographic compare of A and B fields of both operands, skipping
|
313
|
+
# modes and just comparing values.
|
314
|
+
|
315
|
+
def SLT(pc, mem) # :doc:
|
316
|
+
raise MARSRuntimeException, "SLT: immediate B-field not allowed" if @b[0] == ?#
|
317
|
+
if @a[0] == ?#
|
318
|
+
left = field_value(@a)
|
319
|
+
else
|
320
|
+
left = field_value(mem.fetch(dereference(@a, pc, mem)).b)
|
321
|
+
end
|
261
322
|
right = field_value(mem.fetch(dereference(@b, pc, mem)).b)
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
=begin rdoc
|
281
|
-
A Warrior is a core warrior -- an object of this class is a simple struct that
|
282
|
-
has the program name, assembled code, the starting location, and the symbol table.
|
323
|
+
if left < right
|
324
|
+
pc.next
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# Fork a new thread at the address specified by A. The new thread goes at the end
|
329
|
+
# of the queue. Immediate operands are not allowed. Durham doesn't mention it, but
|
330
|
+
# implies only A is dereferenced, so ignore B.
|
331
|
+
|
332
|
+
def SPL(pc, mem) # :doc:
|
333
|
+
raise MARSRuntimeException, "SPL: immediate A-field not allowed" if @a[0] == ?#
|
334
|
+
target = dereference(@a, pc, mem)
|
335
|
+
pc.add_thread(target)
|
336
|
+
end
|
337
|
+
|
338
|
+
end # class Word
|
283
339
|
|
284
|
-
|
285
|
-
|
286
|
-
|
340
|
+
|
341
|
+
=begin rdoc
|
342
|
+
|
343
|
+
== Warrior
|
344
|
+
|
345
|
+
A Warrior is a core warrior -- an object of this class is a simple struct that
|
346
|
+
has the program name, assembled code, the starting location, and the symbol table.
|
347
|
+
|
348
|
+
A static method (Warrior.load) will load an assembled Warrior into memory, making
|
349
|
+
sure the max number of programs is not exceeded. The addr attribute is set to the
|
350
|
+
starting location of the program when it is loaded.
|
287
351
|
=end
|
288
352
|
|
289
353
|
class Warrior
|
290
354
|
attr_reader :name, :code, :symbols, :errors
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
355
|
+
|
356
|
+
# Create a new Warrior by reading and assembling the instructions in +filename+.
|
357
|
+
# The +code+ array is empty if there are any assembly errors.
|
358
|
+
|
359
|
+
def initialize(filename)
|
360
|
+
if filename.class == Symbol
|
361
|
+
filename = File.join(@@marsDirectory, filename.to_s + ".txt")
|
295
362
|
end
|
296
363
|
|
297
364
|
if ! File.exists?(filename)
|
@@ -300,20 +367,27 @@ module MARSLab
|
|
300
367
|
|
301
368
|
@name, @code, @symbols, @errors = MARS.assemble( File.open(filename).readlines )
|
302
369
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
370
|
+
if @errors.length > 0
|
371
|
+
puts "Syntax errors in #{filename}:"
|
372
|
+
puts errors
|
373
|
+
@code.clear
|
374
|
+
end
|
308
375
|
end
|
376
|
+
|
377
|
+
# Create a string with essential information about this object.
|
309
378
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
379
|
+
def inspect
|
380
|
+
sprintf "Name: #{@name} Code: #{@code.inspect}"
|
381
|
+
end
|
382
|
+
|
383
|
+
alias to_s inspect
|
384
|
+
|
385
|
+
# Load the code for the Warrior object reference by +app+ into the MARS main memory.
|
386
|
+
# If an address is specified, load that code starting at that location, otherwise
|
387
|
+
# choose a random location. The memory addresses used by the program are recorded
|
388
|
+
# so programs aren't accidentally loaded on top of each other.
|
389
|
+
|
390
|
+
def Warrior.load(app, addr = :random)
|
317
391
|
if app.class != Warrior
|
318
392
|
puts "Argument must be a Warrior object, not a #{app.class}"
|
319
393
|
return nil
|
@@ -365,6 +439,9 @@ module MARSLab
|
|
365
439
|
|
366
440
|
@@in_use = Array.new
|
367
441
|
|
442
|
+
# Clear the data structure that records which memory locations are in use, allowing
|
443
|
+
# new programs to be loaded into any location.
|
444
|
+
|
368
445
|
def Warrior.reset
|
369
446
|
@@in_use = Array.new
|
370
447
|
end
|
@@ -374,30 +451,24 @@ module MARSLab
|
|
374
451
|
# Return true if a program loaded between lb and ub would not overlap another
|
375
452
|
# program already loaded (including a buffer surrounding loaded programs).
|
376
453
|
|
377
|
-
def Warrior.check_loc(lb, ub)
|
454
|
+
def Warrior.check_loc(lb, ub) # :doc:
|
378
455
|
@@in_use.each do |range|
|
379
456
|
return false if range.include?(lb) || range.include?(ub)
|
380
457
|
end
|
381
458
|
return true
|
382
459
|
end
|
383
|
-
|
460
|
+
|
384
461
|
end # class Warrior
|
385
462
|
|
386
463
|
|
387
464
|
=begin rdoc
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
that was used in the most recent call to next (i.e. when that program dies).
|
396
|
-
|
397
|
-
To help visualize the operation of a program the class keeps a history of memory
|
398
|
-
references made by each thread. A call to next automatically records the program counter
|
399
|
-
value, but a semantic routine can also call log(x) to append location x to a history.
|
400
|
-
Call history to get the history vectors for all threads.
|
465
|
+
|
466
|
+
== PC
|
467
|
+
|
468
|
+
The PC (program counter) class is used to keep track of the next instruction to execute when a program is running.
|
469
|
+
A PC object has an array of locations to hold the next instruction from each thread, plus the
|
470
|
+
index of the thread to use on the next instruction fetch cycle.
|
471
|
+
|
401
472
|
=end
|
402
473
|
|
403
474
|
class PC
|
@@ -405,6 +476,10 @@ module MARSLab
|
|
405
476
|
|
406
477
|
@@hmax = 10 # see also @@threadMax in Draw -- allowed to be a different value
|
407
478
|
|
479
|
+
# Create a new program counter. The +id+ argument is a tag that can be used to
|
480
|
+
# identify which program is running at this location. The PC is intialized with
|
481
|
+
# one thread starting at location +addr+.
|
482
|
+
|
408
483
|
def initialize(id, addr)
|
409
484
|
@id = id
|
410
485
|
@addrs = [addr]
|
@@ -414,6 +489,9 @@ module MARSLab
|
|
414
489
|
@first = addr
|
415
490
|
end
|
416
491
|
|
492
|
+
# Restore this program counter to its original state, a single thread starting
|
493
|
+
# at the location passed when the object was created.
|
494
|
+
|
417
495
|
def reset
|
418
496
|
@addrs = [@first]
|
419
497
|
@history.clear
|
@@ -421,6 +499,9 @@ module MARSLab
|
|
421
499
|
@current = {:thread => nil, :addr => nil}
|
422
500
|
return @first
|
423
501
|
end
|
502
|
+
|
503
|
+
# Create a string showing the name of the program and the current instruction
|
504
|
+
# for each thread.
|
424
505
|
|
425
506
|
def inspect
|
426
507
|
s = "[ "
|
@@ -434,6 +515,8 @@ module MARSLab
|
|
434
515
|
|
435
516
|
alias to_s inspect
|
436
517
|
|
518
|
+
# Add a new thread, which will begin execution at location +addr+.
|
519
|
+
#--
|
437
520
|
# Not sure if this is right -- new thread appended to list, as per Durham, but
|
438
521
|
# the execution stays with the current thread (thread switch happens in 'next' method)
|
439
522
|
|
@@ -442,6 +525,9 @@ module MARSLab
|
|
442
525
|
@history << Array.new
|
443
526
|
self
|
444
527
|
end
|
528
|
+
|
529
|
+
# Return the address of the next instruction to execute for this program.
|
530
|
+
# If more than one thread is active, make the next thread the current thread.
|
445
531
|
|
446
532
|
def next
|
447
533
|
return nil if @addrs.empty?
|
@@ -454,9 +540,15 @@ module MARSLab
|
|
454
540
|
return addr
|
455
541
|
end
|
456
542
|
|
543
|
+
# Implement a branch instruction by setting the next instruction address for the
|
544
|
+
# current thread to +loc+.
|
545
|
+
|
457
546
|
def branch(loc)
|
458
547
|
@addrs[@current[:thread]] = loc
|
459
548
|
end
|
549
|
+
|
550
|
+
# Remove the current thread from the list of active threads. The return value
|
551
|
+
# is the number of remaining threads.
|
460
552
|
|
461
553
|
def kill_thread
|
462
554
|
return 0 if @addrs.empty?
|
@@ -466,16 +558,18 @@ module MARSLab
|
|
466
558
|
return @addrs.length
|
467
559
|
end
|
468
560
|
|
469
|
-
|
470
|
-
|
471
|
-
|
561
|
+
# Record the location of a memory operation in the history vector for the current thread.
|
562
|
+
# The history vector is used by the methods that display the progress of a program on
|
563
|
+
# the RubyLabs canvas.
|
472
564
|
|
473
565
|
def log(loc)
|
474
|
-
|
475
|
-
|
476
|
-
|
566
|
+
a = @history[@current[:thread]]
|
567
|
+
a << loc
|
568
|
+
a.shift if a.length > @@hmax
|
477
569
|
end
|
478
570
|
|
571
|
+
# Return a reference to the set of history vectors for this Warrior object.
|
572
|
+
|
479
573
|
def history
|
480
574
|
return @history
|
481
575
|
end
|
@@ -483,25 +577,35 @@ module MARSLab
|
|
483
577
|
end # class PC
|
484
578
|
|
485
579
|
=begin rdoc
|
486
|
-
|
487
|
-
|
580
|
+
|
581
|
+
== Memory
|
582
|
+
|
583
|
+
An object of the Memory class is a 1-D array of the specified size. Items in a Memory
|
584
|
+
are Word objects.
|
585
|
+
|
586
|
+
According to the Core War standard, memory should be initialized with DAT #0 instructions
|
587
|
+
before each contest. For efficiency, newly (re)initialized Memory objects have +nil+
|
588
|
+
at each location, but +nil+ is treated the same as DAT #0.
|
589
|
+
|
488
590
|
=end
|
489
|
-
|
490
|
-
|
491
|
-
|
591
|
+
|
592
|
+
class Memory
|
593
|
+
|
492
594
|
attr_reader :array
|
493
595
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
596
|
+
# Create a new Memory of the specified size.
|
597
|
+
|
598
|
+
def initialize(size)
|
599
|
+
@array = Array.new(size)
|
600
|
+
end
|
601
|
+
|
602
|
+
# Create a string describing this Memory object.
|
603
|
+
|
604
|
+
def to_s
|
605
|
+
sprintf "Memory [0..#{@array.size-1}]"
|
606
|
+
end
|
607
|
+
|
608
|
+
# Print the +n+ words in memory starting at +loc+.
|
505
609
|
|
506
610
|
def dump(loc, n)
|
507
611
|
(loc...(loc+n)).each do |x|
|
@@ -510,51 +614,61 @@ module MARSLab
|
|
510
614
|
end
|
511
615
|
end
|
512
616
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
617
|
+
# Return the size (number of words that can be stored) of this Memory object.
|
618
|
+
|
619
|
+
def size
|
620
|
+
return @array.size
|
621
|
+
end
|
622
|
+
|
623
|
+
# Return the Word object stored in location +loc+ in this Memory object.
|
624
|
+
|
625
|
+
def fetch(loc)
|
626
|
+
return ( @array[loc] || Word.new("DAT", "#0", "#0", nil) )
|
627
|
+
end
|
628
|
+
|
629
|
+
# Store object +val+ in location +loc+.
|
630
|
+
|
631
|
+
def store(loc, val)
|
632
|
+
@array[loc] = val.clone
|
633
|
+
end
|
634
|
+
|
635
|
+
# Same as +fetch+, but return only the designated field of the Word stored at location +loc+.
|
636
|
+
|
637
|
+
def fetch_field(loc, field)
|
638
|
+
instr = self.fetch(loc)
|
639
|
+
return field == :a ? instr.a : instr.b
|
640
|
+
end
|
641
|
+
|
642
|
+
# Same as +store+, but overwrite only the designated field of the Word in location +loc+.
|
643
|
+
|
644
|
+
def store_field(loc, val, field)
|
645
|
+
instr = self.fetch(loc)
|
646
|
+
part = (field == :a) ? instr.a : instr.b
|
647
|
+
mode = @@modes.include?(part[0]) ? part[0].chr : ""
|
648
|
+
part.replace(mode + val.to_s)
|
649
|
+
self.store(loc, instr)
|
650
|
+
end
|
651
|
+
|
652
|
+
end # class Memory
|
653
|
+
|
546
654
|
=begin rdoc
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
655
|
+
|
656
|
+
== MiniMARS
|
657
|
+
|
658
|
+
A miniature machine (MiniMARS) object is used to test a Redcode program. It is essentially
|
659
|
+
a MARS VM connected to a "thumb drive" that contains the assembled code for a single program.
|
660
|
+
By single-stepping through the program users can learn how the code works or debug a
|
661
|
+
program they are developing.
|
662
|
+
|
553
663
|
=end
|
554
664
|
|
555
|
-
|
556
|
-
|
557
|
-
|
665
|
+
class MiniMARS
|
666
|
+
attr_reader :mem, :pc, :state
|
667
|
+
|
668
|
+
# Create a VM that has a memory with the assembled form of the Redcode program in +file+.
|
669
|
+
# has been loaded into location 0. The optional second argument defines the size
|
670
|
+
# of the memory (otherwise the memory is just big enough to hold the program).
|
671
|
+
|
558
672
|
def initialize(file, size = nil)
|
559
673
|
w = Warrior.new(file)
|
560
674
|
@mem = size ? Memory.new(size) : Memory.new(w.code.length)
|
@@ -566,27 +680,41 @@ module MARSLab
|
|
566
680
|
@pc = PC.new(w.name, w.symbols[:start])
|
567
681
|
@state = :ready
|
568
682
|
end
|
569
|
-
|
683
|
+
|
684
|
+
# Create a string with a short description of this VM.
|
685
|
+
|
570
686
|
def inspect
|
571
687
|
return "#<MiniMARS mem = #{@mem.array.inspect} pc = #{@pc}>"
|
572
688
|
end
|
573
689
|
|
574
690
|
alias to_s inspect
|
575
691
|
|
692
|
+
# Print a string that describes the program status (running or halted, current
|
693
|
+
# instruction address, ...)
|
694
|
+
|
576
695
|
def status
|
577
696
|
s = "Run: #{@state}"
|
578
697
|
s += " PC: #{@pc}" unless @state == :halt
|
579
698
|
puts s
|
580
699
|
end
|
581
700
|
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
701
|
+
# Execute the next instruction in the program loaded into this VM. The
|
702
|
+
# return value is the Word object for the instruction just executed.
|
703
|
+
|
704
|
+
def step
|
705
|
+
return "machine halted" if @state == :halt
|
706
|
+
instr = @mem.fetch(@pc.next)
|
707
|
+
@state = instr.execute(@pc, @mem)
|
708
|
+
return instr
|
709
|
+
end
|
710
|
+
|
711
|
+
# Execute instructions in the program loaded into this VM until it hits
|
712
|
+
# a HALT (DAT) instruction. The return value is the number of instructions
|
713
|
+
# executed. The optional argument is a maximum number of steps
|
714
|
+
# to execute; afer executing this number of instructions the method will
|
715
|
+
# return, whether or not the program halts.
|
716
|
+
|
717
|
+
def run(nsteps = 1000)
|
590
718
|
count = 0
|
591
719
|
loop do
|
592
720
|
break if @state == :halt || nsteps <= 0
|
@@ -595,43 +723,56 @@ module MARSLab
|
|
595
723
|
count += 1
|
596
724
|
end
|
597
725
|
return count
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
726
|
+
end
|
727
|
+
|
728
|
+
# Reset the program counter to 0, so the program starts over. But the
|
729
|
+
# contents of memory are not restored, they are left as they were after the
|
730
|
+
# last instruction executed.
|
731
|
+
|
732
|
+
def reset
|
733
|
+
@pc.reset
|
734
|
+
puts "warning: memory may not be in initial state"
|
735
|
+
end
|
736
|
+
|
737
|
+
# Print the contents of the VM memory. If two arguments are passed they
|
738
|
+
# are used as the addresses of the first and last words to print.
|
739
|
+
|
740
|
+
def dump(*args)
|
741
|
+
if args.empty?
|
742
|
+
@mem.dump(0, @mem.array.length)
|
608
743
|
else
|
609
744
|
x = args[0]
|
610
745
|
y = args[1] || x
|
611
746
|
@mem.dump(x, y-x+1)
|
612
747
|
end
|
613
748
|
return nil
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
749
|
+
end
|
750
|
+
|
751
|
+
end # MiniMARS
|
752
|
+
|
618
753
|
|
619
754
|
=begin rdoc
|
620
|
-
|
621
|
-
|
755
|
+
|
756
|
+
== MARS
|
757
|
+
|
758
|
+
The MARS class defines a singleton object that has the methods used to assemble, load, and execute
|
759
|
+
up to three Redcode programs at a time.
|
760
|
+
|
622
761
|
=end
|
623
762
|
|
624
763
|
class MARS
|
625
764
|
|
626
|
-
|
765
|
+
# -------- Assembler ----------
|
627
766
|
|
628
|
-
|
629
|
-
|
767
|
+
# line format:
|
768
|
+
# <label> <opcode> <A-mode><A-field>, <B-mode><B-field> <comment>
|
630
769
|
|
631
|
-
|
632
|
-
|
770
|
+
# Parsing methods strip off and return the item they are looking for, or raise
|
771
|
+
# an error if the line doesn't start with a well-formed item
|
772
|
+
|
773
|
+
# Labels start in column 0, can be any length
|
633
774
|
|
634
|
-
def MARS.parseLabel(s)
|
775
|
+
def MARS.parseLabel(s) # :nodoc:
|
635
776
|
return nil if s =~ /^\s/
|
636
777
|
x = s[/^\w+/] # set x to the label
|
637
778
|
if x.nil? # x is nil if label didn't start with a word char
|
@@ -644,8 +785,10 @@ module MARSLab
|
|
644
785
|
return x # return it
|
645
786
|
end
|
646
787
|
|
647
|
-
|
648
|
-
|
788
|
+
# Expect opcodes to be separated from labels (or start of line) by white space
|
789
|
+
|
790
|
+
def MARS.parseOpCode(s) # :nodoc:
|
791
|
+
if s !~ /^\s/
|
649
792
|
raise RedcodeSyntaxError, "illegal label in '#{s}'"
|
650
793
|
end
|
651
794
|
s.lstrip!
|
@@ -657,7 +800,7 @@ module MARSLab
|
|
657
800
|
return x.upcase # return it
|
658
801
|
end
|
659
802
|
|
660
|
-
def MARS.parseOperand(s)
|
803
|
+
def MARS.parseOperand(s) # :nodoc:
|
661
804
|
s.lstrip!
|
662
805
|
return s if s.length == 0
|
663
806
|
x = s[/^[#{@@modes}]?[+-]?\w+/]
|
@@ -668,7 +811,7 @@ module MARSLab
|
|
668
811
|
return x.upcase
|
669
812
|
end
|
670
813
|
|
671
|
-
def MARS.parseSeparator(s)
|
814
|
+
def MARS.parseSeparator(s) # :nodoc:
|
672
815
|
s.lstrip!
|
673
816
|
return if s.length == 0
|
674
817
|
if s[0] == ?,
|
@@ -677,17 +820,21 @@ module MARSLab
|
|
677
820
|
raise RedcodeSyntaxError, "operands must be separated by a comma"
|
678
821
|
end
|
679
822
|
end
|
823
|
+
|
824
|
+
# Helper method called by the assembler to break an input line into its constituent
|
825
|
+
# parts. Calls its own helpers named +parseLabel+, +parseOpCode+, and +parseOperand+;
|
826
|
+
# see marslab.rb for more information.
|
680
827
|
|
681
828
|
def MARS.parse(s)
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
829
|
+
label = MARS.parseLabel(s)
|
830
|
+
op = MARS.parseOpCode(s)
|
831
|
+
a = MARS.parseOperand(s)
|
832
|
+
MARS.parseSeparator(s)
|
833
|
+
b = MARS.parseOperand(s)
|
687
834
|
return [label,op,a,b]
|
688
835
|
end
|
689
836
|
|
690
|
-
def MARS.saveWord(instr, label, code, symbols)
|
837
|
+
def MARS.saveWord(instr, label, code, symbols) # :nodoc:
|
691
838
|
if instr.op == "EQU"
|
692
839
|
operand = instr.a
|
693
840
|
arg = operand[/[+-]?\d+/]
|
@@ -709,7 +856,7 @@ module MARSLab
|
|
709
856
|
end
|
710
857
|
end
|
711
858
|
|
712
|
-
def MARS.translate(s, symbols, loc)
|
859
|
+
def MARS.translate(s, symbols, loc) # :nodoc:
|
713
860
|
if s.length == 0
|
714
861
|
s.insert(0, "#0")
|
715
862
|
return true
|
@@ -725,11 +872,12 @@ module MARSLab
|
|
725
872
|
return false
|
726
873
|
end
|
727
874
|
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
875
|
+
# A simple two-pass assembler for Redcode. The input is an array of strings read
|
876
|
+
# from a file. The result of the call is an array of 4 items:
|
877
|
+
# +name+:: the name of the program (if there was a name pseudo-op in the source code)
|
878
|
+
# +code+:: the assembled code, in the form of an array of Word objects
|
879
|
+
# +symbols+:: a Hash with labels and their values
|
880
|
+
# +errors+:: an array of error messages
|
733
881
|
|
734
882
|
def MARS.assemble(strings)
|
735
883
|
code = Array.new # save Word objects here
|
@@ -738,34 +886,33 @@ module MARSLab
|
|
738
886
|
name = "unknown" # default program name
|
739
887
|
name += @@entries.size.to_s
|
740
888
|
symbols[:start] = 0 # default starting address
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
889
|
+
|
890
|
+
# Pass 1 -- create list of Word objects, build the symbol table:
|
891
|
+
|
892
|
+
strings.each_with_index do |line, lineno|
|
893
|
+
line.rstrip!
|
894
|
+
next if line.length == 0 # skip blank lines
|
895
|
+
if line[0] == ?; # comments have ; in column 0
|
896
|
+
if md = line.match(/;\s*name\s+(\w+)/) # does comment have program name?
|
897
|
+
name = md[1] # yes -- save it
|
898
|
+
end
|
899
|
+
next
|
900
|
+
end
|
901
|
+
if n = line.index(";") # if comment at end remove it
|
902
|
+
line.slice!(n..-1)
|
903
|
+
end
|
756
904
|
begin
|
757
905
|
label, op, a, b = MARS.parse(line)
|
758
|
-
# puts "parse '#{label}' '#{op}' '#{a}' '#{b}'"
|
759
906
|
instr = Word.new(op, a, b, lineno+1)
|
760
|
-
MARS.saveWord(instr, label, code, symbols)
|
907
|
+
MARS.saveWord(instr, label, code, symbols)
|
761
908
|
rescue RedcodeSyntaxError
|
762
909
|
errors << " line #{lineno+1}: #{$!}"
|
763
910
|
end
|
764
|
-
|
765
|
-
|
766
|
-
|
911
|
+
end
|
912
|
+
|
913
|
+
# Pass 2 -- translate labels into ints on each instruction:
|
767
914
|
# TODO -- deal with expressions, e.g. "JMP label + 1"
|
768
|
-
|
915
|
+
|
769
916
|
code.each_with_index do |instr, loc|
|
770
917
|
if instr.op == "DAT" && instr.b.length == 0
|
771
918
|
instr.a, instr.b = instr.b, instr.a
|
@@ -773,23 +920,21 @@ module MARSLab
|
|
773
920
|
begin
|
774
921
|
MARS.translate(instr.a, symbols, loc) or raise RedcodeSyntaxError, "unknown/illegal label"
|
775
922
|
MARS.translate(instr.b, symbols, loc) or raise RedcodeSyntaxError, "unknown/illegal label"
|
776
|
-
|
923
|
+
rescue RedcodeSyntaxError
|
777
924
|
errors << " line #{instr.lineno}: #{$!}"
|
778
925
|
end
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
926
|
+
end
|
927
|
+
|
928
|
+
return [name, code, symbols, errors]
|
929
|
+
end
|
783
930
|
|
784
931
|
# -------- End Assembler ----------
|
785
932
|
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
def MARS.step
|
933
|
+
# Execute one instruction from each active program. If a thread dies remove the PC
|
934
|
+
# from the array for that program. A program dies when its last thread dies. The
|
935
|
+
# return value is the number of programs still running.
|
936
|
+
|
937
|
+
def MARS.step
|
793
938
|
if @@entries.empty?
|
794
939
|
puts "no programs loaded"
|
795
940
|
return 0
|
@@ -805,11 +950,11 @@ module MARSLab
|
|
805
950
|
@@pcs.each_with_index do |pc, id|
|
806
951
|
loc = pc.next
|
807
952
|
instr = @@mem.fetch(loc)
|
808
|
-
|
953
|
+
printf("%04d: %s\n", pc.current[:addr], instr) if @@params[:tracing]
|
809
954
|
|
810
955
|
if @@drawing
|
811
956
|
MARS.updateCells(pc)
|
812
|
-
sleep(@@params[:
|
957
|
+
sleep(@@params[:pause])
|
813
958
|
end
|
814
959
|
|
815
960
|
begin
|
@@ -840,28 +985,26 @@ module MARSLab
|
|
840
985
|
|
841
986
|
return @@pcs.size
|
842
987
|
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
988
|
+
end
|
989
|
+
|
990
|
+
# Run the programs in memory, stopping when the number of surviving programs is +nleft+.
|
991
|
+
# To hold a contest that continues until only one program survives call <tt>Mars.run(1)</tt>,
|
992
|
+
# but when testing a single program the call can be <tt>Mars.run(0)</tt>. This method will
|
993
|
+
# also return when the maximum number of instructions has been executed (determined by the runtime
|
994
|
+
# parameter +maxRounds+).
|
995
|
+
|
996
|
+
def MARS.run(nleft = 0)
|
852
997
|
nrounds = @@params[:maxRounds]
|
853
998
|
loop do
|
854
999
|
nprog = MARS.step
|
855
1000
|
nrounds -= 1
|
856
1001
|
break if nprog == nleft || nrounds <= 0
|
857
1002
|
end
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
def MARS.state
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
# Print information about the machine and any programs in memory.
|
1006
|
+
|
1007
|
+
def MARS.state
|
865
1008
|
puts "MARS CPU with #{@@mem.size}-word memory"
|
866
1009
|
if @@entries.length == 0
|
867
1010
|
puts "no programs loaded"
|
@@ -877,13 +1020,21 @@ module MARSLab
|
|
877
1020
|
puts " #{key}: #{val}"
|
878
1021
|
end
|
879
1022
|
return true
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
|
1026
|
+
# Set the value of one of the run-time options. The options, and their default values, are:
|
1027
|
+
# memSize:: size of the virtual machine memory (4096 words)
|
1028
|
+
# buffer:: minimum distance between code for two warriors (100 words)
|
1029
|
+
# tracing:: when true, print detailed trace as machine runs (false)
|
1030
|
+
# pause:: amount of time to pause between screen updates when programs are run (0.01 sec)
|
1031
|
+
# maxRounds:: number of turns to give each program before calling a draw (1000)
|
1032
|
+
#
|
1033
|
+
# Example:
|
1034
|
+
# >>!blue MARS.set_option(:pause, 0.5)
|
1035
|
+
# => 0.5
|
1036
|
+
#
|
1037
|
+
# Note: Call MARS.state to see the current settings for the options.
|
887
1038
|
|
888
1039
|
def MARS.set_option(key, val)
|
889
1040
|
if ! @@entries.empty?
|
@@ -901,7 +1052,7 @@ module MARSLab
|
|
901
1052
|
puts ":#{key} must be an integer"
|
902
1053
|
return nil
|
903
1054
|
end
|
904
|
-
when :
|
1055
|
+
when :pause
|
905
1056
|
if val.class != Float
|
906
1057
|
puts ":#{key} must be an floating point number (e.g. 0.05)"
|
907
1058
|
return nil
|
@@ -913,18 +1064,20 @@ module MARSLab
|
|
913
1064
|
end
|
914
1065
|
else
|
915
1066
|
puts "Unknown option: #{key}"
|
1067
|
+
puts "Call MARS.state to see a list of options and their current settings."
|
916
1068
|
return nil
|
917
1069
|
end
|
918
1070
|
@@params[key] = val
|
919
1071
|
end
|
920
|
-
|
921
|
-
=begin rdoc
|
922
|
-
view() -- open a visual view of the machine state
|
923
1072
|
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
1073
|
+
# Initialize the RubyLabs canvas with a drawing of the MARS VM main memory. The
|
1074
|
+
# display will show one rectangle for each memory cell. When a core warrior is
|
1075
|
+
# loaded into memory, the rectangles for the cells it occupies will change color,
|
1076
|
+
# with a different color for each contestant. As a program runs, any memory cells
|
1077
|
+
# it references will be filled with that program's color. To keep the screen from
|
1078
|
+
# having too much color, cells gradually fade back to gray if they are not referenced
|
1079
|
+
# for a long time.
|
1080
|
+
|
928
1081
|
def MARS.view(userOptions = {} )
|
929
1082
|
|
930
1083
|
options = @@viewOptions.merge(userOptions)
|
@@ -938,42 +1091,40 @@ module MARSLab
|
|
938
1091
|
|
939
1092
|
cells = []
|
940
1093
|
for i in 0...@@displayMemSize
|
941
|
-
|
942
|
-
|
943
|
-
|
1094
|
+
x = (i % options[:cellCols]) * cellsize + padding
|
1095
|
+
y = (i / options[:cellCols]) * cellsize + padding
|
1096
|
+
cells << Canvas::Rectangle.new( x, y, x+cellsize, y+cellsize, :outline => "#888888", :fill => options[:cellColor] )
|
944
1097
|
end
|
945
1098
|
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
1099
|
+
palettes = [
|
1100
|
+
Canvas.palette( [204,204,204], [204,100,100], options[:traceSize] ),
|
1101
|
+
Canvas.palette( [204,204,204], [100,100,204], options[:traceSize] ),
|
1102
|
+
Canvas.palette( [204,204,204], [100,204,100], options[:traceSize] ),
|
1103
|
+
]
|
1104
|
+
palettes[0] << "#FF0000"
|
1105
|
+
palettes[1] << "#0000FF"
|
1106
|
+
palettes[2] << "#00FF00"
|
1107
|
+
|
955
1108
|
@@drawing = MARSView.new(cells, palettes, options)
|
956
1109
|
return true
|
957
1110
|
|
958
1111
|
end
|
1112
|
+
|
1113
|
+
# Close the RubyLabs Canvas window.
|
959
1114
|
|
960
1115
|
def MARS.close_view
|
961
1116
|
Canvas.close
|
962
1117
|
end
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
=begin rdoc
|
974
|
-
Run a contest. Parameters are names of up to three programs. Make a Warrior
|
975
|
-
object for each one, load them, and call run.
|
976
|
-
=end
|
1118
|
+
|
1119
|
+
# Load up to three core warrior programs and start a contest, returning when only one
|
1120
|
+
# program is still running or when the maximum number of turns have been taken. Arguments
|
1121
|
+
# can be symbols, in which case they are names of programs in the MARSLab data directory,
|
1122
|
+
# or strings, in which case they are interpreted as names of Redcode files in the current
|
1123
|
+
# directory.
|
1124
|
+
#
|
1125
|
+
# Example:
|
1126
|
+
# >>!blue MARS.contest(:imp, :dwarf)
|
1127
|
+
# => nil
|
977
1128
|
|
978
1129
|
def MARS.contest(*args)
|
979
1130
|
MARS.reset
|
@@ -987,13 +1138,12 @@ module MARSLab
|
|
987
1138
|
MARS.run(1) # the 1 means stop when only 1 program left running
|
988
1139
|
end
|
989
1140
|
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
1141
|
+
# Reset the machine state by clearing memory and removing all programs from
|
1142
|
+
# the list of active warriors.
|
1143
|
+
|
994
1144
|
def MARS.reset
|
995
1145
|
@@pcs = Array.new
|
996
|
-
|
1146
|
+
@@mem = Memory.new(@@params[:memSize])
|
997
1147
|
@@entries = Array.new
|
998
1148
|
Warrior.reset
|
999
1149
|
if @@drawing
|
@@ -1004,9 +1154,9 @@ module MARSLab
|
|
1004
1154
|
return true
|
1005
1155
|
end
|
1006
1156
|
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1157
|
+
# Print a listing of a Redcode source programs. If the argument is a symbol, it
|
1158
|
+
# is interpreted as the name of a program in the MARSLab data directory; if it is
|
1159
|
+
# a string, it should be the name of a file in the current directory.
|
1010
1160
|
|
1011
1161
|
def MARS.listing(prog, dest = nil)
|
1012
1162
|
filename = prog.to_s
|
@@ -1016,17 +1166,19 @@ module MARSLab
|
|
1016
1166
|
if !File.exists?(filename)
|
1017
1167
|
puts "File not found: #{filename}"
|
1018
1168
|
else
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1169
|
+
File.open(filename).each do |line|
|
1170
|
+
dest.puts line.chomp
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
return nil
|
1024
1174
|
end
|
1025
1175
|
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1176
|
+
# Save a copy of a Redcode source program.
|
1177
|
+
# A program named "x" is saved in a file named "x.txt" in the current directory
|
1178
|
+
# unless a different file name is passed as a second argument.
|
1179
|
+
# If the argument is a symbol, it
|
1180
|
+
# is interpreted as the name of a program in the MARSLab data directory; if it is
|
1181
|
+
# a string, it should be the name of a file in the current directory.
|
1030
1182
|
|
1031
1183
|
def MARS.checkout(prog, filename = nil)
|
1032
1184
|
filename = prog.to_s + ".txt" if filename.nil?
|
@@ -1036,9 +1188,7 @@ module MARSLab
|
|
1036
1188
|
puts "Copy of #{prog} saved in #{filename}"
|
1037
1189
|
end
|
1038
1190
|
|
1039
|
-
|
1040
|
-
Print a list of programs
|
1041
|
-
=end
|
1191
|
+
# Print a list of programs in the MARSLab data directory.
|
1042
1192
|
|
1043
1193
|
def MARS.dir
|
1044
1194
|
puts "Redcode programs in #{@@marsDirectory}:"
|
@@ -1049,13 +1199,26 @@ module MARSLab
|
|
1049
1199
|
end
|
1050
1200
|
return nil
|
1051
1201
|
end
|
1202
|
+
|
1203
|
+
private
|
1204
|
+
|
1205
|
+
# Drawing hook, called from MARS.step when the canvas is active.
|
1206
|
+
|
1207
|
+
def MARS.updateCells(pc)
|
1208
|
+
id = pc.id
|
1209
|
+
a = pc.history[pc.current[:thread]]
|
1210
|
+
d = @@drawing.palettes[id].length - a.length
|
1211
|
+
a.each_with_index do |x, i|
|
1212
|
+
@@drawing.cells[x].fill = @@drawing.palettes[id][i+d]
|
1213
|
+
end
|
1214
|
+
end
|
1052
1215
|
|
1053
1216
|
end # class MARS
|
1054
1217
|
|
1055
1218
|
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1219
|
+
# Make a MiniMARS VM with a single program loaded into memory at location 0.
|
1220
|
+
# By default the size of the memory is the number of words in the program, but a memory
|
1221
|
+
# size can be passed as a second argument.
|
1059
1222
|
|
1060
1223
|
def make_test_machine(file, size = nil)
|
1061
1224
|
begin
|
@@ -1065,39 +1228,9 @@ module MARSLab
|
|
1065
1228
|
return nil
|
1066
1229
|
end
|
1067
1230
|
end
|
1068
|
-
|
1069
|
-
# -- Initializations -- These are "global" vars in the outer MARSLab scope that are
|
1070
|
-
# accessible to all the classes and modules defined inside MARSLab
|
1071
1231
|
|
1072
|
-
@@marsDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'mars')
|
1073
|
-
|
1074
|
-
@@opcodes = ["DAT", "MOV", "ADD", "SUB", "JMP", "JMZ", "JMN", "DJN", "CMP", "SPL", "END", "SLT", "EQU"]
|
1075
|
-
@@modes = "@<#"
|
1076
|
-
|
1077
|
-
@@maxEntries = 3
|
1078
|
-
@@displayMemSize = 4096
|
1079
|
-
|
1080
|
-
@@params = {
|
1081
|
-
:maxRounds => 1000,
|
1082
|
-
:memSize => @@displayMemSize,
|
1083
|
-
:buffer => 100,
|
1084
|
-
:tracing => false,
|
1085
|
-
:pace => 0.01,
|
1086
|
-
}
|
1087
|
-
|
1088
|
-
@@viewOptions = {
|
1089
|
-
:cellSize => 8,
|
1090
|
-
:cellRows => 32,
|
1091
|
-
:cellCols => 128,
|
1092
|
-
:padding => 20,
|
1093
|
-
:traceSize => 10,
|
1094
|
-
:cellColor => '#DDDDDD',
|
1095
|
-
}
|
1096
|
-
|
1097
|
-
@@drawing = nil
|
1098
|
-
|
1099
1232
|
@@pcs = Array.new
|
1100
|
-
|
1233
|
+
@@mem = Memory.new(@@params[:memSize])
|
1101
1234
|
@@entries = Array.new
|
1102
1235
|
|
1103
1236
|
end # MARSLab
|