1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
class Plotter { |
4
|
|
|
|
5
|
|
|
public static function getX($xType, $X, $gameID, $player = null) { |
6
|
|
|
// Special case for Location categories (i.e. Bar, HQ, SafeFed) |
7
|
|
|
if (!is_numeric($X)) { |
8
|
|
|
if ($xType != 'Locations') { |
9
|
|
|
throw new Exception('Non-numeric X only exists for Locations'); |
10
|
|
|
} |
11
|
|
|
return $X; |
12
|
|
|
} |
13
|
|
|
|
14
|
|
|
// In all other cases, X is a numeric ID |
15
|
|
|
$X = (int)$X; |
16
|
|
|
|
17
|
|
|
// Helper function for plots to trade goods |
18
|
|
|
$getGoodWithTransaction = function(int $goodID) use ($xType, $player) { |
19
|
|
|
$good = Globals::getGood($goodID); |
20
|
|
|
if (isset($player) && !$player->meetsAlignmentRestriction($good['AlignRestriction'])) { |
21
|
|
|
create_error('You do not have the correct alignment to see this good!'); |
22
|
|
|
} |
23
|
|
|
$good['TransactionType'] = explode(' ', $xType)[0]; // use 'Buy' or 'Sell' |
24
|
|
|
return $good; |
25
|
|
|
}; |
26
|
|
|
|
27
|
|
|
return match($xType) { |
28
|
|
|
'Technology' => Globals::getHardwareTypes($X), |
29
|
|
|
'Ships' => AbstractSmrShip::getBaseShip($X), |
30
|
|
|
'Weapons' => SmrWeaponType::getWeaponType($X), |
31
|
|
|
'Locations' => SmrLocation::getLocation($X), |
32
|
|
|
'Sell Goods', 'Buy Goods' => $getGoodWithTransaction($X), |
33
|
|
|
'Galaxies' => SmrGalaxy::getGalaxy($gameID, $X), // $X is the galaxyID |
34
|
|
|
}; |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Returns the shortest path from $sector to $x as a Distance object. |
39
|
|
|
* The path is guaranteed reversible ($x -> $sector == $sector -> $x), which |
40
|
|
|
* is not true for findDistanceToX. If $x is not a SmrSector, then this |
41
|
|
|
* function does 2x the work. |
42
|
|
|
*/ |
43
|
|
|
public static function findReversiblePathToX($x, SmrSector $sector, $useFirst, AbstractSmrPlayer $needsToHaveBeenExploredBy = null, AbstractSmrPlayer $player = null) { |
44
|
|
|
if ($x instanceof SmrSector) { |
45
|
|
|
|
46
|
|
|
// To ensure reversibility, always plot lowest to highest. |
47
|
|
|
$reverse = $sector->getSectorID() > $x->getSectorID(); |
48
|
|
|
if ($reverse) { |
49
|
|
|
$start = $x; |
50
|
|
|
$end = $sector; |
51
|
|
|
} else { |
52
|
|
|
$start = $sector; |
53
|
|
|
$end = $x; |
54
|
|
|
} |
55
|
|
|
$path = Plotter::findDistanceToX($end, $start, $useFirst, $needsToHaveBeenExploredBy, $player); |
56
|
|
|
if ($path === false) { |
57
|
|
|
create_error('Unable to plot from ' . $sector->getSectorID() . ' to ' . $x->getSectorID() . '.'); |
58
|
|
|
} |
59
|
|
|
// Reverse if we plotted $x -> $sector (since we want $sector -> $x) |
60
|
|
|
if ($reverse) { |
61
|
|
|
$path->reversePath(); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
} else { |
65
|
|
|
|
66
|
|
|
// At this point we don't know what sector $x will be at |
67
|
|
|
$path = Plotter::findDistanceToX($x, $sector, $useFirst, $needsToHaveBeenExploredBy, $player); |
68
|
|
|
if ($path === false) { |
69
|
|
|
create_error('Unable to find what you\'re looking for, it either hasn\'t been added to this game or you haven\'t explored it yet.'); |
70
|
|
|
} |
71
|
|
|
// Now that we know where $x is, make sure path is reversible |
72
|
|
|
// (i.e. start sector < end sector) |
73
|
|
|
if ($path->getEndSectorID() < $sector->getSectorID()) { |
74
|
|
|
$path = Plotter::findDistanceToX($sector, $path->getEndSector(), true); |
75
|
|
|
$path->reversePath(); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
} |
79
|
|
|
return $path; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Returns the shortest path from $sector to $x as a Distance object. |
84
|
|
|
* $x can be any type implemented by SmrSector::hasX or the string 'Distance'. |
85
|
|
|
* The resulting path prefers neighbors in their order in SmrSector->links, |
86
|
|
|
* (i.e. up, down, left, right). |
87
|
|
|
*/ |
88
|
|
|
public static function findDistanceToX($x, SmrSector $sector, $useFirst, AbstractSmrPlayer $needsToHaveBeenExploredBy = null, AbstractSmrPlayer $player = null, $distanceLimit = 10000, $lowLimit = 0, $highLimit = 100000) { |
89
|
|
|
$warpAddIndex = TURNS_WARP_SECTOR_EQUIVALENCE - 1; |
90
|
|
|
|
91
|
|
|
$checkSector = $sector; |
92
|
|
|
$gameID = $sector->getGameID(); |
93
|
|
|
$distances = array(); |
94
|
|
|
$sectorsTravelled = 0; |
95
|
|
|
$visitedSectors = array(); |
96
|
|
|
$visitedSectors[$checkSector->getSectorID()] = true; |
97
|
|
|
if ($x == 'Distance') { |
98
|
|
|
$distances[0][$checkSector->getSectorID()] = new Distance($gameID, $checkSector->getSectorID()); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
$distanceQ = array(); |
102
|
|
|
for ($i = 0; $i <= TURNS_WARP_SECTOR_EQUIVALENCE; $i++) { |
103
|
|
|
$distanceQ[] = array(); |
104
|
|
|
} |
105
|
|
|
//Warps first as a slight optimisation due to how visitedSectors is set. |
106
|
|
|
if ($checkSector->hasWarp() === true) { |
107
|
|
|
$d = new Distance($gameID, $checkSector->getSectorID()); |
108
|
|
|
$d->addWarpToPath($checkSector->getWarp(), $checkSector->getSectorID()); |
109
|
|
|
$distanceQ[$warpAddIndex][] = $d; |
110
|
|
|
} |
111
|
|
|
foreach ($checkSector->getLinks() as $nextSector) { |
112
|
|
|
if ($nextSector !== 0) { |
113
|
|
|
$visitedSectors[$nextSector] = true; |
114
|
|
|
$d = new Distance($gameID, $checkSector->getSectorID()); |
115
|
|
|
$d->addToPath($nextSector); |
116
|
|
|
$distanceQ[0][] = $d; |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
$maybeWarps = 0; |
120
|
|
|
while ($maybeWarps <= TURNS_WARP_SECTOR_EQUIVALENCE) { |
121
|
|
|
$sectorsTravelled++; |
122
|
|
|
if ($sectorsTravelled > $distanceLimit) { |
123
|
|
|
return $distances; |
124
|
|
|
} |
125
|
|
|
if ($x == 'Distance') { |
126
|
|
|
$distances[$sectorsTravelled] = array(); |
127
|
|
|
} |
128
|
|
|
$distanceQ[] = array(); |
129
|
|
|
if (count($q = array_shift($distanceQ)) === 0) { |
130
|
|
|
$maybeWarps++; |
131
|
|
|
continue; |
132
|
|
|
} |
133
|
|
|
$maybeWarps = 0; |
134
|
|
|
while (($distance = array_shift($q)) !== null) { |
135
|
|
|
$checkSectorID = $distance->getEndSectorID(); |
136
|
|
|
$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. |
137
|
|
|
// 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) |
138
|
|
|
if ($checkSectorID >= $lowLimit && $checkSectorID <= $highLimit) { |
139
|
|
|
$checkSector = SmrSector::getSector($gameID, $checkSectorID); |
140
|
|
|
if ($x == 'Distance') { |
141
|
|
|
$distances[$sectorsTravelled][$checkSector->getSectorID()] = $distance; |
142
|
|
|
} elseif (($needsToHaveBeenExploredBy === null || $needsToHaveBeenExploredBy->hasVisitedSector($checkSector->getSectorID())) === true |
143
|
|
|
&& $checkSector->hasX($x, $player) === true) { |
144
|
|
|
if ($useFirst === true) { |
145
|
|
|
return $distance; |
146
|
|
|
} |
147
|
|
|
$distances[$checkSector->getSectorID()] = $distance; |
148
|
|
|
} |
149
|
|
|
//Warps first as a slight optimisation due to how visitedSectors is set. |
150
|
|
|
if ($checkSector->hasWarp() === true) { |
151
|
|
|
if (!isset($visitedSectors[$checkSector->getWarp()])) { |
152
|
|
|
$cloneDistance = clone($distance); |
153
|
|
|
$cloneDistance->addWarpToPath($checkSector->getWarp(), $checkSector->getSectorID()); |
154
|
|
|
$distanceQ[$warpAddIndex][] = $cloneDistance; |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
foreach ($checkSector->getLinks() as $nextSector) { |
158
|
|
|
if (!isset($visitedSectors[$nextSector])) { |
159
|
|
|
$visitedSectors[$nextSector] = true; |
160
|
|
|
|
161
|
|
|
$cloneDistance = clone($distance); |
162
|
|
|
$cloneDistance->addToPath($nextSector); |
163
|
|
|
$distanceQ[0][] = $cloneDistance; |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
if ($useFirst === true) { |
170
|
|
|
$return = false; |
171
|
|
|
return $return; |
172
|
|
|
} |
173
|
|
|
return $distances; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
public static function calculatePortToPortDistances(array $sectors, $distanceLimit = 10000, $lowLimit = 0, $highLimit = 100000) { |
177
|
|
|
$distances = array(); |
178
|
|
|
foreach ($sectors as $sec) { |
179
|
|
|
if ($sec !== null) { |
180
|
|
|
if ($sec->getSectorID() >= $lowLimit && $sec->getSectorID() <= $highLimit) { |
181
|
|
|
if ($sec->hasPort() === true) { |
182
|
|
|
$distances[$sec->getSectorID()] = self::findDistanceToOtherPorts($sec, $distanceLimit, $lowLimit, $highLimit); |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
} |
187
|
|
|
return $distances; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
public static function findDistanceToOtherPorts(SmrSector $sector, $distanceLimit = 10000, $lowLimit = 0, $highLimit = 100000) { |
191
|
|
|
return self::findDistanceToX('Port', $sector, false, null, null, $distanceLimit, $lowLimit, $highLimit); |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
|