sudokuhandler 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # sudokuhandler
2
+
3
+ Le but du projet Sudoku est de fournir des objets optimisés pour la gestion de sudokus, permettant d'essayer différents algorithmes de résolution.
4
+ Le Sudoku "classique" (9x9) est optimisé au maximum en temps et en mémoire (41 octets).
5
+
6
+ ## Exemple basique
7
+
8
+ ### Création d'un sudoku de 9x9 en diagonale
9
+
10
+ $ irb -r sudoku
11
+ ruby-1.9.2-p290 :000 > s = Sudoku.new 3
12
+ => #<Sudoku::S3 9x9 0,0, ... , 0, 0>
13
+ ruby-1.9.2-p290 :001 > s = Sudoku[3].new
14
+ => #<Sudoku::S3 9x9 0,0, ... , 0, 0>
15
+ ruby-1.9.2-p290 :002 > s.each{|x,y,v| s.set x, x, x+1 if x == y}
16
+ => #<Sudoku::S3 9x9 1,0, ... , 0, 9>
17
+ ruby-1.9.2-p290 :003 > puts s
18
+
19
+ 1 . . . . . . . .
20
+ . 2 . . . . . . .
21
+ . . 3 . . . . . .
22
+
23
+ . . . 4 . . . . .
24
+ . . . . 5 . . . .
25
+ . . . . . 6 . . .
26
+
27
+ . . . . . . 7 . .
28
+ . . . . . . . 8 .
29
+ . . . . . . . . 9
30
+ => nil
31
+
32
+ ###Exportation du sudoku
33
+
34
+ ruby-1.9.2-p290 :004 > s.to_sutxt
35
+ => "3: 1 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 9;"
36
+ ruby-1.9.2-p290 :005 > s2 = Sudoku.parse s.to_sutxt
37
+ => #<Sudoku::S3 9x9 1,0, ... , 0, 9>
38
+
39
+ ### Un peu de logique
40
+
41
+ ruby-1.9.2-p290 :006 > s.possibilities 0, 1
42
+ => [4, 5, 6, 7, 8, 9]
43
+ ruby-1.9.2-p290 :007 > s.valid? 1, 0, 3
44
+ => false
45
+ ruby-1.9.2-p290 :008 > s.valid? 1, 0, 5
46
+ => true
47
+ ruby-1.9.2-p290 :009 > s.col 2
48
+ => [3]
49
+ ruby-1.9.2-p290 :010 > s.row 3
50
+ => [4]
51
+ ruby-1.9.2-p290 :011 > s.square 3,3
52
+ => [4, 5, 6]
53
+
54
+ ### Generateur
55
+
56
+ class MySudoku < Sudoku::S4_15
57
+ include Sudoku::Generator
58
+ end
59
+
60
+ s = MySudoku.new 5
61
+ s.make_valid
62
+ puts s
63
+
64
+ =begin =>>
65
+ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
66
+ 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5
67
+ 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10
68
+ 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
69
+ 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
70
+
71
+ 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1
72
+ 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6
73
+ 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11
74
+ 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
75
+ 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
76
+
77
+ 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2
78
+ 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7
79
+ 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12
80
+ 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
81
+ 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
82
+
83
+ 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3
84
+ 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8
85
+ 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13
86
+ 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
87
+ 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
88
+
89
+ 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4
90
+ 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9
91
+ 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14
92
+ 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
93
+ 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
94
+ =end
95
+
96
+ s.valid? # => true
97
+ s.complete? # => true
98
+ s.completable? # => true
99
+
100
+ ### Utiliser son propre adapteur
101
+
102
+ #### Un adapteur doit au moins définir les méthodes suivantes:
103
+
104
+ * get(x, y)
105
+ * set(x, y, val)
106
+ * each(){|x, y, val| ... }
107
+ * size()
108
+ * initialize(base)
109
+ * initialize_copy(parent) (automatique si on n'utilise que des objets ruby)
110
+
111
+ #### Exemple:
112
+
113
+ NB: cet exemple ne tient pas compte de la gestion des erreurs
114
+
115
+ class MonSuperAdapteur
116
+ attr_reader :size
117
+
118
+ include Sudoku::Generator #methodes de generation automatique
119
+ include Sudoku::Grid #methodes communes a tous les sudokus
120
+
121
+ def initialize base
122
+ @size = base*base
123
+ @data = Array.new(@size*@size){0}
124
+ end
125
+
126
+ def get x, y
127
+ @data[x+y*size]
128
+ end
129
+
130
+ def set x, y, val
131
+ @data[x+y*size] = val
132
+ end
133
+
134
+ def each
135
+ @data.each_with_index do |val, i|
136
+ yield i%size, i/size, val
137
+ end
138
+ self
139
+ end
140
+ end
141
+
142
+ Sudoku[4..7] = MonSuperAdapteur #Tous les sudokus de base 4 à 7 créés automatiquement
143
+ #seront des MonSuperAdapteur
144
+ Sudoku.new(3).class # => Sudoku::S3
145
+ Sudoku.new(4).class # => MonSuperAdapteur
146
+
147
+ Sudoku[0] = MonSuperAdapteur #Tous les sudokus, par defaut
148
+
149
+ Sudoku.new(3).class # => MonSuperAdapteur
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/extensiontask'
4
+ require 'rake/testtask.rb'
5
+ require './lib/sudoku/version'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = 'sudokuhandler'
9
+ s.version = Sudoku::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.summary = "Ruby Sudoku handler"
12
+ s.description = "Highly optimised Sudoku objects and mixins for Ruby"
13
+ s.author = "Titouan Christophe"
14
+ s.email = 'titouanchristophe@gmail.com'
15
+ s.files = FileList["lib/*.rb", "lib/sudoku/*.rb", "ext/sudoku.c", "Rakefile", "README.md"]
16
+ s.homepage = 'http://github.com/titouanc/rb-sudoku'
17
+ s.extensions << 'ext/extconf.rb'
18
+ s.license = 'Creative Commons BY-NC-SA 3.0'
19
+ s.test_files = FileList["tests/*"]
20
+ end
21
+
22
+ Rake::GemPackageTask.new(spec) do |pkg|
23
+ pkg.need_tar = true
24
+ end
25
+
26
+ Rake::ExtensionTask.new(:sudokucore, spec) do |ext|
27
+ ext.ext_dir = 'ext/'
28
+ ext.lib_dir = 'lib/'
29
+ end
30
+
31
+ Rake::TestTask.new do |t|
32
+ t.libs |= ["tests", "lib"]
33
+ t.test_files = FileList['tests/test*.rb']
34
+ t.verbose = true
35
+ end
36
+
37
+ desc "Build gem & install"
38
+ task :install => FileList["pkg/#{spec.full_name}.gem"] do |t|
39
+ sh "gem install #{t.prerequisites.first}"
40
+ end
41
+
42
+ desc "Uninstall sudoku gem"
43
+ task :uninstall do |t|
44
+ sh "gem uninstall #{spec.name}"
45
+ end
46
+
47
+ desc "Push gem to rubygems.org"
48
+ task :push => FileList["pkg/#{spec.full_name}.gem"] do |t|
49
+ sh "gem push #{t.prerequisites.first}"
50
+ end
51
+
52
+ desc "Compile et ouvre uen console"
53
+ task :console => :compile do |t|
54
+ sh "irb -I ./lib -r sudoku"
55
+ end
56
+
57
+ task :default => [:clobber, :repackage, :compile, :test]
data/ext/extconf.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "mkmf"
2
+ create_makefile "sudokucore"
data/ext/sudoku.c ADDED
@@ -0,0 +1,296 @@
1
+ #include <ruby.h>
2
+ #include <stdlib.h>
3
+
4
+ VALUE module_Sudoku;
5
+ VALUE class_S4_15;
6
+ VALUE class_S3;
7
+
8
+ /* ############################################ */
9
+
10
+ typedef struct {
11
+ unsigned char size;
12
+ unsigned char init;
13
+ unsigned char *ptr;
14
+ } S4_15;
15
+
16
+ static int S4_15_alloc_data(S4_15 *this){
17
+ int len;
18
+
19
+ if (! this->init){
20
+ len = this->size*this->size;
21
+ this->ptr = malloc(len*sizeof(char));
22
+ if (! this->ptr){
23
+ rb_raise(rb_eNoMemError, "Cannot allocate Sudoku data (%d bytes)", len);
24
+ return 0;
25
+ }
26
+ this->init = 1;
27
+ }
28
+
29
+ return 1;
30
+ }
31
+
32
+ static void S4_15_dealloc(S4_15 *obj){
33
+ if (obj->init)
34
+ free(obj->ptr);
35
+ free(obj);
36
+ }
37
+
38
+ static VALUE S4_15_alloc(VALUE klass){
39
+ S4_15 *ptr = malloc(sizeof(S4_15));
40
+ if (! ptr)
41
+ rb_raise(rb_eNoMemError, "Cannot allocate Sudoku 4_15 struct (%lu bytes)", sizeof(S4_15));
42
+ ptr->size = 0;
43
+ ptr->init = 0;
44
+ return Data_Wrap_Struct(klass, 0, S4_15_dealloc, ptr);
45
+ }
46
+
47
+ static VALUE S4_15_init(VALUE self, VALUE init_base){
48
+ S4_15 *this;
49
+ unsigned int *cursor;
50
+ register int i;
51
+ int top, len;
52
+ unsigned char base = NUM2UINT(init_base) & 0xff;
53
+
54
+ Data_Get_Struct(self, S4_15, this);
55
+ this->size = base*base;
56
+ if (base > 15 || base < 2)
57
+ rb_raise(rb_eArgError, "S4_15 can only handle bases between 2 and 15 (%u not allowed)", base);
58
+
59
+ S4_15_alloc_data(this);
60
+
61
+ len = this->size*this->size;
62
+ cursor = (unsigned int *) this->ptr;
63
+ top = len/(sizeof(int)/sizeof(char));
64
+ for (i=0; i<top; i++)
65
+ cursor[i] = 0;
66
+
67
+ for (i=top*(sizeof(int)/sizeof(char)); i<len; i++)
68
+ this->ptr[i] = 0;
69
+
70
+ return self;
71
+ }
72
+
73
+ static VALUE S4_15_initCopy(VALUE copy, VALUE orig){
74
+ S4_15 *this, *parent;
75
+ int len;
76
+
77
+ Data_Get_Struct(copy, S4_15, this);
78
+ Data_Get_Struct(orig, S4_15, parent);
79
+
80
+ this->size = parent->size;
81
+
82
+ S4_15_alloc_data(this);
83
+ memcpy(this->ptr, parent->ptr, parent->size*sizeof(char));
84
+
85
+ return copy;
86
+ }
87
+
88
+ static VALUE S4_15_get(VALUE self, VALUE col, VALUE row){
89
+ S4_15 *this;
90
+ int x = NUM2INT(col);
91
+ int y = NUM2INT(row);
92
+
93
+ Data_Get_Struct(self, S4_15, this);
94
+ if (x<0 || x>=this->size || y<0 || y>=this->size)
95
+ rb_raise(rb_eArgError, "Are you sure thre's a %d,%d cell in a %dx%d Sudoku ?", x, y, this->size, this->size);
96
+
97
+ return INT2NUM(this->ptr[x+y*this->size]);
98
+ }
99
+
100
+ static VALUE S4_15_set(VALUE self, VALUE col, VALUE row, VALUE value){
101
+ S4_15 *this;
102
+ int x = NUM2INT(col);
103
+ int y = NUM2INT(row);
104
+ int v = NUM2INT(value);
105
+
106
+ Data_Get_Struct(self, S4_15, this);
107
+ if (x<0 || x>=this->size || y<0 || y>=this->size || v<0 || v>this->size)
108
+ rb_raise(rb_eArgError, "%d,%d => %d not allowed in a %dx%d Sudoku", x, y, v, this->size, this->size);
109
+
110
+ this->ptr[x+y*this->size] = v;
111
+
112
+ return INT2NUM(v);
113
+ }
114
+
115
+ static VALUE S4_15_each(VALUE self){
116
+ register int i;
117
+ int len;
118
+ S4_15 *this;
119
+ VALUE args;
120
+
121
+ Data_Get_Struct(self, S4_15, this);
122
+ len = this->size*this->size;
123
+ for (i=0; i<len; i++){
124
+ args = rb_ary_new3(3, INT2FIX(i%this->size), INT2FIX(i/this->size), INT2FIX(this->ptr[i]));
125
+ rb_yield(args);
126
+ }
127
+
128
+ return self;
129
+ }
130
+
131
+ static VALUE S4_15_size_internal(VALUE self){
132
+ S4_15 *this;
133
+ Data_Get_Struct(self, S4_15, this);
134
+ return INT2FIX(this->size);
135
+ }
136
+
137
+ static void Init_S4_15(VALUE module){
138
+ class_S4_15 = rb_define_class_under(module, "S4_15", rb_cObject);
139
+ rb_define_alloc_func(class_S4_15, S4_15_alloc);
140
+
141
+ rb_define_const(class_S4_15, "SIZE", INT2FIX(9));
142
+
143
+ rb_define_method(class_S4_15, "initialize", S4_15_init, 1);
144
+ rb_define_method(class_S4_15, "initialize_copy", S4_15_initCopy, 1);
145
+ rb_define_method(class_S4_15, "get", S4_15_get, 2);
146
+ rb_define_method(class_S4_15, "set", S4_15_set, 3);
147
+ rb_define_method(class_S4_15, "each", S4_15_each, 0);
148
+ rb_define_method(class_S4_15, "size_internal", S4_15_size_internal, 0);
149
+ }
150
+
151
+ /* ############################################ */
152
+
153
+ static void S3_dealloc(void *ptr){
154
+ free(ptr);
155
+ }
156
+
157
+ static VALUE S3_alloc(VALUE klass){
158
+ char *ptr = malloc(41*sizeof(char));
159
+ if (! ptr)
160
+ rb_raise(rb_eNoMemError, "Cannot allocate Sudoku data (%d bytes)", 41);
161
+ return Data_Wrap_Struct(klass, 0, S3_dealloc, ptr);
162
+ }
163
+
164
+ static VALUE S3_init(VALUE self, VALUE dummy){
165
+ unsigned int *this;
166
+ unsigned char *last;
167
+ register char i;
168
+
169
+ Data_Get_Struct(self, unsigned int, this);
170
+ for (i=0; i<10; i++)
171
+ this[i] = 0;
172
+ last = (char *) &(this[10]);
173
+ *last =0xf; /* 0x0f => 4 derniers bits pas dans le sudoku */
174
+
175
+ return self;
176
+ }
177
+
178
+ static VALUE S3_initCopy(VALUE copy, VALUE orig){
179
+ unsigned char *this, *parent;
180
+ char i;
181
+
182
+ Data_Get_Struct(copy, unsigned char, this);
183
+ Data_Get_Struct(orig, unsigned char, parent);
184
+ if (this == parent)
185
+ return copy;
186
+
187
+ memcpy(this, parent, 41*sizeof(char));
188
+
189
+ return copy;
190
+ }
191
+
192
+ static VALUE S3_get(VALUE self, VALUE col, VALUE row){
193
+ unsigned char *this, x, y, i;
194
+
195
+ Data_Get_Struct(self, unsigned char, this);
196
+ x = NUM2UINT(col)&0xff;
197
+ y = NUM2UINT(row)&0xff;
198
+
199
+ if (x>=9 || y>=9)
200
+ rb_raise(rb_eArgError, "Are you sure thre's a %d,%d cell in a 9x9 Sudoku ?", x, y);
201
+
202
+ i = x + y*9;
203
+ if (i%2 == 0)
204
+ return INT2FIX((this[i/2] >> 4) & 0x0f);
205
+ else
206
+ return INT2FIX(this[i/2] & 0x0f);
207
+ }
208
+
209
+ static VALUE S3_set(VALUE self, VALUE col, VALUE row, VALUE value){
210
+ unsigned char *this, x, y, i, val;
211
+
212
+ Data_Get_Struct(self, unsigned char, this);
213
+ x = NUM2UINT(col)&0x0f;
214
+ y = NUM2UINT(row)&0x0f;
215
+ val = NUM2UINT(value)&0x0f;
216
+
217
+
218
+ if (x>=9 || y>=9 || val > 9)
219
+ rb_raise(rb_eArgError, "%d,%d => %d not allowed in a 9x9 Sudoku", x, y, val);
220
+
221
+ i = x + y*9;
222
+ if (i%2 == 0)
223
+ this[i/2] = (this[i/2]&0x0f) | (val<<4);
224
+ else
225
+ this[i/2] = (this[i/2]&0xf0) | val;
226
+
227
+ return INT2FIX(val);
228
+ }
229
+
230
+ static VALUE S3_each(VALUE self){
231
+ char x=0, y=0, i, val;
232
+ unsigned char *this;
233
+ VALUE args;
234
+
235
+ Data_Get_Struct(self, unsigned char, this);
236
+ for (i=0; i<81; i++){
237
+ val = (i%2 == 0) ? ((this[i/2] >> 4) & 0x0f) : (this[i/2] & 0x0f);
238
+ args = rb_ary_new3(3, INT2FIX(x), INT2FIX(y), INT2FIX(val));
239
+ rb_yield(args);
240
+ if (x<8)
241
+ x++;
242
+ else {
243
+ y++;
244
+ x = 0;
245
+ }
246
+ }
247
+
248
+ return self;
249
+ }
250
+
251
+ static VALUE S3_isComplete(VALUE self){
252
+ char i, val;
253
+ unsigned char *this;
254
+
255
+ Data_Get_Struct(self, unsigned char, this);
256
+ for (i=0; i<41; i++)
257
+ if ((this[i]&0xf0) == 0 || (this[i]&0x0f) == 0)
258
+ return Qfalse;
259
+
260
+ return Qtrue;
261
+ }
262
+
263
+ static VALUE S3_dump(VALUE self){
264
+ char string[83] = {'\0'};
265
+ int i;
266
+ unsigned char *data;
267
+
268
+ Data_Get_Struct(self, unsigned char, data);
269
+ for (i=0; i<41; i++)
270
+ sprintf(&(string[2*i]), "%X", data[i]);
271
+
272
+ return rb_str_new(string, 82);
273
+ }
274
+
275
+ static void Init_S3(VALUE module){
276
+ class_S3 = rb_define_class_under(module, "S3", rb_cObject);
277
+ rb_define_alloc_func(class_S3, S3_alloc);
278
+
279
+ rb_define_const(class_S3, "SIZE", INT2FIX(9));
280
+
281
+ rb_define_method(class_S3, "__initialize", S3_init, 1);
282
+ rb_define_method(class_S3, "initialize_copy", S3_initCopy, 1);
283
+ rb_define_method(class_S3, "get", S3_get, 2);
284
+ rb_define_method(class_S3, "set", S3_set, 3);
285
+ rb_define_method(class_S3, "each", S3_each, 0);
286
+ rb_define_method(class_S3, "complete?", S3_isComplete, 0);
287
+ rb_define_method(class_S3, "dump", S3_dump, 0);
288
+ }
289
+
290
+ /* ############################################ */
291
+
292
+ void Init_sudokucore(){
293
+ module_Sudoku = rb_define_module("Sudoku");
294
+ Init_S3(module_Sudoku);
295
+ Init_S4_15(module_Sudoku);
296
+ }
@@ -0,0 +1,3 @@
1
+ module Sudoku
2
+ VERSION = "0.1.2"
3
+ end
data/lib/sudoku.rb ADDED
@@ -0,0 +1,315 @@
1
+ #
2
+ # Sudoku handler
3
+ #
4
+
5
+ require "sudokucore"
6
+ require "sudoku/version"
7
+
8
+ module Sudoku
9
+ #Exception lancée lors d'erreurs de lecture d'une chaine Sutxt
10
+ class MalformedSutxtError < Exception; end
11
+
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
193
+
194
+ #Sudoku 9x9 (base 3) très rapide
195
+ class S3
196
+ include Grid
197
+ private :__initialize
198
+
199
+ #Argument ignoré, laissé pour des raisons d'uniformité
200
+ def initialize base=3
201
+ __initialize nil
202
+ end
203
+
204
+ def size
205
+ SIZE
206
+ end
207
+ end
208
+
209
+ #Sudokus de base 4 à 15 rapide
210
+ class S4_15
211
+ include Grid
212
+ private :size_internal
213
+
214
+ def size
215
+ if @size
216
+ @size
217
+ else
218
+ @size = size_internal
219
+ end
220
+ end
221
+ end
222
+
223
+ #Sudoku générique
224
+ class Sn
225
+ include Grid
226
+ attr_reader :size, :base, :length
227
+
228
+ def initialize base=3
229
+ @base = base.to_i
230
+ @size = @base*@base
231
+ @length = @size*@size
232
+ @grid = Array.new(@size) do |i|
233
+ Array.new(@size){|j| 0}
234
+ end
235
+ end
236
+
237
+ def set x, y, val
238
+ if x<0 || x>=size || y<0 || y>=size || val<0 || val > size
239
+ raise ArgumentError, "#{x},#{y} => #{val} is impossible in a #{size}x#{size} sudoku"
240
+ end
241
+ @grid[x][y] = val
242
+ end
243
+
244
+ def get x, y
245
+ if x<0 || x>=size || y<0 || y>=size
246
+ raise ArgumentError, "Is there a #{x},#{y} cell in a #{size}x#{size} sudoku ?"
247
+ end
248
+ @grid[x][y]
249
+ end
250
+
251
+ def each
252
+ @size.times do |y|
253
+ @size.times do |x|
254
+ yield x, y, @grid[x][y]
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ ADAPTERS = [
261
+ [3, S3],
262
+ [4..15, S4_15],
263
+ [0, Sn]
264
+ ]
265
+
266
+ class << self
267
+ #Renvoie la classe de la meilleure implémentation pour un sudoku de base n
268
+ def [] n
269
+ n = n.to_i
270
+ ADAPTERS.each do |ad|
271
+ zone = ad[0]
272
+ adapter = ad[1]
273
+
274
+ return adapter if zone == 0
275
+
276
+ case n
277
+ when zone
278
+ return adapter
279
+ end
280
+ end
281
+ end
282
+
283
+ #Ajoute un element
284
+ def []= zone, adapter
285
+ ADAPTERS.unshift [zone, adapter]
286
+ end
287
+
288
+ #Renvoie une instance de la meilleure implémentation pour un sudoku de base n
289
+ def best_grid_for n=3
290
+ n = n.to_i
291
+ self[n].new n
292
+ end
293
+ alias :new :best_grid_for
294
+
295
+ #Renvoie un nouveau Sudoku a partir de la chaine donnee
296
+ def parse str
297
+ unless str =~ /(\d+):(.+);/
298
+ raise MalformedSutxtError, "It doesn't seem to be a sutxt line..."
299
+ end
300
+
301
+ 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
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,182 @@
1
+ require 'test/unit'
2
+ require 'sudoku'
3
+
4
+ module GridTest
5
+ def create
6
+ klass.new base
7
+ end
8
+
9
+ def diagonal
10
+ s = create
11
+ s.each{|x, y, val| s.set x,x,x+1 if x==y}
12
+ s
13
+ end
14
+
15
+ def test_diagonal
16
+ s = diagonal
17
+ (s.size-1).times{|i| assert_equal 1, s.get(i+1, i+1)-s.get(i, i)}
18
+ end
19
+
20
+ def test_limits
21
+ s = create
22
+ assert_raise(ArgumentError){s.get s.size, 1}
23
+ assert_raise(ArgumentError){s.get 1, s.size}
24
+ assert_raise(ArgumentError){s.set s.size, 1, 1}
25
+ assert_raise(ArgumentError){s.set 1, s.size, 1}
26
+ assert_raise(ArgumentError){s.set 1, 1, s.size+1}
27
+ end
28
+
29
+ def test_getset
30
+ s = create
31
+ s.set 0, 0, 1
32
+ assert_equal 1, s.get(0, 0)
33
+ end
34
+
35
+ def test_clone
36
+ s1 = create
37
+ s1.set 0, 0, 1
38
+
39
+ s2 = s1.clone
40
+
41
+ assert_equal 1, s2.get(0, 0)
42
+ end
43
+
44
+ def test_each
45
+ s = diagonal
46
+ s.size.times{|x| assert_equal x+1, s.get(x, x)}
47
+ end
48
+
49
+ def test_size
50
+ s = create
51
+ assert_equal base , s.base
52
+ assert_equal base**2, s.size
53
+ assert_equal base**4, s.length
54
+ end
55
+
56
+ def test_col_row_square
57
+ s = create
58
+ 2.times do |x|
59
+ 2.times do |y|
60
+ s.set x, y, x+y*2+1
61
+ end
62
+ end
63
+ assert_equal [1, 2, 3, 4], s.square(0, 0).sort
64
+ assert_equal [1, 2], s.row(0).sort
65
+ assert_equal [1, 3], s.col(0).sort
66
+ end
67
+
68
+ def test_possibilities
69
+ s = create
70
+ s.set 0,0,1
71
+ s.set 0,1,2
72
+
73
+ assert s.possibilities(1, 1).include?(3)
74
+ assert s.possibilities(1, 1).include?(4)
75
+
76
+ assert s.valid?(1,1,3)
77
+ assert s.valid?(1,1,4)
78
+
79
+ refute s.valid?(1,1,0)
80
+ refute s.valid?(1,1,1)
81
+ assert s.valid?(0,0,1)
82
+ end
83
+
84
+ def test_sutxt
85
+ sutxt = "#{base}:"
86
+ size = base*base
87
+ size.times do |y|
88
+ size.times {|x| sutxt << " #{x+1}"}
89
+ end
90
+ sutxt += ';'
91
+
92
+ s = Sudoku.parse(sutxt)
93
+ assert_equal sutxt, s.to_sutxt
94
+ assert_equal 1, s.get(0,0)
95
+ assert_equal 1, s.get(0,1)
96
+ assert_equal 2, s.get(1,0)
97
+ end
98
+
99
+ def test_complete
100
+ s = create
101
+ refute s.complete?
102
+ assert s.completable?
103
+ assert s.valid?
104
+
105
+ s.each{|x,y,val| s.set x, y, 1}
106
+ assert s.complete?
107
+ assert s.completable?
108
+ refute s.valid?
109
+ end
110
+ end
111
+
112
+ module GeneratorTest
113
+ def generator
114
+ Class.new klass do
115
+ include Sudoku::Generator
116
+ end
117
+ end
118
+
119
+ def test_diagonal
120
+ s = generator.new(base).make_diagonal
121
+ s.size.times{|x| assert_equal x+1, s.get(x, x)}
122
+ end
123
+
124
+ def test_valid
125
+ s = generator.new(base).make_valid
126
+ assert s.valid?
127
+ assert s.completable?
128
+ assert s.complete?
129
+ end
130
+ end
131
+
132
+ class S3Test < Test::Unit::TestCase
133
+ include GridTest
134
+ include GeneratorTest
135
+
136
+ def base; 3; end
137
+ def klass; Sudoku::S3; end
138
+ def create; klass.new; end
139
+
140
+ def test_square
141
+ s = diagonal
142
+ 9.times do |i|
143
+ assert_equal [1+3*(i/3), 2+3*(i/3), 3+3*(i/3)], s.square(i,i)
144
+ end
145
+ end
146
+
147
+ def test_possibilities
148
+ super
149
+ s = diagonal
150
+ assert_equal [1, 4, 5, 6, 7, 8, 9], s.possibilities(0, 0).sort
151
+ end
152
+ end
153
+
154
+ class S4_15Test < Test::Unit::TestCase
155
+ include GridTest
156
+ include GeneratorTest
157
+
158
+ def base; 4; end
159
+ def klass; Sudoku::S4_15; end
160
+
161
+ def test_toolarge
162
+ assert_raise(ArgumentError){klass.new 16}
163
+ end
164
+ end
165
+
166
+ class SnTest < Test::Unit::TestCase
167
+ include GridTest
168
+ include GeneratorTest
169
+
170
+ def base; 2; end
171
+ def klass; Sudoku::Sn; end
172
+ end
173
+
174
+ class SudokuTest < Test::Unit::TestCase
175
+ def test_autoclass
176
+ [S3Test, S4_15Test, SnTest].each do |g|
177
+ grid = g.new nil
178
+ assert_equal grid.klass, Sudoku[grid.base]
179
+ end
180
+ end
181
+ end
182
+
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sudokuhandler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Titouan Christophe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-16 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Highly optimised Sudoku objects and mixins for Ruby
15
+ email: titouanchristophe@gmail.com
16
+ executables: []
17
+ extensions:
18
+ - ext/extconf.rb
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/sudoku.rb
22
+ - lib/sudoku/version.rb
23
+ - ext/sudoku.c
24
+ - Rakefile
25
+ - README.md
26
+ - tests/test_sudoku.rb
27
+ - ext/extconf.rb
28
+ homepage: http://github.com/titouanc/rb-sudoku
29
+ licenses:
30
+ - Creative Commons BY-NC-SA 3.0
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.15
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Ruby Sudoku handler
53
+ test_files:
54
+ - tests/test_sudoku.rb