Issues (11)

sample/puzzle.php (2 issues)

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
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Stratadox\PuzzleSolver\Solutions. Consider adding a return statement or allowing null as return value.

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:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
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