1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace Stratadox\PuzzleSolver\SearchStrategy; |
4
|
|
|
|
5
|
|
|
use Stratadox\PuzzleSolver\Move; |
6
|
|
|
use Stratadox\PuzzleSolver\Puzzle; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Duplicate Node Skipper |
10
|
|
|
* |
11
|
|
|
* Duplicate Node Skipper prevents paths from getting into loops. |
12
|
|
|
* Before considering a new candidate, all previous puzzle states are compared |
13
|
|
|
* with the new state. If the new puzzle state was already reached on this very |
14
|
|
|
* path, the candidate is considered a loop and gets rejected. |
15
|
|
|
* |
16
|
|
|
* Decorator for other search strategies. |
17
|
|
|
* |
18
|
|
|
* @author Stratadox |
19
|
|
|
*/ |
20
|
|
|
final class DuplicateNodeSkipper implements SearchStrategy |
21
|
|
|
{ |
22
|
|
|
/** @var SearchStrategy */ |
23
|
|
|
private $search; |
24
|
|
|
/** @var Puzzle */ |
25
|
|
|
private $puzzle; |
26
|
|
|
|
27
|
|
|
public function __construct(SearchStrategy $search, Puzzle $puzzle) |
28
|
|
|
{ |
29
|
|
|
$this->search = $search; |
30
|
|
|
$this->puzzle = $puzzle; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
public static function forThe(Puzzle $puzzle, SearchStrategy $search): SearchStrategy |
34
|
|
|
{ |
35
|
|
|
return new self($search, $puzzle); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
public function isOngoing(): bool |
39
|
|
|
{ |
40
|
|
|
return $this->search->isOngoing(); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
public function consider(Puzzle $puzzle): bool |
44
|
|
|
{ |
45
|
|
|
if ($this->shouldSkip($this->puzzle, $puzzle->representation(), ...$puzzle->movesSoFar())) { |
46
|
|
|
return false; |
47
|
|
|
} |
48
|
|
|
return $this->search->consider($puzzle); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
public function nextCandidate(): Puzzle |
52
|
|
|
{ |
53
|
|
|
return $this->search->nextCandidate(); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
private function shouldSkip( |
57
|
|
|
Puzzle $visited, |
58
|
|
|
string $current, |
59
|
|
|
Move ...$moves |
60
|
|
|
): bool { |
61
|
|
|
foreach ($moves as $move) { |
62
|
|
|
if ($visited->representation() === $current) { |
63
|
|
|
return true; |
64
|
|
|
} |
65
|
|
|
$visited = $visited->afterMaking($move); |
66
|
|
|
} |
67
|
|
|
return false; |
68
|
|
|
} |
69
|
|
|
} |
70
|
|
|
|