Sudoku::determine_possible_values()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 12
rs 9.4285
1
<?php
2
namespace Sudoxu;
3
4
/**
5
 * @package Sudoxu
6
 * @author  Burak <[email protected]>
7
 */
8
9
class Sudoku
10
{
11
12
    public  $sudoku,
13
            $result,
14
            $number;
15
    private $limit,
16
            $sq,
17
            $chars,
18
            $stack = [
19
                '1','2','3','4','5','6','7','8','9',
20
                '0','A','B','C','D','E','F','G','H',
21
                'I','J','K','L','M','N','O','P','Q',
22
                'R','S','T','U','V','W','X','Y','Z'
23
            ];
24
25
    /**
26
     * Sudoku constructor
27
     *
28
     * @param int $limit
29
     */
30
    public function __construct($limit = 9)
31
    {
32
        ini_set('memory_limit', -1);
33
        set_time_limit(0);
34
35
        $this->number = 0;
36
        $this->limit  = $limit;
37
        $this->sq     = (int)sqrt($this->limit);
38
39
        return $this;
40
41
    }
42
43
    /**
44
     * Limit setter function
45
     *
46
     * @param int $number
47
     * @return $this
48
     */
49
    public function set($number)
50
    {
51
        $this->limit = $number;
52
        $this->sq    = (int)sqrt($this->limit);
53
54
        return $this;
55
    }
56
57
    /**
58
     * Position of the cell in X-axis
59
     *
60
     * @param int $cell
61
     * @return int
62
     */
63
    private function return_row($cell)
64
    {
65
        return (int) floor($cell / $this->limit);
66
    }
67
68
    /**
69
     * Position of the cell in Y-axis
70
     *
71
     * @param int $cell
72
     * @return int
73
     */
74
    private function return_col($cell)
75
    {
76
        return (int) $cell % $this->limit;
77
    }
78
79
    /**
80
     * Position of the block
81
     *
82
     * @param int $cell
83
     * @return int
84
     */
85
    private function return_block($cell)
86
    {
87
        return (int) floor($this->return_row($cell) / $this->sq) * $this->sq + floor($this->return_col($cell) / $this->sq);
88
    }
89
90
    /**
91
     * Determine if this item unique in the row
92
     *
93
     * @param string $item
94
     * @param int    $row
95
     * @return bool
96
     */
97
    private function is_possible_row($item, $row)
98
    {
99
        $possible = true;
100
        for ($x = 0; $x < $this->limit; $x++)
101
        {
102
            if (isset($this->sudoku[$row * $this->limit + $x]) && $this->sudoku[$row * $this->limit + $x] == $item)
103
            {
104
                $possible = false;
105
            }
106
        }
107
108
        return $possible;
109
    }
110
111
    /**
112
     * Determine if this item unique in the column
113
     *
114
     * @param string $item
115
     * @param int    $col
116
     * @return bool
117
     */
118
    private function is_possible_col($item, $col)
119
    {
120
        $possible = true;
121
        for ($x = 0; $x < $this->limit; $x++)
122
        {
123
            if (isset($this->sudoku[$col + $this->limit * $x]) && $this->sudoku[$col + $this->limit * $x] == $item)
124
            {
125
                $possible = false;
126
            }
127
        }
128
129
        return $possible;
130
    }
131
132
    /**
133
     * Determine if this item unique in the square block
134
     *
135
     * @param string $item
136
     * @param int    $block
137
     * @return bool
138
     */
139
    private function is_possible_block($item, $block)
140
    {
141
        $possible = true;
142
        for ($x = 0; $x < $this->limit; $x++)
143
        {
144
            $index = floor($block / $this->sq) * $this->sq * $this->limit + $x % $this->sq + $this->limit * floor($x / $this->sq) + $this->sq * ($block % $this->sq);
145
            if (isset($this->sudoku[$index]) && $this->sudoku[$index] == $item)
146
            {
147
                $possible = false;
148
            }
149
        }
150
151
        return $possible;
152
    }
153
154
    /**
155
     * Determine if this item ok to place here
156
     *
157
     * @param $cell
158
     * @param $item
159
     * @return bool
160
     */
161
    private function is_possible_number($cell, $item)
162
    {
163
        $row   = $this->return_row($cell);
164
        $col   = $this->return_col($cell);
165
        $block = $this->return_block($cell);
166
167
        return ($this->is_possible_row($item, $row) && $this->is_possible_col($item, $col) && $this->is_possible_block($item, $block));
168
    }
169
170
    /**
171
     * Check if the row is unique
172
     *
173
     * @param int $row
174
     * @return bool
175
     */
176
    private function is_correct_row($row)
177
    {
178
        $row_temp = array();
179
        for ($x = 0; $x < $this->limit; $x++)
180
        {
181
            if(!isset($this->sudoku[$row * $this->limit + $x]))
182
            {
183
                $this->sudoku[$row * $this->limit + $x] = null;
184
            }
185
            $row_temp[$x] = $this->sudoku[$row * $this->limit + $x];
186
        }
187
188
        return count(array_diff($this->chars, $row_temp)) == 0;
189
    }
190
191
    /**
192
     * Check if the column is unique
193
     *
194
     * @param int $col
195
     * @return bool
196
     */
197
    private function is_correct_col($col)
198
    {
199
        $col_temp = array();
200
        for ($x = 0; $x < $this->limit; $x++)
201
        {
202
            $col_temp[$x] = $this->sudoku[$col + $x * $this->limit];
203
        }
204
        return count(array_diff($this->chars, $col_temp)) == 0;
205
    }
206
207
    /**
208
     * Check if the block is unique
209
     *
210
     * @param int $block
211
     * @return bool
212
     */
213
    private function is_correct_block($block)
214
    {
215
        $block_temp = array();
216
        for ($x = 0; $x < $this->limit; $x++)
217
        {
218
            $lookingfor = floor($block / $this->sq) * ($this->sq * $this->limit) + ($x % $this->sq) + $this->limit * floor($x / $this->sq) + $this->sq * ($block % $this->sq);
219
220
            if (!isset($this->sudoku[$lookingfor]))
221
            {
222
                $this->sudoku[$lookingfor] = null;
223
            }
224
225
            $block_temp[$x] = $this->sudoku[$lookingfor];
226
        }
227
        return count(array_diff($this->chars, $block_temp)) == 0;
228
    }
229
230
    /**
231
     * Determine if the sudoku is created successfully
232
     *
233
     * @return bool
234
     */
235
    private function is_solved_sudoku()
236
    {
237
        for ($x = 0; $x < $this->limit; $x++)
238
        {
239
            if (!$this->is_correct_block($x) or !$this->is_correct_row($x) or !$this->is_correct_col($x))
240
            {
241
                return false;
242
            }
243
        }
244
        return true;
245
    }
246
247
    /**
248
     * Find possible items
249
     *
250
     * @param int $cell
251
     * @return array
252
     */
253
    private function determine_possible_values($cell)
254
    {
255
        $possible = array();
256
        for ($x = 0; $x < $this->limit; $x++)
257
        {
258
            if ($this->is_possible_number($cell, $this->chars[$x]))
259
            {
260
                array_unshift($possible, $this->chars[$x]);
261
            }
262
        }
263
264
        return $possible;
265
    }
266
267
    /**
268
     * Random item from the possible item box
269
     *
270
     * @param array $possible
271
     * @param int   $cell
272
     * @return string
273
     */
274
    private function determine_random_possible_value($possible, $cell)
275
    {
276
        return $possible[$cell][rand(0, count($possible[$cell]) - 1)];
277
    }
278
279
    /**
280
     * Determine if everything goes well
281
     *
282
     * @return false|array
283
     */
284
    private function scan_sudoku_for_unique()
285
    {
286
        $possible = false;
287
        for ($x = 0; $x < $this->limit * $this->limit; $x++)
288
        {
289
            if (!isset($this->sudoku[$x]))
290
            {
291
                $possible[$x] = $this->determine_possible_values($x);
292
                if (count($possible[$x]) == 0)
293
                {
294
                    return false;
295
                }
296
            }
297
        }
298
299
        return $possible;
300
    }
301
302
    /**
303
     * Remove used item
304
     *
305
     * @param array  $attempt_array
306
     * @param string $item
307
     * @return array
308
     */
309
    private function remove_attempt($attempt_array, $item)
310
    {
311
        $new_array = array();
312
        $count     = count($attempt_array);
313
        for ($x = 0; $x < $count; $x++)
314
        {
315
            if ($attempt_array[$x] != $item)
316
            {
317
                array_unshift($new_array, $attempt_array[$x]);
318
            }
319
        }
320
        return $new_array;
321
    }
322
323
    /**
324
     * Next move
325
     *
326
     * @param $possible
327
     * @return int|null
328
     */
329
    private function next_random($possible)
330
    {
331
        $min_choices = null;
332
        $max         = $this->limit;
333
        for ($x = 0; $x < $this->limit * $this->limit; $x++)
334
        {
335
            if (!isset($possible[$x]))
336
            {
337
                $possible[$x] = null;
338
            }
339
340
            if ((count($possible[$x]) <= $max) && (count($possible[$x]) > 0))
341
            {
342
                $max         = count($possible[$x]);
343
                $min_choices = $x;
344
            }
345
        }
346
        return $min_choices;
347
    }
348
349
    /**
350
     * Basically prepares the variables we are going to use
351
     */
352
    private function build()
353
    {
354
        $this->sudoku = array();
355
        $this->chars  = array();
356
357
        for ($i = 0; $i < $this->limit; $i++)
358
        {
359
            $this->chars[] = $this->stack[$i];
360
        }
361
    }
362
363
364
    /**
365
     * Microtime to calculate time difference
366
     *
367
     * @return float
368
     */
369
    public function microtime()
370
    {
371
        return microtime(true);
372
    }
373
374
    /**
375
     * Kickstarter function
376
     *
377
     * @param string $to
378
     * @return array|string
379
     */
380
    public function generate($to = 'json')
381
    {
382
        $start     = $this->microtime();
383
384
        $this->build();
385
386
        $x         = 0;
387
        $saved     = array();
388
        $saved_sud = array();
389
        while (!$this->is_solved_sudoku())
390
        {
391
            $x++;
392
            $scan = $this->scan_sudoku_for_unique();
393
394
            if ($scan === false)
395
            {
396
                $next_move    = array_pop($saved);
397
                $this->sudoku = array_pop($saved_sud);
398
            } else {
399
                $next_move    = (array) $scan;
400
            }
401
402
            $what_to_try = $this->next_random($next_move);
403
            $attempt     = $this->determine_random_possible_value($next_move, $what_to_try);
404
405
            if (count($next_move[$what_to_try]) > 1)
406
            {
407
                $next_move[$what_to_try] = $this->remove_attempt($next_move[$what_to_try], $attempt);
408
                array_push($saved, $next_move);
409
                array_push($saved_sud, $this->sudoku);
410
            }
411
            $this->sudoku[$what_to_try] = $attempt;
412
        }
413
414
        $timing       = $this->microtime() - $start;
415
        $this->result = array(
416
            'created_in' => round($timing,2),
417
            'timestamp'  => time(),
418
            'difficulty' => 'N/A'
419
        );
420
421
        return $this->to($to);
422
    }
423
424
    /**
425
     * Prepares the defaults of the returned value
426
     *
427
     * @return array
428
     */
429
    private function array2export()
430
    {
431
        return array(
432
            'sudoku' => $this->sudoku,
433
            'limit'  => $this->limit,
434
            'result' => $this->result,
435
        );
436
    }
437
438
    /**
439
     * Return type. JSON, SERIALIZED or an ARRAY
440
     *
441
     * @param string $to
442
     * @return array|string
443
     */
444
    public function to($to)
445
    {
446
447
        if($to == 'json')
448
        {
449
            return json_encode($this->array2export());
450
        }
451
        else if($to == 'serialize')
452
        {
453
            return serialize($this->array2export());
454
        }
455
        // else if($to == 'array')
456
        return (array) $this->array2export();
457
    }
458
}