mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-24 03:55:49 -05:00
Fix team MMR calculation
This commit is contained in:
@@ -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"] }],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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})` : ""}`
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user