From 3579b8676f54eda6514d92e5054273640e56fe2d Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 3 Feb 2022 09:35:38 +0200 Subject: [PATCH] Show team MMR's in looking --- app/components/play/GroupCard.tsx | 6 ++ app/core/mmr/utils.ts | 48 +++++++++ app/models/LFGGroup.server.ts | 10 ++ app/routes/play/looking.tsx | 35 +++++- app/styles/play-looking.css | 5 + package-lock.json | 172 ++++++++++++++++++++++++++++++ package.json | 1 + prisma/seed/script.ts | 16 +++ 8 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 app/core/mmr/utils.ts diff --git a/app/components/play/GroupCard.tsx b/app/components/play/GroupCard.tsx index 08790d925..337258a58 100644 --- a/app/components/play/GroupCard.tsx +++ b/app/components/play/GroupCard.tsx @@ -53,6 +53,12 @@ export function GroupCard({ ); })} + {group.teamMMR && ( +
+ MMR: {!group.teamMMR.exact && <>~} + {group.teamMMR.value} +
+ )} {type === "LIKES_RECEIVED" && ( Math.round(x / 25) * 25; + + const teamMMR = teamSkillToExactMMR(teamSkills); + return roundToNearest25(teamMMR); +} + +function toTwoDecimals(value: number) { + return Number(value.toFixed(2)); +} diff --git a/app/models/LFGGroup.server.ts b/app/models/LFGGroup.server.ts index 2a21216b1..94afc6627 100644 --- a/app/models/LFGGroup.server.ts +++ b/app/models/LFGGroup.server.ts @@ -149,6 +149,16 @@ export function findLookingByType(type: LfgGroupType, ranked?: boolean) { discordDiscriminator: true, discordName: true, discordId: true, + skill: { + select: { + mu: true, + sigma: true, + }, + orderBy: { + createdAt: "desc", + }, + take: 1, + }, }, }, }, diff --git a/app/routes/play/looking.tsx b/app/routes/play/looking.tsx index b0f341a45..1824967ac 100644 --- a/app/routes/play/looking.tsx +++ b/app/routes/play/looking.tsx @@ -18,6 +18,11 @@ import { canUniteWithGroup, isGroupAdmin } from "~/core/play/validators"; import * as LFGGroup from "~/models/LFGGroup.server"; import styles from "~/styles/play-looking.css"; import { parseRequestFormData, requireUser, validate } from "~/utils"; +import { + skillToMMR, + teamSkillToApproximateMMR, + teamSkillToExactMMR, +} from "~/core/mmr/utils"; export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: styles }]; @@ -141,7 +146,12 @@ export type LookingLoaderDataGroup = { discordAvatar: string | null; discordName: string; discordDiscriminator: string; + MMR?: number; }[]; + teamMMR?: { + exact: boolean; + value: number; + }; ranked?: boolean; }; @@ -186,8 +196,19 @@ export const loader: LoaderFunction = async ({ context }) => { invariant(ownGroupWithMembers, "ownGroupWithMembers is undefined"); const ownGroupForResponse: LookingLoaderDataGroup = { id: ownGroup.id, - members: ownGroupWithMembers.members.map((m) => m.user), + members: ownGroupWithMembers.members.map((m) => { + const { skill, ...rest } = m.user; + + return { + ...rest, + MMR: skillToMMR(skill), + }; + }), ranked: ownGroup.ranked ?? undefined, + teamMMR: { + exact: true, + value: teamSkillToExactMMR(ownGroupWithMembers.members), + }, }; return json({ @@ -212,8 +233,18 @@ export const loader: LoaderFunction = async ({ context }) => { members: group.ranked && lookingForMatch ? undefined - : group.members.map((m) => m.user), + : group.members.map((m) => { + const { skill, ...rest } = m.user; + + return { + ...rest, + MMR: skillToMMR(skill), + }; + }), ranked: group.ranked ?? undefined, + teamMMR: lookingForMatch + ? { exact: false, value: teamSkillToApproximateMMR(group.members) } + : undefined, })) .reduce( ( diff --git a/app/styles/play-looking.css b/app/styles/play-looking.css index 28a3f29c8..76fc56e45 100644 --- a/app/styles/play-looking.css +++ b/app/styles/play-looking.css @@ -101,3 +101,8 @@ margin-block-start: 1px; white-space: nowrap; } + +.play-looking__mmr { + font-size: var(--fonts-sm); + font-weight: var(--bold); +} diff --git a/package-lock.json b/package-lock.json index 466456ac1..0e4ecc67a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "react-dom": "^17.0.2", "remix": "^1.1.3", "tiny-invariant": "^1.2.0", + "ts-trueskill": "^3.3.0", "uuid": "^8.3.2", "zod": "^3.11.6" }, @@ -2132,6 +2133,18 @@ "node": ">=4.0.0" } }, + "node_modules/complex.js": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.15.tgz", + "integrity": "sha512-gDBvQU8IG139ZBQTSo2qvDFP+lANMGluM779csXOr6ny1NUtA3wkUnCFjlDNH/moAVfXtvClYt6G0zarFbtz5w==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/compressible": { "version": "2.0.18", "license": "MIT", @@ -2650,6 +2663,11 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" + }, "node_modules/decode-named-character-reference": { "version": "1.0.0", "dev": true, @@ -2942,6 +2960,11 @@ "version": "1.0.3", "license": "MIT" }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "dev": true, @@ -3794,6 +3817,18 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz", + "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/fresh": { "version": "0.5.2", "license": "MIT", @@ -4823,6 +4858,11 @@ "dev": true, "license": "MIT" }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -5278,6 +5318,28 @@ "node": ">=0.10.0" } }, + "node_modules/mathjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-10.1.0.tgz", + "integrity": "sha512-TrpZAR3H9jR0Cv6cnzT+TZhE40Xs2SCaLf/qm2WcWm2tui69Gas/bC/ct5ZLZNWnWvNvJ7H2uHvuRDDl151PHA==", + "dependencies": { + "@babel/runtime": "^7.16.5", + "complex.js": "^2.0.15", + "decimal.js": "^10.3.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.1.2", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/mathml-tag-names": { "version": "2.1.3", "dev": true, @@ -7417,6 +7479,11 @@ "object-assign": "^4.1.1" } }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/semver": { "version": "7.3.5", "dev": true, @@ -8171,6 +8238,11 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tiny-invariant": { "version": "1.2.0", "license": "MIT" @@ -8251,6 +8323,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-gaussian": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-gaussian/-/ts-gaussian-2.0.2.tgz", + "integrity": "sha512-DdMwQc1bgzRwbKQ3VoiV3aK2isz7CJhOmtO2oA+mp9soGpbN/p2pkBaIrZnJBW3jB293CUARLFmNktcTSHjypQ==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/ts-node": { "version": "10.4.0", "dev": true, @@ -8299,6 +8379,19 @@ "node": ">=0.3.1" } }, + "node_modules/ts-trueskill": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-trueskill/-/ts-trueskill-3.3.0.tgz", + "integrity": "sha512-yGZQ9d4xIZg7HBKOnO70Qz//M/roPmw1p6cFFGcCtmWQlU6KMAoVzeScX60N70dD2ZvBxDTXXQFhaWr66zdiYQ==", + "dependencies": { + "mathjs": "^10.1.0", + "ts-gaussian": "^2.0.2", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.12.0", "dev": true, @@ -8445,6 +8538,14 @@ "node": ">= 0.6" } }, + "node_modules/typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/typedarray-to-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz", @@ -10550,6 +10651,11 @@ "version": "1.8.2", "dev": true }, + "complex.js": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.15.tgz", + "integrity": "sha512-gDBvQU8IG139ZBQTSo2qvDFP+lANMGluM779csXOr6ny1NUtA3wkUnCFjlDNH/moAVfXtvClYt6G0zarFbtz5w==" + }, "compressible": { "version": "2.0.18", "requires": { @@ -10884,6 +10990,11 @@ } } }, + "decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" + }, "decode-named-character-reference": { "version": "1.0.0", "dev": true, @@ -11080,6 +11191,11 @@ "escape-html": { "version": "1.0.3" }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "1.0.5", "dev": true @@ -11660,6 +11776,11 @@ "forwarded": { "version": "0.2.0" }, + "fraction.js": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz", + "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==" + }, "fresh": { "version": "0.5.2" }, @@ -12277,6 +12398,11 @@ "version": "0.1.2", "dev": true }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "js-tokens": { "version": "4.0.0" }, @@ -12577,6 +12703,22 @@ "version": "1.1.1", "dev": true }, + "mathjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-10.1.0.tgz", + "integrity": "sha512-TrpZAR3H9jR0Cv6cnzT+TZhE40Xs2SCaLf/qm2WcWm2tui69Gas/bC/ct5ZLZNWnWvNvJ7H2uHvuRDDl151PHA==", + "requires": { + "@babel/runtime": "^7.16.5", + "complex.js": "^2.0.15", + "decimal.js": "^10.3.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.1.2", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.0.0" + } + }, "mathml-tag-names": { "version": "2.1.3", "dev": true @@ -13829,6 +13971,11 @@ "object-assign": "^4.1.1" } }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "semver": { "version": "7.3.5", "dev": true, @@ -14347,6 +14494,11 @@ "version": "2.3.8", "dev": true }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tiny-invariant": { "version": "1.2.0" }, @@ -14396,6 +14548,11 @@ "version": "2.0.2", "dev": true }, + "ts-gaussian": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-gaussian/-/ts-gaussian-2.0.2.tgz", + "integrity": "sha512-DdMwQc1bgzRwbKQ3VoiV3aK2isz7CJhOmtO2oA+mp9soGpbN/p2pkBaIrZnJBW3jB293CUARLFmNktcTSHjypQ==" + }, "ts-node": { "version": "10.4.0", "dev": true, @@ -14420,6 +14577,16 @@ } } }, + "ts-trueskill": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-trueskill/-/ts-trueskill-3.3.0.tgz", + "integrity": "sha512-yGZQ9d4xIZg7HBKOnO70Qz//M/roPmw1p6cFFGcCtmWQlU6KMAoVzeScX60N70dD2ZvBxDTXXQFhaWr66zdiYQ==", + "requires": { + "mathjs": "^10.1.0", + "ts-gaussian": "^2.0.2", + "uuid": "^8.3.2" + } + }, "tsconfig-paths": { "version": "3.12.0", "dev": true, @@ -14520,6 +14687,11 @@ "mime-types": "~2.1.24" } }, + "typed-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.0.0.tgz", + "integrity": "sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==" + }, "typedarray-to-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz", diff --git a/package.json b/package.json index d2c8594d5..752842d17 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "react-dom": "^17.0.2", "remix": "^1.1.3", "tiny-invariant": "^1.2.0", + "ts-trueskill": "^3.3.0", "uuid": "^8.3.2", "zod": "^3.11.6" }, diff --git a/prisma/seed/script.ts b/prisma/seed/script.ts index dc8d9ebd7..a6c894357 100644 --- a/prisma/seed/script.ts +++ b/prisma/seed/script.ts @@ -50,6 +50,7 @@ export async function seed(variation?: SeedVariations) { await prisma.lfgGroupLike.deleteMany(); await prisma.lfgGroupMember.deleteMany(); await prisma.lfgGroup.deleteMany(); + await prisma.skill.deleteMany(); await prisma.user.deleteMany(); await prisma.stage.deleteMany(); @@ -66,6 +67,7 @@ export async function seed(variation?: SeedVariations) { await trustRelationship(nzapUserCreated.id, adminUserCreated.id); await stages(); await tournamentAddMaps(tournament.id); + await skills(); const userIdsInTheSystem = (await prisma.user.findMany()) .map((u) => u.id) @@ -352,6 +354,20 @@ export async function seed(variation?: SeedVariations) { }); } + async function skills() { + const users = (await prisma.user.findMany()).filter( + () => Math.random() < 0.9 + ); + + return prisma.skill.createMany({ + data: users.map((u) => ({ + mu: Math.random() * 40 + 10, + sigma: Math.random() * 10 + 1, + userId: u.id, + })), + }); + } + async function stages() { const modesList = ["TW", "SZ", "TC", "RM", "CB"] as const; const result = [];