Compare commits
15 Commits
4a8644181a
...
sooolve
| Author | SHA1 | Date | |
|---|---|---|---|
| 947a875694 | |||
| 1246331125 | |||
| b68fe81914 | |||
| 9c72891048 | |||
|
|
627c49b961 | ||
|
|
b4157167b5 | ||
|
|
1419898955 | ||
|
|
dd3be67be0 | ||
|
|
c6a32b8d07 | ||
|
|
a8439df736 | ||
|
|
27032d264d | ||
|
|
8afd51b353 | ||
| 2460b7539e | |||
| 0f5020b0b4 | |||
| ac4f859fd8 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
run: ./gradlew assemble
|
||||
|
||||
- name: Test
|
||||
run: ./gradlew test
|
||||
|
||||
@@ -18,9 +18,8 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
// Use JUnit Jupiter for testing.
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'
|
||||
|
||||
implementation 'com.google.guava:guava:31.1-jre'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
|
||||
implementation 'org.json:json:20250107'
|
||||
|
||||
|
||||
551
app/save/save-1.json
Normal file
551
app/save/save-1.json
Normal file
@@ -0,0 +1,551 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"immutable": true,
|
||||
"symbolIndex": 2
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"immutable": true,
|
||||
"symbolIndex": 1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"immutable": true,
|
||||
"symbolIndex": 8
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"immutable": true,
|
||||
"symbolIndex": 2
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"immutable": true,
|
||||
"symbolIndex": 0
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"immutable": true,
|
||||
"symbolIndex": 5
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"immutable": true,
|
||||
"symbolIndex": 8
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"immutable": true,
|
||||
"symbolIndex": 1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"immutable": true,
|
||||
"symbolIndex": 5
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"immutable": true,
|
||||
"symbolIndex": 6
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"immutable": true,
|
||||
"symbolIndex": 0
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"immutable": true,
|
||||
"symbolIndex": 3
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"immutable": true,
|
||||
"symbolIndex": 7
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"immutable": true,
|
||||
"symbolIndex": 6
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"immutable": true,
|
||||
"symbolIndex": 8
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"immutable": true,
|
||||
"symbolIndex": 4
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"immutable": true,
|
||||
"symbolIndex": 6
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"immutable": true,
|
||||
"symbolIndex": 7
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"immutable": true,
|
||||
"symbolIndex": 0
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"immutable": true,
|
||||
"symbolIndex": 3
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
}
|
||||
],
|
||||
"blocks": [
|
||||
{"cellIDs": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
18,
|
||||
19,
|
||||
20
|
||||
]},
|
||||
{"cellIDs": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
21,
|
||||
22,
|
||||
23
|
||||
]},
|
||||
{"cellIDs": [
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]},
|
||||
{"cellIDs": [
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
45,
|
||||
46,
|
||||
47
|
||||
]},
|
||||
{"cellIDs": [
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
48,
|
||||
49,
|
||||
50
|
||||
]},
|
||||
{"cellIDs": [
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
51,
|
||||
52,
|
||||
53
|
||||
]},
|
||||
{"cellIDs": [
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
72,
|
||||
73,
|
||||
74
|
||||
]},
|
||||
{"cellIDs": [
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
75,
|
||||
76,
|
||||
77
|
||||
]},
|
||||
{"cellIDs": [
|
||||
60,
|
||||
61,
|
||||
62,
|
||||
69,
|
||||
70,
|
||||
71,
|
||||
78,
|
||||
79,
|
||||
80
|
||||
]}
|
||||
],
|
||||
"multidoku": [{
|
||||
"blockWidth": 3,
|
||||
"cells": [
|
||||
0,
|
||||
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,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
60,
|
||||
61,
|
||||
62,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
70,
|
||||
71,
|
||||
72,
|
||||
73,
|
||||
74,
|
||||
75,
|
||||
76,
|
||||
77,
|
||||
78,
|
||||
79,
|
||||
80
|
||||
],
|
||||
"blocks": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"constraints": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
]
|
||||
}]
|
||||
}
|
||||
554
app/save/save.json
Normal file
554
app/save/save.json
Normal file
@@ -0,0 +1,554 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"blockID": 0,
|
||||
"immutable": true,
|
||||
"symbolIndex": 5
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"immutable": true,
|
||||
"symbolIndex": 6
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"immutable": true,
|
||||
"symbolIndex": 7
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"immutable": true,
|
||||
"symbolIndex": 1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"immutable": true,
|
||||
"symbolIndex": 2
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"immutable": true,
|
||||
"symbolIndex": 4
|
||||
},
|
||||
{
|
||||
"blockID": 0,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 1,
|
||||
"immutable": true,
|
||||
"symbolIndex": 8
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"immutable": true,
|
||||
"symbolIndex": 7
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 2,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"immutable": true,
|
||||
"symbolIndex": 6
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"immutable": true,
|
||||
"symbolIndex": 8
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"immutable": true,
|
||||
"symbolIndex": 1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"immutable": true,
|
||||
"symbolIndex": 2
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"immutable": true,
|
||||
"symbolIndex": 6
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"immutable": true,
|
||||
"symbolIndex": 8
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 3,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"immutable": true,
|
||||
"symbolIndex": 0
|
||||
},
|
||||
{
|
||||
"blockID": 4,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 5,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"immutable": true,
|
||||
"symbolIndex": 5
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"immutable": true,
|
||||
"symbolIndex": 1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"immutable": true,
|
||||
"symbolIndex": 7
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"immutable": true,
|
||||
"symbolIndex": 8
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"immutable": true,
|
||||
"symbolIndex": 6
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 6,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"immutable": true,
|
||||
"symbolIndex": 4
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"immutable": true,
|
||||
"symbolIndex": 5
|
||||
},
|
||||
{
|
||||
"blockID": 7,
|
||||
"immutable": true,
|
||||
"symbolIndex": 0
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
},
|
||||
{
|
||||
"blockID": 8,
|
||||
"symbolIndex": -1
|
||||
}
|
||||
],
|
||||
"blocks": [
|
||||
{"cellIDs": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
18,
|
||||
19,
|
||||
20
|
||||
]},
|
||||
{"cellIDs": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
21,
|
||||
22,
|
||||
23
|
||||
]},
|
||||
{"cellIDs": [
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]},
|
||||
{"cellIDs": [
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
45,
|
||||
46,
|
||||
47
|
||||
]},
|
||||
{"cellIDs": [
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
48,
|
||||
49,
|
||||
50
|
||||
]},
|
||||
{"cellIDs": [
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
51,
|
||||
52,
|
||||
53
|
||||
]},
|
||||
{"cellIDs": [
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
72,
|
||||
73,
|
||||
74
|
||||
]},
|
||||
{"cellIDs": [
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
75,
|
||||
76,
|
||||
77
|
||||
]},
|
||||
{"cellIDs": [
|
||||
60,
|
||||
61,
|
||||
62,
|
||||
69,
|
||||
70,
|
||||
71,
|
||||
78,
|
||||
79,
|
||||
80
|
||||
]}
|
||||
],
|
||||
"multidoku": [{
|
||||
"blockWidth": 3,
|
||||
"cells": [
|
||||
0,
|
||||
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,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
60,
|
||||
61,
|
||||
62,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
70,
|
||||
71,
|
||||
72,
|
||||
73,
|
||||
74,
|
||||
75,
|
||||
76,
|
||||
77,
|
||||
78,
|
||||
79,
|
||||
80
|
||||
],
|
||||
"blocks": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
],
|
||||
"constraints": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
]
|
||||
}]
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import sudoku.constraint.IConstraint;
|
||||
import sudoku.structure.Difficulty;
|
||||
import sudoku.structure.MultiDoku;
|
||||
import sudoku.structure.SudokuFactory;
|
||||
import sudoku.structure.SudokuFiller;
|
||||
|
||||
public class SudokuSelector {
|
||||
|
||||
@@ -86,7 +87,8 @@ public class SudokuSelector {
|
||||
this.genThread = new Thread(() -> {
|
||||
try {
|
||||
if (!empty) {
|
||||
SudokuFactory.fillDoku(doku, Difficulty.values()[difficulty.get()]);
|
||||
SudokuFiller.fillDoku(doku, Difficulty.values()[difficulty.get()]);
|
||||
// SudokuFactory.fillDoku(doku, Difficulty.values()[difficulty.get()]);
|
||||
}
|
||||
this.onSelect.emit(this.doku);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -9,11 +9,10 @@ import java.util.Random;
|
||||
import sudoku.structure.Cell;
|
||||
import sudoku.structure.MultiDoku;
|
||||
|
||||
//TODO
|
||||
public class HintHelper {
|
||||
|
||||
public static record Hint(Cell cell, int newValue) {
|
||||
|
||||
}
|
||||
public static record Hint(Cell cell, int newValue) {}
|
||||
|
||||
public static Hint getHint(MultiDoku doku, Solver solver) {
|
||||
doku.getStateManager().pushState();
|
||||
@@ -36,7 +35,7 @@ public class HintHelper {
|
||||
if (newValue == Cell.NOSYMBOL)
|
||||
return new Hint(cell, newValue);
|
||||
// we have to change the cell value
|
||||
if (oldValue != Cell.NOSYMBOL && newValue != oldValue)
|
||||
if (oldValue != Cell.NOSYMBOL)
|
||||
return new Hint(cell, newValue);
|
||||
|
||||
// there is a valid move
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sudoku.solver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
@@ -8,18 +9,28 @@ import sudoku.structure.MultiDoku;
|
||||
|
||||
public class MixedSolver implements Solver {
|
||||
|
||||
private Cell findCellToBacktrack(MultiDoku doku, int maxPossibilities) {
|
||||
private List<Cell> findCellToBacktrack(MultiDoku doku, int maxPossibilities) {
|
||||
int symbolCount = doku.getSubGrid(0).getSize();
|
||||
if (maxPossibilities > symbolCount)
|
||||
return null;
|
||||
|
||||
List<Cell> backtrackCells = new ArrayList<>();
|
||||
|
||||
for (Cell cell : doku.getCells()) {
|
||||
if (!cell.isMutable() || !cell.isEmpty())
|
||||
continue;
|
||||
|
||||
List<Integer> possibleSymbols = cell.getPossibleSymbols();
|
||||
if (possibleSymbols.size() == maxPossibilities) {
|
||||
return cell;
|
||||
}
|
||||
backtrackCells.add(cell);
|
||||
}
|
||||
|
||||
// grille non résolvable
|
||||
if (possibleSymbols.size() == 0)
|
||||
return null;
|
||||
}
|
||||
return backtrackCells;
|
||||
}
|
||||
|
||||
/**
|
||||
* Résout le MultiDoku passé en paramètre, avec règles de déduction et
|
||||
@@ -34,8 +45,12 @@ public class MixedSolver implements Solver {
|
||||
throw new CancellationException("User wants to stop the solver");
|
||||
}
|
||||
|
||||
while (!doku.isSolved()) {
|
||||
boolean filledCell = false;
|
||||
if (doku.isSolved())
|
||||
return true;
|
||||
|
||||
int symbolCount = doku.getSubGrid(0).getSize();
|
||||
|
||||
// on remplit les cases par déduction
|
||||
for (Cell cell : doku.getCells()) {
|
||||
if (!cell.isMutable() || !cell.isEmpty())
|
||||
continue;
|
||||
@@ -44,31 +59,41 @@ public class MixedSolver implements Solver {
|
||||
if (possibleSymbols.size() == 1) {
|
||||
cell.setSymbolIndex(possibleSymbols.getFirst());
|
||||
addStep(cell, steps);
|
||||
filledCell = true;
|
||||
if (solve(doku, steps))
|
||||
return true;
|
||||
cell.setSymbolIndex(Cell.NOSYMBOL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// on ne peut plus remplir de cases, on tente de backtrack
|
||||
if (!filledCell) {
|
||||
int maxPossibilities = 2;
|
||||
Cell backtrackCell = null;
|
||||
while (backtrackCell == null) {
|
||||
backtrackCell = findCellToBacktrack(doku, maxPossibilities);
|
||||
for (int maxPossibilities = 2; maxPossibilities <= symbolCount; maxPossibilities++) {
|
||||
List<Cell> backtrackCells = new ArrayList<>();
|
||||
while (backtrackCells.isEmpty()) {
|
||||
backtrackCells = findCellToBacktrack(doku, maxPossibilities);
|
||||
// grille non résolvable
|
||||
if (backtrackCells == null) {
|
||||
return false;
|
||||
}
|
||||
maxPossibilities++;
|
||||
}
|
||||
// on fait du backtracking
|
||||
for (Cell backtrackCell : backtrackCells) {
|
||||
List<Integer> possibilities = backtrackCell.getPossibleSymbols();
|
||||
|
||||
// System.out.println("Backtraing on " + backtrackCell);
|
||||
|
||||
// on tente de placer chacun des symboles
|
||||
for (int symbol : possibilities) {
|
||||
doku.getStateManager().pushState();
|
||||
backtrackCell.setSymbolIndex(symbol);
|
||||
if (solve(doku, steps))
|
||||
if (solve(doku, steps)) {
|
||||
return true;
|
||||
doku.getStateManager().popState();
|
||||
}
|
||||
}
|
||||
|
||||
backtrackCell.setSymbolIndex(Cell.NOSYMBOL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return doku.isSolved();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,28 +4,41 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class qui représente les block de chaque sudoku,
|
||||
* Un block étant un ensemble de cellule avec une contrainte de block qui lui
|
||||
* ait associé
|
||||
* Class qui représente les Block de chaque Sudoku,
|
||||
* Un Block étant un ensemble de cellule avec une contrainte de block qui lui
|
||||
* ait associé.
|
||||
*/
|
||||
public class Block {
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="ATTRIBUTS">
|
||||
|
||||
/**
|
||||
* L'ensemble des cellules du block
|
||||
* L'ensemble des cellules du block.
|
||||
*/
|
||||
private final List<Cell> cells;
|
||||
|
||||
/**
|
||||
* List de sudoku qui contiennent le block
|
||||
* Pour un acces plus rapide aux sudokus
|
||||
* Liste de Sudoku qui contiennent le block
|
||||
* Pour un accès plus rapide aux sudokus.
|
||||
*/
|
||||
private final List<Sudoku> sudokus;
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="METHODES">
|
||||
|
||||
/**
|
||||
* Crée un Block avec une liste de Cell donnée.
|
||||
* @param cells List<Cell>
|
||||
*/
|
||||
public Block(List<Cell> cells) {
|
||||
this.cells = cells;
|
||||
this.sudokus = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un Block avec une liste de Cell vide.
|
||||
*/
|
||||
public Block() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
@@ -33,13 +46,16 @@ public class Block {
|
||||
public List<Cell> getCells() {
|
||||
return cells;
|
||||
}
|
||||
public List<Sudoku> getSudokus() {
|
||||
return sudokus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une Cell au Block
|
||||
*
|
||||
* @param newCell Cell, à ajouter
|
||||
*/
|
||||
void addCell(Cell newCell) {
|
||||
public void addCell(Cell newCell) {
|
||||
this.cells.add(newCell);
|
||||
}
|
||||
|
||||
@@ -57,16 +73,5 @@ public class Block {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsCell(Cell cell) {
|
||||
for (Cell cellTmp : this.cells) {
|
||||
if (cellTmp.equals(cell)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<Sudoku> getSudokus() {
|
||||
return sudokus;
|
||||
}
|
||||
// </editor-fold>
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.util.List;
|
||||
*/
|
||||
public class Cell {
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="ATTRIBUTS">
|
||||
|
||||
/**
|
||||
* Constante de valeur d'index de symbole quand il n'y en a pas,
|
||||
* soit que la Cell est vide.
|
||||
@@ -29,6 +31,10 @@ public class Cell {
|
||||
*/
|
||||
private boolean isMutable = true;
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="METHODES">
|
||||
|
||||
public Cell() {
|
||||
this(Cell.NOSYMBOL);
|
||||
}
|
||||
@@ -46,6 +52,22 @@ public class Cell {
|
||||
return this.symbolIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le Block qui la contient.
|
||||
* @return Block.
|
||||
*/
|
||||
public Block getBlock() {
|
||||
return this.blockContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie si la Cell est modifiable
|
||||
* @return boolean, true si elle est modifiable ou false sinon.
|
||||
*/
|
||||
public boolean isMutable() {
|
||||
return this.isMutable;
|
||||
}
|
||||
|
||||
public void setSymbolIndex(int symbolIndex) {
|
||||
this.symbolIndex = symbolIndex;
|
||||
}
|
||||
@@ -57,22 +79,16 @@ public class Cell {
|
||||
this.isMutable = false;
|
||||
}
|
||||
|
||||
public Block getBlock() {
|
||||
return this.blockContainer;
|
||||
}
|
||||
|
||||
public void setBlock(Block block) {
|
||||
this.blockContainer = block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the current symbolIndex and returns it
|
||||
* @return integer symbolIndex cleared
|
||||
* Vide la Cell.
|
||||
*/
|
||||
public int clearCurrentSymbol() {
|
||||
int i = this.symbolIndex;
|
||||
public void clearCurrentSymbol() {
|
||||
setSymbolIndex(NOSYMBOL);
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,14 +99,6 @@ public class Cell {
|
||||
return this.symbolIndex == Cell.NOSYMBOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie si la Cell est modifiable
|
||||
* @return boolean, true si elle est modifiable ou false sinon.
|
||||
*/
|
||||
public boolean isMutable() {
|
||||
return this.isMutable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide la Cell, en renvoie l'ancien index du symbole qui était dedans.
|
||||
* @return int, index du symbole anciennement contenue dans la Cell.
|
||||
@@ -101,6 +109,11 @@ public class Cell {
|
||||
return oldSymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si la Cell peut prendre ce symbole par rapport aux contraintes de ses Sudokus.
|
||||
* @param value int, index du symbole
|
||||
* @return boolean, true si elle peut, false sinon.
|
||||
*/
|
||||
public boolean canHaveValue(int value) {
|
||||
for (Sudoku s :getBlock().getSudokus()) {
|
||||
int cellIndex = s.getCells().indexOf(this);
|
||||
@@ -116,6 +129,10 @@ public class Cell {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les symboles que peut prendre cette Cell par rapport aux contraintes de ses Sudokus.
|
||||
* @return List<Integer>, la liste des symboles possibles.
|
||||
*/
|
||||
public List<Integer> getPossibleSymbols() {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (int i = 0; i < getBlock().getSudokus().get(0).getSize(); i++) {
|
||||
@@ -125,6 +142,11 @@ public class Cell {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Essaye de placer la valeur et renvoie false si ce n'est pas possible.
|
||||
* @param newValue int, valeur à placer.
|
||||
* @return boolean, true si la Cell à pris la valeur newValue, false sinon.
|
||||
*/
|
||||
public boolean trySetValue(int newValue) {
|
||||
if (!isMutable())
|
||||
return false;
|
||||
@@ -133,4 +155,6 @@ public class Cell {
|
||||
setSymbolIndex(newValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package sudoku.structure;
|
||||
|
||||
/**
|
||||
* Représente les coordonnées d'une Cell
|
||||
* Représente les coordonnées d'une Cell.
|
||||
*/
|
||||
public class Coordinate {
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="ATTRIBUTS">
|
||||
|
||||
/**
|
||||
* L'abscisse de la Cell.
|
||||
*/
|
||||
@@ -14,6 +16,10 @@ public class Coordinate {
|
||||
*/
|
||||
private int y;
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="METHODES">
|
||||
|
||||
public Coordinate(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
@@ -35,16 +41,23 @@ public class Coordinate {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int calculateIndex(int size) {
|
||||
return this.y * size + this.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute la Coordiante donnée à celle-ci.
|
||||
* @param other Coordiante, à ajouter.
|
||||
* @return Coordinate, le résultat de l'addition.
|
||||
*/
|
||||
public Coordinate add(Coordinate other) {
|
||||
return new Coordinate(this.x + other.x, this.y + other.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Soustrait la Coordiante donnée à celle-ci.
|
||||
* @param other Coordiante, à soustraire.
|
||||
* @return Coordinate, le résultat de la soustraction.
|
||||
*/
|
||||
public Coordinate sub(Coordinate other) {
|
||||
return new Coordinate(this.x - other.x, this.y - other.y);
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
}
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
package sudoku.structure;
|
||||
|
||||
//TODO: melvyn va passer par là
|
||||
/**
|
||||
* Les difficultés d'un Sudoku,
|
||||
* représente le nombre de cases à enlever, en proportion, à un Sudoku.
|
||||
* Par exemple, avec 0.1 de factor, on enlevera 10% des cases.
|
||||
*/
|
||||
public enum Difficulty {
|
||||
|
||||
VeryEasy("Très facile", 0.1), Easy("Facile", 0.25), Medium("Moyen", 0.5), Hard("Difficile", 0.75), VeryHard("Impossible", 0.78);
|
||||
VeryEasy("Très facile", 0.1),
|
||||
Easy("Facile", 0.25),
|
||||
Medium("Moyen", 0.5),
|
||||
Hard("Difficile", 0.75),
|
||||
VeryHard("Impossible", 0.78);
|
||||
|
||||
/**
|
||||
* Le pourcentage de Cell à enlever.
|
||||
*/
|
||||
final double factor;
|
||||
/**
|
||||
* Le nom de la difficulté.
|
||||
*/
|
||||
final String displayName;
|
||||
|
||||
private Difficulty(String displayName, double factor) {
|
||||
Difficulty(String displayName, double factor) {
|
||||
this.factor = factor;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public double getFactor() {
|
||||
return factor;
|
||||
}
|
||||
|
||||
private static final String[] difficultyNames;
|
||||
|
||||
static {
|
||||
@@ -31,6 +37,14 @@ public enum Difficulty {
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public double getFactor() {
|
||||
return factor;
|
||||
}
|
||||
|
||||
public static String[] getDifficultyNames() {
|
||||
return difficultyNames;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,99 @@
|
||||
package sudoku.structure;
|
||||
|
||||
import sudoku.io.SudokuSerializer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import sudoku.io.SudokuSerializer;
|
||||
|
||||
/**
|
||||
* @class MultiDoku
|
||||
* @brief Représente une grille de Multidoku.
|
||||
* Une grille de sudoku est un multidoku avec un seul sous-sudoku
|
||||
* Une grille de sudoku est un multidoku avec un seul sous-sudoku.
|
||||
*/
|
||||
public class MultiDoku {
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="ATTRIBUTS">
|
||||
|
||||
/**
|
||||
* Liste des sous-Sudoku contenue dans le multidoku.
|
||||
*/
|
||||
private final List<Sudoku> subGrids;
|
||||
|
||||
/**
|
||||
* Pile, qui contient des états du MultiDoku,
|
||||
* utile pour la résolution.
|
||||
*/
|
||||
private final StateManager stateManager;
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="METHODES">
|
||||
|
||||
public MultiDoku(List<Sudoku> subGrids) {
|
||||
this.subGrids = subGrids;
|
||||
this.stateManager = new StateManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check si le MultiDoku est résolu, c'est à dire complet et cohérent avec ses contraintes.
|
||||
*
|
||||
* @return boolean, true s'il est résolu et false sinon.
|
||||
*/
|
||||
public boolean isSolved() {
|
||||
for (Sudoku sudoku : this.subGrids) {
|
||||
if (!sudoku.isSolved())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des Cells préalablement remplies du MultiDoku.
|
||||
*
|
||||
* @return List<Cell>, vide si aucune Cell n'est remplie.
|
||||
*/
|
||||
public List<Cell> getFilledCells() {
|
||||
List<Cell> result = new ArrayList<>();
|
||||
for (Cell cell : this.getCells()) {
|
||||
if (!cell.isEmpty()) {
|
||||
result.add(cell);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des Cells vides du MultiDoku.
|
||||
*
|
||||
* @return List<Cell>, vide si aucune Cell ne l'est.
|
||||
*/
|
||||
public List<Cell> getEmptyCells() {
|
||||
List<Cell> result = new ArrayList<>();
|
||||
for (Cell cell : this.getCells()) {
|
||||
if (cell.isEmpty()) {
|
||||
result.add(cell);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la 1re Cell vide des sous-Sudoku.
|
||||
*
|
||||
* @return Cell, une Cell vide, ou null s'il n'y en a pas.
|
||||
*/
|
||||
public Cell getFirstEmptyCell() {
|
||||
for (Sudoku sudoku : this.subGrids) {
|
||||
Cell cellTmp = sudoku.getFirstEmptyCell();
|
||||
if (cellTmp != null) {
|
||||
return cellTmp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le nombre de sudoku contenu dans ce MultiDoku.
|
||||
*
|
||||
@@ -60,95 +127,6 @@ public class MultiDoku {
|
||||
return new ArrayList<>(cellsSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Multidoku {");
|
||||
for (Sudoku sudoku : subGrids) {
|
||||
sb.append("\n\t").append(sudoku.toString());
|
||||
}
|
||||
sb.append("\n}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les sous-Sudoku
|
||||
*
|
||||
* @return List<Sudoku>
|
||||
*/
|
||||
public List<Sudoku> getSubGrids() {
|
||||
return this.subGrids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check si le MultiDoku est résolu, c'est à dire complet et cohérent avec ses contraintes.
|
||||
*
|
||||
* @return boolean, true s'il est résolu et false sinon.
|
||||
*/
|
||||
public boolean isSolved() {
|
||||
for (Sudoku sudoku : this.subGrids) {
|
||||
if (!sudoku.isSolved())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la 1re Cell vide des sous-Sudoku.
|
||||
*
|
||||
* @return Cell, une Cell vide, ou null s'il n'y en a pas.
|
||||
*/
|
||||
public Cell getFirstEmptyCell() {
|
||||
for (Sudoku sudoku : this.subGrids) {
|
||||
Cell cellTmp = sudoku.getFirstEmptyCell();
|
||||
if (cellTmp != null) {
|
||||
return cellTmp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des Cells préalablement remplies du MultiDoku.
|
||||
*
|
||||
* @return List<Cell>, vide si aucune Cell n'est remplie.
|
||||
*/
|
||||
public List<Cell> getFilledCells() {
|
||||
List<Cell> result = new ArrayList<>();
|
||||
for (Cell cell : this.getCells()) {
|
||||
if (!cell.isEmpty()) {
|
||||
result.add(cell);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des Cells vides du MultiDoku.
|
||||
*
|
||||
* @return List<Cell>, vide si aucune Cell ne l'est.
|
||||
*/
|
||||
public List<Cell> getEmptyCells() {
|
||||
List<Cell> result = new ArrayList<>();
|
||||
for (Cell cell : this.getCells()) {
|
||||
if (cell.isEmpty()) {
|
||||
result.add(cell);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide une Cell donnée.
|
||||
*
|
||||
* @param cell Cell, à vider.
|
||||
*/
|
||||
public void empty(Cell cell) {
|
||||
List<Cell> cells = getCells();
|
||||
Cell cellToEmpty = cells.get(cells.indexOf(cell));
|
||||
cellToEmpty.setSymbolIndex(Cell.NOSYMBOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie le nombre de Cell contenue dans le MultiDoku.
|
||||
*
|
||||
@@ -167,22 +145,9 @@ public class MultiDoku {
|
||||
}
|
||||
}
|
||||
|
||||
public StateManager getStateManager() {
|
||||
return stateManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie une Cell vide choisie aléatoirement.
|
||||
*
|
||||
* @param rand Random, pour le choix aléatoire.
|
||||
* @return Cell, une Cell vide.
|
||||
* Vide les Cells modifiable.
|
||||
*/
|
||||
public Cell getRandomEmptyCell(Random rand) {
|
||||
List<Cell> emptyCells = getEmptyCells();
|
||||
int randomIndex = rand.nextInt(emptyCells.size());
|
||||
return emptyCells.get(randomIndex);
|
||||
}
|
||||
|
||||
public void clearMutableCells() {
|
||||
for (Sudoku s : getSubGrids()) {
|
||||
for (Cell cell : s.getCells()) {
|
||||
@@ -192,8 +157,53 @@ public class MultiDoku {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les sous-Sudoku
|
||||
*
|
||||
* @return List<Sudoku>
|
||||
*/
|
||||
public List<Sudoku> getSubGrids() {
|
||||
return this.subGrids;
|
||||
}
|
||||
|
||||
public StateManager getStateManager() {
|
||||
return stateManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof MultiDoku otherDoku))
|
||||
return false;
|
||||
if (this.getNbSubGrids() != otherDoku.getNbSubGrids())
|
||||
return false;
|
||||
for (int i = 0; i < this.getNbSubGrids(); i++) {
|
||||
Sudoku sudoku = this.getSubGrid(i);
|
||||
Sudoku otherSudoku = otherDoku.getSubGrid(i);
|
||||
if (sudoku.getSize() != otherSudoku.getSize())
|
||||
return false;
|
||||
for (int j = 0; j < sudoku.getSize() * sudoku.getSize(); j++) {
|
||||
if (sudoku.getCell(i).getSymbolIndex() != otherSudoku.getCell(i).getSymbolIndex())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Multidoku {");
|
||||
for (Sudoku sudoku : subGrids) {
|
||||
sb.append("\n\t").append(sudoku.toString());
|
||||
}
|
||||
sb.append("\n}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public MultiDoku clone() {
|
||||
// TODO: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah
|
||||
// TODO: C'est pas dingue de le faire comme ça...
|
||||
return SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(this));
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
}
|
||||
|
||||
@@ -4,35 +4,49 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
//TODO: doc
|
||||
/**
|
||||
* Une pile, qui réprésente les états d'un MultiDoku,
|
||||
* utile pour les Solvers.
|
||||
*/
|
||||
public class StateManager {
|
||||
|
||||
private final Stack<Map<Cell, Integer>> states;
|
||||
/**
|
||||
* Le MultiDoku auquel il est associé.
|
||||
*/
|
||||
private final MultiDoku doku;
|
||||
|
||||
/**
|
||||
* La pile des états du MultiDoku associé.
|
||||
*/
|
||||
private final Stack<Map<Cell, Integer>> states;
|
||||
|
||||
public StateManager(MultiDoku doku) {
|
||||
this.states = new Stack<>();
|
||||
this.doku = doku;
|
||||
}
|
||||
|
||||
public void forgetState() {
|
||||
states.pop();
|
||||
}
|
||||
|
||||
public Map<Cell, Integer> pushState() {
|
||||
states.add(saveState());
|
||||
return states.getLast();
|
||||
}
|
||||
|
||||
public void popState() {
|
||||
assert (states.size() > 0);
|
||||
assert (!states.isEmpty());
|
||||
restoreState(states.pop());
|
||||
}
|
||||
|
||||
public Map<Cell, Integer> popAndGetState() {
|
||||
assert (states.size() > 0);
|
||||
assert (!states.isEmpty());
|
||||
var currentState = saveState();
|
||||
restoreState(states.pop());
|
||||
return currentState;
|
||||
}
|
||||
|
||||
private void restoreState(Map<Cell, Integer> state) {
|
||||
public void restoreState(Map<Cell, Integer> state) {
|
||||
for (var entry : state.entrySet()) {
|
||||
entry.getKey().setSymbolIndex(entry.getValue());
|
||||
}
|
||||
|
||||
@@ -12,24 +12,33 @@ import sudoku.constraint.IConstraint;
|
||||
*/
|
||||
public class Sudoku {
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="ATTRIBUTS">
|
||||
|
||||
/**
|
||||
* Liste des Block contenus dans le Sudoku.
|
||||
*/
|
||||
private final List<Block> blocks;
|
||||
|
||||
/**
|
||||
* Liste des Cells contenus dans le Sudoku.
|
||||
*/
|
||||
private List<Cell> cells = new ArrayList<>();
|
||||
private final List<Cell> cells;
|
||||
|
||||
/**
|
||||
* Liste des contraintes (TODO) du Sudoku.
|
||||
*/
|
||||
private final List<IConstraint> constraints;
|
||||
|
||||
/**
|
||||
* Largeur des Blocks s'ils sont rectangulaires, valant 0 si ce n'est pas le
|
||||
* cas.
|
||||
* Largeur des Blocks s'ils sont rectangulaires,
|
||||
* valant 0 si ce n'est pas le cas.
|
||||
*/
|
||||
private int blockWidth;
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="METHODES">
|
||||
|
||||
public Sudoku(List<Cell> cells, List<Block> blocks, List<IConstraint> constraints) {
|
||||
this.cells = cells;
|
||||
this.blocks = blocks;
|
||||
@@ -97,31 +106,79 @@ public class Sudoku {
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide la Cell dotn les coordonnées sont renseignées de son symbole.
|
||||
* Vérifie si le Sudoku est résolue, soit complet et cohérent avec ses
|
||||
* contraintes.
|
||||
*
|
||||
* @param x int, abscisse de la Cell voulue.
|
||||
* @param y int, coordonnée de la Cell voulue.
|
||||
* @return boolean, valant true si le Sudoku est résolu, false sinon.
|
||||
*/
|
||||
|
||||
public void clearCell(int x, int y) {
|
||||
assert (isValidCoords(x, y));
|
||||
Cell cell = getCell(x, y);
|
||||
cell.setSymbolIndex(Cell.NOSYMBOL);
|
||||
public boolean isSolved() {
|
||||
boolean isComplete = isComplete();
|
||||
boolean isValid = isValid();
|
||||
return isComplete && isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide toutes les Cell du Sudoku.
|
||||
* Vérifie que le Sudoku est complet, soit qu'il n'y ait aucune case vide.
|
||||
*
|
||||
* @return boolean, true si le Sudoku est complet, false sinon.
|
||||
*/
|
||||
public void clear() {
|
||||
for (int i = 0; i < getSize() * getSize(); i++) {
|
||||
private boolean isComplete() {
|
||||
return getFirstEmptyCell() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le Sudoku est valide, soit qu'il est cohérent avec ses
|
||||
* contraintes.
|
||||
*
|
||||
* @return bollean, true si le Sudoku est valide, false sinon
|
||||
*/
|
||||
private boolean isValid() {
|
||||
for (int i = 0; i < cells.size(); i++) {
|
||||
Cell cell = getCell(i);
|
||||
if (cell.isMutable())
|
||||
cell.setSymbolIndex(Cell.NOSYMBOL);
|
||||
if (cell.isEmpty())
|
||||
continue;
|
||||
|
||||
Coordinate coordinate = toCoords(i);
|
||||
|
||||
int symbolPlaced = cell.empty();
|
||||
if (!canBePlaced(coordinate.getX(), coordinate.getY(), symbolPlaced)) {
|
||||
cell.setSymbolIndex(symbolPlaced);
|
||||
return false;
|
||||
}
|
||||
cell.setSymbolIndex(symbolPlaced);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la Cell aux coordonées données.
|
||||
*
|
||||
* @param x int, abscisse.
|
||||
* @param y int, ordonnée.
|
||||
* @return Cell, si une Cell existe aux coordonnées données, null sinon.
|
||||
*/
|
||||
public Cell getCell(int x, int y) {
|
||||
int index = toIndex(x, y);
|
||||
assert (isValidCoords(x, y));
|
||||
try {
|
||||
return this.cells.get(index);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getBlockWidth() {
|
||||
return blockWidth;
|
||||
/**
|
||||
* Renvoie la 1re Cell vide du Sudoku.
|
||||
*
|
||||
* @return Cell, une Cell vide, ou null s'il n'y en a pas.
|
||||
*/
|
||||
public Cell getFirstEmptyCell() {
|
||||
for (Cell cell : this.cells) {
|
||||
if (cell.isEmpty()) {
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,24 +246,14 @@ public class Sudoku {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Cell getCell(int x, int y) {
|
||||
int index = toIndex(x, y);
|
||||
assert (isValidCoords(x, y));
|
||||
try {
|
||||
return this.cells.get(index);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
public boolean hasConstraint(Constraint constraint) {
|
||||
return this.constraints.contains(constraint.getConstraint());
|
||||
}
|
||||
|
||||
public Cell getCell(int i) {
|
||||
return this.cells.get(i);
|
||||
}
|
||||
|
||||
public List<IConstraint> getConstraints() {
|
||||
return constraints;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return this.blocks.size();
|
||||
}
|
||||
@@ -219,16 +266,19 @@ public class Sudoku {
|
||||
return this.blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une Cell appartient au Sudoku.
|
||||
*
|
||||
* @param cell Cell, cellule dont on veut vérifier l'appartenance au Sudoku.
|
||||
* @return boolean, vaut true si la Cell appartient au Sudoku.
|
||||
*/
|
||||
public boolean contains(Cell cell) {
|
||||
return this.cells.contains(cell);
|
||||
public List<IConstraint> getConstraints() {
|
||||
return constraints;
|
||||
}
|
||||
|
||||
public int getBlockWidth() {
|
||||
return blockWidth;
|
||||
}
|
||||
|
||||
public void setBlockWidth(int blockWidth) {
|
||||
this.blockWidth = blockWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Sudoku {");
|
||||
@@ -243,71 +293,6 @@ public class Sudoku {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la 1re Cell vide du Sudoku.
|
||||
*
|
||||
* @return Cell, une Cell vide, ou null s'il n'y en a pas.
|
||||
*/
|
||||
public Cell getFirstEmptyCell() {
|
||||
for (Cell cell : this.cells) {
|
||||
if (cell.isEmpty()) {
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le Sudoku est résolue, soit complet et cohérent avec ses
|
||||
* contraintes.
|
||||
*
|
||||
* @return boolean, valant true si le Sudoku est résolu, false sinon.
|
||||
*/
|
||||
public boolean isSolved() {
|
||||
boolean isComplete = isComplete();
|
||||
boolean isValid = isValid();
|
||||
return isComplete && isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le Sudoku est complet, soit qu'il n'y ait aucune case vide.
|
||||
*
|
||||
* @return boolean, true si le Sudoku est complet, false sinon.
|
||||
*/
|
||||
private boolean isComplete() {
|
||||
return getFirstEmptyCell() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le Sudoku est valide, soit qu'il est cohérent avec ses
|
||||
* contraintes.
|
||||
*
|
||||
* @return bollean, true si le Sudoku est valide, false sinon
|
||||
*/
|
||||
private boolean isValid() {
|
||||
for (int i = 0; i < cells.size(); i++) {
|
||||
Cell cell = getCell(i);
|
||||
if (cell.isEmpty())
|
||||
continue;
|
||||
|
||||
Coordinate coordinate = toCoords(i);
|
||||
|
||||
int symbolPlaced = cell.empty();
|
||||
if (!canBePlaced(coordinate.getX(), coordinate.getY(), symbolPlaced)) {
|
||||
cell.setSymbolIndex(symbolPlaced);
|
||||
return false;
|
||||
}
|
||||
cell.setSymbolIndex(symbolPlaced);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setBlockWidth(int blockWidth) {
|
||||
this.blockWidth = blockWidth;
|
||||
}
|
||||
|
||||
public boolean hasConstraint(Constraint constraint) {
|
||||
return this.constraints.contains(constraint.getConstraint());
|
||||
}
|
||||
// <editor-fold defaultstate="collapsed" desc="METHODES">
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,11 @@ public class SudokuFactory {
|
||||
* Comprend les contraintes de blocs, de lignes, et de colonnes.
|
||||
*/
|
||||
public static List<IConstraint> DEFAULT_CONSTRAINTS = SudokuFactory
|
||||
.fromConstraints(Arrays.asList(Constraint.Block, Constraint.Column,
|
||||
Constraint.Line));
|
||||
.fromConstraints(Arrays.asList(
|
||||
Constraint.Block,
|
||||
Constraint.Column,
|
||||
Constraint.Line)
|
||||
);
|
||||
|
||||
/**
|
||||
* Créée des Cells et les met dans une liste de taille size.
|
||||
@@ -65,9 +68,6 @@ public class SudokuFactory {
|
||||
int index = ((y + blockY * height) * size + (x + blockX * width));
|
||||
Cell blockCell = cells.get(index);
|
||||
blockCell.setBlock(newBlock);
|
||||
// List<Block> blockContainers = new ArrayList<>();
|
||||
// blockContainers.add(newBlock);
|
||||
// blockCell.setBlockContainers(blockContainers);
|
||||
newBlock.addCell(blockCell);
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class SudokuFactory {
|
||||
*/
|
||||
public static MultiDoku createBasicEmptyRectangleDoku(int widthBlock, int heightBlock,
|
||||
List<IConstraint> constraints) {
|
||||
return new MultiDoku(Arrays.asList(createRectangleSudoku(widthBlock, heightBlock, constraints)));
|
||||
return new MultiDoku(List.of(createRectangleSudoku(widthBlock, heightBlock, constraints)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,63 +96,7 @@ public class SudokuFactory {
|
||||
* @return MultiDoku, MultiDoku vide.
|
||||
*/
|
||||
public static MultiDoku createBasicEmptySquareDoku(int size, List<IConstraint> constraints) {
|
||||
return new MultiDoku(Arrays.asList(createSquareSudoku(size, constraints)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Place des Cells immutables de valeurs fournies, aux Coordinate fournies dans
|
||||
* le MultiDoku doku fourni.
|
||||
*
|
||||
* @param doku MultiDoku, MultiDoku à remplir.
|
||||
* @param immutableCells Map<Coordinate, Integer>, association de Coordinate
|
||||
* coordonnées et Integer valeurs, correspondant aux cases
|
||||
* à remplir.
|
||||
*/
|
||||
public static void setImmutableCells(MultiDoku doku, Map<Coordinate, Integer> immutableCells) {
|
||||
immutableCells.forEach((coordinate, symbol) -> {
|
||||
for (Sudoku sudoku : doku.getSubGrids()) {
|
||||
Cell cell = sudoku.getCell(coordinate.getX(), coordinate.getY());
|
||||
if (cell != null) {
|
||||
cell.setSymbolIndex(symbol);
|
||||
cell.setImmutable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Créée un MultiDoku de difficulté difficulty à partir d'un MultiDoku fourni.
|
||||
*
|
||||
* @param doku MultiDoku, MultiDoku dont on doit vider des Cells.
|
||||
* @param nbCellsToEmpty int, nombre de cases à retirer.
|
||||
* @return boolean, valant true si un MultiDoku de difficulté donnée peut être
|
||||
* créée, false sinon.
|
||||
* @throws Exception si la difficulté n'est pas compatible avec la taille du
|
||||
* MultiDoku.
|
||||
*/
|
||||
public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty, Solver solver) throws Exception {
|
||||
|
||||
if (nbCellsToEmpty >= doku.getCells().size()) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
if (nbCellsToEmpty == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Cell> cellsThatCanBeEmptied = doku.getFilledCells();
|
||||
|
||||
int index = random.nextInt(cellsThatCanBeEmptied.size());
|
||||
Cell cellToEmpty = cellsThatCanBeEmptied.get(index);
|
||||
|
||||
cellToEmpty.empty();
|
||||
|
||||
int nbDokuSultions = solver.countSolution(doku);
|
||||
if (nbDokuSultions == 1) {
|
||||
return newDokuFromFilledOne(doku, --nbCellsToEmpty, solver);
|
||||
}
|
||||
|
||||
return false;
|
||||
return new MultiDoku(List.of(createSquareSudoku(size, constraints)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,6 +171,9 @@ public class SudokuFactory {
|
||||
* Créée un MultiDoku de Blocks carrés de taille size composé de cinq Sudokus,
|
||||
* dont un central qui partage chacun de ses Blockss d'angle avec un autre
|
||||
* Sudoku.
|
||||
* 2 3
|
||||
* 1
|
||||
* 4 5
|
||||
*
|
||||
* @param size int, largeur des Blocks unitraires des Sudokus à crééer.
|
||||
* @return MultiDoku, MultiDoku de forme X.
|
||||
@@ -234,45 +181,25 @@ public class SudokuFactory {
|
||||
public static MultiDoku createBasicXShapedMultidoku(int size, List<IConstraint> constraints) {
|
||||
assert (size > 1);
|
||||
|
||||
/*
|
||||
* 2 3
|
||||
* 1
|
||||
* 4 5
|
||||
*/
|
||||
|
||||
Sudoku sudoku1 = createSquareSudoku(size, constraints);
|
||||
Sudoku sudoku2 = createSquareSudoku(size, constraints);
|
||||
Sudoku sudoku3 = createSquareSudoku(size, constraints);
|
||||
Sudoku sudoku4 = createSquareSudoku(size, constraints);
|
||||
Sudoku sudoku5 = createSquareSudoku(size, constraints);
|
||||
|
||||
linkRectangleSudokus(sudoku1, sudoku2, new Coordinate(1 - size, 1 - size));
|
||||
linkRectangleSudokus(sudoku1, sudoku3, new Coordinate(size - 1, 1 - size));
|
||||
linkRectangleSudokus(sudoku1, sudoku4, new Coordinate(1 - size, size - 1));
|
||||
linkRectangleSudokus(sudoku1, sudoku5, new Coordinate(size - 1, size - 1));
|
||||
|
||||
return new MultiDoku(Arrays.asList(sudoku1, sudoku2, sudoku3, sudoku4, sudoku5));
|
||||
return createBasicXShapedMultidoku(size, size, constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Créée un MultiDoku de Blocks rectangulaires de forme width par height composé
|
||||
* Créée un MultiDoku de Blocks rectangulaires de forme X, de taille width par height composé
|
||||
* de cinq Sudokus,
|
||||
* dont un central qui partage chacun de ses Blocks d'angle avec un autre
|
||||
* Sudoku.
|
||||
* 2 3
|
||||
* 1
|
||||
* 4 5
|
||||
*
|
||||
* @param width int, largeur des Blocks unitraires des Sudokus à crééer.
|
||||
* @param height int, hauteur des Blocks unitraires des Sudokus à crééer.
|
||||
* @param width int, largeur des Blocks unitaires des Sudokus à crééer.
|
||||
* @param height int, hauteur des Blocks unitaires des Sudokus à crééer.
|
||||
* @return MultiDoku, MultiDoku de forme X.
|
||||
*/
|
||||
public static MultiDoku createBasicXShapedMultidoku(int width, int height, List<IConstraint> constraints) {
|
||||
assert (width > 1 && height > 1);
|
||||
|
||||
/*
|
||||
* 2 3
|
||||
* 1
|
||||
* 4 5
|
||||
*/
|
||||
|
||||
Sudoku sudoku1 = createRectangleSudoku(width, height, constraints);
|
||||
Sudoku sudoku2 = createRectangleSudoku(width, height, constraints);
|
||||
Sudoku sudoku3 = createRectangleSudoku(width, height, constraints);
|
||||
@@ -287,30 +214,65 @@ public class SudokuFactory {
|
||||
return new MultiDoku(Arrays.asList(sudoku1, sudoku2, sudoku3, sudoku4, sudoku5));
|
||||
}
|
||||
|
||||
public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception {
|
||||
Solver solver = new RandomSolver();
|
||||
solver.solve(doku);
|
||||
int nbCellsToEmpty = (int) (difficulty.getFactor() * doku.getNbCells());
|
||||
boolean successfull = newDokuFromFilledOne(doku, nbCellsToEmpty, solver);
|
||||
doku.setFilledCellsImmutable();
|
||||
/**
|
||||
* Créée un MultiDoku de Blocks carrés de forme +, de taille size composé
|
||||
* de cinq Sudokus,
|
||||
* dont un central qui partage chacun de ses Blocks de bord avec un autre
|
||||
* Sudoku.
|
||||
* 3
|
||||
* 2 1 4
|
||||
* 5
|
||||
*
|
||||
* @param size int, largeur des Blocks unitaires des Sudokus à crééer.
|
||||
* @return MultiDoku, MultiDoku de forme +.
|
||||
*/
|
||||
public static MultiDoku createBasicPlusShapedMultidoku(int size, List<IConstraint> constraints) {
|
||||
assert (size > 1);
|
||||
|
||||
return createBasicPlusShapedMultidoku(size, size, constraints);
|
||||
}
|
||||
|
||||
public static MultiDoku fromfile(String filePath) {
|
||||
try {
|
||||
String content = Files.readString(Paths.get(filePath));
|
||||
MultiDoku doku = SudokuSerializer.deserializeSudoku(content);
|
||||
return doku;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Créée un MultiDoku de Blocks rectangulaires de forme +, de taille width par height composé
|
||||
* de cinq Sudokus,
|
||||
* dont un central qui partage chacun de ses Blocks de bord avec un autre
|
||||
* Sudoku.
|
||||
* 3
|
||||
* 2 1 4
|
||||
* 5
|
||||
*
|
||||
* @param width int, largeur des Blocks unitaires des Sudokus à crééer.
|
||||
* @param height int, hauteur des Blocks unitaires des Sudokus à crééer.
|
||||
* @return MultiDoku, MultiDoku de forme +.
|
||||
*/
|
||||
public static MultiDoku createBasicPlusShapedMultidoku(int width, int height, List<IConstraint> constraints) {
|
||||
assert (width > 1 && height > 1);
|
||||
|
||||
Sudoku sudoku1 = createRectangleSudoku(width, height, constraints);
|
||||
Sudoku sudoku2 = createRectangleSudoku(width, height, constraints);
|
||||
Sudoku sudoku3 = createRectangleSudoku(width, height, constraints);
|
||||
Sudoku sudoku4 = createRectangleSudoku(width, height, constraints);
|
||||
Sudoku sudoku5 = createRectangleSudoku(width, height, constraints);
|
||||
|
||||
linkRectangleSudokus(sudoku1, sudoku2, new Coordinate(1 - height, 0));
|
||||
linkRectangleSudokus(sudoku1, sudoku3, new Coordinate(0, 1 - width));
|
||||
linkRectangleSudokus(sudoku1, sudoku4, new Coordinate(height - 1, 0));
|
||||
linkRectangleSudokus(sudoku1, sudoku5, new Coordinate(0, width - 1));
|
||||
|
||||
return new MultiDoku(Arrays.asList(sudoku1, sudoku2, sudoku3, sudoku4, sudoku5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un Sudoku vide avec les Blocks aléatoires.
|
||||
*
|
||||
* @param blockSize int, la taille des Block.
|
||||
* @param constraints List<IConstraint>, la liste des contraintes.
|
||||
* @return MultiDoku, avec un seul Sudoku.
|
||||
*/
|
||||
public static MultiDoku createBasicEmptyRandomBlockDoku(int blockSize, List<IConstraint> constraints) {
|
||||
int blockCellCount = blockSize * blockSize;
|
||||
List<Cell> cells = initCells(blockCellCount);
|
||||
List<Cell> homeLessCells = new ArrayList<>();
|
||||
homeLessCells.addAll(cells);
|
||||
List<Cell> homeLessCells = new ArrayList<>(cells);
|
||||
List<Block> blocks = new ArrayList<>();
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < blockCellCount; i++) {
|
||||
@@ -327,9 +289,79 @@ public class SudokuFactory {
|
||||
for (Block block : blocks) {
|
||||
block.getSudokus().add(sudoku);
|
||||
}
|
||||
return new MultiDoku(Arrays.asList(sudoku));
|
||||
return new MultiDoku(List.of(sudoku));
|
||||
}
|
||||
|
||||
/**
|
||||
* Créée un MultiDoku de difficulté difficulty à partir d'un MultiDoku fourni.
|
||||
*
|
||||
* @param doku MultiDoku, MultiDoku dont on doit vider des Cells.
|
||||
* @param nbCellsToEmpty int, nombre de cases à retirer.
|
||||
* @return boolean, valant true si un MultiDoku de difficulté donnée peut être
|
||||
* créée, false sinon.
|
||||
* @throws Exception si la difficulté n'est pas compatible avec la taille du
|
||||
* MultiDoku.
|
||||
*/
|
||||
public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty, Solver solver) throws Exception {
|
||||
|
||||
if (nbCellsToEmpty >= doku.getCells().size()) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
if (nbCellsToEmpty == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Cell> cellsThatCanBeEmptied = doku.getFilledCells();
|
||||
|
||||
int index = random.nextInt(cellsThatCanBeEmptied.size());
|
||||
Cell cellToEmpty = cellsThatCanBeEmptied.get(index);
|
||||
|
||||
cellToEmpty.empty();
|
||||
|
||||
int nbDokuSultions = solver.countSolution(doku);
|
||||
if (nbDokuSultions == 1) {
|
||||
return newDokuFromFilledOne(doku, --nbCellsToEmpty, solver);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rempli un MultiDoku donné par rapport à un difficulté.
|
||||
*
|
||||
* @param doku MultiDoku, vide.
|
||||
* @param difficulty Difficulty, qui correspond au pourcentage de cases à enlever.
|
||||
*/
|
||||
public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception {
|
||||
Solver solver = new RandomSolver();
|
||||
solver.solve(doku);
|
||||
int nbCellsToEmpty = (int) (difficulty.getFactor() * doku.getNbCells());
|
||||
newDokuFromFilledOne(doku, nbCellsToEmpty, solver);
|
||||
doku.setFilledCellsImmutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un MultiDoku à partir d'un fichier de sauvegarde.
|
||||
*
|
||||
* @param filePath String, chemin du fichier.
|
||||
* @return MultiDoku.
|
||||
*/
|
||||
public static MultiDoku fromfile(String filePath) {
|
||||
try {
|
||||
String content = Files.readString(Paths.get(filePath));
|
||||
return SudokuSerializer.deserializeSudoku(content);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforme des Constraint en IConstraint correspondants
|
||||
* @param constraints List<Constraints>
|
||||
* @return List<IConstraints>
|
||||
*/
|
||||
public static List<IConstraint> fromConstraints(List<Constraint> constraints) {
|
||||
List<IConstraint> iconstraints = new ArrayList<>();
|
||||
for (Constraint cons : constraints) {
|
||||
|
||||
78
app/src/main/java/sudoku/structure/SudokuFiller.java
Normal file
78
app/src/main/java/sudoku/structure/SudokuFiller.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package sudoku.structure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import sudoku.solver.BacktrackingSolver;
|
||||
import sudoku.solver.HumanSolver;
|
||||
import sudoku.solver.MixedSolver;
|
||||
import sudoku.solver.RandomSolver;
|
||||
import sudoku.solver.Solver;
|
||||
|
||||
public class SudokuFiller {
|
||||
|
||||
private static boolean isPossiblySolvable(MultiDoku doku) {
|
||||
for (Cell cell : doku.getEmptyCells()) {
|
||||
if (cell.getPossibleSymbols().size() == 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void tryFillRandomCells(MultiDoku doku) {
|
||||
Random r = new Random();
|
||||
while (isPossiblySolvable(doku)) {
|
||||
int cellCount = doku.getEmptyCells().size();
|
||||
if (cellCount == 0)
|
||||
return;
|
||||
Cell cellToFill = doku.getEmptyCells().get(r.nextInt(cellCount));
|
||||
if (!cellToFill.isEmpty() || !cellToFill.isMutable())
|
||||
continue;
|
||||
|
||||
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
||||
int symbolCount = possibleSymbols.size();
|
||||
// la cellule n'a pas de valeur possible
|
||||
if (symbolCount == 0)
|
||||
return;
|
||||
|
||||
cellToFill.setSymbolIndex(possibleSymbols.get(r.nextInt(symbolCount)));
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeRandomCell(MultiDoku doku) {
|
||||
Random r = new Random();
|
||||
int cellCount = doku.getFilledCells().size();
|
||||
doku.getFilledCells().get(r.nextInt(cellCount)).clearCurrentSymbol();
|
||||
}
|
||||
|
||||
private static void makeSolvable(MultiDoku doku) {
|
||||
while (!isPossiblySolvable(doku)) {
|
||||
removeRandomCell(doku);
|
||||
}
|
||||
Solver solver = new BacktrackingSolver();
|
||||
boolean solvable = false;
|
||||
while (!solvable) {
|
||||
removeRandomCell(doku);
|
||||
doku.getStateManager().pushState();
|
||||
solvable = solver.solve(doku);
|
||||
doku.getStateManager().popState();
|
||||
System.out.println("eeeeeeeeeeeee");
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillDoku(MultiDoku doku) {
|
||||
tryFillRandomCells(doku);
|
||||
makeSolvable(doku);
|
||||
}
|
||||
|
||||
private static void setCellsImmutable(MultiDoku doku) {
|
||||
for (Cell cell : doku.getFilledCells())
|
||||
cell.setImmutable();
|
||||
}
|
||||
|
||||
public static void fillDoku(MultiDoku doku, Difficulty difficulty) {
|
||||
fillDoku(doku);
|
||||
setCellsImmutable(doku);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,18 +58,20 @@ public class SudokuSerializerTest {
|
||||
void testSerialize() {
|
||||
Random r = new Random();
|
||||
int testCount = 20;
|
||||
int minSize = 2;
|
||||
int maxSize = 3;
|
||||
for (int i = 0; i < testCount; i++) {
|
||||
int blockWidth = r.nextInt(10) + 1;
|
||||
int blockHeight = r.nextInt(10) + 1;
|
||||
int blockWidth = r.nextInt(maxSize - minSize + 1) + minSize;
|
||||
int blockHeight = r.nextInt(maxSize - minSize + 1) + minSize;
|
||||
testSerializeWithSize(blockWidth, blockHeight);
|
||||
}
|
||||
for (int i = 0; i < testCount; i++) {
|
||||
int blockWidth = r.nextInt(10) + 1;
|
||||
int blockHeight = r.nextInt(10) + 1;
|
||||
int blockWidth = r.nextInt(maxSize - minSize + 1) + minSize;
|
||||
int blockHeight = r.nextInt(maxSize - minSize + 1) + minSize;
|
||||
testSaveWithSize(blockWidth, blockHeight);
|
||||
}
|
||||
for (int i = 0; i < testCount; i++) {
|
||||
int size = r.nextInt(10) + 1;
|
||||
int size = r.nextInt(maxSize - minSize + 1) + minSize;
|
||||
testSerializeX(size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package sudoku.solver;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import gui.constants.Symbols;
|
||||
import sudoku.io.SudokuPrinter;
|
||||
import sudoku.io.SudokuSerializer;
|
||||
import sudoku.structure.*;
|
||||
import sudoku.structure.Cell;
|
||||
import sudoku.structure.Difficulty;
|
||||
import sudoku.structure.MultiDoku;
|
||||
import sudoku.structure.Sudoku;
|
||||
import sudoku.structure.SudokuFactory;
|
||||
|
||||
class SolverTest {
|
||||
private int ns = Cell.NOSYMBOL;
|
||||
@@ -19,13 +24,13 @@ class SolverTest {
|
||||
private static MixedSolver m;
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeSolvers(){
|
||||
public static void initializeSolvers() {
|
||||
h = new HumanSolver();
|
||||
r = new RandomSolver();
|
||||
m = new MixedSolver();
|
||||
}
|
||||
|
||||
private void testSize2(Solver solver){
|
||||
private void testSize2(Solver solver) {
|
||||
MultiDoku mdTest = SudokuFactory.createBasicEmptySquareDoku(2, SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||
MultiDoku mdResult = SudokuFactory.createBasicEmptySquareDoku(2, SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||
Sudoku test = mdTest.getSubGrid(0);
|
||||
@@ -35,25 +40,23 @@ class SolverTest {
|
||||
0, ns, ns, ns,
|
||||
ns, ns, ns, 3,
|
||||
ns, 0, 1, ns);
|
||||
assert (test.setImmutableCellsSymbol(immutableCells));
|
||||
assertTrue(test.setImmutableCellsSymbol(immutableCells));
|
||||
List<Integer> correctCells = List.of(
|
||||
1, 2, 3, 0,
|
||||
0, 3, 2, 1,
|
||||
2, 1, 0, 3,
|
||||
3, 0, 1, 2);
|
||||
assert(result.setCellsSymbol(correctCells));
|
||||
assert(result.isSolved());
|
||||
assertTrue(result.setCellsSymbol(correctCells));
|
||||
assertTrue(result.isSolved());
|
||||
|
||||
assertNotEquals(mdResult, mdTest);
|
||||
solver.solve(mdTest);
|
||||
assert (mdTest.isSolved());
|
||||
for (Cell cell : test.getCells()) {
|
||||
cell.setImmutable();
|
||||
}
|
||||
assertEquals(SudokuSerializer.serializeSudoku(mdTest).toString(),
|
||||
SudokuSerializer.serializeSudoku(mdResult).toString());
|
||||
assertTrue(mdTest.isSolved());
|
||||
|
||||
assertEquals(mdTest, mdResult);
|
||||
}
|
||||
|
||||
private void testSize3(Solver solver){
|
||||
private void testSize3(Solver solver) {
|
||||
MultiDoku mdTest = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||
MultiDoku mdResult = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||
Sudoku test = mdTest.getSubGrid(0);
|
||||
@@ -79,41 +82,38 @@ class SolverTest {
|
||||
3, 1, 6, 0, 2, 8, 4, 7, 5,
|
||||
8, 0, 4, 7, 6, 5, 2, 1, 3,
|
||||
5, 7, 2, 4, 3, 1, 0, 6, 8);
|
||||
assert(result.setCellsSymbol(correctCells));
|
||||
assert(result.isSolved());
|
||||
assert (result.setCellsSymbol(correctCells));
|
||||
assert (result.isSolved());
|
||||
|
||||
assertNotEquals(mdResult, mdTest);
|
||||
solver.solve(mdTest);
|
||||
assert (mdTest.isSolved());
|
||||
for (Cell cell : test.getCells()) {
|
||||
cell.setImmutable();
|
||||
}
|
||||
assertEquals(SudokuSerializer.serializeSudoku(mdTest).toString(),
|
||||
SudokuSerializer.serializeSudoku(mdResult).toString());
|
||||
assertEquals(mdTest, mdResult);
|
||||
}
|
||||
|
||||
private void testMDSize3(Solver solver){
|
||||
private void testMDSize3(Solver solver) {
|
||||
MultiDoku mdTest = SudokuFactory.createBasicXShapedMultidoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||
try {
|
||||
SudokuFactory.fillDoku(mdTest, Difficulty.Easy);
|
||||
} catch (Exception e) {
|
||||
assert(false);
|
||||
assert (false);
|
||||
}
|
||||
MultiDoku result = SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(mdTest));
|
||||
assert(result.isSolved());
|
||||
solver.solve(mdTest);
|
||||
assert (mdTest.isSolved());
|
||||
for (Cell cell : mdTest.getCells()) {
|
||||
cell.setImmutable();
|
||||
}
|
||||
assertEquals(SudokuSerializer.serializeSudoku(mdTest).toString(),
|
||||
SudokuSerializer.serializeSudoku(result).toString());
|
||||
MultiDoku mdResult = SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(mdTest));
|
||||
assertFalse(mdTest.isSolved());
|
||||
assertFalse(mdResult.isSolved());
|
||||
assertTrue(solver.solve(mdTest));
|
||||
assertTrue(mdTest.isSolved());
|
||||
assertFalse(mdResult.isSolved());
|
||||
assertNotEquals(mdTest, mdResult);
|
||||
solver.solve(mdResult);
|
||||
assertEquals(mdTest, mdResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
void solveTest() {
|
||||
initializeSolvers();
|
||||
testSize2(h);
|
||||
testSize3(h);
|
||||
testMDSize3(h);
|
||||
testSize2(m);
|
||||
testSize3(m);
|
||||
testMDSize3(m);
|
||||
|
||||
Reference in New Issue
Block a user