Stratadox /
PuzzleSolver
| 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: