sudokuhandler 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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