sudokuhandler 0.1.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.
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