diff --git a/app/core/mmr/utils.ts b/app/core/mmr/utils.ts index 12edcfc0d..8d0040bc4 100644 --- a/app/core/mmr/utils.ts +++ b/app/core/mmr/utils.ts @@ -71,7 +71,7 @@ export function teamSkillToApproximateMMR(teamSkills: TeamSkill[]) { return roundToNearest25(teamMMR); } -function toTwoDecimals(value: number) { +export function toTwoDecimals(value: number) { return Number(value.toFixed(2)); } diff --git a/app/routes/play/history.$id.tsx b/app/routes/play/history.$id.tsx index cf2ba94dc..d427c9295 100644 --- a/app/routes/play/history.$id.tsx +++ b/app/routes/play/history.$id.tsx @@ -12,6 +12,7 @@ import invariant from "tiny-invariant"; import styles from "~/styles/play-match-history.css"; import clsx from "clsx"; import { sendouQMatchPage } from "~/utils/urls"; +import { toTwoDecimals } from "~/core/mmr/utils"; export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: styles }]; @@ -32,6 +33,9 @@ interface MathHistoryLoaderData { their: number; }; }[]; + stageCount: number; + setWinRate: number; + stageWinRate: number; } export const loader: LoaderFunction = async ({ params }) => { @@ -40,41 +44,65 @@ export const loader: LoaderFunction = async ({ params }) => { const matches = await LFGMatch.findByUserId({ userId: params.id }); if (matches.length === 0) throw new Response(null, { status: 404 }); // TODO: don't show link if it would 404? + const mappedMatches = matches.map( + (match): Unpacked => { + const usersGroup = match.groups.find((g) => + g.members.some((m) => m.memberId === params.id) + ); + invariant(usersGroup, "Unexpected usersGroup undefined"); + + const opposingGroup = match.groups.find((g) => g.id !== usersGroup.id); + invariant(opposingGroup, "Unexpected opposingGroup undefined"); + + return { + id: match.id, + createdAtTimestamp: match.createdAt.getTime(), + teammates: usersGroup.members.map((m) => m.user.discordName), + opponents: opposingGroup.members.map((m) => m.user.discordName), + score: { + our: match.stages + .filter((s) => s.winnerGroupId) + .reduce( + (acc, stage) => + acc + Number(stage.winnerGroupId === usersGroup.id), + 0 + ), + their: match.stages + .filter((s) => s.winnerGroupId) + .reduce( + (acc, stage) => + acc + Number(stage.winnerGroupId !== usersGroup.id), + 0 + ), + }, + }; + } + ); + + const stageCount = mappedMatches.reduce( + (acc, match) => match.score.our + match.score.their + acc, + 0 + ); + return json({ - matches: matches.map( - (match): Unpacked => { - const usersGroup = match.groups.find((g) => - g.members.some((m) => m.memberId === params.id) - ); - invariant(usersGroup, "Unexpected usersGroup undefined"); - - const opposingGroup = match.groups.find((g) => g.id !== usersGroup.id); - invariant(opposingGroup, "Unexpected opposingGroup undefined"); - - return { - id: match.id, - createdAtTimestamp: match.createdAt.getTime(), - teammates: usersGroup.members.map((m) => m.user.discordName), - opponents: opposingGroup.members.map((m) => m.user.discordName), - score: { - our: match.stages - .filter((s) => s.winnerGroupId) - .reduce( - (acc, stage) => - acc + Number(stage.winnerGroupId === usersGroup.id), - 0 - ), - their: match.stages - .filter((s) => s.winnerGroupId) - .reduce( - (acc, stage) => - acc + Number(stage.winnerGroupId !== usersGroup.id), - 0 - ), - }, - }; - } + stageCount: mappedMatches.reduce( + (acc, match) => match.score.our + match.score.their + acc, + 0 ), + setWinRate: toTwoDecimals( + (mappedMatches.reduce( + (acc, match) => Number(match.score.our > match.score.their) + acc, + 0 + ) / + mappedMatches.length) * + 100 + ), + stageWinRate: toTwoDecimals( + (mappedMatches.reduce((acc, match) => match.score.our + acc, 0) / + stageCount) * + 100 + ), + matches: mappedMatches, }); }; @@ -98,6 +126,17 @@ export default function MatchHistoryPage() { return ( <> +

+ Sendou's SendouQ results +

+ + {data.matches.length} {data.matches.length === 1 ? "set" : "sets"}{" "} + played ({data.setWinRate}% winrate) + {" "} + •{" "} + + {data.stageCount} maps played ({data.stageWinRate}% winrate) + {showDate && (

{currentDateString}

)} @@ -107,7 +146,7 @@ export default function MatchHistoryPage() { dateTime={new Date(match.createdAtTimestamp).toISOString()} > {new Date(match.createdAtTimestamp).toLocaleString("en-us", { - hour: "numeric", + hour: "2-digit", minute: "numeric", })} diff --git a/app/styles/play-match-history.css b/app/styles/play-match-history.css index 0b294860d..698a2f0a6 100644 --- a/app/styles/play-match-history.css +++ b/app/styles/play-match-history.css @@ -1,6 +1,18 @@ +.play-match-history__title { + line-height: 2rem; + margin-block-start: var(--s-6); +} + +.play-match-history__winrate-info { + color: var(--text-lighter); + font-size: var(--fonts-sm); + font-weight: var(--semi-bold); +} + .play-match-history__date { border-block-end: 5px solid var(--theme-transparent); margin-block-end: var(--s-5); + margin-block-start: var(--s-4); } .play-match-history__match {