phg_sudoku_solver 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in phg_sudoku_solver.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Miles Porter
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # PhgSudokuSolver
2
+
3
+ Welcome to the PHG Sudoku Solver gem.
4
+
5
+ Miles Porter
6
+ Senior Software Consultant
7
+ Painted Harmony Group, Inc.
8
+ mporter(AT)paintedharmony(DOT)com
9
+
10
+ "I first started doing Sudoku puzzles a few years ago while on a 3 hour plane flight. I was hooked. Eventually, I came
11
+ across a Sudoku that I could not do. I was completely frustrated, and the puzzle that I was working on was printed in a
12
+ weekly newspaper and I didn't want to wait an entire week to see the solution. So, I did what any good software engineer
13
+ would do... I decided to write a program/algorithm to solve the puzzle. At first, I tried the brute-force method of
14
+ plugging in random numbers. I quickly discovered that would never work because there are just too many combinations...
15
+ I continued to think about how to write code to solve Sudoku puzzles. There are, of course, countless sites and free
16
+ software that provide solutions to the problem. I wanted, however, to see if I could do it myself. The result of that
17
+ effort is what you see in front fo you. The code uses a number of different techniques to try and deduce what values
18
+ can be inferred by the puzzle. After all the inferred cells are set, the program uses recursion to complete the puzzle.
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ gem 'phg_sudoku_solver'
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install phg_sudoku_solver
32
+
33
+ ## Usage
34
+
35
+ The following snipit illustrates show to use this gem (taken from tests, mind you.)
36
+
37
+ ± irb
38
+ 1.9.3p194 :001 > require 'phg_sudoku_solver'
39
+ => true
40
+ 1.9.3p194 :002 >
41
+ 1.9.3p194 :003 > a = [ "5xx4x67xx", "xxx5xx9xx", "2xxx17x4x", "xxx72xx1x", "9xxxxxxx8", "x7xx68xxx", "x3x27xxx5", "xx4xx3xxx","xx26x4xx3"]
42
+ => ["5xx4x67xx", "xxx5xx9xx", "2xxx17x4x", "xxx72xx1x", "9xxxxxxx8", "x7xx68xxx", "x3x27xxx5", "xx4xx3xxx", "xx26x4xx3"]
43
+ 1.9.3p194 :004 >
44
+ 1.9.3p194 :005 > s = Sudoku.new(a)
45
+
46
+ [blah blah blah]
47
+
48
+ 1.9.3p194 :010 > x = s.solve
49
+
50
+ [blah blah blah]
51
+
52
+ x.dump_known_cells_str
53
+ => "\\n 5 1 8 | 4 9 6 | 7 3 2 \\n
54
+ 6 4 7 | 5 3 2 | 9 8 1 \\n
55
+ 2 9 3 | 8 1 7 | 5 4 6 \\n
56
+ ----------------------------------------\\n
57
+ 3 8 5 | 7 2 9 | 6 1 4 \\n
58
+ 9 2 6 | 1 4 5 | 3 7 8 \\n
59
+ 4 7 1 | 3 6 8 | 2 5 9 \\n
60
+ ----------------------------------------\\n
61
+ 8 3 9 | 2 7 1 | 4 6 5 \\n
62
+ 1 6 4 | 9 5 3 | 8 2 7 \\n
63
+ 7 5 2 | 6 8 4 | 1 9 3 \\n"
64
+
65
+ (Note: The display has been cleaned up a bit above.)
66
+
67
+ Notes:
68
+ Create a new Sudoku instance by passing in an array of 9 strings. Each string needs to be 9 characters. Any non-
69
+ numeric character (1-9) is considered to be an unsolved cell. You can use spaces, Xs or whatever you like.
70
+
71
+ Call the .solve method on the sudoku instance that you create.
72
+
73
+ To get the results of the solved sudoku, you can
74
+ 1) Call the .dump_known_cells_str method, which will return a formatted string that represents the solved puzzle.
75
+ 2) Iterate over the .get_fixed_value(r,c) method, where r and c represent the row and column. The data returned
76
+ will be the value found for that cell.
77
+
78
+ WHEN SOMETHING GOES WRONG... AND SOMETHING ALWAYS GOES WRONG...
79
+
80
+ 1. If the sudoku entered is invalid, the solve method will return an error.
81
+
82
+ 1.9.3p194 :015 > a = ['123123123']
83
+ => ["123123123"]
84
+ 1.9.3p194 :016 > s = Sudoku.new(a)
85
+ Exception: Sudoku entered appears to be invalid.
86
+ from /Users/miles_r_porter/.rvm/gems/ruby-1.9.3-p194/gems/phg_sudoku_solver-0.0.2/lib/phg_sudoku_solver.rb:47:in `initialize'
87
+ from (irb):16:in `new'
88
+ from (irb):16
89
+ from /Users/miles_r_porter/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
90
+ 1.9.3p194 :017 >
91
+
92
+ 2. Some sudoku are just to complex for the engine to compute a solution in the given maximum iterations
93
+
94
+ 1.9.3p194 :018 > a = ["123456789","xxxxxxxxx","xxxxxxxxx","xxxxxxxxx","xxxxxxxxx","xxxxxxxxx","xxxxxxxxx","xxxxxxxxx","xxxxxxxxx"]
95
+ => ["123456789", "xxxxxxxxx", "xxxxxxxxx", "xxxxxxxxx", "xxxxxxxxx", "xxxxxxxxx", "xxxxxxxxx", "xxxxxxxxx", "xxxxxxxxx"]
96
+ 1.9.3p194 :019 > s = Sudoku.new(a)
97
+
98
+ [blah blah blah]
99
+
100
+ 1.9.3p194 :020 > x = s.solve()
101
+
102
+ 1.9.3p194 :012 > x = s.solve
103
+ Exception: Solution taking too long!\n\n
104
+
105
+ Note: The number of iterations are checked after each recursion, so there total iterations may exceed the max
106
+ iterations set.
107
+
108
+ Oh... and you can set the max iterations:
109
+
110
+ s.set_max_iterations(500)
111
+
112
+ for example.
113
+
114
+ More features will be released at some point. Enjoy!
115
+
116
+
117
+ ## Contributing
118
+
119
+ 1. Fork it
120
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
121
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
122
+ 4. Push to the branch (`git push origin my-new-feature`)
123
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,65 @@
1
+ #
2
+ # Cell.rb
3
+ #
4
+ # Created on September 23, 2013
5
+ #
6
+ # A class that contains the logic for a single cell in a sudoku puzzle.
7
+ #
8
+
9
+ class Cell
10
+
11
+ def initialize *args
12
+ @fixed_value = -1
13
+ @possible_values = []
14
+ if args.size==1
15
+ case args[0].class.to_s
16
+ when "Array"
17
+ @possible_values = args[0]
18
+ when "Fixnum"
19
+ @fixed_value = args[0]
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ def reset_possible_values()
26
+ @possible_values = []
27
+ end
28
+
29
+ def add_possible_value(val)
30
+ @possible_values.push(val)
31
+ if @possible_values.count > 1
32
+ @fixed_value = -1
33
+ end
34
+ end
35
+
36
+ def set_possible_values(val)
37
+ @possible_values = val
38
+ end
39
+
40
+ def set_fixed_value(val)
41
+ @fixed_value = val
42
+ if val>0
43
+ @possible_values = []
44
+ end
45
+ end
46
+
47
+ def get_fixed_value()
48
+ @fixed_value
49
+ end
50
+
51
+ def get_possible_values()
52
+ @possible_values
53
+ end
54
+
55
+ def contains_possible_value(x)
56
+ @possible_values.include?(x)
57
+ end
58
+
59
+ def copy()
60
+ c = Cell.new()
61
+ c.set_fixed_value(@fixed_value)
62
+ c.set_possible_values(@possible_values)
63
+ c
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module PhgSudokuSolver
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,491 @@
1
+ require 'phg_sudoku_solver/version'
2
+ require 'phg_sudoku_solver/cell'
3
+ #
4
+ # Sudoku.rb
5
+ #
6
+ # Created on September 23, 2013
7
+ #
8
+ #
9
+
10
+ class Sudoku
11
+
12
+ def set_max_iterations(iterations)
13
+ @max_iterations=iterations
14
+ end
15
+
16
+ def set_total_iterations(iterations)
17
+ @total_iterations = iterations
18
+ end
19
+
20
+ def initialize (*args)
21
+ @total_iterations = 0
22
+ @max_iterations = 100000
23
+ @debug = false
24
+ @cells = Array.new(9) { Array.new(9) }
25
+
26
+ if args.size==1
27
+ (0..8).each { |row|
28
+ if args[0][row].is_a?(String)
29
+ (0..8).each { |col|
30
+ val = args[0][row][col]
31
+ if /[0-9]/.match(val)
32
+ @cells[row][col] = Cell.new(val.to_i)
33
+ else
34
+ @cells[row][col] = Cell.new()
35
+ end
36
+ }
37
+ end
38
+ }
39
+ elsif args.size==0
40
+ (0..8).each { |row|
41
+ (0..8).each { |col|
42
+ @cells[row][col] = Cell.new()
43
+ }
44
+ }
45
+ end
46
+ unless self.validate_sudoku
47
+ raise Exception.new('Sudoku entered appears to be invalid.')
48
+ end
49
+ end
50
+
51
+ def get_cells
52
+ @cells
53
+ end
54
+
55
+ def set_cells(row, col, cell)
56
+ @cells[row][col]=cell
57
+ end
58
+
59
+ def get_fixed_value(row, col)
60
+ @cells[row][col].get_fixed_value()
61
+ end
62
+
63
+ def set_fixed_value(row, col, val)
64
+ @cells[row][col].set_fixed_value(val)
65
+ end
66
+
67
+ def set_possible_values(row, col, vals)
68
+ @cells[row][col].set_possible_values(vals)
69
+ end
70
+
71
+ def add_possible_value(row, col, val)
72
+ @cells[row][col].add_possible_value(val)
73
+ end
74
+
75
+ def get_possible_values(row, col)
76
+ @cells[row][col].get_possible_values
77
+ end
78
+
79
+ def get_total_iterations
80
+ @total_iterations
81
+ end
82
+
83
+ def solve
84
+ iteration=0
85
+ no_progress_count = 0
86
+
87
+ while @solved_cell_count!=81
88
+ iteration+=1
89
+ @total_iterations+=1
90
+ print_debug 'Iteration: %s SolvedCells: %s\n' % [iteration, @solved_cell_count]
91
+ compute_possible_values()
92
+
93
+ begin
94
+ if validate_sudoku()
95
+ find_row_implied_values()
96
+ find_col_implied_values()
97
+ find_matrix_implied_values()
98
+ else
99
+ return nil, @total_iterations
100
+ end
101
+ rescue Exception => error
102
+ print_debug '\nValidation Error: %s\n' % error
103
+ @solved_cell_count=count_fixed_cells()
104
+ return nil, @total_iterations
105
+ end
106
+
107
+ if @total_iterations > @max_iterations
108
+ print_debug 'Total Iterations... %s\n' % @total_iterations
109
+ print_debug 'This is just taking too long\n'
110
+ raise Exception.new('Solution taking too long!\n\n')
111
+ end
112
+
113
+ if @solved_cell_count==count_fixed_cells()
114
+ no_progress_count+=1
115
+ if no_progress_count>9
116
+ print_debug ('We''re not making progress here!')
117
+ solution = recurse()
118
+ return solution, @total_iterations
119
+ end
120
+ else
121
+ no_progress_count=0
122
+ @solved_cell_count=count_fixed_cells()
123
+ end
124
+ end
125
+ return self, @total_iterations
126
+ end
127
+
128
+ def count_fixed_cells
129
+ fixed_cell_count=0
130
+
131
+ @cells.each() do |row|
132
+ row.each() do |cell|
133
+ fixed_cell_count = fixed_cell_count + 1 if cell.get_fixed_value != -1
134
+ end
135
+ end
136
+ fixed_cell_count
137
+ end
138
+
139
+ def find_row_implied_values
140
+
141
+ (1..9).each { |i|
142
+ (0..8).each { |r|
143
+ fixed_count = 0
144
+ variable_count = 0
145
+ temp_row = -1
146
+ temp_col = -1
147
+ (0..8).each { |c|
148
+ # Start by counting up fixed values for the given number i. (e.g. How many of i do we have?)
149
+ if @cells[r][c].get_fixed_value()==i
150
+ fixed_count+=1
151
+ end
152
+
153
+ # We are checking to see if there is only one i in the possible values for this row.
154
+ # If so, then we will set the fixed value in the cell contains it.
155
+ (0..@cells[r][c].get_possible_values().count).each { |j|
156
+ if @cells[r][c].get_possible_values()[j]==i
157
+ variable_count+=1
158
+ temp_row = r
159
+ temp_col = c
160
+ end
161
+ }
162
+
163
+ }
164
+ if (fixed_count==0) and (variable_count==1)
165
+ @cells[temp_row][temp_col].set_fixed_value(i)
166
+ end
167
+
168
+ if fixed_count>1
169
+ raise('Duplicate fixed values in row check. Make sure you entered correct values.')
170
+ end
171
+ }
172
+ }
173
+ end
174
+
175
+ def find_col_implied_values
176
+ (1..9).each { |i|
177
+ (0..8).each { |c|
178
+ fixed_count = 0
179
+ variable_count = 0
180
+ temp_row = -1
181
+ temp_col = -1
182
+ (0..8).each { |r|
183
+ if @cells[r][c].get_fixed_value()==i
184
+ fixed_count+=1
185
+ end
186
+
187
+ (0..@cells[r][c].get_possible_values().count).each { |j|
188
+ if @cells[r][c].get_possible_values()[j]==i
189
+ variable_count+=1
190
+ temp_row = r
191
+ temp_col = c
192
+ end
193
+ }
194
+
195
+ }
196
+ if (fixed_count==0) and (variable_count==1)
197
+ @cells[temp_row][temp_col].set_fixed_value(i)
198
+ end
199
+
200
+ if fixed_count>1
201
+ raise('Duplicate fixed value in column check. Make sure you entered correct values.')
202
+ end
203
+ }
204
+ }
205
+ end
206
+
207
+ def find_matrix_implied_values
208
+ # In each sub-matrix we check for implied values (i) one at a time...
209
+ [0, 3, 6].each { |row_start|
210
+ [0, 3, 6].each { |col_start|
211
+ (1..9).each { |i|
212
+ temp_row = 0
213
+ temp_col = 0
214
+ fixed_count = 0
215
+ variable_count = 0
216
+ (row_start..row_start+2).each { |r|
217
+ (col_start..col_start+2).each { |c|
218
+
219
+ if @cells[r][c].get_fixed_value()==i
220
+ fixed_count+=1
221
+ end
222
+
223
+ (0..@cells[r][c].get_possible_values().count).each { |j|
224
+ if @cells[r][c].get_possible_values()[j]==i
225
+ variable_count+=1
226
+ temp_row = r
227
+ temp_col = c
228
+ end
229
+ }
230
+ }
231
+ }
232
+
233
+ if (fixed_count==0) and (variable_count==1)
234
+ @cells[temp_row][temp_col].set_fixed_value(i)
235
+ end
236
+
237
+ if fixed_count>1
238
+ raise('Duplicate fixed value in matrix check. Make sure you entered correct values.')
239
+ end
240
+ }
241
+ }
242
+ }
243
+ end
244
+
245
+
246
+ def compute_possible_values
247
+
248
+ (0..8).each { |r|
249
+ (0..8).each { |c|
250
+ if @cells[r][c].get_fixed_value()<=0
251
+ @cells[r][c].reset_possible_values()
252
+
253
+ (1..9).each { |possible_value|
254
+ if check_row_value(r, c, possible_value) and check_col_value(r, c, possible_value) and check_matrix_value(r, c, possible_value)
255
+ add_possible_value(r, c, possible_value)
256
+ end
257
+
258
+ }
259
+ if @cells[r][c].get_possible_values().count==1
260
+ fixed_val = @cells[r][c].get_possible_values()[0]
261
+ @cells[r][c].set_fixed_value(fixed_val)
262
+ print_debug 'Resetting possible values...'
263
+ @cells[r][c].reset_possible_values()
264
+ elsif @cells[r][c].get_possible_values().count>1
265
+ @cells[r][c].set_fixed_value(-1)
266
+ end
267
+ end
268
+ }
269
+ }
270
+ end
271
+
272
+ def check_row_value(row, col, val)
273
+ result = true
274
+ (0..8).each { |current_column|
275
+ if current_column!=col
276
+ if @cells[row][current_column].get_fixed_value()==val
277
+ result = false
278
+ end
279
+ end
280
+ }
281
+ result
282
+ end
283
+
284
+ def check_col_value(row, col, val)
285
+ result = true
286
+ (0..8).each { |r|
287
+ if r!=row
288
+ if @cells[r][col].get_fixed_value()==val
289
+ result = false
290
+ end
291
+ end
292
+ }
293
+ result
294
+ end
295
+
296
+ def check_matrix_value(row, col, val)
297
+ result = true
298
+ row_start = (row/3).floor * 3
299
+ col_start = (col/3).floor * 3
300
+ (row_start..row_start+2).each { |y|
301
+ (col_start..col_start+2).each { |x|
302
+ unless row==y and col==x
303
+ if @cells[y][x].get_fixed_value()==val
304
+ result = false
305
+ end
306
+ end
307
+ }
308
+ }
309
+ result
310
+ end
311
+
312
+ def recurse
313
+ recurse = copy()
314
+ (0..8).each { |r|
315
+ (0..8).each { |c|
316
+ if (recurse.get_fixed_value(r, c)<=0) and (recurse.get_possible_values(r, c).count>0)
317
+ (0..recurse.get_possible_values(r, c).count).each { |j|
318
+ unless recurse.get_possible_values(r, c)[j].nil?
319
+ print_debug '\nStarting recursion with (%s,%s) set to %s\n' % [r, c, recurse.get_possible_values(r, c)[j]]
320
+ recurse.set_fixed_value(r, c, recurse.get_possible_values(r, c)[j])
321
+ print_debug('Recursion starting...')
322
+ recurse, iterations = recurse.solve
323
+ @total_iterations = @total_iterations + iterations
324
+ if recurse!=nil
325
+ return recurse
326
+ else
327
+ recurse = copy()
328
+ end
329
+ end
330
+ }
331
+ end
332
+ }
333
+ }
334
+ print_debug('Dead end found.\n')
335
+ nil
336
+ end
337
+
338
+ def copy
339
+ s = Sudoku.new()
340
+ (0..8).each { |r|
341
+ (0..8).each { |c|
342
+ copied_cell = @cells[r][c].copy
343
+ s.set_cells(r, c, copied_cell)
344
+ }
345
+ }
346
+ s.set_max_iterations(@max_iterations)
347
+ s.set_total_iterations(@total_iterations)
348
+ s.set_debug(@debug)
349
+ s
350
+ end
351
+
352
+ def validate_sudoku
353
+ results = true
354
+ begin
355
+ (0..8).each { |r|
356
+ validate_row(r)
357
+ }
358
+
359
+ (0..8).each { |c|
360
+ validate_column(c)
361
+ }
362
+
363
+ [0, 3, 6].each { |r|
364
+ [0, 3, 6].each { |c|
365
+ validate_matrix(r, c)
366
+ }
367
+ }
368
+ rescue Exception => error
369
+ results = false
370
+ end
371
+ results
372
+ end
373
+
374
+ def validate_row(r)
375
+
376
+ (1..9).each { |i|
377
+ found_count=0
378
+ (0..8).each { |c|
379
+ if @cells[r][c].get_fixed_value()==i
380
+ found_count+=1
381
+ end
382
+ }
383
+ if found_count>1
384
+ raise Exception.new('Row invalid %s. Canceling' % r)
385
+ end
386
+ }
387
+ true
388
+ end
389
+
390
+
391
+ def validate_column(c)
392
+
393
+ (1..9).each { |i|
394
+ found_count = 0
395
+ (0..8).each { |r|
396
+ if @cells[r][c].get_fixed_value()==i
397
+ found_count+=1
398
+ end
399
+ }
400
+ if found_count>1
401
+ raise Exception.new('Row invalid. Canceling')
402
+ end
403
+ }
404
+ true
405
+ end
406
+
407
+
408
+ def validate_matrix(r, c)
409
+ (1..9).each { |i|
410
+ found_count=0
411
+ row_start = (r/3).floor * 3
412
+ col_start = (c/3).floor * 3
413
+ (row_start..row_start+2).each { |y|
414
+ (col_start..col_start+2).each { |x|
415
+ if @cells[y][x].get_fixed_value()==i
416
+ found_count+=1
417
+ end
418
+
419
+ }
420
+ }
421
+ if found_count>1
422
+ raise Exception.new('Matrix invalid! Canceling.')
423
+ end
424
+ }
425
+ true
426
+ end
427
+
428
+
429
+ def set_debug(b)
430
+ @debug = b
431
+ end
432
+
433
+ def dump_to_str
434
+ if @debug
435
+ print '\n'
436
+ (0..8).each { |row|
437
+ (0..8).each { |col|
438
+ printf(' %3s | ' % @cells[row][col].get_fixed_value())
439
+ if col == 2 or col == 5
440
+ print '| '
441
+ end
442
+
443
+ }
444
+ print '\n'
445
+ (0..8).each { |col|
446
+ array = @cells[row][col].get_possible_values()
447
+ if array==[]
448
+ array = '--'
449
+ end
450
+ printf(' %10s ' % array)
451
+ if col == 2 or col == 5
452
+ print '| '
453
+ end
454
+ }
455
+ print '\n\n'
456
+ }
457
+ end
458
+ end
459
+
460
+ def dump_known_cells_str
461
+
462
+ result = '\n'
463
+ for row in (0..8)
464
+ (0..8).each do |col|
465
+ if @cells[row][col].get_fixed_value() > 0
466
+ val = @cells[row][col].get_fixed_value()
467
+ else
468
+ val = ' '
469
+ end
470
+ result+='%3s ' % val
471
+ if col == 2 or col == 5
472
+ result+= '| '
473
+ end
474
+ end
475
+ if row==2 or row==5
476
+ result+= '\n----------------------------------------\n'
477
+ else
478
+ result+= '\n'
479
+ end
480
+ end
481
+ result
482
+ end
483
+
484
+ def print_debug(message)
485
+ if @debug
486
+ print message
487
+ end
488
+ end
489
+
490
+ end
491
+