1 | <?php declare(strict_types=1); |
||
2 | |||
3 | use Stratadox\PuzzleSolver\Find; |
||
4 | use Stratadox\PuzzleSolver\NoSolution; |
||
5 | use Stratadox\PuzzleSolver\PuzzleDescription; |
||
6 | use Stratadox\PuzzleSolver\PuzzleFactory; |
||
7 | use Stratadox\PuzzleSolver\PuzzleSolverFactory; |
||
8 | use Stratadox\PuzzleSolver\Renderer\MovesToFileRenderer; |
||
9 | use Stratadox\PuzzleSolver\Renderer\PuzzleStatesToFileRenderer; |
||
10 | use Stratadox\PuzzleSolver\SearchSettings; |
||
11 | use Stratadox\PuzzleSolver\SolutionRenderer; |
||
12 | use Stratadox\PuzzleSolver\Solutions; |
||
13 | |||
14 | require dirname(__DIR__) . '/vendor/autoload.php'; |
||
15 | |||
16 | const OUT = 'php://stdout'; |
||
17 | |||
18 | $directory = dirname(__DIR__) . str_replace( |
||
19 | '/', |
||
20 | DIRECTORY_SEPARATOR, |
||
21 | '/sample/levels/%s/' |
||
22 | ); |
||
23 | |||
24 | $json = file_get_contents(dirname(__DIR__) . '/sample/puzzles.json'); |
||
25 | $puzzleInfo = gatherPuzzleData(json_decode($json, true), $directory); |
||
26 | |||
27 | $puzzleName = choosePuzzle($puzzleInfo); |
||
28 | echo PHP_EOL . 'Selected puzzle: ' . $puzzleName . PHP_EOL . PHP_EOL; |
||
29 | $levelTag = chooseLevel($puzzleInfo[$puzzleName]); |
||
30 | $puzzleData = $puzzleInfo[$puzzleName][$levelTag]; |
||
31 | $level = file_get_contents($puzzleData['levelFile']); |
||
32 | if (!empty($level)) { |
||
33 | echo 'The chosen level is: ' . PHP_EOL . $level . PHP_EOL . PHP_EOL; |
||
34 | } |
||
35 | |||
36 | $settings = chooseSettings(); |
||
37 | $solutions = solvePuzzle( |
||
38 | $puzzleData['factory'], |
||
39 | $puzzleData['description'], |
||
40 | $settings, |
||
41 | $level |
||
42 | ); |
||
43 | render($solutions, $puzzleData['renderer']); |
||
44 | |||
45 | function gatherPuzzleData(array $puzzleData, string $directory): array |
||
46 | { |
||
47 | $factories = []; |
||
48 | foreach ($puzzleData['factories'] as $type => $factory) { |
||
49 | assert(is_callable($factory)); |
||
50 | $factories[$type] = $factory(); |
||
51 | } |
||
52 | |||
53 | $sep = str_repeat(PHP_EOL, 40); |
||
54 | $time = 600000; |
||
55 | $puzzleInfo = []; |
||
56 | foreach ($puzzleData['puzzles'] as $puzzle) { |
||
57 | $dir = sprintf($directory, $puzzle['type']); |
||
58 | $description = new PuzzleDescription( |
||
59 | Find::fromString($puzzle['find']), |
||
60 | $puzzle['variable_cost'] ?? false, |
||
61 | $puzzle['exhausting'] ?? false, |
||
62 | isset($puzzle['heuristic']) ? new $puzzle['heuristic']() : null |
||
63 | ); |
||
64 | $renderer = ($puzzle['display'] ?? 'states') === 'states' ? |
||
65 | PuzzleStatesToFileRenderer::fromFilenameAndSeparator(OUT, $sep, $time) : |
||
66 | MovesToFileRenderer::fromFilenameAndSeparator(OUT, $sep, $time); |
||
67 | |||
68 | $puzzleInfo[$puzzle['name']] = []; |
||
69 | foreach (scandir($dir) as $file) { |
||
70 | if (substr($file, -4) !== '.txt') { |
||
71 | continue; |
||
72 | } |
||
73 | $puzzleInfo[$puzzle['name']][$file[0]] = [ |
||
74 | 'levelFile' => $dir . $file, |
||
75 | 'factory' => $factories[$puzzle['type']], |
||
76 | 'description' => $description, |
||
77 | 'renderer' => $renderer, |
||
78 | 'isDefault' => (($puzzle['default'] ?? '') . '.txt') === $file, |
||
79 | 'isTheOnlyOne' => (bool) ($puzzle['level'] ?? false), |
||
80 | ]; |
||
81 | } |
||
82 | } |
||
83 | return $puzzleInfo; |
||
84 | } |
||
85 | |||
86 | function choosePuzzle(array $puzzleInfo): string |
||
87 | { |
||
88 | $puzzleChoices = array_keys($puzzleInfo); |
||
89 | $welcome = [ |
||
90 | 'Welcome to the universal puzzle solver!', |
||
91 | 'The following puzzles are installed:' |
||
92 | ]; |
||
93 | foreach ($puzzleChoices as $n => $name) { |
||
94 | $welcome[] = sprintf('Type %d: %s', $n + 1, $name); |
||
95 | } |
||
96 | |||
97 | echo implode(PHP_EOL, $welcome) . PHP_EOL . PHP_EOL; |
||
98 | |||
99 | do { |
||
100 | $puzzleChoice = (int) (readline('Which puzzle would you like to solve? ')) - 1; |
||
101 | } while (!isset($puzzleChoices[$puzzleChoice])); |
||
102 | |||
103 | return $puzzleChoices[$puzzleChoice]; |
||
104 | } |
||
105 | |||
106 | function chooseLevel(array $puzzleInfo): string |
||
107 | { |
||
108 | $welcome = [ |
||
109 | 'The following levels are available:' |
||
110 | ]; |
||
111 | foreach ($puzzleInfo as $letter => $puzzleDate) { |
||
112 | if ($puzzleDate['isTheOnlyOne']) { |
||
113 | return $letter; |
||
114 | } |
||
115 | $n = str_replace(' ', '', substr(basename($puzzleDate['levelFile']), 2, -4)); |
||
116 | $welcome[] = sprintf('%s: %s', $letter, $n); |
||
117 | } |
||
118 | |||
119 | echo implode(PHP_EOL, $welcome) . PHP_EOL . PHP_EOL; |
||
120 | |||
121 | do { |
||
122 | $levelChoice = strtoupper(readline('Which level would you like to solve? ')); |
||
123 | if (!isset($puzzleInfo[$levelChoice])) { |
||
124 | echo sprintf('Level %s not found!', $levelChoice) . PHP_EOL; |
||
125 | $levelChoice = ''; |
||
126 | } |
||
127 | } while (strlen($levelChoice) !== 1); |
||
128 | |||
129 | return $levelChoice; |
||
130 | } |
||
131 | |||
132 | function chooseSettings() |
||
133 | { |
||
134 | $visual = readline('Would you like to visualise the search? [Y]/n '); |
||
135 | if ($visual !== '' && strtolower($visual) !== 'y') { |
||
136 | return SearchSettings::defaults(); |
||
137 | } |
||
138 | return new SearchSettings(OUT, str_repeat(PHP_EOL, 40), 80000); |
||
139 | } |
||
140 | |||
141 | function solvePuzzle( |
||
142 | PuzzleFactory $puzzleFactory, |
||
143 | PuzzleDescription $description, |
||
144 | SearchSettings $settings, |
||
145 | string $level |
||
146 | ): Solutions { |
||
147 | $puzzle = $puzzleFactory->fromString($level); |
||
148 | |||
149 | $solver = PuzzleSolverFactory::make() |
||
150 | ->forAPuzzleWith($description, $settings); |
||
151 | |||
152 | echo PHP_EOL . 'Solving the puzzle...' . PHP_EOL; |
||
153 | try { |
||
154 | return $solver->solve($puzzle); |
||
155 | } catch (NoSolution $exception) { |
||
156 | die($exception->getMessage()); |
||
0 ignored issues
–
show
|
|||
157 | } |
||
158 | } |
||
159 | |||
160 | function render(Solutions $solutions, SolutionRenderer $renderer) |
||
161 | { |
||
162 | $nl = str_repeat(PHP_EOL, 40); |
||
163 | echo $nl . 'Solved!' . str_repeat(PHP_EOL, 5); |
||
164 | sleep(1); |
||
165 | if (count($solutions) === 1) { |
||
166 | $renderer->render($solutions[0]); |
||
167 | echo $nl . $solutions[0]->state()->representation(); |
||
168 | } else { |
||
169 | foreach ($solutions as $i => $solution) { |
||
170 | echo sprintf('%sSolution %d: %s', $nl, $i + 1, str_repeat(PHP_EOL, 5)); |
||
171 | sleep(1); |
||
172 | echo $nl; |
||
173 | $renderer->render($solution); |
||
174 | echo $nl . $solution->state()->representation(); |
||
175 | } |
||
176 | } |
||
177 | } |
||
178 |
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: