Skip to content

Commit

Permalink
Refactor BattleReady, mmrCache to preliminarily support more rating s…
Browse files Browse the repository at this point in the history
…ystems

- Introduces the CachedMMR type, which holds Elo and, optionally, Glicko-2 data.
  Only when using ladders-remote is Glicko-2 data stored.
  • Loading branch information
Slayer95 committed Dec 6, 2024
1 parent cac6abb commit e31f1d2
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 33 deletions.
5 changes: 3 additions & 2 deletions server/ladders-challenges.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {ChallengeType} from './room-battle';
import type {CachedMMR} from './users';

/**
* A bundle of:
Expand All @@ -13,14 +14,14 @@ export class BattleReady {
readonly userid: ID;
readonly formatid: string;
readonly settings: User['battleSettings'];
readonly rating: number;
readonly rating: CachedMMR;
readonly challengeType: ChallengeType;
readonly time: number;
constructor(
userid: ID,
formatid: string,
settings: User['battleSettings'],
rating = 0,
rating: CachedMMR = {elo: 0},
challengeType: ChallengeType = 'challenge'
) {
this.userid = userid;
Expand Down
18 changes: 9 additions & 9 deletions server/ladders-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/

import {FS, Utils} from '../lib';
import type {CachedMMR} from 'users';

// ladderCaches = {formatid: ladder OR Promise(ladder)}
// Use Ladders(formatid).ladder to guarantee a Promise(ladder).
Expand Down Expand Up @@ -156,21 +155,21 @@ export class LadderStore {
}
const ladder = await this.getLadder();
const index = this.indexOfUser(userid);
let rating = 1000;
const ratings = {elo: 1000};
if (index >= 0) {
rating = ladder[index][1];
ratings.elo = ladder[index][1];
}
if (user && user.id === userid) {
user.mmrCache[formatid] = {elo: rating} as CachedMMR;
user.mmrCache[formatid] = ratings;
}
return rating;
return ratings;
}

/**
* Returns a Promise for the Elo of a user
*/
async getElo(userid: string) {
const ratings = await getRating(userid);
const ratings = await this.getRating(userid);
return ratings?.elo ?? 1000;
}

Expand Down Expand Up @@ -258,9 +257,10 @@ export class LadderStore {
}

const p1 = Users.getExact(p1name);
if (p1) p1.mmrCache[formatid] = +p1newElo;
if (p1) p1.updateEloCache(formatid, +p1newElo);
const p2 = Users.getExact(p2name);
if (p2) p2.mmrCache[formatid] = +p2newElo;
if (p2) p2.updateEloCache(formatid, +p2newElo);

void this.save();

if (!room.battle) {
Expand Down Expand Up @@ -288,7 +288,7 @@ export class LadderStore {
room.update();
}

return [p1score, p1newElo, p2newElo];
return [p1score, {elo: p1newElo}, {elo: p2newElo}];
}

/**
Expand Down
11 changes: 5 additions & 6 deletions server/ladders-remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
* @license MIT
*/
import {Utils} from '../lib';
import type {CachedMMR} from 'users';

export class LadderStore {
formatid: string;
Expand Down Expand Up @@ -51,9 +50,9 @@ export class LadderStore {
}
const ratings = {
elo: Utils.ensureValidNumber(Utils.getNumber(data.elo), 1000),
glickoPoints: Utils.ensureValidNumber(Utils.getNumber(data.rpr), 1500),
glickoScore: Utils.ensureValidNumber(Utils.getNumber(data.rpr), 1500),
glickoDeviation: Utils.ensureValidNumber(Utils.getNumber(data.rprd), 130),
} as CachedMMR;
};

if (user && user.id === userid) {
user.mmrCache[formatid] = ratings;
Expand All @@ -65,7 +64,7 @@ export class LadderStore {
* Returns a Promise for the Elo of a user
*/
async getElo(userid: string) {
const ratings = await getRating(userid);
const ratings = await this.getRating(userid);
return ratings?.elo ?? 1000;
}

Expand Down Expand Up @@ -111,8 +110,8 @@ export class LadderStore {

room.rated = Math.min(p1NewElo, p2NewElo);

if (p1) p1.mmrCache[formatid] = +p1NewElo;
if (p2) p2.mmrCache[formatid] = +p2NewElo;
if (p1) p1.updateEloCache(formatid, +p1NewElo);
if (p2) p2.updateEloCache(formatid, +p2NewElo);

room.update();

Expand Down
27 changes: 16 additions & 11 deletions server/ladders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const LadderStore: typeof import('./ladders-remote').LadderStore = (
const SECONDS = 1000;
const PERIODIC_MATCH_INTERVAL = 60 * SECONDS;

import type {ChallengeType} from './room-battle';
import type {ChallengeType, RoomBattlePlayerOptions} from './room-battle';
import type {CachedMMR} from './users';
import {BattleReady, BattleChallenge, GameChallenge, BattleInvite, challenges} from './ladders-challenges';

/**
Expand Down Expand Up @@ -70,7 +71,7 @@ class Ladder extends LadderStore {
return null;
}

let rating = 0;
let rating = {elo: 0} as CachedMMR;
let valResult;
let removeNicknames = !!(user.locked || user.namelocked);

Expand Down Expand Up @@ -111,19 +112,22 @@ class Ladder extends LadderStore {

if (isRated && !Ladders.disabled) {
const uid = user.id;
[valResult, rating] = await Promise.all([
let ratingResult;
[valResult, ratingResult] = await Promise.all([
TeamValidatorAsync.get(this.formatid).validateTeam(team, {removeNicknames, user: uid}),
this.getRating(uid),
]);
if (uid !== user.id) {
// User feedback for renames handled elsewhere.
return null;
}
if (!rating) rating = 1;
// Elo has a hard-floor of 1000.
// (Ab)-use this fact to use 1 as an unknown Elo sentinel value.
rating = ratingResult ?? {elo: 1};
} else {
if (Ladders.disabled) {
connection.popup(`The ladder is temporarily disabled due to technical difficulties - you will not receive ladder rating for this game.`);
rating = 1;
rating = {elo: 1};
}
const validator = TeamValidatorAsync.get(this.formatid);
valResult = await validator.validateTeam(team, {removeNicknames, user: user.id});
Expand Down Expand Up @@ -368,7 +372,7 @@ class Ladder extends LadderStore {
searchRange += elapsed / 300; // +1 every .3 seconds
if (searchRange > 300) searchRange = 300 + (searchRange - 300) / 10; // +1 every 3 sec after 300
if (searchRange > 600) searchRange = 600;
const ratings = matches.map(([search]) => search.rating);
const ratings = matches.map(([search]) => search.rating.elo);
if (Math.max(...ratings) - Math.min(...ratings) > searchRange) return false;

matches[0][1].lastMatch = matches[1][1].id;
Expand Down Expand Up @@ -450,7 +454,7 @@ class Ladder extends LadderStore {
static match(readies: BattleReady[]) {
const formatid = readies[0].formatid;
if (readies.some(ready => ready.formatid !== formatid)) throw new Error(`Format IDs don't match`);
const players = [];
const players = [] as RoomBattlePlayerOptions[];
let missingUser = null;
let minRating = Infinity;
for (const ready of readies) {
Expand All @@ -459,14 +463,15 @@ class Ladder extends LadderStore {
missingUser = ready.userid;
break;
}
players.push({
const playerOpts = {
user,
team: ready.settings.team,
rating: ready.rating,
rating: ready.rating.elo ?? 1000,
hidden: ready.settings.hidden,
inviteOnly: ready.settings.inviteOnly,
});
if (ready.rating < minRating) minRating = ready.rating;
};
players.push(playerOpts);
if (playerOpts.rating < minRating) minRating = playerOpts.rating;
}
if (missingUser) {
for (const ready of readies) {
Expand Down
2 changes: 1 addition & 1 deletion server/room-battle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ export class RoomBattle extends RoomGame<RoomBattlePlayer> {
readonly gameType: string | undefined;
readonly challengeType: ChallengeType;
/**
* The lower player's rating, for searching purposes.
* The lower player's rating, for Elo searching purposes.
* 0 for unrated battles. 1 for unknown ratings.
*/
readonly rated: number;
Expand Down
23 changes: 22 additions & 1 deletion server/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ export interface UserSettings {

export interface CachedMMR {
elo: number;
glickoPoints?: number;
glickoScore?: number;
glickoDeviation?: number;
}

Expand Down Expand Up @@ -1413,6 +1413,27 @@ export class User extends Chat.MessageContext {
updateSearch(connection: Connection | null = null) {
Ladders.updateSearch(this, connection);
}
updateRatingCache(formatid: string, data: any) {
if (!data || !('elo' in data)) return;
if (!('rpr' in data)) {
this.mmrCache[formatid] = {elo: Utils.ensureValidNumber(Utils.getNumber(data.elo), 1000)};
} else {
const ratings = {
elo: Utils.ensureValidNumber(Utils.getNumber(data.elo), 1000),
glickoScore: Utils.ensureValidNumber(Utils.getNumber(data.rpr), 1500),
glickoDeviation: Utils.ensureValidNumber(Utils.getNumber(data.rprd), 130),
};
this.mmrCache[formatid] = ratings;
}
}
updateEloCache(formatid: string, elo: number) {
if (!this.mmrCache[formatid]) {
this.mmrCache[formatid] = {elo} as CachedMMR;
return;
} else {
this.mmrCache[formatid].elo = elo;
}
}
/**
* Moves the user's connections in a given room to another room.
* This function's main use case is for when a room is renamed.
Expand Down
6 changes: 3 additions & 3 deletions test/server/ladders.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const {makeUser} = require('../users-utils');
describe('Matchmaker', function () {
const FORMATID = 'gen7ou';
const addSearch = (player, rating = 1000, formatid = FORMATID) => {
const search = new Ladders.BattleReady(player.id, formatid, player.battleSettings, rating);
const search = new Ladders.BattleReady(player.id, formatid, player.battleSettings, {elo: rating});
Ladders(formatid).addSearch(search, player);
return search;
};
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('Matchmaker', function () {
assert.equal(formatSearches.size, 1);
assert.equal(s1.userid, this.p1.id);
assert.equal(s1.settings.team, this.p1.battleSettings.team);
assert.equal(s1.rating, 1000);
assert.equal(s1.rating.elo, 1000);
});

it('should matchmake users when appropriate', function () {
Expand Down Expand Up @@ -78,7 +78,7 @@ describe('Matchmaker', function () {
const s2 = addSearch(this.p2, 2000);
assert.equal(Ladders.searches.get(FORMATID).searches.size, 2);

s2.rating = 1000;
s2.rating.elo = 1000;
Ladders.Ladder.periodicMatch();
assert.equal(Ladders.searches.get(FORMATID).searches.size, 0);

Expand Down

0 comments on commit e31f1d2

Please sign in to comment.