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/lib/marslab.rb CHANGED
@@ -1,94 +1,151 @@
1
+ module RubyLabs
1
2
 
2
3
  =begin rdoc
3
4
 
4
- == Core Wars Lab
5
+ == MarsLab
5
6
 
6
- Ruby implementation of the MARS virtual machine used in Core Wars.
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
- Currently there is no attempt to translate instructions into a numerical format. MARS
12
- code is a set of Word objects. Each Word has an opcode and two operands (A and B), all of
13
- which are represented as strings. Integer data is represented by a Word where the opcode
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 students can make an instance of a class named MiniMARS, passing
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
- # TODO add [] and []= methods to Word, use it to extract/assign bits or ranges of bits
27
- # TODO define ranges for fields, e.g. @@opcode = 0..3; use to get opcode of Word, e.g. instr[@@opcode]
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
- module RubyLabs
35
-
36
- module MARSLab
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
- # An object of the Word class represents a single item from memory, either a machine
47
- # instruction or a piece of data. Attributes are the opcode, the A operand mode, and
48
- # the B operand (all strings). Instruction execution proceeds according to the description
49
- # in Durham's spec.
50
-
51
- class Word
52
-
53
- attr_reader :op, :lineno
54
- attr_accessor :a, :b
55
-
56
- def initialize(*args)
57
- @op, @a, @b, @lineno = args
58
- end
59
-
60
- def inspect
61
- sprintf "%s %s %s", @op, @a, @b
62
- end
63
-
64
- alias to_s inspect
65
-
66
- # two Words are the same if the strings representing the opcode and
67
- # both operands are the same
68
-
69
- def ==(other)
70
- return (op == other.op && a == other.a && b == other.b)
71
- end
72
-
73
- def clone
74
- Word.new(@op.clone, @a.clone, @b.clone, @lineno)
75
- end
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
- # Return the address of an operand; note that for immediate operands the
85
- # address is the address of the current instruction.
86
-
87
- def dereference(field, pc, mem)
88
- case field[0]
89
- when ?#
90
- return pc.current[:addr]
91
- when ?@
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
- return (field_value(field) + pc.current[:addr]) % mem.size
103
- end
104
- end
105
-
106
- # Execute an instruction. The first parameter is the program counter used
107
- # to fetch the instruction, the second is the machine's memory array.
108
-
109
- def execute(pc, mem)
110
- self.send(@op, pc, mem)
111
- return (@op == "DAT") ? (:halt) : (:continue)
112
- end
113
-
114
- private
115
-
116
- # The DAT instruction is effectively a "halt", but we still need to dereference
117
- # both its operands to generate the side effects in auto-decrement modes.
118
-
119
- def DAT(pc, mem)
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
- end
123
-
124
- # Durham isn't clear on how to handle immediate moves -- does the immediate value
125
- # go in the A or B field of the destination? Guess: B, in case the destination
126
- # is a DAT.
127
-
128
- def MOV(pc, mem)
129
- raise MARSRuntimeException, "MOV: immediate B-field not allowed" if @b[0] == ?#
130
- src = dereference(@a, pc, mem)
131
- val = mem.fetch(src)
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
- end
141
-
142
- # Ambiguity on how to handle immediate A values: add operand to the A or B
143
- # field of the destination? Guess: B (for same reasons given for MOV)
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
- def ADD(pc, mem)
146
- raise MARSRuntimeException, "ADD: immediate B-field not allowed" if @b[0] == ?#
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
- left_operand = mem.fetch(src)
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
- end
160
-
161
- # See note for ADD, re immediate A operand.
162
-
163
- def SUB(pc, mem)
164
- raise MARSRuntimeException, "SUB: immediate B-field not allowed" if @b[0] == ?#
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
- right_operand = mem.fetch(src)
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
- end
178
-
179
- # Durham doesn't mention this explicitly, but since a B operand is allowed it implies
180
- # we have to dereference it in case it has a side effect.
181
-
182
- def JMP(pc, mem)
183
- raise MARSRuntimeException, "JMP: immediate A-field not allowed" if @a[0] == ?#
184
- target = dereference(@a, pc, mem) % mem.size
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
- pc.branch(target)
187
- end
188
-
189
- # Branch to address specified by A if the B-field of the B operand is zero.
190
-
191
- def JMZ(pc, mem)
192
- raise MARSRuntimeException, "JMZ: immediate A-field not allowed" if @a[0] == ?#
193
- target = dereference(@a, pc, mem) % mem.size
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
- pc.branch(target)
197
- end
198
- end
199
-
200
- # As in JMZ, but branch if operand is non-zero
201
-
202
- def JMN(pc, mem)
203
- raise MARSRuntimeException, "JMN: immediate A-field not allowed" if @a[0] == ?#
204
- target = dereference(@a, pc, mem) % mem.size
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
- pc.branch(target)
208
- end
209
- end
210
-
211
- # DJN combines the auto-decrement mode dereference logic with a branch -- take
212
- # the branch if the new value of the B field of the pointer is non-zero.
213
-
214
- def DJN(pc, mem)
215
- raise MARSRuntimeException, "DJN: immediate A-field not allowed" if @a[0] == ?#
216
- target = dereference(@a, pc, mem)
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
- pc.branch(target)
223
- end
283
+ pc.branch(target)
284
+ end
224
285
  pc.log(operand_addr)
225
- end
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
- def CMP(pc, mem)
235
- raise MARSRuntimeException, "CMP: immediate B-field not allowed" if @b[0] == ?#
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
- if @a[0] == ?#
238
- left = field_value(@a)
239
- right = field_value(right.a)
240
- else
241
- left = mem.fetch(dereference(@a, pc, mem))
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
- pc.next
245
- end
246
- end
247
-
248
- # Major ambiguity here -- what does it mean for a word A to be "less than"
249
- # word B? First assumption, don't compare opcodes. Second, we're just going
250
- # to implement one-field comparisons of B fields. Otherwise for full-word operands
251
- # we'd need to do a lexicographic compare of A and B fields of both operands, skipping
252
- # modes and just comparing values.
253
-
254
- def SLT(pc, mem)
255
- raise MARSRuntimeException, "SLT: immediate B-field not allowed" if @b[0] == ?#
256
- if @a[0] == ?#
257
- left = field_value(@a)
258
- else
259
- left = field_value(mem.fetch(dereference(@a, pc, mem)).b)
260
- end
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
- if left < right
263
- pc.next
264
- end
265
- end
266
-
267
- # Fork a new thread at the address specified by A. The new thread goes at the end
268
- # of the queue. Immediate operands are not allowed. Durham doesn't mention it, but
269
- # implies only A is dereferenced, so ignore B.
270
-
271
- def SPL(pc, mem)
272
- raise MARSRuntimeException, "SPL: immediate A-field not allowed" if @a[0] == ?#
273
- target = dereference(@a, pc, mem)
274
- pc.add_thread(target)
275
- end
276
-
277
- end # class Word
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
- A static method (Warrior.load) will load an assembled Warrior into memory, making
285
- sure the max number of programs is not exceeded. The addr attribute is set to the
286
- starting location of the program when it is loaded.
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
- def initialize(filename)
293
- if filename.class == Symbol
294
- filename = File.join(@@marsDirectory, filename.to_s + ".txt")
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
- if @errors.length > 0
304
- puts "Syntax errors in #{filename}:"
305
- puts errors
306
- @code.clear
307
- end
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
- def inspect
311
- sprintf "Name: #{@name} Code: #{@code.inspect}"
312
- end
313
-
314
- alias to_s inspect
315
-
316
- def Warrior.load(app, addr = :random)
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
- The PC class is used to represent the set of program counters for a running program.
389
- It has an array of locations to hold the next instruction from each thread, plus the
390
- index of the thread to use on the next instruction fetch cycle.
391
-
392
- Call next to get the address of the next instruction to execute; as a side effect this
393
- call bumps the thread pointer to the next thread in the program. Call add_thread(n)
394
- to start a new thread running at location n. Call kill_thread to remove the thread
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
- =begin rdoc
470
- record the location of a memory operation in the history vector for the current thread
471
- =end
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
- a = @history[@current[:thread]]
475
- a << loc
476
- a.shift if a.length > @@hmax
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
- An object of the Memory class is a 1-D array of the specified size. Methods namde
487
- store and fetch operate on full words, while store_field and fetch_field operate on partial words.
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
- class Memory
491
-
591
+
592
+ class Memory
593
+
492
594
  attr_reader :array
493
595
 
494
- def initialize(size)
495
- @array = Array.new(size)
496
- end
497
-
498
- def to_s
499
- sprintf "Memory [0..#{@array.size-1}]"
500
- end
501
-
502
- =begin rdoc
503
- dump(loc,n) -- print the n words in memory starting at loc
504
- =end
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
- def size
514
- return @array.size
515
- end
516
-
517
- # Methods for fetching and storing data in memory. According to the standard,
518
- # memory is initialized with DAT #0 instructions, but our array has nils; the
519
- # fetch method returns a DAT #0 from an uninitialized location.
520
-
521
- def fetch(loc)
522
- return ( @array[loc] || Word.new("DAT", "#0", "#0", nil) )
523
- end
524
-
525
- def store(loc, val)
526
- @array[loc] = val.clone
527
- end
528
-
529
- # fetch and store operations that work on partial words
530
-
531
- def fetch_field(loc, field)
532
- instr = self.fetch(loc)
533
- return field == :a ? instr.a : instr.b
534
- end
535
-
536
- def store_field(loc, val, field)
537
- instr = self.fetch(loc)
538
- part = (field == :a) ? instr.a : instr.b
539
- mode = @@modes.include?(part[0]) ? part[0].chr : ""
540
- part.replace(mode + val.to_s)
541
- self.store(loc, instr)
542
- end
543
-
544
- end # class Memory
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
- A miniature machine (MiniMARS) object is used to test a program. Pass the name of
548
- a Recode program to the constructor and get back a VM that has a memory where this
549
- program has been loaded into location 0. Pass an extra parameter to define the size
550
- of the memory (otherwise the memory is just big enough to hold the program). Call the
551
- step method to execute a single instruction, or run to run the program until it hits
552
- a DAT instruction or executes max instructions.
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
- class MiniMARS
556
- attr_reader :mem, :pc, :state
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
- def step
583
- return "machine halted" if @state == :halt
584
- instr = @mem.fetch(@pc.next)
585
- @state = instr.execute(@pc, @mem)
586
- return instr
587
- end
588
-
589
- def run(nsteps = 1000)
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
- end
599
-
600
- def reset
601
- @pc.reset
602
- puts "warning: memory may not be in initial state"
603
- end
604
-
605
- def dump(*args)
606
- if args.empty?
607
- @mem.dump(0, @mem.array.length)
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
- end
615
-
616
- end # MiniMARS
617
-
749
+ end
750
+
751
+ end # MiniMARS
752
+
618
753
 
619
754
  =begin rdoc
620
- The MARS module is a package that has the methods used to assemble, load, and execute
621
- up to three programs at a time.
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
- # -------- Assembler ----------
765
+ # -------- Assembler ----------
627
766
 
628
- # line format:
629
- # <label> <opcode> <A-mode><A-field>, <B-mode><B-field> <comment>
767
+ # line format:
768
+ # <label> <opcode> <A-mode><A-field>, <B-mode><B-field> <comment>
630
769
 
631
- # Parsing methods strip off and return the item they are looking for, or raise
632
- # an error if the line doesn't start with a well-formed item
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) # labels start in column 0, can be any length
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
- def MARS.parseOpCode(s)
648
- if s !~ /^\s/ # expect opcode to be separated from label (or start of line) by white space
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
- label = MARS.parseLabel(s)
683
- op = MARS.parseOpCode(s)
684
- a = MARS.parseOperand(s)
685
- MARS.parseSeparator(s)
686
- b = MARS.parseOperand(s)
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
- =begin rdoc
729
- Assembler -- pass in an array of strings, one instruction per string, get back
730
- the program name, an array of Word objects, a symbol table, and an array
731
- of error messages.
732
- =end
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
- # Pass 1 -- create list of Word objects, build the symbol table:
743
-
744
- strings.each_with_index do |line, lineno|
745
- line.rstrip!
746
- next if line.length == 0 # skip blank lines
747
- if line[0] == ?; # comments have ; in column 0
748
- if md = line.match(/;\s*name\s+(\w+)/) # does comment have program name?
749
- name = md[1] # yes -- save it
750
- end
751
- next
752
- end
753
- if n = line.index(";") # if comment at end remove it
754
- line.slice!(n..-1)
755
- end
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
- end
765
-
766
- # Pass 2 -- translate labels into ints on each instruction:
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
- rescue RedcodeSyntaxError
923
+ rescue RedcodeSyntaxError
777
924
  errors << " line #{instr.lineno}: #{$!}"
778
925
  end
779
- end
780
-
781
- return [name, code, symbols, errors]
782
- end
926
+ end
927
+
928
+ return [name, code, symbols, errors]
929
+ end
783
930
 
784
931
  # -------- End Assembler ----------
785
932
 
786
-
787
- =begin rdoc
788
- step() -- execute one instruction from each active program. If a thread dies remove the PC
789
- from the array for that program. A program dies when its last thread dies.
790
- =end
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
- printf("%04d: %s\n", pc.current[:addr], instr) if @@params[:tracing]
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[:pace])
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
- end
844
-
845
- =begin rdoc
846
- run(n) -- run the programs, stopping when the number of surviving programs is n. For a
847
- contest n will be 1, but for testing it can be 0. Another way to return from this method
848
- is when a specified number of rounds of instruction executions have taken place.
849
- =end
850
-
851
- def MARS.run(nleft = 0)
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
- end
859
-
860
- =begin rdoc
861
- state() -- print info about the machine and any programs in memory
862
- =end
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
- end
881
-
882
-
883
- =begin rdoc
884
- set_option(key, val) -- set the value of one of the run-time options (valid
885
- only when no programs are loaded)
886
- =end
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 :pace
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
- initialize palettes with blends from a dingy color to gray, then
925
- add a bright color as the last item
926
- =end
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
- x = (i % options[:cellCols]) * cellsize + padding
942
- y = (i / options[:cellCols]) * cellsize + padding
943
- cells << Canvas::Rectangle.new( x, y, x+cellsize, y+cellsize, :outline => "#888888", :fill => options[:cellColor] )
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
- palettes = [
947
- Canvas.palette( [204,204,204], [204,100,100], options[:traceSize] ),
948
- Canvas.palette( [204,204,204], [100,100,204], options[:traceSize] ),
949
- Canvas.palette( [204,204,204], [100,204,100], options[:traceSize] ),
950
- ]
951
- palettes[0] << "#FF0000"
952
- palettes[1] << "#0000FF"
953
- palettes[2] << "#00FF00"
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
- def MARS.updateCells(pc)
965
- id = pc.id
966
- a = pc.history[pc.current[:thread]]
967
- d = @@drawing.palettes[id].length - a.length
968
- a.each_with_index do |x, i|
969
- @@drawing.cells[x].fill = @@drawing.palettes[id][i+d]
970
- end
971
- end
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
- =begin rdoc
991
- reset() -- stop the current contest; clear all PCs, reset memory to all nils
992
- =end
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
- @@mem = Memory.new(@@params[:memSize])
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
- =begin rdoc
1008
- Print the code for one of the Redcode source programs.
1009
- =end
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
- File.open(filename).each do |line|
1020
- dest.puts line.chomp
1021
- end
1022
- end
1023
- return nil
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
- =begin rdoc
1027
- Save a copy of a Redcode source program; if no output file name specified
1028
- make a file name from the program name.
1029
- =end
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
- =begin rdoc
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
- =begin rdoc
1057
- Make a test machine
1058
- =end
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
- @@mem = Memory.new(@@params[:memSize])
1233
+ @@mem = Memory.new(@@params[:memSize])
1101
1234
  @@entries = Array.new
1102
1235
 
1103
1236
  end # MARSLab