-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcalculator.php
337 lines (296 loc) · 9.75 KB
/
calculator.php
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
<?php
/**
* This class will do the eventual calculation for the solution of the Sudoku
*
* @author WIM
* @version Release: $Id:$
*/
class SudokuCalculator
{
/**
* The Sudoku handler required for retrieving the default information about the
* Suokdu puzzle
*
* @var object
* @access private
*/
private $Sudoku;
/**
* The generator for generating the possible solutions
*
* @var object
* @access private
*/
private $Generator;
/**
* The painter object is used for drawing solutions onto a board
*
* @var object
* @access private
*/
private $Painter;
/**
* The validator is used to validate a calculated solution
*
* @var object
* @access private
*/
private $Validator;
/**
* The calculated possible solutions
*
* @var array
* @access private
*/
private $solutions = array();
/**
* Keeps count of the number of examend possible solutions
*
* @var integer
* @access private
*/
private $solutionCalculator = 0;
/**
* The final solution
*
* @var array
* @access private
*/
private $finalSolutions = array();
/**
* Initiate the class and set the required handlers
*
* @throws exception
* @return void
* @access public
* @author WIM
*/
public function __construct($Sudoku, $Generator, $Painter, $Validator)
{
// Set the required handlers
if (empty($Sudoku) || !is_object($Sudoku)) {
throw new Exception('No Sudoku class has been defined');
}
$this->Sudoku = $Sudoku;
$this->Generator = $Generator;
$this->Painter = $Painter;
$this->Validator = $Validator;
// Define the pre-required values in this class
$this->availableNumbers = $this->Sudoku->getAvailableNumbers();
$this->countAvailableNumbers = count($this->availableNumbers);
$this->squaresOnBoard = $this->Sudoku->getNumberOfSquaresOnBoard();
$this->blockLength = $this->Sudoku->getBlockBorderLength();
$this->squaresInBlock = $this->Sudoku->getNumberOfSquaresInBlock();
$this->boardLength = $this->Sudoku->getBoardBorderLength();
$this->blocksPerSide = $this->Sudoku->getNumberBlockPerSide();
$this->squaresInBlockRow = $this->squaresInBlock * $this->blocksPerSide;
}
/**
* Check if the string contains all the numbers and has no duplicates
*
* @param string $numberstring The string that needs validation
*
* @throws exception
* @return boolean
* @access public
* @author WIM
*/
public function checkCorrectnessNumberstring($numberstring = '')
{
if ($this->Validator->notEmptyString($numberstring) === false) {
throw new Exception('No (valid) numeric string has been provided');
}
// Validate the string against the Sudoku game rules
$checkRows = $this->checkRows($numberstring);
if ($checkRows === false) {
throw new Exception('A invalid numeric string has been provided');
}
}
/**
* Calculate the solution to the Sudoku
*
* @param string $numberstring The starting string from which the solutions needs
* to be calculated
*
* @throws exception
* @return string The eventual solution to the provided starting position
* @access public
* @author WIM
*/
public function calculateSolution($numberstring = '')
{
if ($this->Validator->notEmptyString($numberstring) === false) {
throw new Exception('No (valid) numeric string has been provided');
}
// Provide the script with endless runingtime
set_time_limit(0);
// Calculate all the possible solutions
$this->backtrackSolution($numberstring);
// Check wether a solution has been found
if (empty($this->finalSolutions)) {
throw new Exception('No solutions could be found for the provided gameset');
}
return $this->finalSolutions[0];
}
/**
* Try to calculate the solution using a backtrace algoritm by changing one
* number at the time
*
* @param string $numberstring The possible solution thus far
*
* @return void
* @access private
* @author WIM
*/
private function backtrackSolution($numberstring)
{
// Find the next spot in the line to calculate
$emptySpot = strpos($numberstring, '0');
// A solution has been found
if ($emptySpot === false) {
$this->finalSolutions[] = $numberstring;
return true;
}
// Replace the empty spot with a number and try to validate it
foreach ($this->availableNumbers as $number) {
$numberstring[$emptySpot] = $number;
// DEBUG: Return the number string to the user
//echo $numberstring . "\n";
//flush();
// Validate the number string
if ($this->checkRows($numberstring) === false) {
continue;
}
$foundSolution = $this->backtrackSolution($numberstring);
if ($foundSolution === true) {
return true;
}
}
// Invalid solution
return false;
}
/**
* Validate a provided gameset
*
* @param string $numberstring Gameset that needs validation
*
* @throws Exception
* @return void
* @access private
* @author WIM
*/
private function checkRows($numberstring)
{
// Check if the string is not longer than the actual board
if (strlen($numberstring) > $this->squaresOnBoard) {
throw new Exception('The provided gameset is longer than the board');
}
if (strlen($numberstring) < $this->squaresOnBoard) {
// Extend the gameset to match the length of the board
$numberstring .= str_repeat('0', $this->squaresOnBoard - strlen($numberstring));
}
// Validate the columns and rows
if ($this->checkBlocks($numberstring) === false
|| $this->checkHorizontalRows($numberstring) === false
|| $this->checkVerticalRows($numberstring) === false
) {
return false;
}
return true;
}
/**
* Validate if a row contains only unique numbers
*
* @param string $numberstring The row that needs validation
*
* @return boolean
* @access private
* @author WIM
*/
private function checkRow($numberstring)
{
$numberstringArray = str_split($numberstring, 1);
$filteredArray = array_filter($numberstringArray);
return count($filteredArray) == count(array_unique($filteredArray));
}
/**
* Validate the rows a gameset
*
* @param string $numberstring The full gameset
*
* @param boolean
* @access private
* @author WIM
*/
private function checkHorizontalRows($numberstring)
{
for ($i = 0; $i < $this->boardLength; $i++) {
// If the count exceeds the upper part of the board, than multiply it to
// compensate the jump to the next part
$additionalIncrement = floor($i / $this->blockLength) * ($this->squaresInBlockRow - $this->squaresInBlock);
// Calculate the first position of the row
$start = ($i * $this->blockLength) + $additionalIncrement;
// Get all the available blocks and there numbers
$row = '';
for ($b = 0; $b < $this->blocksPerSide; $b++) {
$blockStart = $start + ($this->squaresInBlock * $b);
$row .= substr($numberstring, $blockStart, $this->blockLength);
}
if ($this->checkRow($row) === false) {
return false;
}
}
return true;
}
/**
* Validate the columns a gameset
*
* @param string $numberstring The full gameset
*
* @param boolean
* @access private
* @author WIM
*/
private function checkVerticalRows($numberstring)
{
for ($i = 0; $i < $this->boardLength; $i++) {
// If the count exceeds the upper part of the board, than multiply it to
// compensate the jump to the next part
$additionalIncrement = floor($i / $this->blockLength) * ($this->squaresInBlock - $this->blockLength);
$start = $i + $additionalIncrement;
// Get the vertical aligned numbers
$row = '';
for ($b = 0; $b < $this->boardLength; $b++) {
// Get the number on the next row
$additionalRowIncrement = floor($b / $this->blockLength) * ($this->squaresInBlockRow - $this->squaresInBlock);
$rowStart = $start + ($b * $this->blockLength) + $additionalRowIncrement;
// Use the calculated index to retrieve the next number in the line
$row .= substr($numberstring, $rowStart, 1);
}
if ($this->checkRow($row) === false) {
return false;
}
}
return true;
}
/**
* Check the provided input for the blocks
*
* @param string $numberstring The full gameset
*
* @return boolean
* @access private
* @author WIM
*/
private function checkBlocks($numberstring)
{
// Split the string up and validate the pieces individualy
$blocks = str_split($numberstring, count($this->Sudoku->getAvailableNumbers()));
foreach ($blocks as $block) {
if ($this->checkRow($block) === false) {
return false;
}
}
return true;
}
}