rubylabs 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +15 -6
- data/Rakefile +3 -0
- data/VERSION +1 -1
- data/lib/bitlab.rb +593 -328
- data/lib/demos.rb +20 -9
- data/lib/elizalab.rb +660 -507
- data/lib/hashlab.rb +289 -192
- data/lib/introlab.rb +33 -38
- data/lib/iterationlab.rb +117 -61
- data/lib/marslab.rb +608 -475
- data/lib/randomlab.rb +227 -121
- data/lib/recursionlab.rb +197 -140
- data/lib/rubylabs.rb +936 -390
- data/lib/sievelab.rb +32 -24
- data/lib/spherelab.rb +308 -220
- data/lib/tsplab.rb +634 -312
- data/test/bit_test.rb +4 -4
- data/test/tsp_test.rb +18 -0
- metadata +2 -2
data/lib/rubylabs.rb
CHANGED
@@ -1,77 +1,87 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
= RubyLabs
|
4
|
-
|
5
|
-
Common methods used by two or more lab projects.
|
6
|
-
|
7
|
-
Methods used to monitor execution of programs during experiments.
|
8
|
-
|
9
|
-
=end
|
1
|
+
# RubyLabs gem, top level module.
|
10
2
|
|
11
3
|
SCRIPT_LINES__ = Hash.new unless defined? SCRIPT_LINES__
|
12
4
|
|
13
|
-
autoload :IntroLab,
|
14
|
-
autoload :SieveLab,
|
5
|
+
autoload :IntroLab, "introlab.rb"
|
6
|
+
autoload :SieveLab, "sievelab.rb"
|
15
7
|
autoload :IterationLab, "iterationlab.rb"
|
16
8
|
autoload :RecursionLab, "recursionlab.rb"
|
17
|
-
autoload :HashLab,
|
18
|
-
autoload :BitLab,
|
19
|
-
autoload :MARSLab,
|
20
|
-
autoload :RandomLab,
|
21
|
-
autoload :EncryptionLab,
|
22
|
-
autoload :ElizaLab,
|
9
|
+
autoload :HashLab, "hashlab.rb"
|
10
|
+
autoload :BitLab, "bitlab.rb"
|
11
|
+
autoload :MARSLab, "marslab.rb"
|
12
|
+
autoload :RandomLab, "randomlab.rb"
|
13
|
+
autoload :EncryptionLab, "encryptionlab.rb"
|
14
|
+
autoload :ElizaLab, "elizalab.rb"
|
23
15
|
autoload :SphereLab, "spherelab.rb"
|
24
|
-
autoload :TSPLab,
|
16
|
+
autoload :TSPLab, "tsplab.rb"
|
25
17
|
|
26
|
-
autoload :Demos,
|
18
|
+
autoload :Demos, "demos.rb"
|
27
19
|
|
28
20
|
include Math
|
29
21
|
|
30
|
-
module RubyLabs
|
31
|
-
|
32
22
|
=begin rdoc
|
33
|
-
|
34
|
-
=== hello
|
35
23
|
|
36
|
-
|
24
|
+
== RubyLabs
|
25
|
+
|
26
|
+
RubyLabs is a collection of modules for projects described in
|
27
|
+
<em>Explorations in Computing: An Introduction to Computer Science</em>.
|
37
28
|
|
29
|
+
There is one submodule for each project, e.g. RubyLabs::SieveLab has
|
30
|
+
methods used in the Sieve of Eratosthenes project in Chapter 3.
|
31
|
+
The top level RubyLabs module also has common methods used in
|
32
|
+
several projects.
|
38
33
|
=end
|
39
34
|
|
35
|
+
module RubyLabs
|
36
|
+
|
37
|
+
# A simple 'hello world' method to test the installation.
|
38
|
+
|
40
39
|
def hello
|
41
40
|
"Hello, you have successfully installed RubyLabs!"
|
42
41
|
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
log(x) / log(2.0)
|
50
|
-
end
|
51
|
-
|
52
|
-
=begin rdoc
|
53
|
-
Return the larger of a and b
|
54
|
-
=end
|
43
|
+
# Return the larger of +a+ and +b+ (determined by calling <tt>a < b</tt>).
|
44
|
+
#
|
45
|
+
# :call-seq:
|
46
|
+
# max(a,b) => Object
|
47
|
+
#
|
55
48
|
|
56
49
|
def max(a,b)
|
57
|
-
a
|
50
|
+
a < b ? b : a
|
58
51
|
end
|
59
52
|
|
60
|
-
|
61
|
-
|
62
|
-
|
53
|
+
# Return the smaller of +a+ and +b+ (determined by calling <tt>a < b</tt>).
|
54
|
+
#
|
55
|
+
# :call-seq:
|
56
|
+
# min(a,b) => Object
|
57
|
+
#
|
63
58
|
|
64
59
|
def min(a,b)
|
65
60
|
a < b ? a : b
|
66
61
|
end
|
67
62
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
# Compute the logarithm (base 2) of a number +x+.
|
64
|
+
#
|
65
|
+
# :call-seq:
|
66
|
+
# log2(x) => Float
|
67
|
+
#
|
73
68
|
|
74
|
-
|
69
|
+
def log2(x)
|
70
|
+
log(x) / log(2.0)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Measure the execution time of a block of code, which can be any Ruby expression
|
74
|
+
# enclosed in braces after the method name. The return value is the number of seconds
|
75
|
+
# required to evaluate the block.
|
76
|
+
# Any output produced by the block is discarded.
|
77
|
+
#
|
78
|
+
# Example:
|
79
|
+
# >> time { sieve(1000) }
|
80
|
+
# => 0.014921
|
81
|
+
#
|
82
|
+
# :call-seq:
|
83
|
+
# time { f } => Float
|
84
|
+
#
|
75
85
|
|
76
86
|
def time(&f)
|
77
87
|
tstart = Time.now
|
@@ -79,91 +89,132 @@ method will time any arbitrary Ruby expression.
|
|
79
89
|
return Time.now - tstart
|
80
90
|
end
|
81
91
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
the
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
# Monitor the execution of a block of code, which can be any Ruby expression enclosed in braces. Sets up
|
93
|
+
# a callback method which checks to see if a probe has been attached to a line in
|
94
|
+
# a method called by the block. See Source#probe for information about attaching
|
95
|
+
# probes. If a probe is attached, the expression associated with
|
96
|
+
# the probe is evaluated. The value returned from trace is the value returned
|
97
|
+
# by the block.
|
98
|
+
#
|
99
|
+
# See also RubyLabs#count.
|
100
|
+
#
|
101
|
+
# Example -- call a method named +brackets+ to print brackets around array
|
102
|
+
# elements as the arry is being sorted by a call to +isort+:
|
103
|
+
# >> Source.probe( "isort", 5, "puts brackets(a,i)" )
|
104
|
+
# => true
|
105
|
+
# >> trace { isort(cars) }
|
106
|
+
# mazda [ford bmw saab chrysler]
|
107
|
+
# ford mazda [bmw saab chrysler]
|
108
|
+
# bmw ford mazda [saab chrysler]
|
109
|
+
# ...
|
110
|
+
# => ["bmw", "chrysler", "ford", "mazda", "saab"]
|
111
|
+
#
|
112
|
+
# :call-seq:
|
113
|
+
# trace { f } => Object
|
114
|
+
#
|
115
|
+
#--
|
91
116
|
# Debugging aid: put this line at the front of the trace_func:
|
92
117
|
# p [event, file, line, id, binding, classname]
|
93
118
|
|
94
119
|
def trace(&f)
|
95
|
-
|
120
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
96
121
|
if expr = Source.probing(file, id, line)
|
97
122
|
eval(expr, binding) if expr != :count
|
98
123
|
end
|
99
|
-
|
124
|
+
}
|
100
125
|
res = f.call
|
101
|
-
|
102
|
-
|
126
|
+
set_trace_func nil
|
127
|
+
res
|
103
128
|
end
|
104
129
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
130
|
+
# Monitor the execution of a block of code, which can be any Ruby expression enclosed in braces.
|
131
|
+
# If a counting probe (see Source#probe) is attached to any lines of code evaluated by the block
|
132
|
+
# this method will return the total number of times those lines are executed.
|
133
|
+
#
|
134
|
+
# See also RubyLabs#trace.
|
135
|
+
#
|
136
|
+
# Example -- count the number of times line 2 in the method named +less+ is executed
|
137
|
+
# during a call to +isort+:
|
138
|
+
# >> Source.probe("less", 2, :count)
|
139
|
+
# => true
|
140
|
+
# >> count { isort(cars) }
|
141
|
+
# => 8
|
142
|
+
#
|
143
|
+
# :call-seq:
|
144
|
+
# count { f } => Fixnum
|
145
|
+
#
|
116
146
|
|
117
147
|
def count(&f)
|
118
148
|
counter = 0
|
119
|
-
|
149
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
120
150
|
if expr = Source.probing(file, id, line)
|
121
151
|
counter += 1 if expr == :count
|
122
152
|
end
|
123
|
-
|
153
|
+
}
|
124
154
|
f.call
|
125
|
-
|
126
|
-
|
155
|
+
set_trace_func nil
|
156
|
+
return counter
|
127
157
|
end
|
128
158
|
|
129
|
-
=begin rdoc
|
130
159
|
|
131
|
-
|
160
|
+
=begin rdoc
|
132
161
|
|
133
|
-
|
134
|
-
algorithms. Call TestArray.new(n) to make an array of n unique random numbers.
|
162
|
+
== TestArray
|
135
163
|
|
164
|
+
A TestArray is an array of random values that can be used to test searching and sorting
|
165
|
+
algorithms.
|
136
166
|
A method named +random+ will return a random element to use as a search target.
|
137
|
-
|
138
|
-
|
139
|
-
|
167
|
+
If +a+ is a TestArray object,
|
168
|
+
call <tt>a.random(:success)</tt> to get a value that is in the array +a+, or call
|
169
|
+
<tt>a.random(:fail)</tt> to get a number that is not in the array.
|
170
|
+
|
171
|
+
#--
|
172
|
+
The constructor uses a hash to create unique numbers -- it draws random numbers
|
173
|
+
and uses them as keys to insert into the hash, and returns when the hash has n
|
174
|
+
items. The hash is saved so it can be reused by a call to random(:fail) -- this
|
175
|
+
time draw random numbers until one is not a key in the hash. A lot of machinery
|
176
|
+
to keep around for very few calls, but it's efficient enough -- making an array
|
177
|
+
of 100K items takes less than a second.
|
178
|
+
|
179
|
+
An earlier version used a method named test_array to make a regular Array object
|
180
|
+
and augment it with the location method, but the singleton's methods were not passed
|
181
|
+
on to copies made by a call to sort:
|
182
|
+
>> x = test_array(3)
|
183
|
+
=> [16, 13, 4]
|
184
|
+
>> x.sort.random(:fail)
|
185
|
+
NoMethodError: undefined method `random' for [4, 13, 16]:Array
|
186
|
+
|
140
187
|
=end
|
141
188
|
|
142
|
-
# The constructor uses a hash to create unique numbers -- it draws random numbers
|
143
|
-
# and uses them as keys to insert into the hash, and returns when the hash has n
|
144
|
-
# items. The hash is saved so it can be reused by a call to random(:fail) -- this
|
145
|
-
# time draw random numbers until one is not a key in the hash. A lot of machinery
|
146
|
-
# to keep around for very few calls, but it's efficient enough -- making an array
|
147
|
-
# of 100K items takes less than a second.
|
148
|
-
|
149
|
-
# An earlier version used a method named test_array to make a regular Array object
|
150
|
-
# and augment it with the location method, but the singleton's methods were not passed
|
151
|
-
# on to copies made by a call to sort:
|
152
|
-
# >> x = test_array(3)
|
153
|
-
# => [16, 13, 4]
|
154
|
-
# >> x.sort.random(:fail)
|
155
|
-
# NoMethodError: undefined method `random' for [4, 13, 16]:Array
|
156
|
-
|
157
189
|
class TestArray < Array
|
158
190
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
191
|
+
data = File.join(File.dirname(__FILE__), '..', 'data', 'arrays')
|
192
|
+
|
193
|
+
@@sources = {
|
194
|
+
:cars => "#{data}/cars.txt",
|
195
|
+
:colors => "#{data}/colors.txt",
|
196
|
+
:fruits => "#{data}/fruit.txt",
|
197
|
+
:words => "#{data}/wordlist.txt",
|
198
|
+
}
|
199
|
+
|
200
|
+
# Create a new TestArray of size +n+ containing items of the specified +type+.
|
201
|
+
# If a type is not supplied, create an array of integers. Types are identified
|
202
|
+
# by symbols, e.g. <tt>:cars</tt> tells the constructor to return an array of
|
203
|
+
# car names (see TestArray.sources). If a type is specified, the symbol <tt>:all</tt>
|
204
|
+
# can be given instead of an array size, in which case the constructor returns
|
205
|
+
# all items of that type.
|
206
|
+
#
|
207
|
+
# Examples:
|
208
|
+
# >> TestArray.new(5)
|
209
|
+
# => [3, 28, 48, 64, 4]
|
210
|
+
# >> TestArray.new(5, :cars)
|
211
|
+
# => ["lamborghini", "lincoln", "chrysler", "toyota", "rolls-royce"]
|
212
|
+
# >> TestArray.new(:all, :colors)
|
213
|
+
# => ["almond", "antique white", ... "yellow green"]
|
214
|
+
#
|
215
|
+
# :call-seq:
|
216
|
+
# TestArray.new(n, type) => Array
|
217
|
+
#
|
167
218
|
|
168
219
|
def initialize(size, src = nil)
|
169
220
|
|
@@ -179,41 +230,59 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
179
230
|
raise "TestArray: array size must be an integer or :all" unless size.class == Fixnum || size == :all
|
180
231
|
end
|
181
232
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
233
|
+
@h = Hash.new
|
234
|
+
|
235
|
+
# if @max is defined make an array of integers, otherwise src defines the type of data;
|
236
|
+
# size might be :all, in which case return the whole file, and set @all to true so random
|
237
|
+
# doesn't try to make a random value not in the array.
|
238
|
+
|
239
|
+
if @max
|
240
|
+
while @h.size < size
|
241
|
+
@h[ rand( @max ) ] = 1
|
242
|
+
end
|
243
|
+
else
|
244
|
+
fn = @@sources[src] or raise "TestArray: undefined source: #{src}"
|
245
|
+
@words = File.open(fn).readlines
|
246
|
+
if size != :all
|
196
247
|
max = @words.length
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
248
|
+
raise "TestArray: size must be less than #{max} for an array of #{src}" unless size < max
|
249
|
+
while @h.size < size
|
250
|
+
@h[ @words[ rand(max) ].chomp ] = 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
if size == :all
|
256
|
+
self.concat @words.map { |s| s.chomp! }
|
257
|
+
@all = true
|
258
|
+
else
|
259
|
+
self.concat @h.keys
|
209
260
|
for i in 0..length-2
|
210
261
|
r = rand(length-i) + i # i <= r < length
|
211
262
|
self[i],self[r] = self[r],self[i]
|
212
263
|
end
|
213
|
-
|
214
|
-
|
264
|
+
end
|
265
|
+
|
215
266
|
end
|
216
267
|
|
268
|
+
# Return a value that is guaranteed to be in the array or not in the array,
|
269
|
+
# depending on the value of +outcome+. Pass <tt>:success</tt> to get a random
|
270
|
+
# value in the array, or pass <tt>:fail</tt> to get an item of the same type as the
|
271
|
+
# items in the array but which is not itself in the array. Call <tt>a.random(:fail)</tt>
|
272
|
+
# to get a value that will cause a search algorithm to do the maximum number of comparisons.
|
273
|
+
#
|
274
|
+
# Example:
|
275
|
+
# >> a = TestArray.new(10).sort
|
276
|
+
# => [13, 23, 24, 26, 47, 49, 86, 88, 92, 95]
|
277
|
+
# >> x = a.random(:fail)
|
278
|
+
# => 22
|
279
|
+
# >> search(a, x)
|
280
|
+
# => nil
|
281
|
+
#
|
282
|
+
# :call-seq:
|
283
|
+
# a.random(outcome) => Object
|
284
|
+
#
|
285
|
+
|
217
286
|
def random(outcome)
|
218
287
|
if outcome == :success
|
219
288
|
return self[ rand(self.length) ]
|
@@ -231,108 +300,176 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
231
300
|
return nil
|
232
301
|
end
|
233
302
|
end
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
303
|
+
|
304
|
+
# Return a list of types of items that can be passed as arguments
|
305
|
+
# to <tt>TestArray.new</tt>
|
306
|
+
#
|
307
|
+
# :call-seq:
|
308
|
+
# TestArray.sources() => Array
|
309
|
+
#
|
310
|
+
|
311
|
+
def TestArray.sources
|
312
|
+
return @@sources.keys.sort { |a,b| a.to_s <=> b.to_s }
|
313
|
+
end
|
314
|
+
|
315
|
+
end # class TestArray
|
240
316
|
|
241
317
|
|
318
|
+
# Equivalent to calling <tt>TestArray.new</tt>.
|
319
|
+
#
|
320
|
+
# Examples:
|
321
|
+
# >> TestArray(5)
|
322
|
+
# => [65, 79, 60, 88, 30]
|
323
|
+
# >> TestArray(5, :cars)
|
324
|
+
# => ["mini", "opel", "chevrolet", "isuzu", "cadillac"]
|
325
|
+
#
|
326
|
+
# :call-seq:
|
327
|
+
# TestArray(n, type) => Array
|
328
|
+
#
|
329
|
+
|
242
330
|
def TestArray(n, type = nil)
|
243
331
|
TestArray.new(n, type)
|
244
332
|
end
|
245
|
-
|
246
|
-
|
247
|
-
=begin
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
333
|
+
|
334
|
+
|
335
|
+
=begin rdoc
|
336
|
+
|
337
|
+
== Source
|
338
|
+
|
339
|
+
The Source module provides access the source code for a lab
|
340
|
+
project. When IRB reads the modules from a file, the source is
|
341
|
+
saved in a global array named <tt>SCRIPT_LINES__</tt>. The methods
|
342
|
+
defined in this module scan the source code to look for tags that
|
343
|
+
mark the first and last lines of methods described in the book.
|
344
|
+
|
345
|
+
A method name can be passed either as a String or a Symbol. For
|
346
|
+
example, to print a listing of the +isort+ method a user can call
|
347
|
+
Source.listing("isort")
|
348
|
+
or
|
349
|
+
Source.listing(:isort)
|
350
|
+
|
351
|
+
#--
|
352
|
+
Code that will be accessed by methods in this module should be delimited
|
353
|
+
by :begin and :end tags. See the definition of isort in iterationlab.rb
|
354
|
+
for an example of how to name a method and its helper methods.
|
355
|
+
|
253
356
|
=end
|
254
357
|
|
255
358
|
module Source
|
256
359
|
|
257
360
|
@@probes = Hash.new
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
=begin
|
265
|
-
Print the source code for method +name+.
|
266
|
-
=end
|
361
|
+
@@file = Hash.new
|
362
|
+
@@size = Hash.new
|
363
|
+
@@base = Hash.new
|
364
|
+
@@helpers = Hash.new
|
365
|
+
@@line = nil
|
267
366
|
|
367
|
+
# Print a listing (source code along with line numbers) for method +name+.
|
368
|
+
#
|
369
|
+
# :call-seq:
|
370
|
+
# Source.listing(name)
|
371
|
+
#
|
372
|
+
|
268
373
|
def Source.listing(name)
|
269
374
|
begin
|
270
375
|
id = Source.find(name)
|
271
|
-
|
272
|
-
|
376
|
+
for i in @@base[id]..(@@base[id]+@@size[id]-1)
|
377
|
+
line = SCRIPT_LINES__[@@file[id]][i-1].chomp
|
273
378
|
printf "%3d: %s\n", i - @@base[id] + 1, line.gsub(/\t/," ")
|
274
|
-
|
379
|
+
end
|
275
380
|
rescue Exception => e
|
276
381
|
puts e
|
277
382
|
end
|
278
383
|
return true
|
279
384
|
end
|
280
385
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
386
|
+
# Save a copy of the source code for method +name+ in a file. If a file name is not
|
387
|
+
# specified, the output file name is the name of the method with ".rb" appended.
|
388
|
+
# Prompts the user before overwriting an existing file.
|
389
|
+
#
|
390
|
+
# Example -- Write the source for the method +isort+ to "isort.rb":
|
391
|
+
# Source.checkout("isort")
|
392
|
+
#
|
393
|
+
# Example -- Write the source for +isort+ to "mysort.rb"
|
394
|
+
# Source.checkout("isort", "mysort.rb")
|
395
|
+
#
|
396
|
+
# :call-seq:
|
397
|
+
# Source.checkout(name)
|
398
|
+
#
|
286
399
|
|
287
400
|
def Source.checkout(name, newfilename = nil)
|
288
401
|
begin
|
289
402
|
id = Source.find(name)
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
403
|
+
if newfilename.nil?
|
404
|
+
newfilename = id.to_s
|
405
|
+
end
|
406
|
+
if newfilename[-1] == ?? || newfilename[1] == ?!
|
407
|
+
newfilename.chop!
|
408
|
+
end
|
409
|
+
if newfilename !~ /\.rb$/
|
410
|
+
newfilename += ".rb"
|
411
|
+
end
|
412
|
+
if File.exists?(newfilename)
|
413
|
+
print "Replace existing #{newfilename}? [yn] "
|
414
|
+
if STDIN.gets[0] != ?y
|
415
|
+
puts "File not written"
|
416
|
+
return false
|
417
|
+
end
|
418
|
+
end
|
419
|
+
File.open(newfilename, "w") do |f|
|
420
|
+
f.puts "# #{name} method exported from #{File.basename(@@file[id])}"
|
421
|
+
f.puts
|
309
422
|
Source.print_source(f, id)
|
310
|
-
|
311
|
-
|
312
|
-
|
423
|
+
@@helpers[id].each do |m|
|
424
|
+
f.puts
|
425
|
+
xid = Source.find(m)
|
313
426
|
Source.print_source(f, xid)
|
314
|
-
|
315
|
-
|
427
|
+
end
|
428
|
+
end
|
316
429
|
rescue Exception => e
|
317
430
|
puts e
|
318
431
|
end
|
319
|
-
|
320
|
-
|
432
|
+
puts "Saved a copy of source in #{newfilename}"
|
433
|
+
return true
|
321
434
|
end
|
322
435
|
|
323
|
-
|
324
|
-
|
325
|
-
|
436
|
+
# Helper method called from Source.checkout -- print the code for
|
437
|
+
# method +id+ in the file +f+
|
438
|
+
|
439
|
+
def Source.print_source(f, id) # :nodoc:
|
440
|
+
for i in @@base[id]..(@@base[id]+@@size[id]-1)
|
441
|
+
line = SCRIPT_LINES__[@@file[id]][i-1].chomp
|
326
442
|
f.puts line.gsub(/\t/," ")
|
327
|
-
|
443
|
+
end
|
328
444
|
end
|
329
445
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
446
|
+
# Attach a software probe to a line in method +name+. The line can be specified
|
447
|
+
# by line number relative to the start of the method or a string the specifies a pattern. The third argument
|
448
|
+
# is either a string or the symbol <tt>:count</tt>.
|
449
|
+
# If the third argument is a string, it should be a Ruby expression that will be
|
450
|
+
# evaluated whenever the probe is activated via a call to +trace+ (see RubyLabs#trace).
|
451
|
+
# If the third argument is <tt>:count</tt>, a call to +count+ (see RubyLabs#count)
|
452
|
+
# will count the number of times the specified line is executed.
|
453
|
+
#
|
454
|
+
# Example: attach a probe to the +sieve+ method, so that when +sieve+ is called
|
455
|
+
# via +trace+ the statement "p worklist" will be evaluated whenever line 6 is executed:
|
456
|
+
# >> Source.probe("sieve", 6, "p worksheet")
|
457
|
+
# => true
|
458
|
+
#
|
459
|
+
# Example: attach a counting probe, so +count+ can record how many times line 6 is executed:
|
460
|
+
# >> Source.probe("sieve", 6, :count)
|
461
|
+
# => true
|
462
|
+
#
|
463
|
+
# If the second argument to Source.probe is a string, the probe is attached to every line that
|
464
|
+
# contains that string. For example, to attach a counting probe to every line in +merge+ that
|
465
|
+
# has a call to the << operator:
|
466
|
+
# >> Source.probe("merge", '<<', :count)
|
467
|
+
# => true
|
468
|
+
#
|
469
|
+
# :call-seq:
|
470
|
+
# Source.probe(name, line, spec)
|
471
|
+
#
|
472
|
+
|
336
473
|
|
337
474
|
def Source.probe(name, spec, expr = :count)
|
338
475
|
begin
|
@@ -345,24 +482,25 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
345
482
|
puts e
|
346
483
|
end
|
347
484
|
return true
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
=end
|
485
|
+
end
|
486
|
+
|
487
|
+
# -- Method for internal use only --
|
488
|
+
# Return probes (if any) attached to the specified file, method, and line number.
|
489
|
+
# Intended to be called from a trace func callback (which is why the file is one
|
490
|
+
# of the parameters).
|
355
491
|
|
356
|
-
def Source.probing(filename, method, line)
|
492
|
+
def Source.probing(filename, method, line) # :nodoc:
|
357
493
|
return nil if line == @@line
|
358
494
|
@@line = line
|
359
495
|
return nil unless @@probes[method] && @@file[method] == filename
|
360
496
|
return @@probes[method][line]
|
361
497
|
end
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
498
|
+
|
499
|
+
# Print a description of all the currently defined probes.
|
500
|
+
#
|
501
|
+
# :call-seq:
|
502
|
+
# Source.probes()
|
503
|
+
#
|
366
504
|
|
367
505
|
def Source.probes
|
368
506
|
@@probes.each do |name, plist|
|
@@ -371,12 +509,15 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
371
509
|
printf "%s %2d: %s\n", name, n, exprs
|
372
510
|
end
|
373
511
|
end
|
374
|
-
|
512
|
+
return true
|
375
513
|
end
|
376
514
|
|
377
|
-
|
378
|
-
|
379
|
-
|
515
|
+
# Remove all the probes on a designated method, or if no method name is passed,
|
516
|
+
# remove all probes from all methods.
|
517
|
+
#
|
518
|
+
# :call-seq:
|
519
|
+
# Source.clear(name)
|
520
|
+
#
|
380
521
|
|
381
522
|
def Source.clear(name = nil)
|
382
523
|
@@probes.each do |id, plist|
|
@@ -386,62 +527,56 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
386
527
|
return true
|
387
528
|
end
|
388
529
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
decision if monitoring user-defined methods....
|
530
|
+
# Internal use only -- locate the filename, starting line number, and length
|
531
|
+
# of method +name+, record the
|
532
|
+
# information for any methods that need it. This information only needs to
|
533
|
+
# be found once, so it is recorded in a set of class variables. Revisit this
|
534
|
+
# decision if monitoring user-defined methods....
|
395
535
|
|
396
|
-
|
397
|
-
=end
|
398
|
-
|
399
|
-
def Source.find(name)
|
536
|
+
def Source.find(name) # :nodoc:
|
400
537
|
id = name.to_sym
|
401
538
|
return id if @@file[id] # return if we looked for this source previously
|
402
539
|
|
403
540
|
filename, base, size, helpers = nil, nil, nil, nil
|
404
541
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
542
|
+
catch (:found) do
|
543
|
+
SCRIPT_LINES__.each do |file, lines|
|
544
|
+
line_num = 0
|
545
|
+
lines.each do |s|
|
546
|
+
line_num += 1
|
547
|
+
if match = s.match(/:(begin|end)\s+(.*)/)
|
548
|
+
verb = match[1]
|
549
|
+
names = match[2].split.collect{|x| eval(x)}
|
550
|
+
if names[0] == id
|
551
|
+
if verb == "begin"
|
552
|
+
filename = file
|
553
|
+
base = line_num + 1
|
554
|
+
helpers = names[1..-1]
|
555
|
+
else
|
556
|
+
size = line_num - base
|
557
|
+
throw :found
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
raise "Can't find method named '#{name}'" if size.nil?
|
566
|
+
|
567
|
+
@@file[id] = filename
|
568
|
+
@@size[id] = size
|
569
|
+
@@base[id] = base
|
570
|
+
@@probes[id] = Hash.new
|
571
|
+
@@helpers[id] = helpers
|
572
|
+
return id
|
436
573
|
end
|
437
574
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
to make sure line numbers are valid.
|
442
|
-
=end
|
575
|
+
# Internal use only -- make an array of line numbers to use for probing method +name+.
|
576
|
+
# Argument can be a single line number, an array of line numbers, or a pattern. Checks
|
577
|
+
# to make sure line numbers are valid.
|
443
578
|
|
444
|
-
def Source.lines(spec, id)
|
579
|
+
def Source.lines(spec, id) # :nodoc:
|
445
580
|
if spec.class == Fixnum && Source.range_check(spec, id)
|
446
581
|
return [spec]
|
447
582
|
elsif spec.class == Array
|
@@ -453,27 +588,25 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
453
588
|
return res
|
454
589
|
elsif spec.class == String
|
455
590
|
res = Array.new
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
591
|
+
for i in @@base[id]..(@@base[id]+@@size[id]-1)
|
592
|
+
line = SCRIPT_LINES__[@@file[id]][i-1].chomp
|
593
|
+
res << i - @@base[id] + 1 if line.index(spec)
|
594
|
+
end
|
595
|
+
return res
|
461
596
|
else
|
462
597
|
raise "invalid spec: '#{spec}' (must be an integer, array of integers, or a pattern)"
|
463
598
|
end
|
464
599
|
end
|
465
600
|
|
466
|
-
def Source.range_check(n, id)
|
601
|
+
def Source.range_check(n, id) # :nodoc:
|
467
602
|
max = @@size[id]
|
468
603
|
raise "line number must be between 1 and #{max}" unless n >= 1 && n <= max
|
469
604
|
return true
|
470
605
|
end
|
471
606
|
|
472
|
-
|
473
|
-
Internal use only -- show info about method
|
474
|
-
=end
|
607
|
+
# Internal use only -- show info about method to verify it's being found by Source.lines
|
475
608
|
|
476
|
-
def Source.info(name)
|
609
|
+
def Source.info(name) # :nodoc:
|
477
610
|
unless id = Source.find(name)
|
478
611
|
puts "Can't find method named '#{name}'"
|
479
612
|
return
|
@@ -488,26 +621,95 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
488
621
|
|
489
622
|
end # Source
|
490
623
|
|
624
|
+
|
491
625
|
=begin rdoc
|
492
|
-
|
626
|
+
|
627
|
+
== Canvas
|
628
|
+
|
629
|
+
The Canvas module defines a graphics window that can be used
|
630
|
+
for interactive visualizations. Classes in this module describe objects that are
|
631
|
+
drawn in the window; for example, objects of type Canvas::Circle are circles
|
632
|
+
on the canvas.
|
633
|
+
|
634
|
+
In the current implementation all drawing objects are derived from a base class
|
635
|
+
named TkObject, which provides an interface to the Tk library of ActiveTcl.
|
636
|
+
Instances of TkObject are proxies for the actual objects defined in Tk.
|
637
|
+
When the user calls a method that opens the RubyLabs Canvas, the method
|
638
|
+
initializes a pipe to a new process running the Tcl shell (wish). If a TkObject is
|
639
|
+
updated, it sends a Tcl command over the pipe, and the wish process updates
|
640
|
+
the window.
|
641
|
+
|
493
642
|
=end
|
494
643
|
|
495
644
|
module Canvas
|
496
645
|
|
646
|
+
=begin rdoc
|
647
|
+
|
648
|
+
== TkObject
|
649
|
+
|
650
|
+
Base class of all objects defined for RubyLabs::Canvas. Objects derived from
|
651
|
+
this class are proxies for Tk objects in a wish shell, opened when the canvas
|
652
|
+
is initialized.
|
653
|
+
|
654
|
+
Applications should not try to instantiate a TkObject directly,
|
655
|
+
but instead should call a constructor of one of the derived classes. For
|
656
|
+
example, to draw a circle on the canvas:
|
657
|
+
|
658
|
+
c = Canvas::Circle.new(x, y, r)
|
659
|
+
|
660
|
+
Public instance methods defined here are inherited by all objects derived from TkObject,
|
661
|
+
and can be used to manipulate the object. For example, to change the color of the circle:
|
662
|
+
|
663
|
+
c.fill = 'green'
|
664
|
+
|
665
|
+
The current implementation is very sparse: the only operations defined are the
|
666
|
+
ones needs for the visualizations described in the textbook, which are just
|
667
|
+
the basic methods that create an object or move it around on the screen.
|
668
|
+
|
669
|
+
In addition to the usual attributes, these objects have a "pen point", which is
|
670
|
+
the location of an imaginary pen relative to the object's base
|
671
|
+
coordinate. The pen point is used by methods that draw a track as an object is
|
672
|
+
moved (e.g. see the methods that control the motion of the robot in SphereLab)
|
673
|
+
|
674
|
+
=end
|
675
|
+
|
497
676
|
class TkObject
|
498
677
|
|
499
678
|
attr_accessor :name, :coords, :penpoint
|
500
679
|
|
680
|
+
# Initialization: set the object ID to 0 and save the file descriptor
|
681
|
+
# for the connection to the wish shell.
|
682
|
+
#
|
683
|
+
# :call-seq:
|
684
|
+
# TkObject.reset(p)
|
685
|
+
#
|
686
|
+
|
501
687
|
def TkObject.reset(p)
|
502
688
|
@@pipe = p
|
503
689
|
@@id = 0
|
504
690
|
end
|
505
691
|
|
692
|
+
# Return a unique ID number for a new proxy object.
|
693
|
+
#
|
694
|
+
# :call-seq:
|
695
|
+
# TkObject.nextId()
|
696
|
+
#
|
697
|
+
|
506
698
|
def TkObject.nextId
|
507
699
|
@@id += 1
|
508
700
|
return "obj" + @@id.to_s
|
509
701
|
end
|
510
702
|
|
703
|
+
# Translate a Ruby hash +h+ into a Tk option string. Example:
|
704
|
+
# >> Canvas::TkObject.options( { :outline => :black, :fill => :red } )
|
705
|
+
# => "-fill red -outline black"
|
706
|
+
# Note the ordering of the options in the Tk string may not be the same as
|
707
|
+
# the order they are listed when the hash object is created.
|
708
|
+
#
|
709
|
+
# :call-seq:
|
710
|
+
# TkObject.options(h) => String
|
711
|
+
#
|
712
|
+
|
511
713
|
def TkObject.options(h)
|
512
714
|
a = []
|
513
715
|
h.each do |k,v|
|
@@ -516,23 +718,64 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
516
718
|
return a.join(" ")
|
517
719
|
end
|
518
720
|
|
721
|
+
# Bring an object to the foreground (on top of all other objects on the canvas).
|
722
|
+
#
|
723
|
+
# :call-seq:
|
724
|
+
# x.raise()
|
725
|
+
#
|
726
|
+
|
519
727
|
def raise
|
520
728
|
@@pipe.puts ".canvas raise $#{@name}"
|
521
729
|
end
|
522
730
|
|
731
|
+
# Put an object in the background (below all other objects on the canvas).
|
732
|
+
#
|
733
|
+
# :call-seq:
|
734
|
+
# x.lower()
|
735
|
+
#
|
736
|
+
|
523
737
|
def lower
|
524
738
|
@@pipe.puts ".canvas lower $#{@name}"
|
525
739
|
end
|
526
740
|
|
741
|
+
# Remove an object from the canvas.
|
742
|
+
#
|
743
|
+
# :call-seq:
|
744
|
+
# x.erase()
|
745
|
+
#
|
746
|
+
|
527
747
|
def erase
|
528
748
|
@@pipe.puts ".canvas delete $#{@name}"
|
529
749
|
end
|
530
750
|
|
751
|
+
# Assign new coordinates for an object. Can be used to move or resize an
|
752
|
+
# object, but applications should use the more abstract interface defined by class methods in Canvas
|
753
|
+
# (e.g. Canvas#move).
|
754
|
+
#
|
755
|
+
# Example: if an object x is a proxy for a rectangle
|
756
|
+
# that has an upper left corner at (10,10) and
|
757
|
+
# a lower right corner at (20,50), this call will move it down and to the right
|
758
|
+
# by 10 pixels:
|
759
|
+
# >> x.coords = [20, 20, 30, 60]
|
760
|
+
# => [20, 20, 30, 60]
|
761
|
+
#
|
762
|
+
# :call-seq:
|
763
|
+
# x.coords = [...]
|
764
|
+
#
|
765
|
+
|
531
766
|
def coords=(a)
|
532
767
|
@coords = a
|
533
768
|
@@pipe.puts ".canvas coords $#{@name} #{a.join(' ')}"
|
534
769
|
end
|
535
770
|
|
771
|
+
# Set the fill color for an object. Example:
|
772
|
+
# >> x.fill = "green"
|
773
|
+
# => "green"
|
774
|
+
#
|
775
|
+
# :call-seq:
|
776
|
+
# x.fill = String
|
777
|
+
#
|
778
|
+
|
536
779
|
def fill=(x)
|
537
780
|
@@pipe.puts ".canvas itemconfigure $#{name} -fill #{x}"
|
538
781
|
end
|
@@ -540,10 +783,27 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
540
783
|
end
|
541
784
|
|
542
785
|
=begin rdoc
|
543
|
-
|
786
|
+
|
787
|
+
== Line
|
788
|
+
|
789
|
+
A Line object is a proxy for a line segment defined by a pair of (x,y) coordinates.
|
790
|
+
|
791
|
+
There are no instance methods for Lines beyond those defined in the TkObject base class.
|
544
792
|
=end
|
545
793
|
|
546
794
|
class Line < TkObject
|
795
|
+
|
796
|
+
# Create a new line segment that runs from (x0,y0) to (x1,y1). Attributes
|
797
|
+
# of the line can be passed in a hash object at the end of the argument list.
|
798
|
+
#
|
799
|
+
# Example -- create a gray line one pixel wide from (0,0) to (100,100):
|
800
|
+
# >> z = Line.new(0, 0, 100, 100, :fill => :gray, :width => 1)
|
801
|
+
# => #<RubyLabs::Canvas::Line:...>
|
802
|
+
#
|
803
|
+
# :call-seq:
|
804
|
+
# x = Line.new(x0, y0, x1, y1, args = {})
|
805
|
+
#
|
806
|
+
|
547
807
|
def initialize(x0, y0, x1, y1, args = {})
|
548
808
|
raise "No canvas" unless @@pipe
|
549
809
|
@name = TkObject.nextId
|
@@ -553,6 +813,15 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
553
813
|
@@pipe.puts cmnd
|
554
814
|
end
|
555
815
|
|
816
|
+
# Erase all Line objects that are tagged with the ID +tag+. Tags are optional
|
817
|
+
# arguments defined by passing <tt>-tag x</tt> to <tt>Line.new</tt> (see the
|
818
|
+
# visualization methods in tsplab.rb, which uses tags to erase all
|
819
|
+
# edges from a graph in a single call).
|
820
|
+
#
|
821
|
+
# :call-seq:
|
822
|
+
# Line.erase_all(tag)
|
823
|
+
#
|
824
|
+
|
556
825
|
def Line.erase_all(tag)
|
557
826
|
@@pipe.puts ".canvas delete withtag #{tag}"
|
558
827
|
end
|
@@ -560,10 +829,27 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
560
829
|
end
|
561
830
|
|
562
831
|
=begin rdoc
|
563
|
-
|
832
|
+
|
833
|
+
== Circle
|
834
|
+
|
835
|
+
A Circle object is a proxy for a Tk oval.
|
836
|
+
|
837
|
+
There are no instance methods for Circles beyond those defined in the TkObject base class.
|
564
838
|
=end
|
565
839
|
|
566
840
|
class Circle < TkObject
|
841
|
+
|
842
|
+
# Create a new circle with center at (x,y) and radius r. Attributes
|
843
|
+
# of the circle can be passed in a hash object at the end of the argument list.
|
844
|
+
#
|
845
|
+
# Example -- create a green cicle with radius 5 at location (20,20):
|
846
|
+
# >> x = Canvas::Circle.new(20, 20, 5, :fill => "green")
|
847
|
+
# => #<RubyLabs::Canvas::Circle:...>
|
848
|
+
#
|
849
|
+
# :call-seq:
|
850
|
+
# x = Circle.new(x, y, r, args = {})
|
851
|
+
#
|
852
|
+
|
567
853
|
def initialize(x, y, r, args = {})
|
568
854
|
raise "No canvas" unless @@pipe
|
569
855
|
@name = TkObject.nextId
|
@@ -575,10 +861,29 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
575
861
|
end
|
576
862
|
|
577
863
|
=begin rdoc
|
578
|
-
|
864
|
+
|
865
|
+
== Rectangle
|
866
|
+
|
867
|
+
A Rectangle object is a proxy for a Tk rectangle.
|
868
|
+
|
869
|
+
There are no instance methods for Rectangles beyond those defined in the TkObject base class.
|
579
870
|
=end
|
580
871
|
|
581
872
|
class Rectangle < TkObject
|
873
|
+
|
874
|
+
# Create a new rectangle with its upper left corner at (x0,y0)
|
875
|
+
# lower right corner at (x1,y1). Attributes
|
876
|
+
# of the rectangle can be passed in a hash object at the end of the argument list.
|
877
|
+
#
|
878
|
+
# Example -- create a 20x20 square with upper left corner at (10,10), with a dark green
|
879
|
+
# outline and yellow interior:
|
880
|
+
# >> x = Canvas::Rectangle.new(10, 10, 30, 30, :fill => "yellow", :outline => "darkgreen")
|
881
|
+
# => #<RubyLabs::Canvas::Rectangle:...>
|
882
|
+
#
|
883
|
+
# :call-seq:
|
884
|
+
# x = Rectangle.new(x0, y0, x1, y1, args = {})
|
885
|
+
#
|
886
|
+
|
582
887
|
def initialize(x0, y0, x1, y1, args = {})
|
583
888
|
raise "No canvas" unless @@pipe
|
584
889
|
@name = TkObject.nextId
|
@@ -590,12 +895,30 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
590
895
|
end
|
591
896
|
|
592
897
|
=begin rdoc
|
593
|
-
|
594
|
-
|
595
|
-
|
898
|
+
|
899
|
+
== Polygon
|
900
|
+
|
901
|
+
A Polygon object is a proxy for a Tk polygon.
|
902
|
+
|
903
|
+
In addition to the methods defined in the TkObject base class, a method named +rotate+
|
904
|
+
will rotate a Polygon by a specified angle.
|
596
905
|
=end
|
597
906
|
|
598
907
|
class Polygon < TkObject
|
908
|
+
|
909
|
+
# Create a new polygon. The first argument is an array of vertex coordinates. If
|
910
|
+
# the polygon has +n+ vertices, the argument should be a flat array of 2*n points.
|
911
|
+
# Attributes of the polygon can be passed in a hash object at the end of the argument list.
|
912
|
+
#
|
913
|
+
# Example -- create a small triangle in the upper left corner of the canvas, with a black
|
914
|
+
# outline and green interior:
|
915
|
+
# >> x = Canvas::Polygon.new([10,10,20,20,30,10], :fill => "green", :outline => "black")
|
916
|
+
# => #<RubyLabs::Canvas::Polygon ... >
|
917
|
+
#
|
918
|
+
# :call-seq:
|
919
|
+
# x = Polygon.new(a, args = {})
|
920
|
+
#
|
921
|
+
|
599
922
|
def initialize(a, args = {})
|
600
923
|
raise "No canvas" unless @@pipe
|
601
924
|
@name = TkObject.nextId
|
@@ -604,14 +927,56 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
604
927
|
cmnd = "set #{@name} [.canvas create polygon #{@coords.join(" ")} #{TkObject.options(args)}]"
|
605
928
|
@@pipe.puts cmnd
|
606
929
|
end
|
930
|
+
|
931
|
+
# Rotate the polygon by an angle +theta+ (expressed in degrees). The object is
|
932
|
+
# rotated about the point defined by the first pair of (x,y) coordinates. The
|
933
|
+
# return value is an array with the new coordinates.
|
934
|
+
#
|
935
|
+
# Example: Suppose +x+ is a square with coordinates (10,10), (20,10), (20,20), and (10,20).
|
936
|
+
# This call will rotate it clockwise by 45 degrees and return the new vertices:
|
937
|
+
# >> x.rotate(45)
|
938
|
+
# => [10.0, 10.0, 17.07, 17.07, 10.0, 24.14, 2.93, 17.07]
|
939
|
+
|
940
|
+
def rotate(theta)
|
941
|
+
theta = Canvas.radians(theta)
|
942
|
+
a = self.coords
|
943
|
+
x0 = a[0]
|
944
|
+
y0 = a[1]
|
945
|
+
(0...a.length).step(2) do |i|
|
946
|
+
x = a[i] - x0
|
947
|
+
y = a[i+1] - y0
|
948
|
+
a[i] = x0 + x * cos(theta) - y * sin(theta)
|
949
|
+
a[i+1] = y0 + x * sin(theta) + y * cos(theta)
|
950
|
+
end
|
951
|
+
self.coords = a
|
952
|
+
return a
|
953
|
+
end
|
607
954
|
end
|
608
955
|
|
609
956
|
=begin rdoc
|
610
|
-
|
611
|
-
|
957
|
+
|
958
|
+
== Text
|
959
|
+
|
960
|
+
A Text object is a string displayed on the RubyLabs canvas.
|
961
|
+
|
612
962
|
=end
|
613
963
|
|
614
964
|
class Text < TkObject
|
965
|
+
|
966
|
+
# Display string +s+ at location (+x+,+y+) on the canvas. By default the coordinates
|
967
|
+
# specify the location of the top left of the first character in the string (in Tk
|
968
|
+
# terminology, the anchor point for the text is "northwest"). A different anchor
|
969
|
+
# position and other attributes of the text can be passed in a hash object at the
|
970
|
+
# end of the argument list.
|
971
|
+
#
|
972
|
+
# Example -- display a message in dark green letters:
|
973
|
+
# >> x = Canvas::Text.new("hello", 50, 50, :fill => 'darkgreen')
|
974
|
+
# => #<RubyLabs::Canvas::Text: ... >
|
975
|
+
#
|
976
|
+
# :call-seq:
|
977
|
+
# x = Text.new(s, x, y, args = {})
|
978
|
+
#
|
979
|
+
|
615
980
|
def initialize(s, x, y, args = {})
|
616
981
|
raise "No canvas" unless @@pipe
|
617
982
|
@name = TkObject.nextId
|
@@ -623,15 +988,48 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
623
988
|
@@pipe.puts cmnd
|
624
989
|
end
|
625
990
|
|
991
|
+
# Replace the string displayed by a text object. Example: erase the current string
|
992
|
+
# for object +x+ and replace it with "hello, world":
|
993
|
+
# >> x.update("hello, world")
|
994
|
+
# => nil
|
995
|
+
# The new string is displayed at the same location and with the same attributes
|
996
|
+
# as the old string.
|
997
|
+
|
626
998
|
def update(s)
|
627
999
|
@@pipe.puts ".canvas itemconfigure $#{name} -text \"#{s}\""
|
628
1000
|
end
|
629
1001
|
end
|
630
1002
|
|
1003
|
+
=begin rdoc
|
1004
|
+
|
1005
|
+
== Font
|
1006
|
+
|
1007
|
+
A Font object defines the appearance of strings displayed by a Text object. A Font
|
1008
|
+
object is a proxy for a Tk font object. The proxy is never used by an application;
|
1009
|
+
instead, when text is created, it is passed the name of the Tk font (see the example in
|
1010
|
+
the description of Font.new).
|
1011
|
+
|
1012
|
+
=end
|
1013
|
+
|
631
1014
|
class Font < TkObject
|
632
1015
|
|
633
1016
|
@@fonts = []
|
634
1017
|
|
1018
|
+
# Create a font named +name+ with attributes defined in +args+. When creating
|
1019
|
+
# a new Text object, +name+ can be passed as an argument to Text.new so the
|
1020
|
+
# string will be displayed in the specified font.
|
1021
|
+
#
|
1022
|
+
# Example -- create a new font for displaying fixed width text, and then display
|
1023
|
+
# a message using this font:
|
1024
|
+
# >> f = Canvas::Font.new('code', :family => 'Courier', :size => 20)
|
1025
|
+
# => #<RubyLabs::Canvas::Font:0x1012587b0 @name="code">
|
1026
|
+
# >> t = Canvas::Text.new("Go Ducks!", 100, 100, :font => 'code')
|
1027
|
+
# => #<RubyLabs::Canvas::Text:0x101249300 @coords=[100, 100], @name="obj3", @penpoint=nil>
|
1028
|
+
#
|
1029
|
+
# :call-seq:
|
1030
|
+
# x = Font.new(name,args)
|
1031
|
+
#
|
1032
|
+
|
635
1033
|
def initialize(name, args)
|
636
1034
|
@name = name
|
637
1035
|
if @@fonts.index(name).nil?
|
@@ -643,22 +1041,32 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
643
1041
|
|
644
1042
|
end
|
645
1043
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
1044
|
+
# Initialize a drawing canvas with the specified width and height. If this is the first
|
1045
|
+
# call in an IRB session, open a connection to a wish shell and send it Tcl commands to
|
1046
|
+
# make a window with a single widget, a canvas centered in the middle. The +title+ argument
|
1047
|
+
# passed to Canvas.init becomes part of the new window.
|
1048
|
+
#
|
1049
|
+
# If this is not the first call to Canvas.init, the existing Tk canvas is resized according
|
1050
|
+
# to the new width and height arguments and the window is renamed using the new name.
|
1051
|
+
#
|
1052
|
+
# An optional fourth argument can be the symbol <tt>:debug</tt>, in which case the return
|
1053
|
+
# value is the Ruby Pipe object used to communicate with Tk. The Pipe can be useful for
|
1054
|
+
# developing new TkObject objects, since it can be used to see how Tcl commands are
|
1055
|
+
# processed. Example:
|
1056
|
+
#
|
1057
|
+
# >> p = Canvas.init(200, 200, "Test", :debug)
|
1058
|
+
# => #<IO:0x1012a1cd0>
|
1059
|
+
# >> p.puts ".canvas create text 30 30 -text hello"
|
1060
|
+
# => nil
|
1061
|
+
#
|
1062
|
+
# :call-seq:
|
1063
|
+
# Canvas.init(width, height, title, opts = nil)
|
1064
|
+
#
|
1065
|
+
#--
|
1066
|
+
# TODO Read the path to wish from a configuration file, so users can alter it
|
1067
|
+
# TODO there are probably other configuration options that can go there, too
|
1068
|
+
# TODO use popen3 on OSX, capture stderr
|
656
1069
|
|
657
|
-
@@tkpipe = nil
|
658
|
-
@@title = ""
|
659
|
-
@@height = 0
|
660
|
-
@@width = 0
|
661
|
-
|
662
1070
|
def Canvas.init(width, height, title, *opts)
|
663
1071
|
|
664
1072
|
@@title = "RubyLabs::#{title}"
|
@@ -669,7 +1077,7 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
669
1077
|
if @@tkpipe.nil?
|
670
1078
|
if Canvas.OS == "Windows"
|
671
1079
|
@@tkpipe = IO.popen("wish", "w")
|
672
|
-
|
1080
|
+
elsif Canvas.OS == "Linux"
|
673
1081
|
@@tkpipe = IO.popen("/opt/ActiveTcl-8.5/bin/wish", "w")
|
674
1082
|
else
|
675
1083
|
@@tkpipe = IO.popen("/usr/local/bin/wish", "w")
|
@@ -687,9 +1095,16 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
687
1095
|
@@tkpipe.puts "wm geometry . #{width+2*pad}x#{height+2*pad}+30+50"
|
688
1096
|
@@tkpipe.puts "wm title . #{@@title}"
|
689
1097
|
|
690
|
-
|
1098
|
+
return opts[0] == :debug ? @@tkpipe : true
|
691
1099
|
end
|
692
1100
|
|
1101
|
+
# Send an +exit+ command to the wish shell, which closes the drawing window and
|
1102
|
+
# terminates the shell.
|
1103
|
+
#
|
1104
|
+
# :call-seq:
|
1105
|
+
# Canvas.close()
|
1106
|
+
#
|
1107
|
+
|
693
1108
|
def Canvas.close
|
694
1109
|
if @@tkpipe
|
695
1110
|
@@tkpipe.puts "exit"
|
@@ -698,22 +1113,37 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
698
1113
|
end
|
699
1114
|
end
|
700
1115
|
|
701
|
-
|
702
|
-
|
703
|
-
|
1116
|
+
# Return the current width of the canvas.
|
1117
|
+
#
|
1118
|
+
# :call-seq:
|
1119
|
+
# Canvas.width() => Fixnum
|
1120
|
+
#
|
704
1121
|
|
705
1122
|
def Canvas.width
|
706
1123
|
@@width
|
707
1124
|
end
|
708
1125
|
|
1126
|
+
# Return the current height of the canvas.
|
1127
|
+
#
|
1128
|
+
# :call-seq:
|
1129
|
+
# Canvas.height() => Fixnum
|
1130
|
+
#
|
1131
|
+
|
709
1132
|
def Canvas.height
|
710
1133
|
@@height
|
711
1134
|
end
|
712
1135
|
|
1136
|
+
# Return a reference to the Pipe object used to communicate with the wish shell.
|
1137
|
+
#
|
1138
|
+
# :call-seq:
|
1139
|
+
# Canvas.pipe() => IO
|
1140
|
+
#
|
1141
|
+
|
713
1142
|
def Canvas.pipe
|
714
1143
|
@@tkpipe
|
715
1144
|
end
|
716
1145
|
|
1146
|
+
#--
|
717
1147
|
# Idea for the future, abandoned for now: allow applications to define a coordinate
|
718
1148
|
# system, e.g. cartesian with the origin in the middle of the canvas, and map coords
|
719
1149
|
# pass to line, rectangle, etc from user-specified coordinates to Tk coordinates.
|
@@ -737,14 +1167,22 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
737
1167
|
# a[i], a[i+1] = @@map.call( a[i], a[i+1] )
|
738
1168
|
# end
|
739
1169
|
# end
|
1170
|
+
#++
|
740
1171
|
|
741
|
-
#
|
1172
|
+
# Return a string that uses the <tt>RUBY_PLATFORM</tt> environment variable to
|
1173
|
+
# determine the host operating system type. Possible return values are "Mac OS X",
|
1174
|
+
# "Linux", or "Windows".
|
1175
|
+
#
|
1176
|
+
# :call-seq:
|
1177
|
+
# Canvas.OS() => String
|
1178
|
+
#
|
1179
|
+
#--
|
742
1180
|
# TODO: find out what the platform string is for the legacy windows one-click installer;
|
743
1181
|
# the new installer uses the "mingw" compiler.
|
744
1182
|
|
745
1183
|
def Canvas.OS
|
746
1184
|
if RUBY_PLATFORM =~ %r{darwin}
|
747
|
-
return "OS X"
|
1185
|
+
return "Mac OS X"
|
748
1186
|
elsif RUBY_PLATFORM =~ %r{linux}
|
749
1187
|
return "Linux"
|
750
1188
|
elsif RUBY_PLATFORM =~ %r{mingw}
|
@@ -754,9 +1192,20 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
754
1192
|
end
|
755
1193
|
end
|
756
1194
|
|
757
|
-
|
758
|
-
|
759
|
-
|
1195
|
+
# Move an object by a distance +dx+ vertically and a distance +dy+ horizontally.
|
1196
|
+
# If the fourth argument is the symbol <tt>:track</tt> a line segment is drawn
|
1197
|
+
# connecting the pen position of the object's previous location with the
|
1198
|
+
# pen position of its new location. The return value is an array with the new
|
1199
|
+
# coordinates.
|
1200
|
+
#
|
1201
|
+
# Example: Suppose +x+ is a Polygon with coordinates (10,10), (20,20), and (30,10).
|
1202
|
+
# This call moves it down and to the right by 10 pixels and returns the new location:
|
1203
|
+
# >> Canvas.move(x, 10, 10)
|
1204
|
+
# => [20, 20, 30, 30, 40, 20]
|
1205
|
+
#
|
1206
|
+
# :call-seq:
|
1207
|
+
# Canvas.move(obj, dx, dy, track = nil)
|
1208
|
+
#
|
760
1209
|
|
761
1210
|
def Canvas.move(obj, dx, dy, option = nil)
|
762
1211
|
a = obj.coords
|
@@ -778,91 +1227,116 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
778
1227
|
return a
|
779
1228
|
end
|
780
1229
|
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
#
|
787
|
-
#
|
788
|
-
#
|
789
|
-
#
|
790
|
-
|
791
|
-
# obj.peny = yoff
|
792
|
-
# @@objects << obj
|
793
|
-
# return obj
|
794
|
-
# end
|
795
|
-
|
796
|
-
=begin rdoc
|
797
|
-
Rotate an object by an angle theta (expressed in degrees). The object is
|
798
|
-
rotated about the point defined by the first pair of (x,y) coordinates.
|
799
|
-
=end
|
800
|
-
|
801
|
-
def Canvas.rotate(obj, theta)
|
802
|
-
theta = Canvas.radians(theta)
|
803
|
-
a = obj.coords
|
804
|
-
x0 = a[0]
|
805
|
-
y0 = a[1]
|
806
|
-
(0...a.length).step(2) do |i|
|
807
|
-
x = a[i] - x0
|
808
|
-
y = a[i+1] - y0
|
809
|
-
a[i] = x0 + x * cos(theta) - y * sin(theta)
|
810
|
-
a[i+1] = y0 + x * sin(theta) + y * cos(theta)
|
811
|
-
end
|
812
|
-
obj.coords = a
|
813
|
-
return a
|
814
|
-
end
|
815
|
-
|
1230
|
+
# Convert an angle from degrees to radians. Example:
|
1231
|
+
# >> Math::PI / 4
|
1232
|
+
# => 0.785398163397448
|
1233
|
+
# >> Canvas.radians(45)
|
1234
|
+
# => 0.785398163397448
|
1235
|
+
#
|
1236
|
+
# :call-seq:
|
1237
|
+
# Canvas.radians(deg) => Float
|
1238
|
+
#
|
1239
|
+
|
816
1240
|
def Canvas.radians(deg)
|
817
1241
|
deg * Math::PI / 180
|
818
1242
|
end
|
819
1243
|
|
1244
|
+
# Convert an angle from radians to degrees. Example:
|
1245
|
+
# >> Canvas.degrees( Math::PI / 2 )
|
1246
|
+
# => 90.0
|
1247
|
+
#
|
1248
|
+
# :call-seq:
|
1249
|
+
# Canvas.degrees(rad) => Float
|
1250
|
+
#
|
1251
|
+
|
820
1252
|
def Canvas.degrees(rad)
|
821
1253
|
180 * rad / Math::PI
|
822
1254
|
end
|
823
1255
|
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
1256
|
+
# Make a range of colors starting from +first+ and going to +last+ in +n+ steps.
|
1257
|
+
# Color arguments should be be 3-tuples of integer RGB values. The
|
1258
|
+
# result is an array that starts with +first+, has +n+-1 intermediate colors,
|
1259
|
+
# and ends with +last+.
|
1260
|
+
#
|
1261
|
+
# Example:
|
1262
|
+
# >> Canvas.palette( [255,0,0], [0,0,0], 10)
|
1263
|
+
# => ["#FF0000", "#E60000", "#CD0000", ... "#1E0000", "#000000"]
|
1264
|
+
# The return value is an array of 11 colors starting with red and ending with black.
|
1265
|
+
#
|
1266
|
+
# :call-seq:
|
1267
|
+
# Canvas.palette(first, last, n) => Array
|
1268
|
+
#
|
832
1269
|
|
833
1270
|
def Canvas.palette(first, last, n)
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
1271
|
+
d = Array.new(3)
|
1272
|
+
3.times { |i| d[i] = (first[i] - last[i]) / n }
|
1273
|
+
a = [first]
|
1274
|
+
(n-1).times do |i|
|
1275
|
+
a << a.last.clone
|
1276
|
+
3.times { |j| a.last[j] -= d[j] }
|
1277
|
+
end
|
1278
|
+
a << last
|
1279
|
+
a.map { |c| sprintf("#%02X%02X%02X",c[0],c[1],c[2]) }
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
@@tkpipe = nil
|
1283
|
+
@@title = ""
|
1284
|
+
@@height = 0
|
1285
|
+
@@width = 0
|
1286
|
+
|
845
1287
|
end # Canvas
|
846
1288
|
|
847
1289
|
=begin rdoc
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
a
|
1290
|
+
|
1291
|
+
== PriorityQueue
|
1292
|
+
|
1293
|
+
A PriorityQueue is a collection of objects that is always maintained in order.
|
1294
|
+
Any kind of object can be in the queue as long as it can be compared with the < operator.
|
1295
|
+
More precisely, if an object +x+ responds to the < operator, and +x+ < +y+ is
|
1296
|
+
defined for every object +y+ already in the queue, then +x+ can be inserted.
|
1297
|
+
|
1298
|
+
The methods that insert and remove items check to see if an instance variable
|
1299
|
+
named <tt>@on_canvas</tt> is +true+. If so, a method named +update+ is called.
|
1300
|
+
+update+ is not defined here, but is added to the class by modules that
|
1301
|
+
use the queue during visualizations (see the definition of the priority queue
|
1302
|
+
used in the Huffman tree project in bitlab.rb for an example).
|
1303
|
+
|
1304
|
+
Several methods of the Array class are defined dynamically in the PriorityQueue
|
1305
|
+
class when this module
|
1306
|
+
is loaded. These methods have the same meaning for PriorityQueue objects as
|
1307
|
+
they do for Array objects:
|
1308
|
+
|
1309
|
+
+length+:: return the number of items in the queue
|
1310
|
+
+first+:: return a reference to the first item in the queue
|
1311
|
+
+last+:: return a reference to the last item in the queue
|
1312
|
+
+to_s+:: generate a String representation of the queue (by calling Array#to_s)
|
1313
|
+
+inspect+:: generate a String representation of the queue (by calling Array#inspect)
|
1314
|
+
+clear+:: remove all items from the queue
|
1315
|
+
<tt>empty?</tt>:: return +true+ if the queue has no items
|
1316
|
+
|
858
1317
|
=end
|
859
1318
|
|
860
1319
|
class PriorityQueue
|
861
1320
|
|
1321
|
+
# Create a new, initially empty, priority queue.
|
1322
|
+
|
862
1323
|
def initialize
|
863
1324
|
@q = Array.new
|
864
1325
|
end
|
865
|
-
|
1326
|
+
|
1327
|
+
# Insert an item into the queue. The item's location is determined by how
|
1328
|
+
# it compares with other items, according to the < operator. Specifically,
|
1329
|
+
# when item +x+ is added to a queue, it it put before the first item +y+
|
1330
|
+
# where +x+ < +y+.
|
1331
|
+
#
|
1332
|
+
# Example: suppose object +q+ has three strings:
|
1333
|
+
# >> q
|
1334
|
+
# => ["Au", "He", "O"]
|
1335
|
+
# This expression adds the string "Fe" to the queue:
|
1336
|
+
# >> q << "Fe"
|
1337
|
+
# => ["Au", "Fe", "He", "O"]
|
1338
|
+
# The new string went into the second position because "Fe" < "Au" is false but "Fe" < "He" is true.
|
1339
|
+
|
866
1340
|
def <<(obj)
|
867
1341
|
raise "Object cannot be inserted into priority queue" unless obj.respond_to?(:<)
|
868
1342
|
i = 0
|
@@ -875,57 +1349,129 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
|
|
875
1349
|
return @q
|
876
1350
|
end
|
877
1351
|
|
1352
|
+
# Remove the first item from the queue, returning a reference to that item.
|
1353
|
+
#
|
1354
|
+
# Example: suppose object +q+ has three strings:
|
1355
|
+
# >> q
|
1356
|
+
# => ["Au", "He", "O"]
|
1357
|
+
# Then a call to shift removes the string "Au" and leaves the remaining items in order in the queue:
|
1358
|
+
# >> x = q.shift
|
1359
|
+
# => "Au"
|
1360
|
+
# >> q
|
1361
|
+
# => ["He", "O"]
|
1362
|
+
#
|
1363
|
+
|
878
1364
|
def shift
|
879
1365
|
res = @q.shift
|
880
1366
|
update(:shift, res) if @on_canvas
|
881
1367
|
return res
|
882
1368
|
end
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
1369
|
+
|
1370
|
+
# Return the item at location +i+ in the queue. *Note*: unlike Array objects, an
|
1371
|
+
# index expression cannot be used on the left side of an assignment. If
|
1372
|
+
# +q+ is a PriorityQueue object,
|
1373
|
+
# x = q[i]
|
1374
|
+
# is valid, but
|
1375
|
+
# q[i] = x
|
1376
|
+
# is undefined.
|
887
1377
|
|
888
1378
|
def [](i)
|
889
1379
|
@q[i]
|
890
1380
|
end
|
891
1381
|
|
1382
|
+
# Call +f+ for every item in the queue, and return an array with
|
1383
|
+
# the result of each call (essentially the same as the +map+ operation
|
1384
|
+
# defined for Ruby's Enumeration interface).
|
1385
|
+
#
|
1386
|
+
# Example:
|
1387
|
+
# >> q
|
1388
|
+
# => ["avocado", "boysenberry", "clementine", "elderberry", "loquat"]
|
1389
|
+
# >> q.collect { |x| x.length }
|
1390
|
+
# => [7, 11, 10, 10, 6]
|
1391
|
+
|
892
1392
|
def collect(&f)
|
893
1393
|
@q.map { |x| f.call(x) }
|
894
1394
|
end
|
895
1395
|
|
1396
|
+
# Evaluate block +b+ for every item in the queue (equivalent to <tt>Array#each</tt>)
|
1397
|
+
#
|
1398
|
+
# Example:
|
1399
|
+
# >> q
|
1400
|
+
# => ["Au", "He", "O"]
|
1401
|
+
# >> q.each { |x| puts x }
|
1402
|
+
# Ar
|
1403
|
+
# He
|
1404
|
+
# O
|
1405
|
+
# => ["Au", "He", "O"]
|
1406
|
+
|
896
1407
|
def each(&b)
|
897
1408
|
@q.each &b
|
898
1409
|
end
|
899
1410
|
|
1411
|
+
# Evaluate block +b+ for every item in the queue, also passing the item's
|
1412
|
+
# location in the queue to the block (equivalent to <tt>Array#each_with_index</tt>)
|
1413
|
+
#
|
1414
|
+
# Example:
|
1415
|
+
# >> q
|
1416
|
+
# => ["Au", "He", "O"]
|
1417
|
+
# >> q.each_with_index { |x,i| puts "#{i}: #{x}" }
|
1418
|
+
# 0: Ar
|
1419
|
+
# 1: He
|
1420
|
+
# 2: O
|
1421
|
+
# => ["Au", "He", "O"]
|
1422
|
+
|
900
1423
|
def each_with_index(&b)
|
901
1424
|
@q.each_with_index &b
|
902
1425
|
end
|
903
1426
|
|
1427
|
+
%w{length first last to_s inspect clear empty?}.each do |name|
|
1428
|
+
eval "def #{name}() @q.#{name} end"
|
1429
|
+
end
|
1430
|
+
|
904
1431
|
end # PriorityQueue
|
905
1432
|
|
906
1433
|
end # RubyLabs
|
907
1434
|
|
908
|
-
class Fixnum
|
909
|
-
|
910
1435
|
=begin rdoc
|
911
1436
|
|
912
|
-
|
913
|
-
to numbers between 0 and 25.
|
1437
|
+
== Fixnum
|
914
1438
|
|
915
|
-
|
916
|
-
|
917
|
-
|
1439
|
+
When the RubyLabs module is loaded it defines a new method named +ord+ to the
|
1440
|
+
Fixnum class. In Ruby 1.8, using the <tt>[]</tt> operator to access items in a String object
|
1441
|
+
returns the ASCII value of a character. The +ord+ method defined here (and used by hash functions defined in hashlab.rb)
|
1442
|
+
maps the ASCII value of a letter to a number between 0 and 25.
|
1443
|
+
|
1444
|
+
The BitLab module also extends Fixnum by defining a method named +code+ that returns a Code
|
1445
|
+
object containing the binary or hexadecimal representation of an integer.
|
1446
|
+
#--
|
1447
|
+
NOTE: +ord+ is built in to Ruby 1.9, so this method will have to be renamed or reimplemented
|
1448
|
+
when RubyLabs is ported to 1.9.
|
918
1449
|
=end
|
919
1450
|
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
1451
|
+
class Fixnum
|
1452
|
+
|
1453
|
+
# If a number is the ASCII code for a letter from the Roman alphabet (upper or lower case,
|
1454
|
+
# in the range 'A' to 'Z') map it to a number between 0 and 25, otherwise just return the
|
1455
|
+
# value of the number.
|
1456
|
+
#
|
1457
|
+
# Example:
|
1458
|
+
# >> "Ducks!".each_byte { |x| puts x.ord }
|
1459
|
+
# 3
|
1460
|
+
# 20
|
1461
|
+
# 2
|
1462
|
+
# 10
|
1463
|
+
# 18
|
1464
|
+
# 33
|
1465
|
+
|
1466
|
+
def ord
|
1467
|
+
if self >= ?a && self <= ?z
|
1468
|
+
self - ?a
|
1469
|
+
elsif self >= ?A && self <= ?Z
|
1470
|
+
self - ?A
|
1471
|
+
else
|
1472
|
+
self
|
1473
|
+
end
|
1474
|
+
end
|
929
1475
|
|
930
1476
|
end # Fixnum
|
931
1477
|
|