sudokuhandler 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +149 -0
- data/Rakefile +57 -0
- data/ext/extconf.rb +2 -0
- data/ext/sudoku.c +296 -0
- data/lib/sudoku/version.rb +3 -0
- data/lib/sudoku.rb +315 -0
- data/tests/test_sudoku.rb +182 -0
- metadata +54 -0
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
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
|
+
}
|
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
|