Passed
Push — master ( 128fe0...fa7a2a )
by Nico
10:40
created

ShipMover   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 337
Duplicated Lines 0 %

Test Coverage

Coverage 32.93%

Importance

Changes 0
Metric Value
eloc 146
c 0
b 0
f 0
dl 0
loc 337
ccs 55
cts 167
cp 0.3293
rs 8.96
wmc 43

12 Methods

Rating   Name   Duplication   Size   Complexity  
A leaveFleetIfNotFleetLeader() 0 5 4
A moveShipsByOneField() 0 37 4
A alertReactionCheck() 0 24 5
A saveShips() 0 11 4
A initTractoredShips() 0 16 3
A addInformationMerge() 0 3 1
A __construct() 0 8 1
A initWrappers() 0 8 3
A areAllShipsDestroyed() 0 3 1
A checkAndMove() 0 43 3
B travelFlightRoute() 0 71 8
B postFlightInformations() 0 54 6

How to fix   Complexity   

Complex Class

Complex classes like ShipMover often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShipMover, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Stu\Module\Spacecraft\Lib\Movement;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\Common\Collections\Collection;
7
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Stu\Lib\Information\InformationWrapper;
9
use Stu\Module\PlayerSetting\Lib\UserEnum;
0 ignored issues
show
Bug introduced by
The type Stu\Module\PlayerSetting\Lib\UserEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Stu\Module\Spacecraft\Lib\Battle\AlertDetection\AlertReactionFacadeInterface;
11
use Stu\Module\Ship\Lib\Fleet\LeaveFleetInterface;
12
use Stu\Module\Spacecraft\Lib\Message\MessageCollectionInterface;
13
use Stu\Module\Spacecraft\Lib\Message\MessageFactoryInterface;
14
use Stu\Module\Spacecraft\Lib\Movement\Component\PreFlight\ConditionCheckResult;
15
use Stu\Module\Spacecraft\Lib\Movement\Component\PreFlight\PreFlightConditionsCheckInterface;
16
use Stu\Module\Spacecraft\Lib\Movement\Route\FlightRouteInterface;
17
use Stu\Module\Spacecraft\Lib\SpacecraftWrapperInterface;
18
use Stu\Orm\Entity\ShipInterface;
19
use Stu\Orm\Entity\SpacecraftInterface;
20
use Stu\Orm\Repository\SpacecraftRepositoryInterface;
21
22
//TODO unit tests
23
final class ShipMover implements ShipMoverInterface
24
{
25 2
    public function __construct(
26
        private SpacecraftRepositoryInterface $spacecraftRepository,
27
        private ShipMovementInformationAdderInterface $shipMovementInformationAdder,
28
        private PreFlightConditionsCheckInterface $preFlightConditionsCheck,
29
        private LeaveFleetInterface $leaveFleet,
30
        private AlertReactionFacadeInterface $alertReactionFacade,
31
        private MessageFactoryInterface $messageFactory
32 2
    ) {}
33
34 1
    #[Override]
35
    public function checkAndMove(
36
        SpacecraftWrapperInterface $leadWrapper,
37
        FlightRouteInterface $flightRoute
38
    ): MessageCollectionInterface {
39
40 1
        $messages = $this->messageFactory->createMessageCollection();
41
42 1
        $leadSpacecraft = $leadWrapper->get();
43 1
        $leadSpacecraftName = $leadSpacecraft->getName();
44 1
        $isFleetMode = $leadSpacecraft instanceof ShipInterface ? $leadSpacecraft->isFleetLeader() : false;
45
46 1
        $wrappers = $this->initWrappers($leadWrapper, $isFleetMode);
47 1
        $initialTractoredShips = $this->initTractoredShips($wrappers);
48
49
        // fly until destination arrived
50 1
        $hasTravelled = $this->travelFlightRoute(
51 1
            $leadWrapper,
52 1
            $wrappers,
53 1
            $isFleetMode,
54 1
            $flightRoute,
55 1
            $messages
56 1
        );
57
58
        //skip save and log info if flight did not happen
59 1
        if (!$hasTravelled) {
60 1
            return $messages;
61
        }
62
63
        // save all ships
64
        $this->saveShips($wrappers, $initialTractoredShips);
65
66
        // add post flight informations
67
        $this->postFlightInformations(
68
            $leadWrapper,
69
            $leadSpacecraftName,
70
            $wrappers,
71
            $flightRoute,
72
            $isFleetMode,
73
            $messages
74
        );
75
76
        return $messages;
77
    }
78
79
    /** @return Collection<int, covariant SpacecraftWrapperInterface> */
80 1
    private function initWrappers(SpacecraftWrapperInterface $leadWrapper, bool $isFleetMode): Collection
81
    {
82 1
        $fleetWrapper = $leadWrapper->getFleetWrapper();
83
84 1
        return
85 1
            $isFleetMode && $fleetWrapper !== null
86
            ? $fleetWrapper->getShipWrappers()
87 1
            : new ArrayCollection([$leadWrapper->get()->getId() => $leadWrapper]);
88
    }
89
90
    /** @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers */
0 ignored issues
show
Bug introduced by
The type Stu\Module\Spacecraft\Lib\Movement\covariant was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
91 1
    private function travelFlightRoute(
92
        SpacecraftWrapperInterface $leadWrapper,
93
        Collection $wrappers,
94
        bool $isFleetMode,
95
        FlightRouteInterface $flightRoute,
96
        MessageCollectionInterface $messages
97
    ): bool {
98
99 1
        $hasTravelled = false;
100 1
        $fleetWrapper = $leadWrapper->getFleetWrapper();
101 1
        $hasToLeaveFleet = $fleetWrapper !== null && !$isFleetMode;
102
103 1
        $isFixedFleetMode = $isFleetMode
104 1
            && $fleetWrapper !== null
105 1
            && $fleetWrapper->get()->isFleetFixed();
106
107 1
        while (!$flightRoute->isDestinationArrived()) {
108 1
            $nextWaypoint = $flightRoute->getNextWaypoint();
109
110
            // nächstes Feld nicht passierbar
111 1
            if (!$nextWaypoint->getFieldType()->getPassable()) {
112
                $flightRoute->abortFlight();
113
                $messages->addInformation('Das nächste Feld kann nicht passiert werden');
114
                break;
115
            }
116
117 1
            $activeWrappers = $wrappers->filter(fn(SpacecraftWrapperInterface $wrapper): bool => !$wrapper->get()->isDestroyed());
118
119
            // check all flight pre conditions
120 1
            $conditionCheckResult = $this->preFlightConditionsCheck->checkPreconditions(
121 1
                $leadWrapper,
122 1
                $activeWrappers->toArray(),
123 1
                $flightRoute,
124 1
                $isFixedFleetMode
125 1
            );
126
127 1
            if (!$conditionCheckResult->isFlightPossible()) {
128 1
                $flightRoute->abortFlight();
129 1
                $messages->addInformation('Der Weiterflug wurde aus folgenden Gründen abgebrochen:');
130 1
                $this->addInformationMerge($conditionCheckResult->getInformations(), $messages);
131 1
                break;
132
            }
133
134
            $this->addInformationMerge($conditionCheckResult->getInformations(), $messages);
135
136
            $movedTractoredShipWrappers = [];
137
138
            // move every possible ship by one field
139
            $this->moveShipsByOneField(
140
                $activeWrappers,
141
                $flightRoute,
142
                $conditionCheckResult,
143
                $hasToLeaveFleet,
144
                $hasTravelled,
145
                $messages
146
            );
147
148
            // alert reaction check
149
            $this->alertReactionCheck(
150
                $leadWrapper,
151
                $movedTractoredShipWrappers,
152
                $messages
153
            );
154
155
            if ($this->areAllShipsDestroyed($activeWrappers)) {
156
                $flightRoute->abortFlight();
157
                $messages->addInformation('Es wurden alle Schiffe zerstört');
158
            }
159
        }
160
161 1
        return $hasTravelled;
162
    }
163
164
    /**
165
     * @param Collection<int, SpacecraftWrapperInterface> $activeWrappers
166
     */
167
    private function moveShipsByOneField(
168
        Collection $activeWrappers,
169
        FlightRouteInterface $flightRoute,
170
        ConditionCheckResult $conditionCheckResult,
171
        bool $hasToLeaveFleet,
172
        bool &$hasTravelled,
173
        MessageCollectionInterface $messages
174
    ): void {
175
176
        foreach ($activeWrappers as $wrapper) {
177
178
            $ship = $wrapper->get();
179
180
            if ($conditionCheckResult->isNotBlocked($ship)) {
181
182
                $this->leaveFleetIfNotFleetLeader($ship, $hasToLeaveFleet, $messages);
183
184
                $flightRoute->enterNextWaypoint(
185
                    $wrapper,
186
                    $messages
187
                );
188
189
                $tractoredShipWrapper = $wrapper->getTractoredShipWrapper();
190
                if ($tractoredShipWrapper !== null) {
191
                    $flightRoute->enterNextWaypoint(
192
                        $tractoredShipWrapper,
193
                        $messages
194
                    );
195
196
                    $movedTractoredShipWrappers[] = [$wrapper->get(), $tractoredShipWrapper];
197
                }
198
199
                $hasTravelled = true;
200
            }
201
        }
202
203
        $flightRoute->stepForward();
204
    }
205
206
    /** @param array<array{0: ShipInterface, 1: SpacecraftWrapperInterface}> $movedTractoredShipWrappers */
207
    private function alertReactionCheck(
208
        SpacecraftWrapperInterface $leadWrapper,
209
        array $movedTractoredShipWrappers,
210
        MessageCollectionInterface $messages
211
    ): void {
212
        $alertRedInformations = new InformationWrapper();
213
        $this->alertReactionFacade->doItAll($leadWrapper, $alertRedInformations);
214
215
        if (!$alertRedInformations->isEmpty()) {
216
            $this->addInformationMerge($alertRedInformations->getInformations(), $messages);
217
        }
218
219
        // alert red check for tractored ships
220
        foreach ($movedTractoredShipWrappers as [$tractoringShip, $tractoredShipWrapper]) {
221
            if (!$tractoringShip->isDestroyed()) {
222
                $alertRedInformations = new InformationWrapper();
223
                $this->alertReactionFacade->doItAll(
224
                    $tractoredShipWrapper,
225
                    $alertRedInformations,
226
                    $tractoringShip
227
                );
228
229
                if (!$alertRedInformations->isEmpty()) {
230
                    $this->addInformationMerge($alertRedInformations->getInformations(), $messages);
231
                }
232
            }
233
        }
234
    }
235
236
    /**
237
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
238
     *
239
     * @return array<ShipInterface>
240
     */
241 1
    private function initTractoredShips(Collection $wrappers): array
242
    {
243 1
        $tractoredShips = [];
244
245 1
        foreach ($wrappers as $fleetShipWrapper) {
246 1
            $fleetShip = $fleetShipWrapper->get();
247
248 1
            $tractoredShip = $fleetShip->getTractoredShip();
249
            if (
250 1
                $tractoredShip !== null
251
            ) {
252
                $tractoredShips[] = $tractoredShip;
253
            }
254
        }
255
256 1
        return $tractoredShips;
257
    }
258
259
    private function leaveFleetIfNotFleetLeader(SpacecraftInterface $ship, bool $hasToLeaveFleet, MessageCollectionInterface $messages): void
260
    {
261
        if ($hasToLeaveFleet && $ship instanceof ShipInterface) {
262
            if ($this->leaveFleet->leaveFleet($ship)) {
263
                $messages->addInformation(sprintf('Die %s hat die Flotte verlassen', $ship->getName()));
264
            }
265
        }
266
    }
267
268
    /**
269
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
270
     * @param array<ShipInterface> $initialTractoredShips
271
     */
272
    private function saveShips(Collection $wrappers, array $initialTractoredShips): void
273
    {
274
        foreach ($wrappers as $wrapper) {
275
            $ship = $wrapper->get();
276
            if (!$ship->isDestroyed()) {
277
                $this->spacecraftRepository->save($ship);
278
            }
279
        }
280
281
        foreach ($initialTractoredShips as $tractoredShip) {
282
            $this->spacecraftRepository->save($tractoredShip);
283
        }
284
    }
285
286
    /**
287
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
288
     */
289
    private function postFlightInformations(
290
        SpacecraftWrapperInterface $leadWrapper,
291
        string $leadSpacecraftName,
292
        Collection $wrappers,
293
        FlightRouteInterface $flightRoute,
294
        bool $isFleetMode,
295
        MessageCollectionInterface $messages
296
    ): void {
297
298
        //add tractor info
299
        foreach ($wrappers as $wrapper) {
300
            $ship = $wrapper->get();
301
302
            $tractoredShip = $ship->getTractoredShip();
303
            if ($tractoredShip !== null) {
304
                $this->shipMovementInformationAdder->pulledTractoredShip(
305
                    $ship,
306
                    $tractoredShip,
307
                    $flightRoute->getRouteMode(),
308
                    $messages
309
                );
310
            }
311
        }
312
313
        $leadSpacecraft = $leadWrapper->get();
314
315
        //add destination info
316
        if ($this->areAllShipsDestroyed($wrappers)) {
317
            $this->shipMovementInformationAdder->reachedDestinationDestroyed(
318
                $leadSpacecraft,
319
                $leadSpacecraftName,
320
                $isFleetMode,
321
                $flightRoute->getRouteMode(),
322
                $messages
323
            );
324
        } else {
325
            $this->shipMovementInformationAdder->reachedDestination(
326
                $leadSpacecraft,
327
                $isFleetMode,
328
                $flightRoute->getRouteMode(),
329
                $messages
330
            );
331
        }
332
333
        //add info about anomalies
334
        foreach ($leadWrapper->get()->getLocation()->getAnomalies() as $anomaly) {
335
            $messages->addInformation(sprintf(
336
                '[b][color=yellow]In diesem Sektor befindet sich eine %s-Anomalie[/color][/b]',
337
                $anomaly->getAnomalyType()->getName()
338
            ));
339
        }
340
        // add info about buyos
341
        foreach ($leadWrapper->get()->getLocation()->getBuoys() as $buoy) {
342
            $messages->addInformation(sprintf('[b][color=yellow]Boje entdeckt: [/color][/b]%s', $buoy->getText()));
343
        }
344
    }
345
346
    /**
347
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
348
     */
349
    private function areAllShipsDestroyed(Collection $wrappers): bool
350
    {
351
        return !$wrappers->exists(fn(int $key, SpacecraftWrapperInterface $wrapper): bool => !$wrapper->get()->isDestroyed());
352
    }
353
354
    /**
355
     * @param array<string> $value
356
     */
357 1
    private function addInformationMerge(array $value, MessageCollectionInterface $messages): void
358
    {
359 1
        $messages->add($this->messageFactory->createMessage(UserEnum::USER_NOONE, null, $value));
360
    }
361
}
362