Issues (11)

puzzles/Sudoku/Sudoku.php (1 issue)

1
<?php declare(strict_types=1);
2
3
namespace Stratadox\PuzzleSolver\Puzzle\Sudoku;
4
5
use Stratadox\PuzzleSolver\Moves;
6
use function assert;
7
8
final class Sudoku
9
{
10
    /** @var int[][]|null[][] */
11
    private $numbers;
12
    /** @var bool[][] */
13
    private $rows = [];
14
    /** @var bool[][] */
15
    private $cols = [];
16
    /** @var bool[][][] */
17
    private $boxes = [];
18
    /** @var int[]|null */
19
    private $firstEmptyPosition;
20
21
    public function __construct(array ...$numbers)
22
    {
23
        $this->numbers = $numbers;
24
        foreach ($numbers as $r => $row) {
25
            foreach ($row as $c => $number) {
26
                if (null !== $number) {
27
                    $this->rows[$r][$number] = true;
28
                    $this->cols[$c][$number] = true;
29
                    $this->boxes[$r / 3][$c / 3][$number] = true;
30
                } elseif (null === $this->firstEmptyPosition) {
31
                    $this->firstEmptyPosition = [$r, $c];
32
                }
33
            }
34
        }
35
    }
36
37
    public function isFull(): bool
38
    {
39
        return null === $this->firstEmptyPosition;
40
    }
41
42
    public function possibilitiesForNextEmptyPosition(): Moves
43
    {
44
        if (null === $this->firstEmptyPosition) {
45
            return Moves::none();
46
        }
47
        [$r, $c] = $this->firstEmptyPosition;
48
        return Entry::allPossibilitiesFor($r, $c)->filterWith(function (Entry $entry) {
0 ignored issues
show
Bug Best Practice introduced by
The expression return Stratadox\PuzzleS...ion(...) { /* ... */ }) returns the type Stratadox\Collection\Collection which includes types incompatible with the type-hinted return Stratadox\PuzzleSolver\Moves.
Loading history...
49
            return $this->allows($entry);
50
        });
51
    }
52
53
    // For immutable sudoku puzzle implementations
54
    public function withFilledIn(Entry ...$entries): self
55
    {
56
        $numbers = $this->numbers;
57
        foreach ($entries as $entry) {
58
            assert($this->allows($entry));
59
            $numbers[$entry->row()][$entry->column()] = $entry->number();
60
        }
61
        return new self(...$numbers);
62
    }
63
64
    public function __toString(): string
65
    {
66
        $sudoku = "\n+-------+-------+-------+ \n";
67
        foreach ($this->numbers as $r => $row) {
68
            $sudoku .= '| ';
69
            foreach ($row as $c => $value) {
70
                $sudoku .= ($value ?: ' ') . ' ';
71
                if ($c % 3 === 2) {
72
                    $sudoku .= '| ';
73
                }
74
            }
75
            $sudoku .= "\n";
76
            if ($r % 3 === 2) {
77
                $sudoku .= "+-------+-------+-------+ \n";
78
            }
79
        }
80
        return $sudoku;
81
    }
82
83
    private function allows(Entry $entry): bool
84
    {
85
        return null === $this->numbers[$entry->row()][$entry->column()]
86
            && $this->canAddInRow($entry->row(), $entry->number())
87
            && $this->canAddInColumn($entry->column(), $entry->number())
88
            && $this->canAddInRegion($entry->row(), $entry->column(), $entry->number());
89
    }
90
91
    private function canAddInRow(int $row, int $number): bool
92
    {
93
        return !isset($this->rows[$row][$number]);
94
    }
95
96
    private function canAddInColumn(int $column, int $number): bool
97
    {
98
        return !isset($this->cols[$column][$number]);
99
    }
100
101
    private function canAddInRegion(int $row, int $column, int $number): bool
102
    {
103
        return !isset($this->boxes[$row / 3][$column / 3][$number]);
104
    }
105
}
106