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 +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
|