sudokuhandler 0.1.2 → 0.1.3

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/lib/sudoku.rb CHANGED
@@ -4,192 +4,14 @@
4
4
 
5
5
  require "sudokucore"
6
6
  require "sudoku/version"
7
+ require "sudoku/grid"
7
8
 
8
9
  module Sudoku
9
10
  #Exception lancée lors d'erreurs de lecture d'une chaine Sutxt
10
11
  class MalformedSutxtError < Exception; end
11
12
 
12
- #Permet de generer rapidement un sudoku
13
- module Generator
14
- #Remplit la diagonale descendante de 1 a self.size
15
- def make_diagonal
16
- each{|x,y,val| set x,x,x+1 if x==y}
17
- self
18
- end
19
-
20
- #Remplit tout le sudoku de maniere a ce qu'il soit valide
21
- def make_valid
22
- pattern = Array.new(size){|i| i+1}
23
- size.times do |y|
24
- size.times do |x|
25
- set x, y, pattern[x]
26
- end
27
- base.times{|i| pattern.push pattern.shift}
28
- pattern.push pattern.shift if base - (y%base) == 1
29
- end
30
- self
31
- end
32
- end
33
-
34
- #Taches communes a tous les Sudokus
35
- module Grid
36
- #Renvoie le contenu de la ligne x
37
- def col x
38
- res = []
39
- size.times do |y|
40
- val = get x, y
41
- res << val if val != 0
42
- end
43
- res
44
- end
45
-
46
- #Renvoie le contenu de la ligne y
47
- def row y
48
- res = []
49
- size.times do |x|
50
- val = get x, y
51
- res << val if val != 0
52
- end
53
- res
54
- end
55
-
56
- #Renvoie le contenu du carré contenant la case x,y
57
- def square x, y
58
- xmin = x - (x%base)
59
- ymin = y - (y%base)
60
- res = []
61
-
62
- base.times do |xx|
63
- base.times do |yy|
64
- val = get xx+xmin, yy+ymin
65
- res << val if val != 0
66
- end
67
- end
68
-
69
- res
70
- end
71
-
72
- #Renvoie true si le sudoku ne contient aucune case vide
73
- def complete?
74
- each{|x,y,val| return false if val == 0}
75
- true
76
- end
77
-
78
- #Renvoie true si chaque case a au moins 1 possibilité à ce stade
79
- def completable?
80
- completed = 0
81
- each do |x, y, val|
82
- return false if possibilities(x, y).empty?
83
- end
84
- return true
85
- end
86
-
87
- #Renvoie toutes les possibilités pour la case x,y
88
- def possibilities x, y
89
- res = Array.new(size){|i| i+1}
90
- xmin = x-x%base
91
- ymin = y-y%base
92
-
93
- size.times do |i|
94
- res.delete get(x,i) if i!=y
95
- res.delete get(i,y) if i!=x
96
- xx, yy = xmin+i%base, ymin+i/base
97
- res.delete get(xx, yy) if xx!=x && yy!=y
98
- end
99
-
100
- res
101
- end
102
-
103
- #Renvoie true si tous les nombres de la grille sont valides
104
- def valid_grid?
105
- each do |x, y, val|
106
- next if val.zero?
107
- return false unless valid_cell? x, y, val
108
- end
109
-
110
- true
111
- end
112
-
113
- #Renvoie true si val est possible en x,y
114
- def valid_cell? x, y, val
115
- val = val.to_i
116
- xmin = x-x%base
117
- ymin = y-y%base
118
-
119
- size.times do |i|
120
- return false if i!=y && get(x,i)==val
121
- return false if i!=x && get(i,y)==val
122
- xx, yy = xmin+i%base, ymin+i/base
123
- return false if xx!=x && yy!=y && get(xx, yy) == val
124
- end
125
-
126
- true
127
- end
128
-
129
- #Si aucun argument n'est passé => valid_grid?
130
- #Si 3 arguments sont passés => valid_cell? x, y, val
131
- def valid? *args
132
- if args.empty?
133
- valid_grid?
134
- elsif args.length == 3
135
- valid_cell? *args
136
- else
137
- raise ArgumentError, "wrong number of arguments(#{args.length} for 0 or 3)"
138
- end
139
- end
140
-
141
- #Renvoie la base du sudoku
142
- def base
143
- if @base
144
- @base
145
- else
146
- @base = (size**0.5).to_i
147
- end
148
- end
149
-
150
- #Renvoie le nombre de cases dans le sudoku
151
- def length
152
- if @length
153
- @length
154
- else
155
- @length = size*size
156
- end
157
- end
158
-
159
- #Représentation texte humainement lisible
160
- def to_s
161
- res = ""
162
- width = size.to_s.length
163
- zero = ".".center width+1
164
-
165
- size.times do |y|
166
- res += "\n" if y>0 && y%base == 0
167
- size.times do |x|
168
- res += " " if x>0 && x%base == 0
169
- val = get x, y
170
- res += val.zero? ? zero : "#{val.to_s.center width} "
171
- end
172
- res += "\n"
173
- end
174
- res
175
- end
176
-
177
- #Représentation courte (utile dans irb)
178
- def inspect
179
- "#<#{self.class} #{size}x#{size} [#{get 0, 0}, #{get 0, 1}, ... , #{get size-2, size-1}, #{get size-1, size-1}]>"
180
- end
181
-
182
- #Représentation pour l'enregistrement
183
- def to_sutxt
184
- res = "#{base}:"
185
- size.times do |y|
186
- size.times do |x|
187
- res << " #{get x, y}"
188
- end
189
- end
190
- res+';'
191
- end
192
- end
13
+ #Exception lancée lors d'operations sur deux sudokus incompatibles
14
+ class NotCompatibleError < Exception; end
193
15
 
194
16
  #Sudoku 9x9 (base 3) très rapide
195
17
  class S3
@@ -201,6 +23,8 @@ module Sudoku
201
23
  __initialize nil
202
24
  end
203
25
 
26
+ #Renvoie le coté du Sudoku
27
+ # @return (Fixnum)
204
28
  def size
205
29
  SIZE
206
30
  end
@@ -211,6 +35,8 @@ module Sudoku
211
35
  include Grid
212
36
  private :size_internal
213
37
 
38
+ #Renvoie le coté du Sudoku
39
+ # @return (Fixnum)
214
40
  def size
215
41
  if @size
216
42
  @size
@@ -234,6 +60,8 @@ module Sudoku
234
60
  end
235
61
  end
236
62
 
63
+ #Renvoie le coté du Sudoku
64
+ # @return (Fixnum)
237
65
  def set x, y, val
238
66
  if x<0 || x>=size || y<0 || y>=size || val<0 || val > size
239
67
  raise ArgumentError, "#{x},#{y} => #{val} is impossible in a #{size}x#{size} sudoku"
@@ -241,6 +69,8 @@ module Sudoku
241
69
  @grid[x][y] = val
242
70
  end
243
71
 
72
+ #Renvoie la valeur en x,y
73
+ # @return (Fixnum)
244
74
  def get x, y
245
75
  if x<0 || x>=size || y<0 || y>=size
246
76
  raise ArgumentError, "Is there a #{x},#{y} cell in a #{size}x#{size} sudoku ?"
@@ -248,15 +78,20 @@ module Sudoku
248
78
  @grid[x][y]
249
79
  end
250
80
 
81
+ #Parcourt tout le sudoku
82
+ # @yield [x, y, val] la position et la valeur courante
83
+ # @return (self)
251
84
  def each
252
85
  @size.times do |y|
253
86
  @size.times do |x|
254
87
  yield x, y, @grid[x][y]
255
88
  end
256
89
  end
90
+ self
257
91
  end
258
92
  end
259
93
 
94
+ #Adaptateurs de base
260
95
  ADAPTERS = [
261
96
  [3, S3],
262
97
  [4..15, S4_15],
@@ -264,8 +99,11 @@ module Sudoku
264
99
  ]
265
100
 
266
101
  class << self
267
- #Renvoie la classe de la meilleure implémentation pour un sudoku de base n
268
- def [] n
102
+ #Renvoie la classe de la première implémentation dont la zone comprend n
103
+ # @param [Fixnum] n La base du sudoku
104
+ # @return [Class] La première classe dont la zone comprend n, ou la première classe
105
+ # donc la zone est 0 (zone par défaut)
106
+ def best_class_for n
269
107
  n = n.to_i
270
108
  ADAPTERS.each do |ad|
271
109
  zone = ad[0]
@@ -279,37 +117,35 @@ module Sudoku
279
117
  end
280
118
  end
281
119
  end
120
+ alias :[] :best_class_for
282
121
 
283
- #Ajoute un element
284
- def []= zone, adapter
122
+ #Ajoute un adapteur pour la zone definie.
123
+ # @param [Range, Fixnum] zone La zone de validité de l'adapteur
124
+ # @param [Class] adapter L'adaptateur à ajouter
125
+ def define_class_for zone, adapter
285
126
  ADAPTERS.unshift [zone, adapter]
286
127
  end
128
+ alias :[]= :define_class_for
287
129
 
288
130
  #Renvoie une instance de la meilleure implémentation pour un sudoku de base n
131
+ # @param [Fixnum] n La base du sudoku
132
+ # @return [Grid]
289
133
  def best_grid_for n=3
290
134
  n = n.to_i
291
- self[n].new n
135
+ best_class_for(n).new n
292
136
  end
293
137
  alias :new :best_grid_for
294
138
 
295
139
  #Renvoie un nouveau Sudoku a partir de la chaine donnee
140
+ # @param [String] str Une chaine Sutxt
141
+ # @return [Grid] Un sudoku rempli avec les données Sutxt
296
142
  def parse str
297
143
  unless str =~ /(\d+):(.+);/
298
144
  raise MalformedSutxtError, "It doesn't seem to be a sutxt line..."
299
145
  end
300
146
 
301
147
  base = $1.to_i
302
- data = $2.split(/\s+/).delete_if(&:empty?).map(&:to_i)
303
- unless data.length == base**4
304
- raise MalformedSutxtError, "Expecting #{base**4} numbers, #{data.length} given"
305
- end
306
-
307
- res = self.best_grid_for base
308
- res.each do |x, y, val|
309
- res.set x, y, data[x+y*res.size]
310
- end
311
-
312
- return res
148
+ return best_grid_for(base).load(str)
313
149
  end
314
150
  end
315
151
  end
@@ -0,0 +1,40 @@
1
+ module Sudoku
2
+ #Permet de generer rapidement un sudoku
3
+ module Generator
4
+ #Remplit la diagonale descendante de 1 a self.size
5
+ # @return [self]
6
+ def make_diagonal
7
+ each{|x,y,val| set x,x,x+1 if x==y}
8
+ self
9
+ end
10
+
11
+ #Remplit tout le sudoku de maniere a ce qu'il soit valide
12
+ # @return [self]
13
+ def make_valid
14
+ pattern = Array.new(size){|i| i+1}.shuffle
15
+ size.times do |y|
16
+ size.times do |x|
17
+ set x, y, pattern[x]
18
+ end
19
+ base.times{|i| pattern.push pattern.shift}
20
+ pattern.push pattern.shift if base - (y%base) == 1
21
+ end
22
+ self
23
+ end
24
+
25
+ #Cree un sudoku valide et laisse des cases vides au hasard
26
+ # @param [Fixnum] seed Le parametre a utiliser pour rand()
27
+ # @return [self]
28
+ def make_valid_incomplete seed=2
29
+ pattern = Array.new(size){|i| i+1}.shuffle
30
+ size.times do |y|
31
+ size.times do |x|
32
+ set x, y, pattern[x] if rand(seed) == seed-1
33
+ end
34
+ base.times{|i| pattern.push pattern.shift}
35
+ pattern.push pattern.shift if base - (y%base) == 1
36
+ end
37
+ self
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ SUDOKU_LIBDIR = File.dirname File.expand_path(__FILE__)
2
+
3
+ require "#{SUDOKU_LIBDIR}/logic"
4
+ require "#{SUDOKU_LIBDIR}/solver"
5
+ require "#{SUDOKU_LIBDIR}/generator"
6
+
7
+ module Sudoku
8
+ #Interface commune des sudokus
9
+ module Grid
10
+ include Logic
11
+ include Generator
12
+ include Solver
13
+ end
14
+ end
@@ -0,0 +1,242 @@
1
+ module Sudoku
2
+ #Logique de base du Sudoku
3
+ module Logic
4
+ #Renvoie le contenu de la ligne x
5
+ # @return (Array)
6
+ def col x
7
+ res = []
8
+ size.times do |y|
9
+ val = get x, y
10
+ res << val if val != 0
11
+ end
12
+ res
13
+ end
14
+
15
+ #Renvoie le contenu de la ligne y
16
+ # @return (Array)
17
+ def row y
18
+ res = []
19
+ size.times do |x|
20
+ val = get x, y
21
+ res << val if val != 0
22
+ end
23
+ res
24
+ end
25
+
26
+ #Renvoie le contenu du carré contenant la case x,y
27
+ # @return (Array)
28
+ def square x, y
29
+ xmin = x - (x%base)
30
+ ymin = y - (y%base)
31
+ res = []
32
+
33
+ base.times do |xx|
34
+ base.times do |yy|
35
+ val = get xx+xmin, yy+ymin
36
+ res << val if val != 0
37
+ end
38
+ end
39
+
40
+ res
41
+ end
42
+
43
+ #Renvoie true si le sudoku ne contient aucune case vide
44
+ def complete?
45
+ each{|x,y,val| return false if val == 0}
46
+ true
47
+ end
48
+
49
+ #Renvoie true si chaque case a au moins 1 possibilité à ce stade
50
+ def completable?
51
+ completed = 0
52
+ each do |x, y, val|
53
+ return false if possibilities(x, y).empty?
54
+ end
55
+ return true
56
+ end
57
+
58
+ #Renvoie toutes les possibilités pour la case x,y
59
+ # @return (Array)
60
+ def possibilities x, y
61
+ res = Array.new(size){|i| i+1}
62
+ xmin = x-x%base
63
+ ymin = y-y%base
64
+
65
+ size.times do |i|
66
+ res.delete get(x,i) if i!=y
67
+ res.delete get(i,y) if i!=x
68
+ xx, yy = xmin+i%base, ymin+i/base
69
+ res.delete get(xx, yy) if xx!=x && yy!=y
70
+ end
71
+
72
+ res
73
+ end
74
+
75
+ #Renvoie true si tous les nombres de la grille sont valides
76
+ def valid_grid?
77
+ each do |x, y, val|
78
+ next if val.zero?
79
+ return false unless valid_cell? x, y, val
80
+ end
81
+
82
+ true
83
+ end
84
+
85
+ #Renvoie true si val est possible en x,y
86
+ def valid_cell? x, y, val
87
+ return true if val.zero?
88
+ val = val.to_i
89
+ xmin = x-x%base
90
+ ymin = y-y%base
91
+
92
+ size.times do |i|
93
+ return false if i!=y && get(x,i)==val
94
+ return false if i!=x && get(i,y)==val
95
+ xx, yy = xmin+i%base, ymin+i/base
96
+ return false if xx!=x && yy!=y && get(xx, yy) == val
97
+ end
98
+
99
+ true
100
+ end
101
+
102
+ # @overload valid?(x, y, val)
103
+ # Vérifie si val est valide en x,y
104
+ # @param [Fixnum] x La colonne de la valeur à vérifier
105
+ # @param [Fixnum] y La ligne de la valeur à vérifier
106
+ # @param [Fixnum] val La valeur à vérifier
107
+ # @return [Boolean] true si la valeur est valide, false sinon
108
+ # @overload valid?
109
+ # Vérifie que toutes les valeurs du Sudoku sont vlaides
110
+ # @return [Boolean] true si toutes les valeurs sont valides, false sinon
111
+ def valid? *args
112
+ if args.empty?
113
+ valid_grid?
114
+ elsif args.length == 3
115
+ valid_cell? *args
116
+ else
117
+ raise ArgumentError, "wrong number of arguments(#{args.length} for 0 or 3)"
118
+ end
119
+ end
120
+
121
+ #Renvoie la base du sudoku
122
+ # @return (Fixnum)
123
+ def base
124
+ if @base
125
+ @base
126
+ else
127
+ @base = (size**0.5).to_i
128
+ end
129
+ end
130
+
131
+ #Renvoie le nombre de cases dans le sudoku
132
+ # @return (Fixnum)
133
+ def length
134
+ if @length
135
+ @length
136
+ else
137
+ @length = size*size
138
+ end
139
+ end
140
+
141
+ # Compte le nombre d'occurences pour les valeurs
142
+ # @return [Hash] L'association valeur => occurences
143
+ # @overload count(*values)
144
+ # Compte les occurences pour les valeurs passées en paramètres
145
+ # @param [*Fixnum] values Les valeurs à compter
146
+ # @overload count
147
+ # Compte les occurences de chaque valeur
148
+ def count *values
149
+ values = Array.new(size){|i| i+1} if values.empty?
150
+
151
+ res = {}
152
+ values.each do |val|
153
+ if val<0 || val>size
154
+ raise ArgumentError, "Impossible value #{val} in a #{size}x#{size} sudoku"
155
+ end
156
+ res[val] = 0
157
+ end
158
+
159
+ each do |x, y, val|
160
+ res[val] += 1 if values.include? val
161
+ end
162
+
163
+ res
164
+ end
165
+
166
+ #Représentation texte humainement lisible
167
+ # @return (String)
168
+ def to_s
169
+ res = ""
170
+ width = size.to_s.length
171
+ zero = ".".center width+1
172
+
173
+ size.times do |y|
174
+ res += "\n" if y>0 && y%base == 0
175
+ size.times do |x|
176
+ res += " " if x>0 && x%base == 0
177
+ val = get x, y
178
+ res += val.zero? ? zero : "#{val.to_s.center width} "
179
+ end
180
+ res += "\n"
181
+ end
182
+ res
183
+ end
184
+
185
+ #Représentation courte (utile dans irb)
186
+ # @return [String]
187
+ def inspect
188
+ "#<#{self.class} #{size}x#{size} [#{get 0, 0}, #{get 0, 1}, ... , #{get size-2, size-1}, #{get size-1, size-1}]>"
189
+ end
190
+
191
+ #Représentation pour l'enregistrement
192
+ # @return (String)
193
+ def to_sutxt
194
+ res = "#{base}:"
195
+ size.times do |y|
196
+ size.times do |x|
197
+ res << " #{get x, y}"
198
+ end
199
+ end
200
+ res+';'
201
+ end
202
+
203
+ #Charge un Sudoku depuis une chaine Sutxt
204
+ # @return (self)
205
+ # @raise [MalformedSutxtError] Chaine Sutxt mal formatee
206
+ # @raise [NotCompatibleError] La chaine Sutxt correspond a un Sudoku de base differente
207
+ def load sutxt_str
208
+ unless sutxt_str =~ /(\d+):(.+);/
209
+ raise MalformedSutxtError, "It doesn't seem to be a sutxt line..."
210
+ end
211
+
212
+ sutxt_base = $1.to_i
213
+ unless sutxt_base == base
214
+ raise NotCompatibleError, "A #{base} sudoku cannot load a #{sutxt_base} Sutxt"
215
+ end
216
+
217
+ data = $2.split(/\s+/).delete_if(&:empty?).map(&:to_i)
218
+ unless data.length == length
219
+ raise MalformedSutxtError, "Expecting #{length} numbers, #{data.length} given"
220
+ end
221
+
222
+ size.times do |y|
223
+ size.times do |x|
224
+ set x, y, data.shift
225
+ end
226
+ end
227
+ self
228
+ end
229
+
230
+ #Charge un sudoku depuis un autre sudoku
231
+ # @param [Grid] l'autre Sudoku
232
+ # @return (self)
233
+ # @raise [NotCompatibleError] L'autre sudoku est de base differente
234
+ def import other
235
+ unless size == other.size
236
+ raise NotCompatibleError, "Cannot import a #{other.base} sudoku in a #{base} sudoku"
237
+ end
238
+ other.each{|x,y,v| set x,y,v}
239
+ self
240
+ end
241
+ end
242
+ end