Sub abilities sorting Closes #1313

This commit is contained in:
Kalle
2024-06-27 15:13:05 +03:00
parent b11923ea0b
commit 6abcb2e65f
3 changed files with 178 additions and 3 deletions

View File

@@ -0,0 +1,162 @@
import {
type Ability,
type BuildAbilitiesTuple,
abilities,
} from "~/modules/in-game-lists";
import { mainOnlyAbilitiesShort } from "~/modules/in-game-lists/abilities";
const abilityToIndex = abilities.reduce(
(acc, ability, index) => {
acc[ability.name] = index;
return acc;
},
{} as Record<Ability, number>,
);
const mainOnlyAbilitiesSet = new Set(mainOnlyAbilitiesShort);
const abilitySorter = (a: Ability, b: Ability) =>
abilityToIndex[a] - abilityToIndex[b];
export function sortAbilities(
abilities: BuildAbilitiesTuple,
): BuildAbilitiesTuple {
const m1 = abilities[0][0];
const m2 = abilities[1][0];
const m3 = abilities[2][0];
const oldMainAbilities = [abilities[0][0], abilities[1][0], abilities[2][0]];
const sortedMainAbilities = [m1, m2, m3]
.filter((ability) => !mainOnlyAbilitiesSet.has(ability as any))
.sort(abilitySorter);
const newMainAbilities = oldMainAbilities.map((ability) =>
mainOnlyAbilitiesSet.has(ability as any)
? ability
: sortedMainAbilities.pop(),
);
const subAbilities = subAbilitiesSorted(abilities);
return switchSubRowsIfBetter([
[newMainAbilities[0], ...subAbilities.slice(0, 3)],
[newMainAbilities[1], ...subAbilities.slice(3, 6)],
[newMainAbilities[2], ...subAbilities.slice(6, 9)],
] as BuildAbilitiesTuple);
}
const sortAbilityCount = (a: [Ability, number], b: [Ability, number]) => {
if (a[1] === b[1]) {
return abilitySorter(a[0], b[0]);
}
return b[1] - a[1];
};
function subAbilitiesSorted(abilities: BuildAbilitiesTuple): Ability[] {
const subAbilitiesUnsorted = [
abilities[0].slice(1),
abilities[1].slice(1),
abilities[2].slice(1),
].flat();
const counts = Array.from(
subAbilitiesUnsorted
.reduce((acc, cur) => {
if (!acc.has(cur)) {
acc.set(cur, 1);
} else {
acc.set(cur, acc.get(cur)! + 1);
}
return acc;
}, new Map<Ability, number>())
.entries(),
).sort(sortAbilityCount);
const subAbilities: Ability[][] = [[], [], []];
while (counts.length > 0) {
const [ability, count] = counts[0];
if (count >= 3) {
for (const row of subAbilities) {
if (row.length === 0) {
row.push(ability, ability, ability);
counts[0][1] -= 3;
break;
}
}
} else if (count === 2) {
let found = false;
for (const row of subAbilities) {
if (row.length === 0) {
row.push(ability, ability);
counts[0][1] -= 2;
found = true;
break;
}
}
if (!found) {
// treat them as singles
counts.push([ability, 1]);
counts.push([ability, 1]);
counts.shift();
}
} else {
let found = false;
for (const row of subAbilities) {
if (row.length < 3) {
row.push(ability);
counts[0][1] -= 1;
found = true;
break;
}
}
if (!found) {
throw new Error("Absurd state in ability sorting");
}
}
if (counts[0][1] === 0) {
counts.shift();
}
counts.sort(sortAbilityCount);
}
return subAbilities.flat();
}
function switchSubRowsIfBetter(
abilities: BuildAbilitiesTuple,
): BuildAbilitiesTuple {
const desiredMoves: [source: number, target: number][] = [];
for (const [i, row] of abilities.entries()) {
const [m, s1] = row;
// already in a good place
if (m === s1) continue;
for (const [j, row2] of abilities.entries()) {
if (i === j) continue;
const [m2, s21] = row2;
// already a nice row
if (m2 === s21) {
continue;
}
if (m2 === s1 && !desiredMoves.some(([, target]) => target === j)) {
desiredMoves.push([i, j]);
break;
}
}
}
for (const [source, target] of desiredMoves) {
const temp = abilities[source].slice(1);
abilities[source].splice(1, 3, ...abilities[target].slice(1));
abilities[target].splice(1, 3, ...temp);
}
return abilities;
}

View File

@@ -11,6 +11,7 @@ import type {
MainWeaponId,
} from "~/modules/in-game-lists/types";
import invariant from "~/utils/invariant";
import { sortAbilities } from "../core/ability-sorting.server";
const buildsByWeaponIdStm = sql.prepare(/* sql */ `
with "Top500Weapon" as (
@@ -267,7 +268,7 @@ function augmentBuild<T>({
...row,
modes,
weapons,
abilities: dbAbilitiesToArrayOfArrays(abilities),
abilities: sortAbilities(dbAbilitiesToArrayOfArrays(abilities)),
};
}

View File

@@ -1,6 +1,7 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { getUserId } from "~/features/auth/core/user.server";
import * as BuildRepository from "~/features/builds/BuildRepository.server";
import { sortAbilities } from "~/features/builds/core/ability-sorting.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import type { MainWeaponId } from "~/modules/in-game-lists";
import { notFoundIfFalsy, privatelyCachedJson } from "~/utils/remix";
@@ -19,12 +20,23 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
showPrivate: loggedInUser?.id === user.id,
});
if (builds.length === 0 && loggedInUser?.id !== user.id) {
const skippedViaSearchParams =
new URL(request.url).searchParams.get("exact") === "true";
const skipAbilitySorting =
loggedInUser?.id === user.id || skippedViaSearchParams;
const buildsWithAbilitiesSorted = skipAbilitySorting
? builds
: builds.map((build) => ({
...build,
abilities: sortAbilities(build.abilities),
}));
if (buildsWithAbilitiesSorted.length === 0 && loggedInUser?.id !== user.id) {
throw new Response(null, { status: 404 });
}
const sortedBuilds = sortBuilds({
builds,
builds: buildsWithAbilitiesSorted,
buildSorting: user.buildSorting,
weaponPool: user.weapons,
});