mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-14 07:06:14 -05:00
Take Remeda in use replacing just-utils and own utils
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { sub } from "date-fns";
|
||||
import capitalize from "just-capitalize";
|
||||
import shuffle from "just-shuffle";
|
||||
import { nanoid } from "nanoid";
|
||||
import * as R from "remeda";
|
||||
import { ADMIN_DISCORD_ID, ADMIN_ID, INVITE_CODE_LENGTH } from "~/constants";
|
||||
import { db, sql } from "~/db/sql";
|
||||
import type { SeedVariation } from "~/features/api-private/routes/seed";
|
||||
@@ -66,7 +65,7 @@ import {
|
||||
import { rankedModesShort } from "~/modules/in-game-lists/modes";
|
||||
import type { TournamentMapListMap } from "~/modules/tournament-map-list-generator";
|
||||
import { SENDOUQ_DEFAULT_MAPS } from "~/modules/tournament-map-list-generator/constants";
|
||||
import { nullFilledArray, pickRandomItem } from "~/utils/arrays";
|
||||
import { nullFilledArray } from "~/utils/arrays";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { mySlugify } from "~/utils/urls";
|
||||
@@ -365,7 +364,7 @@ async function userProfiles() {
|
||||
if (id === ADMIN_ID || id === NZAP_TEST_ID) continue;
|
||||
if (Math.random() < 0.15) continue; // 85% have weapons
|
||||
|
||||
const weapons = shuffle([...mainWeaponIds]);
|
||||
const weapons = R.shuffle(mainWeaponIds);
|
||||
|
||||
for (let j = 0; j < faker.helpers.arrayElement([1, 2, 3, 4, 5]); j++) {
|
||||
sql
|
||||
@@ -430,7 +429,7 @@ const randomPreferences = (): UserMapModePreferences => {
|
||||
|
||||
return {
|
||||
mode,
|
||||
stages: shuffle([...stageIds])
|
||||
stages: R.shuffle(stageIds)
|
||||
.filter((stageId) => !BANNED_MAPS[mode].includes(stageId))
|
||||
.slice(0, AMOUNT_OF_MAPS_IN_POOL_PER_MODE),
|
||||
};
|
||||
@@ -457,7 +456,7 @@ async function userQWeaponPool() {
|
||||
if (id === 2) continue; // no weapons for N-ZAP
|
||||
if (Math.random() < 0.2) continue; // 80% have weapons
|
||||
|
||||
const weapons = shuffle([...mainWeaponIds]).slice(
|
||||
const weapons = R.shuffle(mainWeaponIds).slice(
|
||||
0,
|
||||
faker.helpers.arrayElement([1, 2, 3, 4]),
|
||||
);
|
||||
@@ -599,7 +598,7 @@ function syncPlusTiers() {
|
||||
}
|
||||
|
||||
function badgesToAdmin() {
|
||||
const availableBadgeIds = shuffle(
|
||||
const availableBadgeIds = R.shuffle(
|
||||
(sql.prepare(`select "id" from "Badge"`).all() as any[]).map((b) => b.id),
|
||||
).slice(0, 8) as number[];
|
||||
|
||||
@@ -619,7 +618,7 @@ function badgesToAdmin() {
|
||||
}
|
||||
|
||||
function getAvailableBadgeIds() {
|
||||
return shuffle(
|
||||
return R.shuffle(
|
||||
(sql.prepare(`select "id" from "Badge"`).all() as any[]).map((b) => b.id),
|
||||
);
|
||||
}
|
||||
@@ -634,7 +633,7 @@ function badgesToUsers() {
|
||||
).map((u) => u.id) as number[];
|
||||
|
||||
for (const id of availableBadgeIds) {
|
||||
userIds = shuffle(userIds);
|
||||
userIds = R.shuffle(userIds);
|
||||
for (
|
||||
let i = 0;
|
||||
i <
|
||||
@@ -720,7 +719,7 @@ function calendarEvents() {
|
||||
const userIds = userIdsInRandomOrder();
|
||||
|
||||
for (let id = 1; id <= AMOUNT_OF_CALENDAR_EVENTS; id++) {
|
||||
const shuffledTags = shuffle(Object.keys(persistedTags));
|
||||
const shuffledTags = R.shuffle(Object.keys(persistedTags));
|
||||
|
||||
sql
|
||||
.prepare(
|
||||
@@ -746,7 +745,7 @@ function calendarEvents() {
|
||||
)
|
||||
.run({
|
||||
id,
|
||||
name: `${capitalize(faker.word.adjective())} ${capitalize(
|
||||
name: `${R.capitalize(faker.word.adjective())} ${R.capitalize(
|
||||
faker.word.noun(),
|
||||
)}`,
|
||||
description: faker.lorem.paragraph(),
|
||||
@@ -861,7 +860,7 @@ async function calendarEventResults() {
|
||||
.fill(null)
|
||||
.map((_, i) => ({
|
||||
placement: i + 1,
|
||||
teamName: capitalize(faker.word.noun()),
|
||||
teamName: R.capitalize(faker.word.noun()),
|
||||
players: new Array(
|
||||
faker.helpers.arrayElement([1, 2, 3, 4, 4, 4, 4, 4, 5, 6]),
|
||||
)
|
||||
@@ -1321,7 +1320,7 @@ function calendarEventWithToToolsTeams(
|
||||
event !== "LUTI" &&
|
||||
(Math.random() < 0.8 || id === 1)
|
||||
) {
|
||||
const shuffledPairs = shuffle(availablePairs.slice());
|
||||
const shuffledPairs = R.shuffle(availablePairs.slice());
|
||||
|
||||
let SZ = 0;
|
||||
let TC = 0;
|
||||
@@ -1410,7 +1409,7 @@ function tournamentSubs() {
|
||||
)
|
||||
.map(() => {
|
||||
while (true) {
|
||||
const weaponId = pickRandomItem(mainWeaponIds);
|
||||
const weaponId = R.sample(mainWeaponIds, 1)[0]!;
|
||||
if (!includedWeaponIds.includes(weaponId)) {
|
||||
includedWeaponIds.push(weaponId);
|
||||
return weaponId;
|
||||
@@ -1426,7 +1425,7 @@ function tournamentSubs() {
|
||||
)
|
||||
.map(() => {
|
||||
while (true) {
|
||||
const weaponId = pickRandomItem(mainWeaponIds);
|
||||
const weaponId = R.sample(mainWeaponIds, 1)[0]!;
|
||||
if (!includedWeaponIds.includes(weaponId)) {
|
||||
includedWeaponIds.push(weaponId);
|
||||
return weaponId;
|
||||
@@ -1443,7 +1442,7 @@ function tournamentSubs() {
|
||||
}
|
||||
|
||||
const randomAbility = (legalTypes: AbilityType[]) => {
|
||||
const randomOrderAbilities = shuffle([...abilities]);
|
||||
const randomOrderAbilities = R.shuffle([...abilities]);
|
||||
|
||||
return randomOrderAbilities.find((a) => legalTypes.includes(a.type))!.name;
|
||||
};
|
||||
@@ -1451,16 +1450,16 @@ const randomAbility = (legalTypes: AbilityType[]) => {
|
||||
const adminWeaponPool = mainWeaponIds.filter(() => Math.random() > 0.8);
|
||||
async function adminBuilds() {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const randomOrderHeadGear = shuffle(headGearIds.slice());
|
||||
const randomOrderClothesGear = shuffle(clothesGearIds.slice());
|
||||
const randomOrderShoesGear = shuffle(shoesGearIds.slice());
|
||||
const randomOrderHeadGear = R.shuffle(headGearIds.slice());
|
||||
const randomOrderClothesGear = R.shuffle(clothesGearIds.slice());
|
||||
const randomOrderShoesGear = R.shuffle(shoesGearIds.slice());
|
||||
// filter out sshot to prevent test flaking
|
||||
const randomOrderWeaponIds = shuffle(
|
||||
const randomOrderWeaponIds = R.shuffle(
|
||||
adminWeaponPool.filter((id) => id !== 40).slice(),
|
||||
);
|
||||
|
||||
await BuildRepository.create({
|
||||
title: `${capitalize(faker.word.adjective())} ${capitalize(
|
||||
title: `${R.capitalize(faker.word.adjective())} ${R.capitalize(
|
||||
faker.word.noun(),
|
||||
)}`,
|
||||
ownerId: ADMIN_ID,
|
||||
@@ -1514,10 +1513,10 @@ async function manySplattershotBuilds() {
|
||||
for (let i = 0; i < 499; i++) {
|
||||
const SPLATTERSHOT_ID = 40;
|
||||
|
||||
const randomOrderHeadGear = shuffle(headGearIds.slice());
|
||||
const randomOrderClothesGear = shuffle(clothesGearIds.slice());
|
||||
const randomOrderShoesGear = shuffle(shoesGearIds.slice());
|
||||
const randomOrderWeaponIds = shuffle(mainWeaponIds.slice()).filter(
|
||||
const randomOrderHeadGear = R.shuffle(headGearIds.slice());
|
||||
const randomOrderClothesGear = R.shuffle(clothesGearIds.slice());
|
||||
const randomOrderShoesGear = R.shuffle(shoesGearIds.slice());
|
||||
const randomOrderWeaponIds = R.shuffle(mainWeaponIds.slice()).filter(
|
||||
(id) => id !== SPLATTERSHOT_ID,
|
||||
);
|
||||
|
||||
@@ -1525,7 +1524,7 @@ async function manySplattershotBuilds() {
|
||||
|
||||
await BuildRepository.create({
|
||||
private: 0,
|
||||
title: `${capitalize(faker.word.adjective())} ${capitalize(
|
||||
title: `${R.capitalize(faker.word.adjective())} ${R.capitalize(
|
||||
faker.word.noun(),
|
||||
)}`,
|
||||
ownerId,
|
||||
@@ -1642,7 +1641,7 @@ function otherTeams() {
|
||||
const teamName =
|
||||
i === 3
|
||||
? "Team Olive"
|
||||
: `${capitalize(faker.word.adjective())} ${capitalize(
|
||||
: `${R.capitalize(faker.word.adjective())} ${R.capitalize(
|
||||
faker.word.noun(),
|
||||
)}`;
|
||||
const teamCustomUrl = mySlugify(teamName);
|
||||
@@ -1845,7 +1844,7 @@ function xRankPlacements() {
|
||||
|
||||
function userFavBadges() {
|
||||
// randomly choose Sendou's favorite badge
|
||||
const badgeList = shuffle(
|
||||
const badgeList = R.shuffle(
|
||||
(
|
||||
sql
|
||||
.prepare(
|
||||
@@ -2042,13 +2041,15 @@ const randomMapList = (
|
||||
): TournamentMapListMap[] => {
|
||||
const szOnly = faker.helpers.arrayElement([true, false]);
|
||||
|
||||
let modePattern = shuffle([...modesShort]).filter(() => Math.random() > 0.15);
|
||||
let modePattern = R.shuffle([...modesShort]).filter(
|
||||
() => Math.random() > 0.15,
|
||||
);
|
||||
if (modePattern.length === 0) {
|
||||
modePattern = shuffle([...rankedModesShort]);
|
||||
modePattern = R.shuffle([...rankedModesShort]);
|
||||
}
|
||||
|
||||
const mapList: TournamentMapListMap[] = [];
|
||||
const stageIdsShuffled = shuffle([...stageIds]);
|
||||
const stageIdsShuffled = R.shuffle([...stageIds]);
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const mode = modePattern.pop()!;
|
||||
@@ -2071,7 +2072,7 @@ const AMOUNT_OF_USERS_WITH_SKILLS = 100;
|
||||
async function playedMatches() {
|
||||
const _groupMembers = (() => {
|
||||
return new Array(AMOUNT_OF_USERS_WITH_SKILLS).fill(null).map(() => {
|
||||
const users = shuffle(
|
||||
const users = R.shuffle(
|
||||
userIdsInAscendingOrderById().slice(0, AMOUNT_OF_USERS_WITH_SKILLS),
|
||||
);
|
||||
|
||||
@@ -2082,14 +2083,14 @@ async function playedMatches() {
|
||||
userIdsInAscendingOrderById()
|
||||
.slice(0, AMOUNT_OF_USERS_WITH_SKILLS)
|
||||
.map((id) => {
|
||||
const weapons = shuffle([...mainWeaponIds]);
|
||||
const weapons = R.shuffle([...mainWeaponIds]);
|
||||
return [id, weapons[0]];
|
||||
}),
|
||||
);
|
||||
|
||||
let matchDate = new Date(Date.UTC(2023, 9, 15, 0, 0, 0, 0));
|
||||
for (let i = 0; i < MATCHES_COUNT; i++) {
|
||||
const groupMembers = shuffle([..._groupMembers]);
|
||||
const groupMembers = R.shuffle([..._groupMembers]);
|
||||
const groupAlphaMembers = groupMembers.pop()!;
|
||||
invariant(groupAlphaMembers, "groupAlphaMembers not found");
|
||||
|
||||
@@ -2229,7 +2230,7 @@ async function playedMatches() {
|
||||
mainWeaponIds.find((id) => id > defaultWeapons[mu.user]) ?? 0
|
||||
);
|
||||
|
||||
const shuffled = shuffle([...mainWeaponIds]);
|
||||
const shuffled = R.shuffle([...mainWeaponIds]);
|
||||
|
||||
return shuffled[0];
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import * as R from "remeda";
|
||||
import {
|
||||
AUTO_BOMB_ID,
|
||||
type Ability,
|
||||
@@ -21,12 +23,7 @@ import {
|
||||
POINT_SENSOR_ID,
|
||||
} from "~/modules/in-game-lists";
|
||||
import invariant from "~/utils/invariant";
|
||||
import {
|
||||
cutToNDecimalPlaces,
|
||||
roundToNDecimalPlaces,
|
||||
sumArray,
|
||||
} from "~/utils/number";
|
||||
import { semiRandomId } from "~/utils/strings";
|
||||
import { cutToNDecimalPlaces, roundToNDecimalPlaces } from "~/utils/number";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
import {
|
||||
DAMAGE_TYPE,
|
||||
@@ -289,7 +286,7 @@ export function fullInkTankOptions(
|
||||
if (typeof mainWeaponInkConsume !== "number") continue;
|
||||
|
||||
result.push({
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
subsUsed: subsFromFullInkTank,
|
||||
type,
|
||||
value: effectToRounded(
|
||||
@@ -463,7 +460,7 @@ function damages(args: StatFunctionInput): AnalyzedBuild["stats"]["damages"] {
|
||||
type,
|
||||
value: subValue.Damage / 10,
|
||||
distance: subValue.Distance,
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
multiShots: multiShot[args.weaponSplId],
|
||||
});
|
||||
}
|
||||
@@ -474,7 +471,7 @@ function damages(args: StatFunctionInput): AnalyzedBuild["stats"]["damages"] {
|
||||
if (typeof value !== "number") continue;
|
||||
|
||||
result.push({
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
type,
|
||||
value: value / 10,
|
||||
shotsToSplat: shotsToSplat({
|
||||
@@ -505,7 +502,7 @@ function specialWeaponDamages(
|
||||
type,
|
||||
value: subValue.Damage / 10,
|
||||
distance: subValue.Distance,
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
multiShots: multiShot[args.weaponSplId],
|
||||
});
|
||||
}
|
||||
@@ -516,7 +513,7 @@ function specialWeaponDamages(
|
||||
if (typeof value !== "number") continue;
|
||||
|
||||
result.push({
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
type,
|
||||
value: value / 10,
|
||||
shotsToSplat: shotsToSplat({
|
||||
@@ -532,9 +529,9 @@ function specialWeaponDamages(
|
||||
// Artifically combined damages
|
||||
if (args.mainWeaponParams.specialWeaponId === ZIPCASTER_ID) {
|
||||
result.unshift({
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
distance: 0,
|
||||
value: sumArray(result.map((v) => v.value)),
|
||||
value: R.sum(result.map((v) => v.value)),
|
||||
type: result[0].type,
|
||||
});
|
||||
}
|
||||
@@ -545,9 +542,9 @@ function specialWeaponDamages(
|
||||
);
|
||||
|
||||
result.splice(firstCannonDamageIdx, 0, {
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
distance: 0,
|
||||
value: sumArray(cannonDamages.map((v) => v.value)),
|
||||
value: R.sum(cannonDamages.map((v) => v.value)),
|
||||
type: "SPECIAL_CANNON",
|
||||
});
|
||||
}
|
||||
@@ -604,7 +601,7 @@ function subWeaponDefenseDamages(
|
||||
params: args.subWeaponParams,
|
||||
}),
|
||||
distance: subValue.Distance,
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
subWeaponId: id,
|
||||
});
|
||||
}
|
||||
@@ -612,12 +609,12 @@ function subWeaponDefenseDamages(
|
||||
// Burst Bomb direct damage
|
||||
if (id === BURST_BOMB_ID) {
|
||||
arrayValues.unshift({
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
subWeaponId: id,
|
||||
distance: 0,
|
||||
baseValue: sumArray(arrayValues.map((v) => v.baseValue)),
|
||||
baseValue: R.sum(arrayValues.map((v) => v.baseValue)),
|
||||
value: cutToNDecimalPlaces(
|
||||
sumArray(arrayValues.map((v) => v.value)),
|
||||
R.sum(arrayValues.map((v) => v.value)),
|
||||
1,
|
||||
),
|
||||
type,
|
||||
@@ -639,7 +636,7 @@ function subWeaponDefenseDamages(
|
||||
|
||||
arrayValues = [
|
||||
{
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
subWeaponId: id,
|
||||
distance: [
|
||||
Math.min(
|
||||
@@ -658,7 +655,7 @@ function subWeaponDefenseDamages(
|
||||
type,
|
||||
},
|
||||
{
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
subWeaponId: id,
|
||||
distance: [
|
||||
Math.min(
|
||||
@@ -683,7 +680,7 @@ function subWeaponDefenseDamages(
|
||||
if (typeof value !== "number") continue;
|
||||
|
||||
result.push({
|
||||
id: semiRandomId(),
|
||||
id: nanoid(),
|
||||
type,
|
||||
baseValue: value / 10,
|
||||
value: subWeaponDamageValue({
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
abilitiesShort,
|
||||
isAbility,
|
||||
} from "~/modules/in-game-lists";
|
||||
import { atOrError, nullFilledArray, removeDuplicates } from "~/utils/arrays";
|
||||
import { atOrError, nullFilledArray } from "~/utils/arrays";
|
||||
import { damageTypeTranslationString } from "~/utils/i18next";
|
||||
import invariant from "~/utils/invariant";
|
||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||
@@ -79,6 +79,7 @@ import {
|
||||
isStackableAbility,
|
||||
} from "../core/utils";
|
||||
import "../analyzer.css";
|
||||
import * as R from "remeda";
|
||||
import { SendouSwitch } from "~/components/elements/Switch";
|
||||
|
||||
export const CURRENT_PATCH = "9.3";
|
||||
@@ -1117,7 +1118,7 @@ function subDefenseGraphOptions({
|
||||
}),
|
||||
);
|
||||
|
||||
const distanceKeys = removeDuplicates(
|
||||
const distanceKeys = R.unique(
|
||||
analyzedBuilds[0].stats.subWeaponDefenseDamages
|
||||
.filter((d) => (d as SubWeaponDamage).subWeaponId === subWeaponId)
|
||||
.filter((d) => d.value < 100)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Expression, ExpressionBuilder, Transaction } from "kysely";
|
||||
import { sql } from "kysely";
|
||||
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
|
||||
import * as R from "remeda";
|
||||
import { db } from "~/db/sql";
|
||||
import type {
|
||||
CalendarEventTag,
|
||||
@@ -13,7 +14,6 @@ import { MapPool } from "~/features/map-list-generator/core/map-pool";
|
||||
import * as Progression from "~/features/tournament-bracket/core/Progression";
|
||||
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { sumArray } from "~/utils/number";
|
||||
import type { Unwrapped } from "~/utils/types";
|
||||
|
||||
// TODO: convert from raw to using the "exists" function
|
||||
@@ -317,7 +317,7 @@ async function tournamentParticipantCount({
|
||||
|
||||
return {
|
||||
teams: rows.length,
|
||||
players: sumArray(rows.map((row) => row.memberCount)),
|
||||
players: R.sum(rows.map((row) => row.memberCount)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as R from "remeda";
|
||||
import { Button } from "~/components/Button";
|
||||
import { WeaponCombobox } from "~/components/Combobox";
|
||||
import { WeaponImage } from "~/components/Image";
|
||||
@@ -9,7 +10,6 @@ import type { TierName } from "~/features/mmr/mmr-constants";
|
||||
import { TIERS } from "~/features/mmr/mmr-constants";
|
||||
import { languagesUnified } from "~/modules/i18n/config";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import { capitalize } from "~/utils/strings";
|
||||
import { LFG } from "../lfg-constants";
|
||||
import type { LFGFilter } from "../lfg-types";
|
||||
|
||||
@@ -287,7 +287,7 @@ function TierFilterFields({
|
||||
>
|
||||
{TIERS.map((tier) => (
|
||||
<option key={tier.name} value={tier.name}>
|
||||
{capitalize(tier.name.toLowerCase())}
|
||||
{R.capitalize(tier.name.toLowerCase())}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import shuffle from "just-shuffle";
|
||||
import * as R from "remeda";
|
||||
import type { ModeShort } from "../../../../modules/in-game-lists";
|
||||
|
||||
export function modesOrder(
|
||||
@@ -6,10 +6,10 @@ export function modesOrder(
|
||||
modes: ModeShort[],
|
||||
): ModeShort[] {
|
||||
if (type === "EQUAL") {
|
||||
return shuffle(modes);
|
||||
return R.shuffle(modes);
|
||||
}
|
||||
|
||||
const withoutSZ = shuffle(modes.filter((mode) => mode !== "SZ"));
|
||||
const withoutSZ = R.shuffle(modes.filter((mode) => mode !== "SZ"));
|
||||
|
||||
return withoutSZ.flatMap((mode) => [mode, "SZ"]);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import type {
|
||||
TLUiStylePanelProps,
|
||||
} from "@tldraw/tldraw";
|
||||
import clsx from "clsx";
|
||||
import randomInt from "just-random-integer";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as R from "remeda";
|
||||
import { usePlannerBg } from "~/hooks/usePlannerBg";
|
||||
import type { LanguageCode } from "~/modules/i18n/config";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists";
|
||||
@@ -156,8 +156,8 @@ export default function Planner() {
|
||||
size: [imageSizePx, imageSizePx],
|
||||
isLocked: false,
|
||||
point: [
|
||||
randomInt(imageSpawnBoxLeft, imageSpawnBoxRight),
|
||||
randomInt(imageSpawnBoxTop, imageSpawnBoxBottom),
|
||||
R.randomInteger(imageSpawnBoxLeft, imageSpawnBoxRight),
|
||||
R.randomInteger(imageSpawnBoxTop, imageSpawnBoxBottom),
|
||||
],
|
||||
cb: () => editor?.setCurrentTool("select"),
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as R from "remeda";
|
||||
import type {
|
||||
AbilityPoints,
|
||||
AnalyzedBuild,
|
||||
@@ -10,7 +11,6 @@ import type {
|
||||
SpecialWeaponId,
|
||||
SubWeaponId,
|
||||
} from "~/modules/in-game-lists";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { roundToNDecimalPlaces } from "~/utils/number";
|
||||
import {
|
||||
@@ -135,9 +135,7 @@ export function resolveAllUniqueDamageTypes({
|
||||
? analyzed.stats.specialWeaponDamages.map((d) => d.type)
|
||||
: analyzed.stats.damages.map((d) => d.type);
|
||||
|
||||
return removeDuplicates(damageTypes).filter(
|
||||
(dmg) => !dmg.includes("SECONDARY"),
|
||||
);
|
||||
return R.unique(damageTypes).filter((dmg) => !dmg.includes("SECONDARY"));
|
||||
}
|
||||
|
||||
function resolveFilteredDamages({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import shuffle from "just-shuffle";
|
||||
import { type InferResult, sql } from "kysely";
|
||||
import * as R from "remeda";
|
||||
import { db } from "~/db/sql";
|
||||
import type { Tables, TablesInsertable } from "~/db/tables";
|
||||
import * as PlusSuggestionRepository from "~/features/plus-suggestions/PlusSuggestionRepository.server";
|
||||
@@ -126,7 +126,7 @@ export async function usersForVoting(loggedInUser: {
|
||||
});
|
||||
}
|
||||
|
||||
return shuffle(result.filter(({ user }) => user.id !== loggedInUser.id));
|
||||
return R.shuffle(result.filter(({ user }) => user.id !== loggedInUser.id));
|
||||
}
|
||||
|
||||
export async function hasVoted(args: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import shuffle from "just-shuffle";
|
||||
import * as R from "remeda";
|
||||
import type { ParsedMemento, UserMapModePreferences } from "~/db/tables";
|
||||
import {
|
||||
type DbMapPoolList,
|
||||
@@ -100,7 +100,7 @@ export function mapLottery(
|
||||
const mapPoolList: DbMapPoolList = [];
|
||||
|
||||
for (const mode of modes) {
|
||||
const stageIdsFromPools = shuffle(
|
||||
const stageIdsFromPools = R.shuffle(
|
||||
preferences.flatMap((preference) => {
|
||||
// if they disliked the mode don't include their maps
|
||||
// they are just saved in the DB so they can be restored later
|
||||
@@ -178,7 +178,7 @@ export function mapModePreferencesToModeList(
|
||||
combinedMap.set(mode, combinedScore);
|
||||
}
|
||||
|
||||
const result = shuffle(modesShort).filter((mode) => {
|
||||
const result = R.shuffle(modesShort).filter((mode) => {
|
||||
const score = combinedMap.get(mode)!;
|
||||
|
||||
// if opinion is split, don't include
|
||||
@@ -196,7 +196,7 @@ export function mapModePreferencesToModeList(
|
||||
if (result.length === 0) {
|
||||
const bestScore = Math.max(...combinedMap.values());
|
||||
|
||||
const leastWorstModesResult = shuffle(modesShort).filter((mode) => {
|
||||
const leastWorstModesResult = R.shuffle(modesShort).filter((mode) => {
|
||||
// turf war never included if not positive
|
||||
if (mode === "TW") return false;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import * as R from "remeda";
|
||||
import type { UserWithPlusTier } from "~/db/tables";
|
||||
import { getUserId } from "~/features/auth/core/user.server";
|
||||
import { sumArray } from "~/utils/number";
|
||||
import * as TeamRepository from "../TeamRepository.server";
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
@@ -55,7 +55,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const membersToCommonPlusTierRating = (
|
||||
members: Pick<UserWithPlusTier, "plusTier">[],
|
||||
) => {
|
||||
return sumArray(
|
||||
return R.sum(
|
||||
members
|
||||
.map((m) => m.plusTier ?? 100)
|
||||
.sort((a, b) => a - b)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link, useFetcher, useLoaderData } from "@remix-run/react";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as R from "remeda";
|
||||
import { Avatar } from "~/components/Avatar";
|
||||
import { Button, LinkButton } from "~/components/Button";
|
||||
import { Flag } from "~/components/Flag";
|
||||
@@ -16,7 +17,6 @@ import { StarIcon } from "~/components/icons/Star";
|
||||
import { UsersIcon } from "~/components/icons/Users";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { isAdmin } from "~/permissions";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||
import {
|
||||
TEAM_SEARCH_PAGE,
|
||||
@@ -131,7 +131,7 @@ function TeamBanner() {
|
||||
</div>
|
||||
) : null}
|
||||
<div className="team__banner__flags">
|
||||
{removeDuplicates(
|
||||
{R.unique(
|
||||
team.members
|
||||
.map((member) => member.country)
|
||||
.filter((country) => country !== null),
|
||||
@@ -154,7 +154,7 @@ function MobileTeamNameCountry() {
|
||||
return (
|
||||
<div className="team__mobile-name-country">
|
||||
<div className="stack horizontal sm">
|
||||
{removeDuplicates(
|
||||
{R.unique(
|
||||
team.members
|
||||
.map((member) => member.country)
|
||||
.filter((country) => country !== null),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import * as R from "remeda";
|
||||
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
|
||||
import { idObject } from "~/utils/zod";
|
||||
import { findPlacementsByPlayerId } from "../queries/findPlacements.server";
|
||||
@@ -13,7 +13,7 @@ export const loader = async (args: LoaderFunctionArgs) => {
|
||||
const placements = notFoundIfFalsy(findPlacementsByPlayerId(params.id));
|
||||
|
||||
const primaryName = placements[0].name;
|
||||
const aliases = removeDuplicates(
|
||||
const aliases = R.unique(
|
||||
placements
|
||||
.map((placement) => placement.name)
|
||||
.filter((name) => name !== primaryName),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as R from "remeda";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { removeDuplicates } from "../../../utils/arrays";
|
||||
import invariant from "../../../utils/invariant";
|
||||
import { Tournament } from "./Tournament";
|
||||
import { PADDLING_POOL_255 } from "./tests/mocks";
|
||||
@@ -47,9 +47,7 @@ describe("round robin standings", () => {
|
||||
|
||||
const standings = tournamentPP255.bracketByIdx(0)!.standings;
|
||||
|
||||
const groupIds = removeDuplicates(
|
||||
standings.map((standing) => standing.groupId),
|
||||
);
|
||||
const groupIds = R.unique(standings.map((standing) => standing.groupId));
|
||||
expect(
|
||||
groupIds.length,
|
||||
"Paddling Pool 255 should have groups from Group A to Group I",
|
||||
@@ -81,7 +79,7 @@ describe("round robin standings", () => {
|
||||
|
||||
const standings = tournamentPP255.bracketByIdx(0)!.standings;
|
||||
|
||||
const placements = removeDuplicates(
|
||||
const placements = R.unique(
|
||||
standings.map((standing) => standing.placement),
|
||||
).sort((a, b) => a - b);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { sub } from "date-fns";
|
||||
import * as R from "remeda";
|
||||
import type { Tables, TournamentStageSettings } from "~/db/tables";
|
||||
import { TOURNAMENT } from "~/features/tournament";
|
||||
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
|
||||
import type { Round } from "~/modules/brackets-model";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
@@ -259,7 +259,7 @@ export abstract class Bracket {
|
||||
}
|
||||
|
||||
get participantTournamentTeamIds() {
|
||||
return removeDuplicates(
|
||||
return R.unique(
|
||||
this.data.match
|
||||
.flatMap((match) => [match.opponent1?.id, match.opponent2?.id])
|
||||
.filter(Boolean),
|
||||
@@ -472,7 +472,7 @@ class SingleEliminationBracket extends Bracket {
|
||||
}
|
||||
|
||||
private hasThirdPlaceMatch() {
|
||||
return removeDuplicates(this.data.match.map((m) => m.group_id)).length > 1;
|
||||
return R.unique(this.data.match.map((m) => m.group_id)).length > 1;
|
||||
}
|
||||
|
||||
get standings(): Standing[] {
|
||||
@@ -511,7 +511,7 @@ class SingleEliminationBracket extends Bracket {
|
||||
this.participantTournamentTeamIds.length - teams.length;
|
||||
|
||||
const result: Standing[] = [];
|
||||
for (const roundId of removeDuplicates(teams.map((team) => team.lostAt))) {
|
||||
for (const roundId of R.unique(teams.map((team) => team.lostAt))) {
|
||||
const teamsLostThisRound: { id: number }[] = [];
|
||||
while (teams.length && teams[0].lostAt === roundId) {
|
||||
teamsLostThisRound.push(teams.shift()!);
|
||||
@@ -669,7 +669,7 @@ class DoubleEliminationBracket extends Bracket {
|
||||
this.participantTournamentTeamIds.length - teams.length;
|
||||
|
||||
const result: Standing[] = [];
|
||||
for (const roundId of removeDuplicates(teams.map((team) => team.lostAt))) {
|
||||
for (const roundId of R.unique(teams.map((team) => team.lostAt))) {
|
||||
const teamsLostThisRound: { id: number }[] = [];
|
||||
while (teams.length && teams[0].lostAt === roundId) {
|
||||
teamsLostThisRound.push(teams.shift()!);
|
||||
@@ -892,9 +892,7 @@ class RoundRobinBracket extends Bracket {
|
||||
const relevantMatchesFinished =
|
||||
standings.length === this.participantTournamentTeamIds.length;
|
||||
|
||||
const uniquePlacements = removeDuplicates(
|
||||
standings.map((s) => s.placement),
|
||||
);
|
||||
const uniquePlacements = R.unique(standings.map((s) => s.placement));
|
||||
|
||||
// 1,3,5 -> 1,2,3 e.g.
|
||||
const placementNormalized = (p: number) => {
|
||||
@@ -1182,9 +1180,7 @@ class SwissBracket extends Bracket {
|
||||
});
|
||||
});
|
||||
|
||||
const uniquePlacements = removeDuplicates(
|
||||
standings.map((s) => s.placement),
|
||||
);
|
||||
const uniquePlacements = R.unique(standings.map((s) => s.placement));
|
||||
|
||||
// 1,3,5 -> 1,2,3 e.g.
|
||||
const placementNormalized = (p: number) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as R from "remeda";
|
||||
import type { TournamentRoundMaps } from "~/db/tables";
|
||||
import type {
|
||||
ModeShort,
|
||||
@@ -5,7 +6,6 @@ import type {
|
||||
StageId,
|
||||
} from "~/modules/in-game-lists";
|
||||
import type { TournamentMapListMap } from "~/modules/tournament-map-list-generator";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
@@ -132,7 +132,7 @@ export function mapsListWithLegality(args: MapListWithStatusesArgs) {
|
||||
}
|
||||
})();
|
||||
|
||||
const modesIncluded = removeDuplicates(mapPool.map((m) => m.mode));
|
||||
const modesIncluded = R.unique(mapPool.map((m) => m.mode));
|
||||
|
||||
const unavailableStagesSet = unavailableStages(args);
|
||||
const unavailableModesSetAll = unavailableModes(args);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import compare from "just-compare";
|
||||
import * as R from "remeda";
|
||||
import type { PreparedMaps } from "~/db/tables";
|
||||
import { nullFilledArray, removeDuplicates } from "~/utils/arrays";
|
||||
import { nullFilledArray } from "~/utils/arrays";
|
||||
import invariant from "~/utils/invariant";
|
||||
import type { Bracket } from "./Bracket";
|
||||
import type { Tournament } from "./Tournament";
|
||||
@@ -32,11 +32,11 @@ export function resolvePreparedForTheBracket({
|
||||
] of tournament.ctx.settings.bracketProgression.entries()) {
|
||||
if (
|
||||
bracket.type === bracketPreparingFor.type &&
|
||||
compare(
|
||||
R.isDeepEqual(
|
||||
bracket.sources?.map((s) => s.bracketIdx),
|
||||
bracketPreparingFor.sources?.map((s) => s.bracketIdx),
|
||||
) &&
|
||||
compare(bracket.settings, bracketPreparingFor.settings)
|
||||
R.isDeepEqual(bracket.settings, bracketPreparingFor.settings)
|
||||
) {
|
||||
const bracketMaps = preparedByBracket?.[anotherBracketIdx];
|
||||
|
||||
@@ -121,7 +121,7 @@ function trimMapsByTeamCount({
|
||||
nullFilledArray(teamCount).map((_, i) => i + 1),
|
||||
).round;
|
||||
|
||||
const groupIds = removeDuplicates(preparedMaps.maps.map((r) => r.groupId));
|
||||
const groupIds = R.unique(preparedMaps.maps.map((r) => r.groupId));
|
||||
|
||||
const result = { ...preparedMaps };
|
||||
for (const groupId of groupIds) {
|
||||
@@ -179,7 +179,7 @@ function thirdPlaceMatchDisappeared({
|
||||
}
|
||||
|
||||
const preparedHasThirdPlace =
|
||||
removeDuplicates(preparedMaps.maps.map((r) => r.groupId)).length > 1;
|
||||
R.unique(preparedMaps.maps.map((r) => r.groupId)).length > 1;
|
||||
|
||||
return preparedHasThirdPlace && teamCount < 4;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// todo
|
||||
|
||||
import compare from "just-compare";
|
||||
import * as R from "remeda";
|
||||
import type { Tables, TournamentStageSettings } from "~/db/tables";
|
||||
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
|
||||
import {
|
||||
@@ -582,7 +580,7 @@ export function changedBracketProgression(
|
||||
const oldBracket = oldProgression[i];
|
||||
const newBracket = newProgression.at(i);
|
||||
|
||||
if (!newBracket || !compare(oldBracket, newBracket)) {
|
||||
if (!newBracket || !R.isDeepEqual(oldBracket, newBracket)) {
|
||||
changed.push(i);
|
||||
}
|
||||
}
|
||||
@@ -604,7 +602,7 @@ export function changedBracketProgressionFormat(
|
||||
!newBracket ||
|
||||
newBracket.name !== oldBracket.name ||
|
||||
newBracket.type !== oldBracket.type ||
|
||||
!compare(newBracket.settings, oldBracket.settings)
|
||||
!R.isDeepEqual(newBracket.settings, oldBracket.settings)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as R from "remeda";
|
||||
import type {
|
||||
Tables,
|
||||
TournamentStage,
|
||||
@@ -13,7 +14,6 @@ import type { Match, Stage } from "~/modules/brackets-model";
|
||||
import type { ModeShort } from "~/modules/in-game-lists";
|
||||
import { modesShort, rankedModesShort } from "~/modules/in-game-lists/modes";
|
||||
import { isAdmin } from "~/permissions";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import {
|
||||
databaseTimestampNow,
|
||||
databaseTimestampToDate,
|
||||
@@ -663,7 +663,7 @@ export class Tournament {
|
||||
return ["CB"];
|
||||
}
|
||||
default: {
|
||||
const pickedModes = removeDuplicates(
|
||||
const pickedModes = R.unique(
|
||||
this.ctx.toSetMapPool.map((map) => map.mode),
|
||||
);
|
||||
if (pickedModes.length === 0) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as R from "remeda";
|
||||
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import { TOURNAMENT } from "../../tournament/tournament-constants";
|
||||
|
||||
export function getRounds(args: {
|
||||
@@ -52,7 +52,7 @@ export function getRounds(args: {
|
||||
|
||||
const hasThirdPlaceMatch =
|
||||
args.type === "single" &&
|
||||
removeDuplicates(args.bracketData.match.map((m) => m.group_id)).length > 1;
|
||||
R.unique(args.bracketData.match.map((m) => m.group_id)).length > 1;
|
||||
const namedRounds = rounds.map((round, i) => {
|
||||
const name = () => {
|
||||
if (
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import shuffle from "just-shuffle";
|
||||
import type { Rating } from "node_modules/openskill/dist/types";
|
||||
import { ordinal } from "openskill";
|
||||
import * as R from "remeda";
|
||||
import {
|
||||
identifierToUserIds,
|
||||
rate,
|
||||
userIdsToIdentifier,
|
||||
} from "~/features/mmr/mmr-utils";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import invariant from "~/utils/invariant";
|
||||
import type { Tables } from "../../../db/tables";
|
||||
import type { AllMatchResult } from "../queries/allMatchResultsByTournamentId.server";
|
||||
@@ -134,12 +133,12 @@ export function calculateIndividualPlayerSkills({
|
||||
: match.opponentTwo.id;
|
||||
|
||||
const participants = match.maps.flatMap((m) => m.participants);
|
||||
const winnerUserIds = removeDuplicates(
|
||||
const winnerUserIds = R.unique(
|
||||
participants
|
||||
.filter((p) => p.tournamentTeamId === winnerTeamId)
|
||||
.map((p) => p.userId),
|
||||
);
|
||||
const loserUserIds = removeDuplicates(
|
||||
const loserUserIds = R.unique(
|
||||
participants
|
||||
.filter((p) => p.tournamentTeamId !== winnerTeamId)
|
||||
.map((p) => p.userId),
|
||||
@@ -282,7 +281,7 @@ function selectMostPopular<T>(items: T[]): T {
|
||||
return mostPopularItems[0][0];
|
||||
}
|
||||
|
||||
return shuffle(mostPopularItems)[0][0];
|
||||
return R.shuffle(mostPopularItems)[0][0];
|
||||
}
|
||||
|
||||
function mapResultDeltas(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as R from "remeda";
|
||||
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import type * as Progression from "../Progression";
|
||||
import { Tournament } from "../Tournament";
|
||||
import type { TournamentData } from "../Tournament.server";
|
||||
@@ -49,11 +49,12 @@ export const testTournament = ({
|
||||
data?: TournamentManagerDataSet;
|
||||
ctx?: Partial<TournamentData["ctx"]>;
|
||||
}) => {
|
||||
const participant = removeDuplicates(
|
||||
data.match
|
||||
.flatMap((m) => [m.opponent1?.id, m.opponent2?.id])
|
||||
.filter(Boolean),
|
||||
) as number[];
|
||||
const participant = R.pipe(
|
||||
data.match,
|
||||
R.flatMap((m) => [m.opponent1?.id, m.opponent2?.id]),
|
||||
R.filter(R.isTruthy),
|
||||
R.unique<number[]>,
|
||||
);
|
||||
|
||||
return new Tournament({
|
||||
data,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/** Map list generation logic for "TO pick" as in the map list is defined beforehand by TO and teams don't pick */
|
||||
|
||||
import shuffle from "just-shuffle";
|
||||
import * as R from "remeda";
|
||||
import type { Tables, TournamentRoundMaps } from "~/db/tables";
|
||||
import type { Round } from "~/modules/brackets-model";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists";
|
||||
import { SENDOUQ_DEFAULT_MAPS } from "~/modules/tournament-map-list-generator/constants";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import { logger } from "~/utils/logger";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
|
||||
@@ -159,8 +158,8 @@ function modeOrder({
|
||||
iteration: number;
|
||||
flavor: GenerateTournamentRoundMaplistArgs["flavor"];
|
||||
}) {
|
||||
const modes = removeDuplicates(pool.map((x) => x.mode));
|
||||
const shuffledModes = shuffle(modes);
|
||||
const modes = R.unique(pool.map((x) => x.mode));
|
||||
const shuffledModes = R.shuffle(modes);
|
||||
shuffledModes.sort((a, b) => {
|
||||
const aFreq = modeFrequency.get(a) ?? 0;
|
||||
const bFreq = modeFrequency.get(b) ?? 0;
|
||||
@@ -265,9 +264,9 @@ function resolveStage(
|
||||
}
|
||||
}
|
||||
|
||||
const stage = shuffle(equallyGoodOptionsIgnoringCombo)[0];
|
||||
const stage = R.shuffle(equallyGoodOptionsIgnoringCombo)[0];
|
||||
if (typeof stage !== "number") {
|
||||
const fallback = shuffle(SENDOUQ_DEFAULT_MAPS[mode].slice())[0];
|
||||
const fallback = R.shuffle(SENDOUQ_DEFAULT_MAPS[mode])[0];
|
||||
logger.warn(
|
||||
`No stage found for mode ${mode} iteration ${currentIteration}, using fallback ${fallback}`,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { TFunction } from "i18next";
|
||||
import * as R from "remeda";
|
||||
import type { Tables, TournamentRoundMaps } from "~/db/tables";
|
||||
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists";
|
||||
@@ -7,8 +8,6 @@ import {
|
||||
seededRandom,
|
||||
sourceTypes,
|
||||
} from "~/modules/tournament-map-list-generator";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import { sumArray } from "~/utils/number";
|
||||
import type { FindMatchById } from "../tournament-bracket/queries/findMatchById.server";
|
||||
import type { TournamentLoaderData } from "../tournament/loaders/to.$id.server";
|
||||
import type { Standing } from "./core/Bracket";
|
||||
@@ -110,7 +109,7 @@ export function everyMatchIsOver(
|
||||
) {
|
||||
// winners, losers & grand finals+bracket reset are all different stages
|
||||
const isDoubleElimination =
|
||||
removeDuplicates(bracket.match.map((match) => match.group_id)).length === 3;
|
||||
R.unique(bracket.match.map((match) => match.group_id)).length === 3;
|
||||
|
||||
// tournament didn't start yet
|
||||
if (bracket.match.length === 0) return false;
|
||||
@@ -241,7 +240,7 @@ export function isSetOverByResults({
|
||||
}
|
||||
|
||||
if (countType === "PLAY_ALL") {
|
||||
return sumArray(Array.from(winCounts.values())) === count;
|
||||
return R.sum(Array.from(winCounts.values())) === count;
|
||||
}
|
||||
|
||||
const maxWins = Math.max(...Array.from(winCounts.values()));
|
||||
@@ -260,7 +259,7 @@ export function isSetOverByScore({
|
||||
countType: TournamentRoundMaps["type"];
|
||||
}) {
|
||||
if (countType === "PLAY_ALL") {
|
||||
return sumArray(scores) === count;
|
||||
return R.sum(scores) === count;
|
||||
}
|
||||
|
||||
const matchOverAtXWins = Math.ceil(count / 2);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as R from "remeda";
|
||||
import type { Standing } from "~/features/tournament-bracket/core/Bracket";
|
||||
import * as Progression from "~/features/tournament-bracket/core/Progression";
|
||||
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
|
||||
/** Calculates SPR (Seed Performance Rating) - see https://www.pgstats.com/articles/introducing-spr-and-uf */
|
||||
export function calculateSPR({
|
||||
@@ -11,7 +11,7 @@ export function calculateSPR({
|
||||
standings: Standing[];
|
||||
teamId: number;
|
||||
}) {
|
||||
const uniquePlacements = removeDuplicates(
|
||||
const uniquePlacements = R.unique(
|
||||
standings.map((standing) => standing.placement),
|
||||
).sort((a, b) => a - b);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as R from "remeda";
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists";
|
||||
import { removeDuplicatesByProperty } from "~/utils/arrays";
|
||||
import { parseDBArray } from "~/utils/sql";
|
||||
|
||||
const stm = sql.prepare(/* sql */ `
|
||||
@@ -111,10 +111,7 @@ export function setHistoryByTeamId(
|
||||
...row,
|
||||
matches: parseDBArray(row.matches),
|
||||
// TODO: there is probably a way to do this in SQL
|
||||
players: removeDuplicatesByProperty(
|
||||
parseDBArray(row.players),
|
||||
(u: Pick<Tables["User"], "id">) => u.id,
|
||||
),
|
||||
players: R.uniqueBy(parseDBArray(row.players), (u) => u.id),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import { useAutoRerender } from "~/hooks/useAutoRerender";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { useSearchParamState } from "~/hooks/useSearchParamState";
|
||||
import { modesShort, rankedModesShort } from "~/modules/in-game-lists/modes";
|
||||
import { filterOutFalsy } from "~/utils/arrays";
|
||||
import invariant from "~/utils/invariant";
|
||||
import {
|
||||
LOG_IN_URL,
|
||||
@@ -403,10 +402,7 @@ function RegistrationProgress({
|
||||
const completedIfTruthy = (condition: unknown) =>
|
||||
condition ? "completed" : "incomplete";
|
||||
|
||||
const steps: Array<{
|
||||
name: string;
|
||||
status: "completed" | "incomplete" | "notice";
|
||||
}> = filterOutFalsy([
|
||||
const steps = [
|
||||
{
|
||||
name: t("tournament:pre.steps.name"),
|
||||
status: completedIfTruthy(name),
|
||||
@@ -432,10 +428,10 @@ function RegistrationProgress({
|
||||
tournament.isLeagueSignup
|
||||
? {
|
||||
name: "Google Sheet",
|
||||
status: "notice",
|
||||
status: "notice" as const,
|
||||
}
|
||||
: null,
|
||||
]);
|
||||
].filter((step) => step !== null);
|
||||
|
||||
const regClosesBeforeStart =
|
||||
tournament.registrationClosesAt.getTime() !==
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ActionFunction, redirect } from "@remix-run/node";
|
||||
import * as R from "remeda";
|
||||
import { z } from "zod";
|
||||
import { BUILD } from "~/constants";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
@@ -15,7 +16,6 @@ import type {
|
||||
BuildAbilitiesTuple,
|
||||
MainWeaponId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import { unJsonify } from "~/utils/kysely.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
|
||||
@@ -194,7 +194,7 @@ function refreshCache({
|
||||
...oldBuildWeapons.map(({ weaponSplId }) => weaponSplId),
|
||||
];
|
||||
|
||||
const dedupedWeaponSplIds = removeDuplicates(allWeaponSplIds);
|
||||
const dedupedWeaponSplIds = R.unique(allWeaponSplIds);
|
||||
|
||||
refreshBuildsCacheByWeaponSplIds(dedupedWeaponSplIds);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as R from "remeda";
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import type { MainWeaponId, ModeShort, StageId } from "~/modules/in-game-lists";
|
||||
import { removeDuplicates } from "~/utils/arrays";
|
||||
import { parseDBArray, parseDBJsonArray } from "~/utils/sql";
|
||||
import { weaponIdToArrayWithAlts } from "../../../modules/in-game-lists/weapon-ids";
|
||||
import { VODS_PAGE_BATCH_SIZE } from "../vods-constants";
|
||||
@@ -87,7 +87,7 @@ export function findVods({
|
||||
|
||||
return {
|
||||
...vod,
|
||||
weapons: removeDuplicates(parseDBArray(vod.weapons)),
|
||||
weapons: R.unique(parseDBArray(vod.weapons)),
|
||||
pov: playerNames[0] ?? players[0],
|
||||
};
|
||||
})
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// TODO: when more examples of permissions profile difference between
|
||||
// this implementation and one that takes arrays
|
||||
|
||||
import shuffle from "just-shuffle";
|
||||
import invariant from "~/utils/invariant";
|
||||
|
||||
// (not all arrays need to necessarily run but they need to be defined)
|
||||
export function allTruthy(arr: unknown[]) {
|
||||
return arr.every(Boolean);
|
||||
@@ -55,48 +52,10 @@ export function isDefined<T>(value: T | undefined | null): value is T {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export function removeDuplicates<T>(arr: T[]): T[] {
|
||||
const seen = new Set<T>();
|
||||
|
||||
return arr.filter((item) => {
|
||||
if (seen.has(item)) return false;
|
||||
seen.add(item);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function removeDuplicatesByProperty<T>(
|
||||
arr: T[],
|
||||
getter: (arg0: T) => number | string,
|
||||
): T[] {
|
||||
const seen = new Set();
|
||||
return arr.filter((item) => {
|
||||
const id = getter(item);
|
||||
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function nullFilledArray(size: number): null[] {
|
||||
return new Array(size).fill(null);
|
||||
}
|
||||
|
||||
export function pickRandomItem<T>(array: T[]): T {
|
||||
invariant(array.length > 0, "Can't pick from empty array");
|
||||
|
||||
const shuffled = shuffle(structuredClone(array));
|
||||
|
||||
return shuffled[0];
|
||||
}
|
||||
|
||||
export function filterOutFalsy<T>(arr: (T | null | undefined)[]): T[] {
|
||||
return arr.filter(Boolean) as T[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average of an array of numbers. If the array is empty, returns null.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as R from "remeda";
|
||||
|
||||
export function roundToNDecimalPlaces(num: number, n = 2) {
|
||||
return Number((Math.round(num * 10 ** n) / 10 ** n).toFixed(n));
|
||||
}
|
||||
@@ -9,12 +11,8 @@ export function cutToNDecimalPlaces(num: number, n = 2) {
|
||||
return Number(n > 0 ? result.replace(/\.?0+$/, "") : result);
|
||||
}
|
||||
|
||||
export function sumArray(arr: number[]) {
|
||||
return arr.reduce((acc, curr) => acc + curr, 0);
|
||||
}
|
||||
|
||||
export function averageArray(arr: number[]) {
|
||||
return sumArray(arr) / arr.length;
|
||||
return R.sum(arr) / arr.length;
|
||||
}
|
||||
|
||||
export function safeNumberParse(value: string | null) {
|
||||
|
||||
@@ -5,10 +5,6 @@ export function inGameNameWithoutDiscriminator(inGameName: string) {
|
||||
return inGameName.split("#")[0];
|
||||
}
|
||||
|
||||
export function semiRandomId() {
|
||||
return String(Math.random());
|
||||
}
|
||||
|
||||
export const rawSensToString = (sens: number) =>
|
||||
`${sens > 0 ? "+" : ""}${sens / 10}`;
|
||||
|
||||
@@ -53,10 +49,6 @@ export function gearTypeToInitial(gearType: GearType) {
|
||||
}
|
||||
}
|
||||
|
||||
export function capitalize(str: string) {
|
||||
return str[0].toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export function pathnameFromPotentialURL(maybeUrl: string) {
|
||||
try {
|
||||
return new URL(maybeUrl).pathname.replace("/", "");
|
||||
|
||||
44
package-lock.json
generated
44
package-lock.json
generated
@@ -33,10 +33,6 @@
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"i18next-http-backend": "^2.6.2",
|
||||
"isbot": "^5.1.25",
|
||||
"just-capitalize": "^3.2.0",
|
||||
"just-compare": "^2.3.0",
|
||||
"just-random-integer": "^4.2.0",
|
||||
"just-shuffle": "^4.2.0",
|
||||
"kysely": "^0.27.6",
|
||||
"lru-cache": "^11.1.0",
|
||||
"markdown-to-jsx": "^7.7.4",
|
||||
@@ -56,6 +52,7 @@
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-use": "^17.6.0",
|
||||
"react-use-draggable-scroll": "^0.4.7",
|
||||
"remeda": "^2.21.2",
|
||||
"remix-auth": "^4.1.0",
|
||||
"remix-auth-oauth2": "^3.4.0",
|
||||
"remix-i18next": "^6.4.1",
|
||||
@@ -11131,30 +11128,6 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/just-capitalize": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/just-capitalize/-/just-capitalize-3.2.0.tgz",
|
||||
"integrity": "sha512-FK8U9A5AHCIGxlEXg3RFJkb9Nz/fS9luJlrfRf0bFBZU6xnIQ6tbwl+HitMJLwCFszZqVaXQcyeoy8/PYABS6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/just-compare": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/just-compare/-/just-compare-2.3.0.tgz",
|
||||
"integrity": "sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/just-random-integer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/just-random-integer/-/just-random-integer-4.2.0.tgz",
|
||||
"integrity": "sha512-MfabwcY+RQNCVCmZZkTYDpk/AT315+7Rkoj59+abzYxgUOiSdoZ4G5hbK/4VZqdHScu95Wnd+1MqxyUETM6xuQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/just-shuffle": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/just-shuffle/-/just-shuffle-4.2.0.tgz",
|
||||
"integrity": "sha512-/dDmNseAWLf3XkFY9xf3/BdQoiy27LNUy/7uG4zdSAX526nIHMYPYeJ4pN4lT1/pgNEX8XCXPtUB6gJqTpBEng==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
|
||||
@@ -14695,6 +14668,15 @@
|
||||
"integrity": "sha512-agFFS3RzrLXJl5LY5xg/xYyXvUuVAnkhgKO7RaO9J1Ssth6yvbO+PIiV67V59MB5NCdAK2flvGvNT4mdKVniFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/remeda": {
|
||||
"version": "2.21.2",
|
||||
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.2.tgz",
|
||||
"integrity": "sha512-wdhkMDou8HRpD7RnxKJ/FHJWEGXRH7jV/pb0NsdLLSoBo+G9RjtxcY41hVhogLfEMkThk6aySKjs+Yd6PnpzBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.37.0"
|
||||
}
|
||||
},
|
||||
"node_modules/remix-auth": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remix-auth/-/remix-auth-4.1.0.tgz",
|
||||
@@ -16212,9 +16194,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.36.0.tgz",
|
||||
"integrity": "sha512-3T/PUdKTCnkUmhQU6FFJEHsLwadsRegktX3TNHk+2JJB9HlA8gp1/VXblXVDI93kSnXF2rdPx0GMbHtJIV2LPg==",
|
||||
"version": "4.39.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz",
|
||||
"integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
@@ -49,10 +49,6 @@
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"i18next-http-backend": "^2.6.2",
|
||||
"isbot": "^5.1.25",
|
||||
"just-capitalize": "^3.2.0",
|
||||
"just-compare": "^2.3.0",
|
||||
"just-random-integer": "^4.2.0",
|
||||
"just-shuffle": "^4.2.0",
|
||||
"kysely": "^0.27.6",
|
||||
"lru-cache": "^11.1.0",
|
||||
"markdown-to-jsx": "^7.7.4",
|
||||
@@ -72,6 +68,7 @@
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-use": "^17.6.0",
|
||||
"react-use-draggable-scroll": "^0.4.7",
|
||||
"remeda": "^2.21.2",
|
||||
"remix-auth": "^4.1.0",
|
||||
"remix-auth-oauth2": "^3.4.0",
|
||||
"remix-i18next": "^6.4.1",
|
||||
|
||||
Reference in New Issue
Block a user