DigValidator::isUniquelySolvableAfterDigging()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 18
c 0
b 0
f 0
rs 9.8666
cc 4
nc 3
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace CoenMooij\Sudoku\Validator;
6
7
use CoenMooij\Sudoku\Exception\UnsolvableException;
8
use CoenMooij\Sudoku\Puzzle\Grid;
9
use CoenMooij\Sudoku\Puzzle\Location;
10
use CoenMooij\Sudoku\Solver\BacktrackSolver;
11
12
class DigValidator
13
{
14
    /**
15
     * @var BacktrackSolver
16
     */
17
    private $solver;
18
19
    public function __construct(BacktrackSolver $solver)
20
    {
21
        $this->solver = $solver;
22
    }
23
24
    public function isDiggableAndUniquelySolvableAfterDigging(Grid $grid, Location $location, int $bound): bool
25
    {
26
        return $this->isDiggable($grid, $location, $bound)
27
            && $this->isUniquelySolvableAfterDigging($grid, $location);
28
    }
29
30
    private function isDiggable(Grid $grid, Location $location, int $bound): bool
31
    {
32
        return !$grid->isEmpty($location)
33
            && $this->rowIsDiggable($grid, $location, $bound)
34
            && $this->columnIsDiggable($grid, $location, $bound)
35
            && $this->blockIsDiggable($grid, $location, $bound);
36
    }
37
38
    private function isUniquelySolvableAfterDigging(Grid $grid, Location $location): bool
39
    {
40
        $gridCopy = clone $grid;
41
        $gridCopy->empty($location);
42
        $possibleValues = $gridCopy->getAllPossibilitiesFor($location);
43
        if (count($possibleValues) > 1) {
44
            $possibleValues = array_diff($possibleValues, [$grid->get($location)]);
45
            foreach ($possibleValues as $possibleValue) {
46
                $gridCopy->set($location, $possibleValue);
47
                try {
48
                    $this->solver->solve($gridCopy);
49
                } catch (UnsolvableException $exception) {
50
                    return false;
51
                }
52
            }
53
        }
54
55
        return true;
56
    }
57
58
    private function rowIsDiggable(Grid $grid, Location $location, int $bound): bool
59
    {
60
        $row = $grid->getRow($location->getRow());
61
62
        return $this->sectionIsDiggable($row, $bound);
63
    }
64
65
    private function columnIsDiggable(Grid $grid, Location $location, int $bound): bool
66
    {
67
        $column = $grid->getColumn($location->getColumn());
68
69
        return $this->sectionIsDiggable($column, $bound);
70
    }
71
72
    private function blockIsDiggable(Grid $grid, Location $location, int $bound): bool
73
    {
74
        $block = $grid->getBlockAt($location);
75
76
        return $this->sectionIsDiggable($block, $bound);
77
    }
78
79
    private function sectionIsDiggable(array $values, int $bound): bool
80
    {
81
        return $this->numberOfNonEmptyValues($values) > $bound;
82
    }
83
84
    /**
85
     * @param int[] $values
86
     *
87
     * @return int
88
     */
89
    private function numberOfNonEmptyValues(array $values): int
90
    {
91
        return count(array_diff($values, [Grid::EMPTY_VALUE]));
92
    }
93
}
94