From 244f6aab8a5becbd58a077ba5fc817fa376227b6 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 27 Feb 2022 10:49:49 +0200 Subject: [PATCH] Fix team MMR calculation --- .eslintrc.js | 1 + app/core/mmr/utils.test.ts | 37 ++++++++++++++++++++++++++++++++++- app/core/mmr/utils.ts | 39 +++++++++++++++++++++++-------------- app/core/play/utils.ts | 8 ++------ app/hooks/common.ts | 6 +++--- app/routes/play/looking.tsx | 10 ++-------- package.json | 3 ++- prisma/seed/index.ts | 1 + 8 files changed, 71 insertions(+), 34 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 484bd4ae1..fe40118fb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,5 +14,6 @@ module.exports = { rules: { "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], "no-constant-condition": ["error", { checkLoops: false }], + "no-console": ["error", { allow: ["warn", "error"] }], }, }; diff --git a/app/core/mmr/utils.test.ts b/app/core/mmr/utils.test.ts index c22a1ca08..5425daf3a 100644 --- a/app/core/mmr/utils.test.ts +++ b/app/core/mmr/utils.test.ts @@ -1,9 +1,15 @@ import { suite } from "uvu"; -import { adjustSkills, muSigmaToSP, resolveOwnMMR } from "./utils"; +import { + adjustSkills, + muSigmaToSP, + resolveOwnMMR, + teamSkillToExactMMR, +} from "./utils"; import * as assert from "uvu/assert"; const AdjustSkills = suite("adjustSkills()"); const ResolveOwnMMR = suite("resolveOwnMMR()"); +const TeamSkillToExactMMR = suite("TeamSkillToExactMMR()"); const MU_AT_START = 20; const SIGMA_AT_START = 4; @@ -93,5 +99,34 @@ ResolveOwnMMR("Hides topX if not good", () => { assert.not.ok(own?.topX); }); +TeamSkillToExactMMR("Sums up MMR's", () => { + const MU = 20; + const SIGMA = 7; + const skills = new Array(4).fill(null).map((_) => ({ mu: MU, sigma: SIGMA })); + + const teamMMR = teamSkillToExactMMR( + skills.map((s) => ({ user: { skill: [{ mu: s.mu, sigma: s.sigma }] } })) + ); + + assert.equal(teamMMR, muSigmaToSP({ mu: MU, sigma: SIGMA }) * 4); +}); + +TeamSkillToExactMMR("Pads team MMR", () => { + const MU = 20; + const SIGMA = 7; + const skills = new Array(3).fill(null).map((_) => ({ mu: MU, sigma: SIGMA })); + + const teamMMR = teamSkillToExactMMR( + skills.map((s) => ({ user: { skill: [{ mu: s.mu, sigma: s.sigma }] } })) + ); + + assert.equal(teamMMR, muSigmaToSP({ mu: MU, sigma: SIGMA }) * 3 + 1000); + + const teamMMRNoSkills = teamSkillToExactMMR([]); + + assert.equal(teamMMRNoSkills, 4 * 1000); +}); + AdjustSkills.run(); ResolveOwnMMR.run(); +TeamSkillToExactMMR.run(); diff --git a/app/core/mmr/utils.ts b/app/core/mmr/utils.ts index 1cde0787b..12edcfc0d 100644 --- a/app/core/mmr/utils.ts +++ b/app/core/mmr/utils.ts @@ -1,5 +1,6 @@ +import clone from "just-clone"; import { expose, rate, Rating } from "ts-trueskill"; -import { MMR_TOPX_VISIBILITY_CUTOFF } from "~/constants"; +import { LFG_GROUP_FULL_SIZE, MMR_TOPX_VISIBILITY_CUTOFF } from "~/constants"; import { PlayFrontPageLoader } from "~/routes/play/index"; /** Get first skill object of the array (should be ordered so that most recent skill is first) and convert it into MMR. */ @@ -31,7 +32,28 @@ interface TeamSkill { export function teamSkillToExactMMR(teamSkills: TeamSkill[]) { let sum = 0; - for (const { user } of teamSkills) { + const teamSkillsClone = clone(teamSkills); + while (teamSkillsClone.length < LFG_GROUP_FULL_SIZE) { + teamSkillsClone.push({ user: { skill: [] } }); + } + + const defaultRating = new Rating(); + const skillsWithDefaults = teamSkillsClone.reduce((acc: TeamSkill[], cur) => { + if (cur.user.skill.length === 0) { + return [ + { + user: { + skill: [{ mu: defaultRating.mu, sigma: defaultRating.sigma }], + }, + }, + ...acc, + ]; + } + + return [cur, ...acc]; + }, []); + + for (const { user } of skillsWithDefaults) { const MMR = skillArrayToMMR(user.skill); if (!MMR) continue; @@ -53,19 +75,6 @@ function toTwoDecimals(value: number) { return Number(value.toFixed(2)); } -export function teamHasSkill(teamSkills: TeamSkill[]) { - let hasSkill = false; - - for (const { user } of teamSkills) { - if (user.skill.length > 0) { - hasSkill = true; - break; - } - } - - return hasSkill; -} - interface AdjustSkill { mu: number; sigma: number; diff --git a/app/core/play/utils.ts b/app/core/play/utils.ts index 9b5ba784b..40c142525 100644 --- a/app/core/play/utils.ts +++ b/app/core/play/utils.ts @@ -4,11 +4,7 @@ import * as LFGGroup from "~/models/LFGGroup.server"; import { PlayFrontPageLoader } from "~/routes/play/index"; import { LookingLoaderData } from "~/routes/play/looking"; import { Unpacked } from "~/utils"; -import { - skillArrayToMMR, - teamHasSkill, - teamSkillToApproximateMMR, -} from "../mmr/utils"; +import { skillArrayToMMR, teamSkillToApproximateMMR } from "../mmr/utils"; import { canUniteWithGroup } from "./validators"; export interface UniteGroupInfoArg { @@ -167,7 +163,7 @@ export function otherGroupsForResponse({ }), ranked: ranked(), teamMMR: - lookingForMatch && group.ranked && teamHasSkill(group.members) + lookingForMatch && group.ranked ? { exact: false, value: teamSkillToApproximateMMR(group.members), diff --git a/app/hooks/common.ts b/app/hooks/common.ts index 8926761ff..cc9bc85bf 100644 --- a/app/hooks/common.ts +++ b/app/hooks/common.ts @@ -27,9 +27,9 @@ export const useEvents = ( `/events?${new URLSearchParams(target).toString()}` ); - source.addEventListener("open", () => { - console.log("SSE opened!"); - }); + // source.addEventListener("open", () => { + // console.log("SSE opened!"); + // }); source.addEventListener("message", (e) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument diff --git a/app/routes/play/looking.tsx b/app/routes/play/looking.tsx index d279551e3..59ffcc074 100644 --- a/app/routes/play/looking.tsx +++ b/app/routes/play/looking.tsx @@ -16,11 +16,7 @@ import { GroupCard } from "~/components/play/GroupCard"; import { LookingInfoText } from "~/components/play/LookingInfoText"; import { Tab } from "~/components/Tab"; import { LFG_GROUP_FULL_SIZE } from "~/constants"; -import { - skillArrayToMMR, - teamHasSkill, - teamSkillToExactMMR, -} from "~/core/mmr/utils"; +import { skillArrayToMMR, teamSkillToExactMMR } from "~/core/mmr/utils"; import { addInfoFromOldSendouInk } from "~/core/play/playerInfos/playerInfos.server"; import { groupExpirationStatus, @@ -279,9 +275,7 @@ export const loader: LoaderFunction = async ({ context }) => { }), ranked: ownGroup.ranked ?? undefined, teamMMR: - lookingForMatch && - isRanked && - teamHasSkill(ownGroupWithMembers.members) + lookingForMatch && isRanked ? { exact: true, value: teamSkillToExactMMR(ownGroupWithMembers.members), diff --git a/package.json b/package.json index 42e6257b1..c7dfd1cbd 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "typecheck": "tsc --noEmit", "cy:open": "npx cypress open", "cy:run": "npx cypress run", - "test:unit": "uvu -r tsm -r tsconfig-paths/register -i cypress" + "test:unit": "uvu -r tsm -r tsconfig-paths/register -i cypress", + "tests": "npm run lint:styles && npm run lint:ts && npm run prettier:check && npm run typecheck" }, "dependencies": { "@dnd-kit/core": "^5.0.1", diff --git a/prisma/seed/index.ts b/prisma/seed/index.ts index f100057bc..37c4b846a 100644 --- a/prisma/seed/index.ts +++ b/prisma/seed/index.ts @@ -10,6 +10,7 @@ const variation = SeedVariationsSchema.optional().parse(maybeVariation); seed(variation) .then(() => { + // eslint-disable-next-line no-console console.log( `🌱 All done with seeding${variation ? ` (variation: ${variation})` : ""}` );