sendou.ink/app/features/tournament-bracket/core/summarizer.test.ts
Kalle e7bbb565be
SendouQ (#1455)
* Tables

* Clocks

* Maplist preference selector

* Fix SSR

* Nav icon

* RankedOrScrim

* Map pool

* Create group

* Redirect logic

* Persist map pool

* Advance from preparing page

* Rename query

* Fix merge

* Fix migration order

* Seed groups

* Find looking groups SQL

* Renders something

* More UI work

* Back to 30min

* Likes/dislikes

* Always return own group

* Fix like order

* 3 tc/rm/cb -> 2

* Show only 3 weapons

* Pass group size

* Handle both liked and liked by same group

* Fix SQL

* Group preference frontend work

* Morphing

* Styling

* Don't show group controls if not manager

* Give/remove manager

* Leave group

* Leave with confirm

* Delete likes when morphing groups

* Clocks consistency

* Remove bad invariant

* Persist settings to local storage

* Fix initial value flashing

* Fix never resolving loading indicator

* REFRESH_GROUP

* Flip animations

* Tweaks

* Auto refresh logic

* Groups of 4 seed

* Reduce throwing

* Load full groups initial

* Create match

* Match UI initial

* Score reporter initial

* Push footer down on match page

* Score reporter knows when set ended

* Score reporting untested

* Show score after report

* Align better

* Look again with same group functionality

* More migrations

* Team on match page

* Show confirmer before reporting score

* Report weapons

* Report weapos again by admin + skill changing

* Handle no tiebreaker given to MapPool

* Remove unranked

* Remove support for "team id skill"

* no-wrap -> nowrap

* Preparing page work

* Use common GroupCard component

* Add some metas

* MemberAdder in looking page

* Fix GroupCard actions

* Fix SZ only map list including other modes

* Add season info

* Prompt login

* Joining team

* Manage group on preparing page

* Manage group on preparing page

* Seed past matches

* Add to seed

* No map list preference when full group + fix expiry

* Fix skill matchesCount calculation

* Tiers initial work

* Some progress on tiers

* Tiering logic

* MMR in group cards

* Name to challenge

* Team MMR

* Big team rank icons

* Adjust todos

* Match score report with confirm

* Allow regular members to report score

* Handle reporting weapons edge cases

* Add tier images

* Improve GroupCard spacing

* Refactor looking page

* Looking mobile UI

* Calculate skill only for current season

* Divide groups visually when reporting weapons

* Fix match page weapons sorting

* Add cache to user skills+tier calculation

* Admin report match score

* Initial leaderboard

* Cached leaderboard

* Weapon category lb's

* Populate SkillTeamUser in SendouQ

* Team leaderboard filtered down

* Add TODOs

* Seasons initlal

* Season weapons initial

* Weapons stylized

* Show rest weapons as +

* Hide peak if same as current

* Load matches SQL initial

* Season matches UI initial

* Take user id in account

* Add weapons

* Paginated matches

* Fix pages count logic

* Scroll top on data change

* Day headers for matches

* Link from user page to user seasons page

* Summarize maps + ui initial

* Map stats

* Player info tabs

* MMR chart

* Chart adjustments

* Handle basing team MMR on player MMR

* Set initial MMR

* Add info about discord to match page

* Season support to tournaments

* Get tournament skills as well for the graph

* WIP

* New team rating logic + misc other

* tiered -> tiered.server

* Update season starting time

* TODOs

* Add rules page

* Hide elements correctly when off-season

* Fix crash when only one player with skill

* How-to video

* Fix StartRank showing when not logged in

* Make user leaderboard the default

* Make Skill season non-nullable

* Add suggested pass to match

* Add rule

* identifierToUserIds helper

* Fix tiers not showing
2023-08-12 22:42:54 +03:00

328 lines
7.2 KiB
TypeScript

import { suite } from "uvu";
import * as assert from "uvu/assert";
import { tournamentSummary } from "./summarizer.server";
import { ordinal, rating } from "openskill";
import type { AllMatchResult } from "../queries/allMatchResultsByTournamentId.server";
const TournamentSummary = suite("tournamentSummary()");
function summarize({ results }: { results?: AllMatchResult[] } = {}) {
return tournamentSummary({
finalStandings: [
{
placement: 1,
players: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }],
tournamentTeam: {
id: 1,
},
},
{
placement: 2,
players: [{ id: 5 }, { id: 6 }, { id: 7 }, { id: 8 }],
tournamentTeam: {
id: 2,
},
},
{
placement: 3,
players: [{ id: 9 }, { id: 10 }, { id: 11 }, { id: 12 }],
tournamentTeam: {
id: 3,
},
},
{
placement: 4,
players: [{ id: 13 }, { id: 14 }, { id: 15 }, { id: 16 }],
tournamentTeam: {
id: 4,
},
},
],
results: results ?? [
{
maps: [
{
mode: "SZ",
stageId: 1,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 0,
},
},
],
teams: [
{
id: 1,
members: [
{ userId: 1 },
{ userId: 2 },
{ userId: 3 },
{ userId: 4 },
{ userId: 20 },
],
},
{
id: 2,
members: [{ userId: 5 }, { userId: 6 }, { userId: 7 }, { userId: 8 }],
},
{
id: 3,
members: [
{ userId: 9 },
{ userId: 10 },
{ userId: 11 },
{ userId: 12 },
],
},
{
id: 4,
members: [
{ userId: 13 },
{ userId: 14 },
{ userId: 15 },
{ userId: 16 },
],
},
],
queryCurrentTeamRating: () => rating(),
queryCurrentUserRating: () => rating(),
queryTeamPlayerRatingAverage: () => rating(),
});
}
TournamentSummary("calculates final standings", () => {
const summary = summarize();
// each player of each team should have one result
assert.equal(summary.tournamentResults.length, 4 * 4);
});
TournamentSummary(
"winners skill should go up, losers skill should go down",
() => {
const summary = summarize();
const winnerSkill = summary.skills.find((s) => s.userId === 1);
const loserSkill = summary.skills.find((s) => s.userId === 5);
assert.ok(winnerSkill);
assert.ok(loserSkill);
assert.ok(ordinal(winnerSkill) > ordinal(loserSkill));
}
);
const resultsWith20: AllMatchResult[] = [
{
maps: [
{
mode: "SZ",
stageId: 1,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 0,
},
},
{
maps: [
{
mode: "SZ",
stageId: 1,
userIds: [1, 20, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 20, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 0,
},
},
];
TournamentSummary("winning more than once makes the skill go up more", () => {
const summary = summarize({
results: resultsWith20,
});
const twoTimeWinnerSkill = summary.skills.find((s) => s.userId === 1);
const oneTimeWinnerSkill = summary.skills.find((s) => s.userId === 2);
assert.ok(twoTimeWinnerSkill);
assert.ok(oneTimeWinnerSkill);
assert.ok(ordinal(twoTimeWinnerSkill) > ordinal(oneTimeWinnerSkill));
});
TournamentSummary("calculates team skills (many rosters for same team)", () => {
const summary = summarize({
results: resultsWith20,
});
const teamOneRosterOne = summary.skills.find(
(s) => s.identifier === "1-2-3-4"
);
const teamOneRosterTwo = summary.skills.find(
(s) => s.identifier === "1-3-4-20"
);
assert.ok(teamOneRosterOne);
assert.ok(teamOneRosterTwo);
});
const resultsWithSubbedRoster: AllMatchResult[] = [
{
maps: [
{
mode: "SZ",
stageId: 1,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 2,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 20, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 1,
},
},
];
TournamentSummary(
"In the case of sub calculates skill based on the most common roster",
() => {
const summary = summarize({
results: resultsWithSubbedRoster,
});
const teamOneRosterOne = summary.skills.find(
(s) => s.identifier === "1-2-3-4"
);
const teamOneRosterTwo = summary.skills.find(
(s) => s.identifier === "1-3-4-20"
);
assert.ok(teamOneRosterOne);
assert.not.ok(teamOneRosterTwo);
}
);
TournamentSummary("calculates results of mates", () => {
const summary = summarize();
const result = summary.playerResultDeltas.find(
(r) => r.ownerUserId === 1 && r.otherUserId === 2
);
assert.ok(result);
assert.equal(result.setWins, 1);
assert.equal(result.setLosses, 0);
assert.equal(result.mapWins, 2);
assert.equal(result.mapLosses, 0);
assert.equal(result.type, "MATE");
});
TournamentSummary("calculates results of opponents", () => {
const summary = summarize();
const result = summary.playerResultDeltas.find(
(r) => r.ownerUserId === 1 && r.otherUserId === 5
);
assert.ok(result);
assert.equal(result.setWins, 1);
assert.equal(result.setLosses, 0);
assert.equal(result.mapWins, 2);
assert.equal(result.mapLosses, 0);
assert.equal(result.type, "ENEMY");
});
TournamentSummary("calculates results of opponents (losing side)", () => {
const summary = summarize();
const result = summary.playerResultDeltas.find(
(r) => r.ownerUserId === 5 && r.otherUserId === 1
);
assert.ok(result);
assert.equal(result.setWins, 0);
assert.equal(result.setLosses, 1);
assert.equal(result.mapWins, 0);
assert.equal(result.mapLosses, 2);
assert.equal(result.type, "ENEMY");
});
TournamentSummary("calculates map results", () => {
const summary = summarize();
const result = summary.mapResultDeltas.filter((r) => r.userId === 1);
assert.equal(result.length, 2);
assert.ok(result.every((r) => r.wins === 1 && r.losses === 0));
});
TournamentSummary.run();