rubylabs 0.9.0 → 0.9.1
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/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
|