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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/codeStyleSettings.xml +13 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/inspectionProfiles/Project_Default.xml +7 -0
- data/.idea/inspectionProfiles/profiles_settings.xml +7 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/phg_sudoku_solver.iml +19 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +712 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +123 -0
- data/Rakefile +1 -0
- data/lib/phg_sudoku_solver/cell.rb +65 -0
- data/lib/phg_sudoku_solver/version.rb +3 -0
- data/lib/phg_sudoku_solver.rb +491 -0
- data/phg_sudoku_solver.gemspec +20 -0
- data/test/cell_test.rb +63 -0
- data/test/sudoku_test.rb +407 -0
- metadata +70 -0
data/Gemfile
ADDED
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,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
|
+
|