Calculator::findPotentialStamina()   B
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 3
1
<?php
2
3
/*
4
 * (c) Jérémy Marodon <[email protected]>
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Th3Mouk\PokemonGoIVCalculator\Calculator;
10
11
use Illuminate\Support\Collection;
12
use Th3Mouk\PokemonGoIVCalculator\Entities\IvCombinaison;
13
use Th3Mouk\PokemonGoIVCalculator\Entities\Level;
14
use Th3Mouk\PokemonGoIVCalculator\Entities\Pokemon;
15
use Th3Mouk\PokemonGoIVCalculator\Entities\StaminaLevelCombinaison;
16
use Th3Mouk\PokemonGoIVCalculator\Extractors\LevelExtractor;
17
use Th3Mouk\PokemonGoIVCalculator\Extractors\Pokedex;
18
19
class Calculator
20
{
21
    private const AVAILABLE_OPTIONS = ['atk', 'def', 'hp'];
22
23
    private $attack_range;
24
    private $defense_range;
25
    private $stamina_range;
26
27
    /**
28
     * @var Collection
29
     */
30
    private $potentialLevels;
31
32
    /**
33
     * @var Collection
34
     */
35
    private $potentialStamina;
36
37
    /**
38
     * @var Collection
39
     */
40
    private $potentialCombinaisons;
41
42
    /**
43
     * Calculator constructor.
44
     */
45
    public function __construct()
46
    {
47
        $this->attack_range = range(0, 15);
48
        $this->defense_range = range(0, 15);
49
        $this->stamina_range = range(0, 15);
50
51
        $this->potentialLevels = new Collection();
52
        $this->potentialStamina = new Collection();
53
        $this->potentialCombinaisons = new Collection();
54
    }
55
56
    /**
57
     * Process all operations to retrieve differents IV combinaisons
58
     * @param  string  $pokemonName
59
     * @param  int     $cp
60
     * @param  int     $hp
61
     * @param  int     $dusts
62
     * @param  int     $global
63
     * @param  int     $maxStat
64
     * @param  array   $bestStats
65
     * @param  bool    $upgraded
66
     * @return Pokemon
67
     */
68
    public function calculate(
69
        string $pokemonName,
70
        int $cp,
71
        int $hp,
72
        int $dusts,
73
        int $global,
74
        int $maxStat,
75
        array $bestStats,
76
        bool $upgraded = false
77
    ): Pokemon {
78
        $pokemon = (new Pokedex())->get($pokemonName);
79
80
        $pokemon->setCp($cp);
81
        $pokemon->setHp($hp);
82
83
        $bestStats = $this->cleanBestStats($bestStats);
84
85
        $this->setRanges($bestStats, $maxStat);
86
87
        $this->potentialLevels = (new LevelExtractor())->getDustFiltered($dusts);
88
89
        $this
90
            ->findPotentialStamina($pokemon, $hp, $upgraded)
91
            ->findPotentialCombinaisons($pokemon, $cp)
92
            ->cleanImpossibleCombinaisons($bestStats, $global);
93
94
        $pokemon->setIvCombinaisons($this->potentialCombinaisons);
95
96
        return $pokemon;
97
    }
98
99
    /**
100
     * Retrieve the possible level and stamina IV to match HP
101
     * @param  Pokemon $pokemon
102
     * @param  int     $hp
103
     * @param  bool    $upgraded
104
     * @return $this
105
     */
106
    private function findPotentialStamina(
107
        Pokemon $pokemon,
108
        int $hp,
109
        bool $upgraded
110
    ) {
111
        foreach ($this->potentialLevels as $data) {
112
            $level = new Level(
113
                $data->level, $data->dust, $data->cpScalar, $upgraded
114
            );
115
116
            foreach ($this->stamina_range as $staminaIV) {
117
                if ($this->testHP($pokemon, $level, $hp, $staminaIV)) {
118
                    $combinaison = new StaminaLevelCombinaison(
119
                        $level,
120
                        $staminaIV
121
                    );
122
                    $this->potentialStamina->push($combinaison);
123
                }
124
            }
125
        }
126
127
        return $this;
128
    }
129
130
    /**
131
     * Test remaining combinaisons which match CP
132
     * @param  Pokemon $pokemon
133
     * @param  int     $cp
134
     * @return $this
135
     */
136
    private function findPotentialCombinaisons(Pokemon $pokemon, int $cp)
137
    {
138
        foreach ($this->potentialStamina as $staminaCombinaison) {
139
            foreach ($this->attack_range as $attackIV) {
140
                foreach ($this->defense_range as $defenseIV) {
141
                    if ($this->testCP(
142
                        $pokemon,
143
                        $staminaCombinaison->getLevel(),
144
                        $cp,
145
                        $attackIV,
146
                        $defenseIV,
147
                        $staminaCombinaison->getStamina())
148
                    ) {
149
                        $combinaison = new IvCombinaison(
150
                            $staminaCombinaison->getLevel(),
151
                            $attackIV,
152
                            $defenseIV,
153
                            $staminaCombinaison->getStamina()
154
                        );
155
156
                        $this->potentialCombinaisons->push($combinaison);
157
                    }
158
                }
159
            }
160
        }
161
162
        return $this;
163
    }
164
165
    /**
166
     * Remove impossible combinaisons with coach indications
167
     * @param  array $bestStats
168
     * @param  int   $global
169
     * @return $this
170
     */
171
    private function cleanImpossibleCombinaisons(array $bestStats, int $global)
172
    {
173
        $this->potentialCombinaisons = $this->potentialCombinaisons
174
            // Eliminate impossible combinaison for global combinaison
175
            ->filter(function ($combinaison) use ($global) {
176
                if ($combinaison->getTotal() <= $this->getMaxGlobalEvaluation($global) &&
177
                    $combinaison->getTotal() > $this->getMaxGlobalEvaluation($global-1)) {
178
                    return true;
179
                }
180
                return false;
181
            })
182
            // Eliminate impossible combinaisons with best stats
183
            ->filter(function ($combinaison) use ($bestStats) {
184
                $nonBestStats = array_diff(self::AVAILABLE_OPTIONS, $bestStats);
185
                foreach ($nonBestStats as $nonBestStat) {
186
                    if ($combinaison->getAbbreviated($nonBestStat) >= $combinaison->getMaximalIv()) {
187
                        return false;
188
                    }
189
                }
190
                return true;
191
            })
192
            // Eliminate impossible combinaisons with equals best stats
193
            ->filter(function ($combinaison) use ($bestStats) {
194
                foreach ($bestStats as $bestStat) {
195
                    if ($combinaison->getAbbreviated($bestStat) != $combinaison->getMaximalIv()) {
196
                        return false;
197
                    }
198
                }
199
                return true;
200
            });
201
202
        return $this;
203
    }
204
205
    /**
206
     * Remove unavailable options give by user
207
     * @param $bestStats
208
     * @return array
209
     */
210
    private function cleanBestStats($bestStats): array
211
    {
212
        return array_intersect(self::AVAILABLE_OPTIONS, $bestStats);
213
    }
214
215
    /**
216
     * Remove impossible values for differents IV with coach indications
217
     * @param array $bestStats
218
     * @param int   $maxStat
219
     */
220
    private function setRanges(array $bestStats, int $maxStat)
221
    {
222
        $this->setRange($bestStats, $maxStat, 'atk', $this->attack_range);
223
        $this->setRange($bestStats, $maxStat, 'def', $this->defense_range);
224
        $this->setRange($bestStats, $maxStat, 'hp', $this->stamina_range);
225
    }
226
227
    private function setRange($bestStats, $maxStat, $option, &$property)
228
    {
229
        if (in_array($option, $bestStats)) {
230
            $property = $this->getMaxRange($maxStat);
231
        } else {
232
            $property = $this->getLowerRange($maxStat);
233
        }
234
    }
235
236
    /**
237
     * Calculate if a combinaison of value match the given CP
238
     * @param  Pokemon $pokemon
239
     * @param  Level   $level
240
     * @param  int     $cp
241
     * @param  int     $attackIV
242
     * @param  int     $defenseIV
243
     * @param  int     $staminaIV
244
     * @return bool
245
     */
246
    private function testCP(
247
        Pokemon $pokemon,
248
        Level $level,
249
        int $cp,
250
        int $attackIV,
251
        int $defenseIV,
252
        int $staminaIV
253
    ) {
254
        $attackFactor = $pokemon->getBaseAttack() + $attackIV;
255
        $defenseFactor = pow($pokemon->getBaseDefense() + $defenseIV, 0.5);
256
        $staminaFactor = pow($pokemon->getBaseStamina() + $staminaIV, 0.5);
257
        $scalarFactor = pow($level->getCpScalar(), 2);
258
259
        return $cp == floor($attackFactor * $defenseFactor * $staminaFactor * $scalarFactor / 10);
260
    }
261
262
    /**
263
     * Calculate if a combinaison of value match the given HP
264
     * @param  Pokemon $pokemon
265
     * @param  Level   $level
266
     * @param  int     $hp
267
     * @param  int     $staminaIV
268
     * @return bool
269
     */
270
    private function testHP(
271
        Pokemon $pokemon,
272
        Level $level,
273
        int $hp,
274
        int $staminaIV
275
    ) {
276
        return $hp == (int) floor(($pokemon->getBaseStamina() + $staminaIV) * $level->getCpScalar());
277
    }
278
279
    /**
280
     * Return the range of stats given by the coach
281
     * @param  int   $maxStat
282
     * @return array
283
     */
284
    private function getMaxRange(int $maxStat)
285
    {
286
        switch ($maxStat) {
287
            case 1:
288
                return range(0, 7);
289
            case 2:
290
                return range(8, 12);
291
            case 3:
292
                return range(13, 14);
293
            case 4:
294
                return [15];
295
        };
296
        return range(0, 15);
297
    }
298
299
    /**
300
     * Return the range of stats non cited by the coach
301
     * @param  int   $maxStat
302
     * @return array
303
     */
304
    private function getLowerRange(int $maxStat)
305
    {
306
        switch ($maxStat) {
307
            case 1:
308
                return range(0, 6);
309
            case 2:
310
                return range(0, 11);
311
            case 3:
312
                return range(0, 13);
313
            case 4:
314
                return range(0, 14);
315
        };
316
        return range(0, 15);
317
    }
318
319
    /**
320
     * Get the threshold of the global evaluation given by the coach
321
     * @param $global
322
     * @return int
323
     */
324
    private function getMaxGlobalEvaluation($global)
325
    {
326
        switch ($global) {
327
            case 0:
328
                return -1;
329
            case 1:
330
                // 0-22
331
                return 22;
332
            case 2:
333
                // 23-29
334
                return 29;
335
            case 3:
336
                // 30-36
337
                return 36;
338
        }
339
        // 37-45
340
        return 45;
341
    }
342
343
    /**
344
     * Get attack_range
345
     *
346
     * @return array
347
     */
348
    public function getAttackRange(): array
349
    {
350
        return $this->attack_range;
351
    }
352
353
    /**
354
     * Get defense_range
355
     *
356
     * @return array
357
     */
358
    public function getDefenseRange(): array
359
    {
360
        return $this->defense_range;
361
    }
362
363
    /**
364
     * Get stamina_range
365
     *
366
     * @return array
367
     */
368
    public function getStaminaRange(): array
369
    {
370
        return $this->stamina_range;
371
    }
372
373
    /**
374
     * Get potentialLevels
375
     *
376
     * @return Collection
377
     */
378
    public function getPotentialLevels(): Collection
379
    {
380
        return $this->potentialLevels;
381
    }
382
383
    /**
384
     * Get potentialStamina
385
     *
386
     * @return Collection
387
     */
388
    public function getPotentialStamina(): Collection
389
    {
390
        return $this->potentialStamina;
391
    }
392
393
    /**
394
     * Get potentialCombinaisons
395
     *
396
     * @return Collection
397
     */
398
    public function getPotentialCombinaisons(): Collection
399
    {
400
        return $this->potentialCombinaisons;
401
    }
402
}
403