Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Failed Conditions
Push — main ( d9cfb9...10f5c7 )
by Dan
32s queued 21s
created

Plotter::findDistanceToX()   D

Complexity

Conditions 20
Paths 40

Size

Total Lines 77
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 54
nc 40
nop 8
dl 0
loc 77
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php declare(strict_types=1);
2
3
use Smr\Exceptions\UserError;
4
use Smr\HardwareType;
5
use Smr\Path;
6
use Smr\PlotGroup;
7
use Smr\TradeGood;
8
use Smr\TransactionType;
9
10
class Plotter {
11
12
	public static function getX(PlotGroup $xType, int|string $X, int $gameID, AbstractSmrPlayer $player = null): mixed {
13
		// Special case for Location categories (i.e. Bar, HQ, SafeFed)
14
		if (!is_numeric($X)) {
15
			if ($xType != PlotGroup::Locations) {
16
				throw new Exception('Non-numeric X only exists for Locations');
17
			}
18
			return $X;
19
		}
20
21
		// In all other cases, X is either an int or a numeric string
22
		if (is_string($X)) {
23
			$X = str2int($X);
0 ignored issues
show
Bug introduced by
The function str2int was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

23
			$X = /** @scrutinizer ignore-call */ str2int($X);
Loading history...
24
		}
25
26
		// Helper function for plots to trade goods
27
		$getGoodWithTransaction = function(int $goodID) use ($xType, $player) {
28
			$good = TradeGood::get($goodID);
29
			if (isset($player) && !$player->meetsAlignmentRestriction($good->alignRestriction)) {
30
				throw new Exception('Player trying to access alignment-restricted good!');
31
			}
32
			return [
33
				'Type' => 'Good',
34
				'GoodID' => $goodID,
35
				'TransactionType' => TransactionType::from(explode(' ', $xType->value)[0]),
36
			];
37
		};
38
39
		return match ($xType) {
40
			PlotGroup::Technology => HardwareType::get($X),
41
			PlotGroup::Ships => SmrShipType::get($X),
42
			PlotGroup::Weapons => SmrWeaponType::getWeaponType($X),
43
			PlotGroup::Locations => SmrLocation::getLocation($gameID, $X),
44
			PlotGroup::SellGoods, PlotGroup::BuyGoods => $getGoodWithTransaction($X),
45
			PlotGroup::Galaxies => SmrGalaxy::getGalaxy($gameID, $X), // $X is the galaxyID
46
		};
47
	}
48
49
	/**
50
	 * Returns the shortest path from $sector to $x as a Distance object.
51
	 * The path is guaranteed reversible ($x -> $sector == $sector -> $x), which
52
	 * is not true for findDistanceToX. If $x is not a SmrSector, then this
53
	 * function does 2x the work.
54
	 */
55
	public static function findReversiblePathToX(mixed $x, SmrSector $sector, AbstractSmrPlayer $needsToHaveBeenExploredBy = null, AbstractSmrPlayer $player = null): Path {
56
		if ($x instanceof SmrSector) {
57
58
			// To ensure reversibility, always plot lowest to highest.
59
			$reverse = $sector->getSectorID() > $x->getSectorID();
60
			if ($reverse) {
61
				$start = $x;
62
				$end = $sector;
63
			} else {
64
				$start = $sector;
65
				$end = $x;
66
			}
67
			$path = self::findDistanceToX($end, $start, true, $needsToHaveBeenExploredBy, $player);
68
			if ($path === false) {
0 ignored issues
show
introduced by
The condition $path === false is always true.
Loading history...
69
				throw new UserError('Unable to plot from ' . $sector->getSectorID() . ' to ' . $x->getSectorID() . '.');
70
			}
71
			// Reverse if we plotted $x -> $sector (since we want $sector -> $x)
72
			if ($reverse) {
73
				$path->reversePath();
74
			}
75
76
		} else {
77
78
			// At this point we don't know what sector $x will be at
79
			$path = self::findDistanceToX($x, $sector, true, $needsToHaveBeenExploredBy, $player);
80
			if ($path === false) {
0 ignored issues
show
introduced by
The condition $path === false is always true.
Loading history...
81
				throw new UserError('Unable to find what you\'re looking for, it either hasn\'t been added to this game or you haven\'t explored it yet.');
82
			}
83
			// Now that we know where $x is, make sure path is reversible
84
			// (i.e. start sector < end sector)
85
			if ($path->getEndSectorID() < $sector->getSectorID()) {
86
				$endSector = SmrSector::getSector($sector->getGameID(), $path->getEndSectorID());
87
				$path = self::findDistanceToX($sector, $endSector, true);
88
				if ($path === false) {
89
					throw new Exception('Unable to find reverse path');
90
				}
91
				$path->reversePath();
92
			}
93
94
		}
95
		return $path;
96
	}
97
98
	/**
99
	 * Returns the shortest path from $sector to $x as a Path object.
100
	 * The resulting path prefers neighbors in their order in SmrSector->links,
101
	 * (i.e. up, down, left, right).
102
	 *
103
	 * @param mixed $x If the string 'Distance', then distances to all visited sectors will
104
	 *                 be returned. Otherwise, must be a type implemented by SmrSector::hasX,
105
	 *                 and will only return distances to sectors for which hasX returns true.
106
	 *
107
	 * @return ($useFirst is true ? Smr\Path|false : array<int, Smr\Path>)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($useFirst at position 1 could not be parsed: Unknown type name '$useFirst' at position 1 in ($useFirst.
Loading history...
108
	 */
109
	public static function findDistanceToX(mixed $x, SmrSector $sector, bool $useFirst, AbstractSmrPlayer $needsToHaveBeenExploredBy = null, AbstractSmrPlayer $player = null, int $distanceLimit = 10000, int $lowLimit = 0, int $highLimit = 100000): array|Path|false {
110
		$warpAddIndex = TURNS_WARP_SECTOR_EQUIVALENCE - 1;
111
112
		$checkSector = $sector;
113
		$gameID = $sector->getGameID();
114
		$distances = [];
115
		$sectorsTravelled = 0;
116
		$visitedSectors = [];
117
		$visitedSectors[$checkSector->getSectorID()] = true;
118
119
		$distanceQ = [];
120
		for ($i = 0; $i <= TURNS_WARP_SECTOR_EQUIVALENCE; $i++) {
121
			$distanceQ[] = [];
122
		}
123
		//Warps first as a slight optimisation due to how visitedSectors is set.
124
		if ($checkSector->hasWarp() === true) {
125
			$d = new Path($checkSector->getSectorID());
126
			$d->addWarp($checkSector->getWarp());
127
			$distanceQ[$warpAddIndex][] = $d;
128
		}
129
		foreach ($checkSector->getLinks() as $nextSector) {
130
			$visitedSectors[$nextSector] = true;
131
			$d = new Path($checkSector->getSectorID());
132
			$d->addLink($nextSector);
133
			$distanceQ[0][] = $d;
134
		}
135
		$maybeWarps = 0;
136
		while ($maybeWarps <= TURNS_WARP_SECTOR_EQUIVALENCE) {
137
			$sectorsTravelled++;
138
			if ($sectorsTravelled > $distanceLimit) {
139
				break;
140
			}
141
			$distanceQ[] = [];
142
			$q = array_shift($distanceQ);
143
			if (count($q) === 0) {
144
				$maybeWarps++;
145
				continue;
146
			}
147
			$maybeWarps = 0;
148
			while (($distance = array_shift($q)) !== null) {
149
				$checkSectorID = $distance->getEndSectorID();
150
				$visitedSectors[$checkSectorID] = true; // This is here for warps, because they are delayed visits if we set this before the actual visit we'll get sectors marked as visited long before they are actually visited - causes problems when it's quicker to walk to the warp exit than to warp there.
151
																// We still need to mark walked sectors as visited before we go to each one otherwise we get a huge number of paths being checked twice (up then left, left then up are essentially the same but if we set up-left as visited only when we actually check it then it gets queued up twice - nasty)
152
				if ($checkSectorID >= $lowLimit && $checkSectorID <= $highLimit) {
153
					$checkSector = SmrSector::getSector($gameID, $checkSectorID);
154
					// Does this sector satisfy our criteria?
155
					if ($x == 'Distance' || (($needsToHaveBeenExploredBy === null || $needsToHaveBeenExploredBy->hasVisitedSector($checkSector->getSectorID())) === true
156
							&& $checkSector->hasX($x, $player) === true)) {
157
						if ($useFirst === true) {
158
							return $distance;
159
						}
160
						$distances[$checkSector->getSectorID()] = $distance;
161
					}
162
					//Warps first as a slight optimisation due to how visitedSectors is set.
163
					if ($checkSector->hasWarp() === true) {
164
						if (!isset($visitedSectors[$checkSector->getWarp()])) {
165
							$cloneDistance = clone($distance);
166
							$cloneDistance->addWarp($checkSector->getWarp());
167
							$distanceQ[$warpAddIndex][] = $cloneDistance;
168
						}
169
					}
170
					foreach ($checkSector->getLinks() as $nextSector) {
171
						if (!isset($visitedSectors[$nextSector])) {
172
							$visitedSectors[$nextSector] = true;
173
174
							$cloneDistance = clone($distance);
175
							$cloneDistance->addLink($nextSector);
176
							$distanceQ[0][] = $cloneDistance;
177
						}
178
					}
179
				}
180
			}
181
		}
182
		if ($useFirst === true) {
183
			return false;
184
		}
185
		return $distances;
186
	}
187
188
	/**
189
	 * @param array<int, \SmrPort> $ports
190
	 * @param array<int, bool> $races
191
	 * @return array<int, array<int, Smr\Path>>
192
	 */
193
	public static function calculatePortToPortDistances(array $ports, array $races, int $distanceLimit = 10000, int $lowLimit = 0, int $highLimit = 100000): array {
194
		$distances = [];
195
		foreach ($ports as $port) {
196
			$sectorID = $port->getSectorID();
197
			if ($races[$port->getRaceID()] && $sectorID >= $lowLimit && $sectorID <= $highLimit) {
198
				$distances[$sectorID] = self::findDistanceToOtherPorts($port->getSector(), $distanceLimit, $lowLimit, $highLimit);
199
			}
200
		}
201
		return $distances;
202
	}
203
204
	/**
205
	 * @return array<int, Smr\Path>
206
	 */
207
	public static function findDistanceToOtherPorts(SmrSector $sector, int $distanceLimit = 10000, int $lowLimit = 0, int $highLimit = 100000): array {
208
		return self::findDistanceToX('Port', $sector, false, null, null, $distanceLimit, $lowLimit, $highLimit);
209
	}
210
211
}
212