sendou.ink/app/modules/analyzer/abilityChunksCalc.ts
William Lam 2da0738dc6
Ability Chunks Required section in Build Analyzer (#1120)
* Added Ability Chunks Required section in Build Analyzer

* Renamed a variable to be more precisely correct

* Added reference

* Removed some extra Javadoc comments

* Prettier fix

* We now only render the AbilityChunksRequired section only if the main abilities array contains a value other than "UNKNOWN"

* Improved React keys naming for performance reasons

* Ability Chunks map is now converted to an array & sorted by value (descending) before it gets rendered as visual components

* Fixed typing error

* Moved logical function to a new file in the analyzer module called abilityChunksCalc.ts
- Refactored for loop content to be cleaner
- Removed & changed some comments

* More for loop refactoring

* We now pass the entire build into abilityChunksCalc.ts

* Refactored map() to flatMap() so we avoid unknowns/null/undefined

* Refactored code to process mainAbilities and subAbilities

* Fixed subability list construction logic & typing in updateAbilityChunksMap()

* Got my first unit test working

* Added working unit tests, also changed sort order slightly

* Added a "real" build for testing

* Removed residual console.warn() call

* Moved constants to abilityChunksCalc.ts

* Ability chunk calculation is now correct for sub abilities

* Uncommented tests & improved their descriptions

* Rearranged expected output to match sorted order for clarity (even though it doesn't have to be)

* Fixed Prettier error

* Spacing

* Moved comments around

* More spacing

* Prettier error on test file

* Improved check in the tests

* Added a second "real" build to tests for good measure

* Added error message to empty array test

* Updated comments again

* More comments updated

* Update test name

* Ability Chunks section is now shown if we have at least one selected ability (handles edge case for primary slot-only abilities being the only chosen ability)
2022-11-16 00:10:48 +02:00

86 lines
3.1 KiB
TypeScript

import { abilities } from "../in-game-lists";
import type {
AbilityWithUnknown,
BuildAbilitiesTupleWithUnknown,
} from "../in-game-lists/types";
import type { AbilityChunks } from "./types";
// Reference for Ability Chunks numbers: https://splatoonwiki.org/wiki/Ability_chunk#Splatoon_3
const MAIN_REQUIRED_ABILITY_CHUNKS_COUNT = 45;
const PRIMARY_SLOT_ONLY_REQUIRED_ABILITY_CHUNKS_COUNT = 15;
const SUB_REQUIRED_ABILITY_CHUNKS_COUNT = 10;
// From a given build, create a map of <Ability, number>, then return it as an Array after sorting by value, descending.
// The data structure describes the number of Ability chunks required for any given build.
export function getAbilityChunksMapAsArray(
build: BuildAbilitiesTupleWithUnknown
) {
const abilityChunksMap: AbilityChunks = new Map<AbilityWithUnknown, number>();
updateAbilityChunksMap(abilityChunksMap, build);
// Sort by value (number, descending) first, then sort by name (string, ascending)
return Array.from(abilityChunksMap).sort(
(a, b) => b[1] - a[1] || a[0].localeCompare(b[0])
);
}
function updateAbilityChunksMap(
abilityChunksMap: AbilityChunks,
build: BuildAbilitiesTupleWithUnknown
) {
for (const gear of build) {
// Handles the incremental amount of ability chunks required for the same ability for 1 piece of gear
const abilityChunksMapForGear = new Map<AbilityWithUnknown, number>();
for (const [index, selectedAbility] of gear.entries()) {
if (!selectedAbility) continue;
if (selectedAbility === "UNKNOWN") continue;
// Ability is in main slot
if (index === 0) {
const primarySlotOnlyAbilityRef = abilities.filter(
(a) =>
a.name === selectedAbility && a.abilityChunkTypesRequired.length > 0
);
// Extra processing is required for Main abilities that are primary slot-only abilities,
// as they are comprised of 3 stackable ability chunks at a lower ability chunk count than usual.
if (primarySlotOnlyAbilityRef.length === 1) {
const primaryAbility = primarySlotOnlyAbilityRef[0];
if (!primaryAbility) continue;
for (const ability of primaryAbility.abilityChunkTypesRequired) {
abilityChunksMap.set(
ability,
(abilityChunksMap.get(ability) ?? 0) +
PRIMARY_SLOT_ONLY_REQUIRED_ABILITY_CHUNKS_COUNT
);
}
} else {
abilityChunksMap.set(
selectedAbility,
(abilityChunksMap.get(selectedAbility) ?? 0) +
MAIN_REQUIRED_ABILITY_CHUNKS_COUNT
);
}
}
// Ability is in a sub slot
else {
// 1 Ability in sub slot = 10 chunks, 2 abilities = 20 chunks, 3 abilities = 30 chunks
abilityChunksMapForGear.set(
selectedAbility,
(abilityChunksMapForGear.get(selectedAbility) ?? 0) +
SUB_REQUIRED_ABILITY_CHUNKS_COUNT
);
abilityChunksMap.set(
selectedAbility,
(abilityChunksMap.get(selectedAbility) ?? 0) +
(abilityChunksMapForGear.get(selectedAbility) ?? 0)
);
}
}
}
}