retoo-rudoku 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. data/README.rdoc +7 -0
  2. data/lib/rudoku.rb +351 -0
  3. metadata +54 -0
@@ -0,0 +1,7 @@
1
+ = rudoku
2
+
3
+ Rudoku is a simple Sudoku solver.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 Reto Schüttel
@@ -0,0 +1,351 @@
1
+ # Copyright (c) 2007-2009 by Reto Schüttel <reto (ät) schuettel (dut) ch>
2
+
3
+ # Please contact me if you like this released under a OSS license!
4
+
5
+ class Rudoku
6
+ NRS = { 1 => true, 2 => true, 3 => true,
7
+ 4 => true, 5 => true, 6 => true,
8
+ 7 => true, 8 => true, 9 => true, }
9
+
10
+ class Board
11
+ attr_reader :fields, :blocks, :rows, :cols
12
+
13
+ def initialize(board)
14
+ @fields = []
15
+ @board = []
16
+ @rows = []
17
+ @cols = []
18
+ @blocks = []
19
+ @missing = []
20
+ @stats = {
21
+ :missing => 0,
22
+ :solved_by_only_one_possible => 0,
23
+ :solved_by_no_other_possible => 0,
24
+ :solved_by_presolve => 0
25
+ }
26
+
27
+ # initialize rows, columns & blocks
28
+ 0.upto(8) do |i|
29
+ @rows[i] = Row.new(self, i)
30
+ @cols[i] = Col.new(self, i)
31
+ @blocks[i] = Block.new(self, i)
32
+ end
33
+
34
+ i = 0
35
+ board.each_with_index do |row, y|
36
+ @board[y] = []
37
+
38
+ row.each_with_index do |value, x|
39
+ f = Field.new(self, i, x, y)
40
+ f.value = value
41
+
42
+ @board[y][x] = f
43
+ @fields << f
44
+ @missing << f if f.missing?
45
+ i += 1
46
+ end
47
+ end
48
+
49
+ # initialize the helper constructs
50
+ @blocks.each do |block|
51
+ block.initialize_area()
52
+ end
53
+
54
+ @stats[:missing] = @missing.length
55
+
56
+ @counter = 0
57
+
58
+ raise "Invalid field" if not valid_field?
59
+ end
60
+
61
+
62
+ def pre_solve
63
+ begin
64
+ #puts "try"
65
+ changed = @missing.reject! do |f|
66
+ # create a list of all fields which are wmissing
67
+ # in the same block, not including t the current field f
68
+ nrs = f.available_nrs
69
+
70
+
71
+ #puts "#{f} hat #{nrs.length} möglichkeiten: #{nrs.join(" ")}" if nrs.length == 2
72
+ #f.mark if nrs.length ==2
73
+ if nrs.length == 1
74
+ @stats[:solved_by_only_one_possible] += 1
75
+ #f.mark
76
+ f.value = nrs.first
77
+ true
78
+ else
79
+ # use the scanning method for possible other solutions
80
+ # returns false or true
81
+
82
+ if v = f.nr_only_possible_in_this_field
83
+ # f.mark
84
+ @stats[:solved_by_no_other_possible] += 1
85
+ f.value = v
86
+ #f.mark
87
+ #return
88
+ true
89
+ else
90
+ false
91
+ end
92
+ end
93
+ end
94
+ end while changed
95
+
96
+ @stats[:solved_by_presolve] =
97
+ @stats[:solved_by_no_other_possible] +
98
+ @stats[:solved_by_only_one_possible]
99
+ end
100
+
101
+ # returns false if there's no possible next move, else return the valid path
102
+ def solve(level = 0)
103
+ fm = @missing.shift
104
+
105
+ if fm.nil?
106
+ return true
107
+ end
108
+
109
+ available_nrs = fm.available_nrs
110
+
111
+ unless available_nrs.empty?
112
+ # try all the available nrs on this field
113
+ available_nrs.each do |nr|
114
+
115
+ raise "nr is nil??" if nr.nil?
116
+ # set it
117
+ fm.value = nr
118
+
119
+ # and try to fill the next field
120
+ result = solve(level + 1)
121
+
122
+ # return true if we found a solution
123
+ return true if result
124
+ end
125
+ end
126
+
127
+ # seems like we are in a 'sackgasse', reset the value and
128
+ # go back to the next stepp
129
+ fm.value = nil
130
+ @missing.unshift(fm)
131
+
132
+ return false
133
+ end
134
+
135
+ def print_field(p = [])
136
+ p = [p] unless p.kind_of?(Array)
137
+
138
+ @board.each_with_index do |row, y|
139
+ #row.map{|f| f.value}.map{|f| f || "_"}.each_with_index do |f, x|
140
+ row.each_with_index do |f, x|
141
+ t = ( f.value ? f.value.to_s : "_" ) + ( f.marked? ? "<" : " " )
142
+
143
+ print "#{t} "
144
+
145
+ #if p.any?{|o| x == o.x && y == o.y}
146
+ # print "#{f}<"
147
+ #else
148
+ # print "#{f} "
149
+ #end
150
+ print " " if x % 3 == 2
151
+ end
152
+ puts
153
+ puts if y % 3 == 2
154
+ end
155
+
156
+ #puts "Lösung"
157
+ #0.upto(8) do |i|
158
+ # print "#{@board[i][i].value} "
159
+ #end
160
+ #puts
161
+
162
+ #puts
163
+ puts "Stats"
164
+ @stats.each do |key, value|
165
+ puts "#{key}: #{value}"
166
+ end
167
+
168
+ end
169
+
170
+ def valid_field?
171
+ true
172
+ end
173
+
174
+ def get(x, y)
175
+ @board[y][x]
176
+ end
177
+
178
+ def get_row(y)
179
+ @rows[y]
180
+ end
181
+
182
+ def get_col(x)
183
+ @cols[x]
184
+ end
185
+
186
+ def get_block(x, y)
187
+ block_nr = x / 3 + y/3*3
188
+ @blocks[block_nr] or raise "Unitialized block at #{x}/#{y} block_nr #{block_nr}"
189
+ end
190
+
191
+ def find_first_missing
192
+ @fields.find{|f| f.missing?}
193
+ end
194
+ end
195
+
196
+ class Field
197
+ attr_reader :index, :x, :y, :value, :block
198
+
199
+ def initialize(board, i, x, y)
200
+ @board = board
201
+ @index = i
202
+ @x = x
203
+ @y = y
204
+ @marked = false
205
+
206
+ @row = @board.get_row(y) or raise
207
+ @col = @board.get_col(x) or raise
208
+ @block = @board.get_block(x, y) or raise
209
+ end
210
+
211
+ def mark
212
+ @marked = true
213
+ end
214
+
215
+ def marked?
216
+ @marked
217
+ end
218
+
219
+ def value=(v)
220
+ unless value.nil?
221
+ add(value)
222
+ end
223
+
224
+ unless v.nil?
225
+ remove(v)
226
+ end
227
+ @value = v
228
+ end
229
+
230
+ def remove(v)
231
+ @block.remove(v)
232
+ @row.remove(v)
233
+ @col.remove(v)
234
+ end
235
+
236
+ def add(v)
237
+ @block.add(v)
238
+ @row.add(v)
239
+ @col.add(v)
240
+ end
241
+
242
+ def missing?
243
+ @value.nil?
244
+ end
245
+
246
+ def available_nrs
247
+ @row.available_nrs & @col.available_nrs & @block.available_nrs
248
+ end
249
+
250
+ def to_s
251
+ x.to_s + ":" + y.to_s
252
+ end
253
+
254
+ def nr_only_possible_in_this_field
255
+ neighbours = block.missing_fields.reject{|m| m == self}
256
+
257
+ available_nrs.each do |nr|
258
+ # iterate over all neighbours and check if theres a Nr (nr)
259
+ # which can't be anywere else
260
+ if neighbours.all?{|n| not n.available_nrs.include?(nr) }
261
+ # okay, nr isn't possible in all the other missing
262
+ # fields in the neighbourhood, that means it can only be
263
+ # on f
264
+ return nr
265
+ end
266
+ end
267
+
268
+ nil
269
+ end
270
+
271
+ end
272
+
273
+ class Area
274
+ attr_reader :board
275
+
276
+ def initialize(b, coord)
277
+ @available_nrs = NRS.dup
278
+ @board = b or raise "b not defined"
279
+ end
280
+
281
+ def available_nrs
282
+ @available_nrs.keys
283
+ end
284
+
285
+ def remove(v)
286
+ raise if v == true
287
+
288
+ @available_nrs.delete(v)
289
+ raise "bb" if v == true
290
+ end
291
+
292
+ def add(v)
293
+ @available_nrs[v] = true
294
+ end
295
+
296
+ def info(mode, v)
297
+ #puts "#{type} #{mode} #{v} available_nrs contains #{@available_nrs.join(", ")}"
298
+ end
299
+
300
+ # TODO: this could be cached/resued/
301
+ def missing_fields
302
+ fields.reject{|f| not f.missing?}
303
+ end
304
+ end
305
+
306
+ class Row < Area
307
+ def initialize(b, y)
308
+ @y = y
309
+ super
310
+ end
311
+
312
+ def type
313
+ "row #{@y}"
314
+ end
315
+ end
316
+
317
+ class Col < Area
318
+ def initialize(b, x)
319
+ @x = x
320
+ super
321
+ end
322
+
323
+ def type
324
+ "col #{@x}"
325
+ end
326
+ end
327
+
328
+ class Block < Area
329
+ attr_accessor :fields
330
+
331
+ def initialize(b, n)
332
+ @n = n
333
+ @x = n % 3
334
+ @y = n / 3
335
+ super
336
+ end
337
+
338
+ def initialize_area
339
+ f_x = @x * 3
340
+ f_y = @y * 3
341
+
342
+ @fields = []
343
+
344
+ f_y.upto(f_y + 2) do |y|
345
+ f_x.upto(f_x + 2) do |x|
346
+ @fields << board.get(x, y)
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: retoo-rudoku
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - "Reto Sch\xC3\xBCttel"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-23 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Rudoku is a simple Sudoku solver.
17
+ email: reto <hugh> at <yoh!> schuettel doto ch
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - lib/rudoku.rb
26
+ - README.rdoc
27
+ has_rdoc: true
28
+ homepage: http://github.com/retoo/rudoku
29
+ post_install_message:
30
+ rdoc_options:
31
+ - --charset=UTF-8
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: "0"
39
+ version:
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ requirements:
47
+ - none
48
+ rubyforge_project:
49
+ rubygems_version: 1.2.0
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Sudoku Engine.
53
+ test_files: []
54
+