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::getX()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 6
eloc 21
nc 4
nop 4
dl 0
loc 34
rs 8.9617
c 1
b 1
f 0
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