Show relative team MMR for ranked teams

This commit is contained in:
Kalle
2022-02-28 20:27:42 +02:00
parent e0e8a68de5
commit 4f8f45fc9e
11 changed files with 244 additions and 6 deletions

View File

@@ -0,0 +1,18 @@
export function ArrowDownIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
);
}

View File

@@ -0,0 +1,18 @@
export function ArrowUpIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 15l7-7 7 7"
/>
</svg>
);
}

View File

@@ -0,0 +1,16 @@
export function DoubleArrowDownIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M15.707 4.293a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-5-5a1 1 0 011.414-1.414L10 8.586l4.293-4.293a1 1 0 011.414 0zm0 6a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-5-5a1 1 0 111.414-1.414L10 14.586l4.293-4.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
);
}

View File

@@ -0,0 +1,18 @@
export function DoubleArrowUpIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 11l7-7 7 7M5 19l7-7 7 7"
/>
</svg>
);
}

View File

@@ -0,0 +1,18 @@
export function MinusIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M20 12H4"
/>
</svg>
);
}

View File

@@ -5,6 +5,11 @@ import type {
LookingActionSchema,
LookingLoaderDataGroup,
} from "~/routes/play/looking";
import { ArrowDownIcon } from "../icons/ArrowDown";
import { ArrowUpIcon } from "../icons/ArrowUp";
import { DoubleArrowDownIcon } from "../icons/DoubleArrowDown";
import { DoubleArrowUpIcon } from "../icons/DoubleArrowUp";
import { MinusIcon } from "../icons/Minus";
import { GroupMembers } from "./GroupMembers";
export function GroupCard({
@@ -69,6 +74,7 @@ export function GroupCard({
{group.teamMMR.value}
</div>
)}
{group.MMRRelation && <MMRRelation relation={group.MMRRelation} />}
<input type="hidden" name="targetGroupId" value={group.id} />
{action === "UNITE_GROUPS" && (
<input
@@ -96,3 +102,51 @@ export function GroupCard({
</fetcher.Form>
);
}
function MMRRelation({
relation,
}: {
relation: NonNullable<LookingLoaderDataGroup["MMRRelation"]>;
}) {
switch (relation) {
case "CLOSE": {
return (
<div className="play__card__mmr-relation">
<MinusIcon /> Close SP
</div>
);
}
case "BIT_HIGHER": {
return (
<div className="play__card__mmr-relation">
<ArrowUpIcon /> A bit higher SP
</div>
);
}
case "BIT_LOWER": {
return (
<div className="play__card__mmr-relation">
<ArrowDownIcon /> A bit lower SP
</div>
);
}
case "HIGHER": {
return (
<div className="play__card__mmr-relation">
<DoubleArrowUpIcon /> Higher SP
</div>
);
}
case "LOWER": {
return (
<div className="play__card__mmr-relation">
<DoubleArrowDownIcon /> Lower SP
</div>
);
}
default: {
const exhaustive: never = relation;
throw new Error(`Unknown relation: ${JSON.stringify(exhaustive)}`);
}
}
}

View File

@@ -23,6 +23,9 @@ export const MMR_TOPX_VISIBILITY_CUTOFF = 50;
export const LFG_AMOUNT_OF_STAGES_TO_GENERATE = 7;
// export const LFG_AMOUNT_OF_STAGES_TO_GENERATE = 9;
export const CLOSE_MMR_LIMIT = 250;
export const BIT_HIGHER_MMR_LIMIT = 750;
export const checkInClosesDate = (startTime: string): Date => {
return new Date(new Date(startTime).getTime() - 1000 * 10);
};

View File

@@ -1,6 +1,8 @@
import { suite } from "uvu";
import * as assert from "uvu/assert";
import { BIT_HIGHER_MMR_LIMIT } from "~/constants";
import {
calculateDifference,
groupsToWinningAndLosingPlayerIds,
scoresAreIdentical,
uniteGroupInfo,
@@ -12,6 +14,7 @@ const ScoresAreIdentical = suite("scoresAreIdentical()");
const GroupsToWinningAndLosingPlayerIds = suite(
"groupsToWinningAndLosingPlayerIds()"
);
const CalculateDifference = suite("calculateDifference()");
const SMALL_GROUP: UniteGroupInfoArg = { id: "small", memberCount: 1 };
const BIG_GROUP: UniteGroupInfoArg = { id: "big", memberCount: 3 };
@@ -96,6 +99,29 @@ GroupsToWinningAndLosingPlayerIds(
}
);
CalculateDifference("Close", () => {
assert.equal(calculateDifference({ ourMMR: 0, theirMMR: 0 }), "CLOSE");
assert.equal(calculateDifference({ ourMMR: 0, theirMMR: 1 }), "CLOSE");
assert.equal(calculateDifference({ ourMMR: 1, theirMMR: 0 }), "CLOSE");
});
CalculateDifference("Higher/lower", () => {
assert.equal(calculateDifference({ ourMMR: 0, theirMMR: 10_000 }), "HIGHER");
assert.equal(calculateDifference({ ourMMR: 10_000, theirMMR: 0 }), "LOWER");
});
CalculateDifference.only("A bit higher/lower", () => {
assert.equal(
calculateDifference({ ourMMR: 0, theirMMR: BIT_HIGHER_MMR_LIMIT }),
"BIT_HIGHER"
);
assert.equal(
calculateDifference({ ourMMR: 0, theirMMR: -BIT_HIGHER_MMR_LIMIT }),
"BIT_LOWER"
);
});
UniteGroupInfo.run();
ScoresAreIdentical.run();
GroupsToWinningAndLosingPlayerIds.run();
CalculateDifference.run();

View File

@@ -1,10 +1,22 @@
import invariant from "tiny-invariant";
import { LFG_GROUP_FULL_SIZE, LFG_GROUP_INACTIVE_MINUTES } from "~/constants";
import {
BIT_HIGHER_MMR_LIMIT,
CLOSE_MMR_LIMIT,
LFG_GROUP_FULL_SIZE,
LFG_GROUP_INACTIVE_MINUTES,
} from "~/constants";
import * as LFGGroup from "~/models/LFGGroup.server";
import { PlayFrontPageLoader } from "~/routes/play/index";
import { LookingLoaderData } from "~/routes/play/looking";
import {
LookingLoaderData,
LookingLoaderDataGroup,
} from "~/routes/play/looking";
import { Unpacked } from "~/utils";
import { skillArrayToMMR, teamSkillToApproximateMMR } from "../mmr/utils";
import {
skillArrayToMMR,
teamSkillToApproximateMMR,
teamSkillToExactMMR,
} from "../mmr/utils";
import { canUniteWithGroup } from "./validators";
export interface UniteGroupInfoArg {
@@ -119,6 +131,7 @@ export function otherGroupsForResponse({
likes,
lookingForMatch,
ownGroup,
useRelativeSkillLevel,
}: {
groups: LFGGroup.FindLookingAndOwnActive;
likes: {
@@ -127,6 +140,7 @@ export function otherGroupsForResponse({
};
lookingForMatch: boolean;
ownGroup: Unpacked<LFGGroup.FindLookingAndOwnActive>;
useRelativeSkillLevel: boolean;
}) {
return groups
.filter(
@@ -140,7 +154,7 @@ export function otherGroupsForResponse({
)
.filter((group) => group.id !== ownGroup.id)
.filter(filterExpiredGroups)
.map((group) => {
.map((group): LookingLoaderDataGroup => {
const ranked = () => {
if (lookingForMatch && !ownGroup.ranked) return false;
@@ -162,8 +176,12 @@ export function otherGroupsForResponse({
};
}),
ranked: ranked(),
MMRRelation:
ownGroup.ranked && group.ranked && useRelativeSkillLevel
? resolveMMRRelation({ group, ownGroup })
: undefined,
teamMMR:
lookingForMatch && group.ranked
lookingForMatch && group.ranked && !useRelativeSkillLevel
? {
exact: false,
value: teamSkillToApproximateMMR(group.members),
@@ -223,3 +241,35 @@ export function countGroups(
{ TWIN: 0, QUAD: 0, "VERSUS-RANKED": 0, "VERSUS-UNRANKED": 0 }
);
}
function resolveMMRRelation({
group,
ownGroup,
}: {
group: Unpacked<LFGGroup.FindLookingAndOwnActive>;
ownGroup: Unpacked<LFGGroup.FindLookingAndOwnActive>;
}): NonNullable<LookingLoaderDataGroup["MMRRelation"]> {
return calculateDifference({
ourMMR: teamSkillToExactMMR(ownGroup.members),
theirMMR: teamSkillToExactMMR(group.members),
});
}
export function calculateDifference({
ourMMR,
theirMMR,
}: {
ourMMR: number;
theirMMR: number;
}) {
const difference = Math.abs(ourMMR - theirMMR);
const ownIsBigger = ourMMR > theirMMR;
if (difference <= CLOSE_MMR_LIMIT) return "CLOSE";
if (difference > BIT_HIGHER_MMR_LIMIT && ownIsBigger) return "LOWER";
if (difference > BIT_HIGHER_MMR_LIMIT && !ownIsBigger) return "HIGHER";
if (ownIsBigger) return "BIT_LOWER";
if (!ownIsBigger) return "BIT_HIGHER";
throw new Error("Unexpected calculateMMRRelation scenario");
}

View File

@@ -35,6 +35,8 @@ import {
validate,
} from "~/utils";
const USE_RELATIVE_SKILL_LEVEL = true;
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
@@ -215,6 +217,7 @@ export type LookingLoaderDataGroup = {
exact: boolean;
value: number;
};
MMRRelation?: "LOWER" | "BIT_LOWER" | "CLOSE" | "BIT_HIGHER" | "HIGHER";
ranked?: boolean;
};
@@ -269,7 +272,7 @@ export const loader: LoaderFunction = async ({ context }) => {
}),
ranked: ownGroup.ranked ?? undefined,
teamMMR:
lookingForMatch && isRanked
lookingForMatch && isRanked && !USE_RELATIVE_SKILL_LEVEL
? {
exact: true,
value: teamSkillToExactMMR(ownGroupWithMembers.members),
@@ -280,6 +283,7 @@ export const loader: LoaderFunction = async ({ context }) => {
isCaptain: isGroupAdmin({ group: ownGroup, user }),
lastActionAtTimestamp: ownGroup.lastActionAt.getTime(),
...otherGroupsForResponse({
useRelativeSkillLevel: USE_RELATIVE_SKILL_LEVEL,
groups: groupsOfType,
lookingForMatch,
ownGroup,

View File

@@ -99,6 +99,19 @@
font-size: var(--fonts-xxs);
}
.play__card__mmr-relation {
display: flex;
width: 100%;
justify-content: center;
font-size: var(--fonts-sm);
font-weight: var(--semi-bold);
gap: var(--s-2);
}
.play__card__mmr-relation > svg {
width: 1rem;
}
.play__card__member-power {
display: flex;
margin-top: -4px;