mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 01:09:02 -05:00
* 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
328 lines
7.2 KiB
TypeScript
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();
|