diff --git a/server/ladders-local.ts b/server/ladders-local.ts index 0e8026f4f4dc..14ee0774b4eb 100644 --- a/server/ladders-local.ts +++ b/server/ladders-local.ts @@ -171,7 +171,30 @@ export class LadderStore { updateRow(row: LadderRow, score: number, foeElo: number) { let elo = row[1]; - elo = this.calculateElo(elo, score, foeElo); + // The K factor determines how much your Elo changes when you win or + // lose games. Larger K means more change. + // In the "original" Elo, K is constant, but it's common for K to + // get smaller as your rating goes up + let K = 50; + + // dynamic K-scaling (optional) + if (elo < 1200) { + if (score < 0.5) { + K = 10 + (elo - 1000) * 40 / 200; + } else if (score > 0.5) { + K = 90 - (elo - 1000) * 40 / 200; + } + } else if (elo > 1350 && elo <= 1600) { + K = 40; + } else { + K = 32; + } + + // main Elo formula + const E = 1 / (1 + Math.pow(10, (foeElo - elo) / 400)); + elo += K * (score - E); + + if (elo < 1000) elo = 1000; row[1] = elo; if (score > 0.6) { @@ -310,35 +333,4 @@ export class LadderStore { } return Promise.all(ratings); } - - /** - * Calculates Elo based on a match result - */ - private calculateElo(previousUserElo: number, score: number, foeElo: number): number { - // The K factor determines how much your Elo changes when you win or - // lose games. Larger K means more change. - // In the "original" Elo, K is constant, but it's common for K to - // get smaller as your rating goes up - let K = 50; - - // dynamic K-scaling (optional) - if (previousUserElo < 1200) { - if (score < 0.5) { - K = 10 + (previousUserElo - 1000) * 40 / 200; - } else if (score > 0.5) { - K = 90 - (previousUserElo - 1000) * 40 / 200; - } - } else if (previousUserElo > 1350 && previousUserElo <= 1600) { - K = 40; - } else { - K = 32; - } - - // main Elo formula - const E = 1 / (1 + Math.pow(10, (foeElo - previousUserElo) / 400)); - - const newElo = previousUserElo + K * (score - E); - - return Math.max(newElo, 1000); - } } diff --git a/server/ladders-remote.ts b/server/ladders-remote.ts index 63169ba26998..e65758eae5ea 100644 --- a/server/ladders-remote.ts +++ b/server/ladders-remote.ts @@ -70,43 +70,25 @@ export class LadderStore { const formatid = this.formatid; const p1 = Users.getExact(p1name); const p2 = Users.getExact(p2name); - - const ladderUpdatePromise = LoginServer.request('ladderupdate', { + room.update(); + room.send(`||Ladder updating...`); + const [data, error] = await LoginServer.request('ladderupdate', { p1: p1name, p2: p2name, score: p1score, format: formatid, }); - - // calculate new Elo scores and display to room while loginserver updates the ladder - const [p1OldElo, p2OldElo] = (await Promise.all([this.getRating(p1!.id), this.getRating(p2!.id)])).map(Math.round); - const p1NewElo = Math.round(this.calculateElo(p1OldElo, p1score, p2OldElo)); - const p2NewElo = Math.round(this.calculateElo(p2OldElo, 1 - p1score, p1OldElo)); - - const p1Act = (p1score > 0.9 ? `winning` : (p1score < 0.1 ? `losing` : `tying`)); - let p1Reasons = `${p1NewElo - p1OldElo} for ${p1Act}`; - if (!p1Reasons.startsWith('-')) p1Reasons = '+' + p1Reasons; - room.addRaw(Utils.html`${p1name}'s rating: ${p1OldElo} → ${p1NewElo}
(${p1Reasons})`); - - const p2Act = (p1score > 0.9 || p1score < 0 ? `losing` : (p1score < 0.1 ? `winning` : `tying`)); - let p2Reasons = `${p2NewElo - p2OldElo} for ${p2Act}`; - if (!p2Reasons.startsWith('-')) p2Reasons = '+' + p2Reasons; - room.addRaw(Utils.html`${p2name}'s rating: ${p2OldElo} → ${p2NewElo}
(${p2Reasons})`); - - room.rated = Math.min(p1NewElo, p2NewElo); - - if (p1) p1.mmrCache[formatid] = +p1NewElo; - if (p2) p2.mmrCache[formatid] = +p2NewElo; - - room.update(); - - - const [data, error] = await ladderUpdatePromise; let problem = false; if (error) { + if (error.message === 'stream interrupt') { + room.add(`||Ladder updated, but score could not be retrieved.`); + } else { + room.add(`||Ladder (probably) updated, but score could not be retrieved (${error.message}).`); + } problem = true; } else if (!room.battle) { + Monitor.warn(`room expired before ladder update was received`); problem = true; } else if (!data) { room.add(`|error|Unexpected response ${data} from ladder server.`); @@ -126,7 +108,37 @@ export class LadderStore { return [p1score, null, null]; } - return [p1score, data!.p1rating, data!.p2rating]; + let p1rating; + let p2rating; + try { + p1rating = data!.p1rating; + p2rating = data!.p2rating; + + let oldelo = Math.round(p1rating.oldelo); + let elo = Math.round(p1rating.elo); + let act = (p1score > 0.9 ? `winning` : (p1score < 0.1 ? `losing` : `tying`)); + let reasons = `${elo - oldelo} for ${act}`; + if (!reasons.startsWith('-')) reasons = '+' + reasons; + room.addRaw(Utils.html`${p1name}'s rating: ${oldelo} → ${elo}
(${reasons})`); + let minElo = elo; + + oldelo = Math.round(p2rating.oldelo); + elo = Math.round(p2rating.elo); + act = (p1score > 0.9 || p1score < 0 ? `losing` : (p1score < 0.1 ? `winning` : `tying`)); + reasons = `${elo - oldelo} for ${act}`; + if (!reasons.startsWith('-')) reasons = '+' + reasons; + room.addRaw(Utils.html`${p2name}'s rating: ${oldelo} → ${elo}
(${reasons})`); + if (elo < minElo) minElo = elo; + room.rated = minElo; + + if (p1) p1.mmrCache[formatid] = +p1rating.elo; + if (p2) p2.mmrCache[formatid] = +p2rating.elo; + room.update(); + } catch (e) { + room.addRaw(`There was an error calculating rating changes.`); + room.update(); + } + return [p1score, p1rating, p2rating]; } /** @@ -137,37 +149,4 @@ export class LadderStore { static async visualizeAll(username: string) { return [`Please use the official client at play.pokemonshowdown.com`]; } - - /** - * Calculates Elo for quick display, matching the formula on loginserver - */ - // see lib/ntbb-ladder.lib.php in the pokemon-showdown-client repo for the login server implementation - // *intentionally* different from calculation in ladders-local, due to the high activity on the main server - private calculateElo(previousUserElo: number, score: number, foeElo: number): number { - // The K factor determines how much your Elo changes when you win or - // lose games. Larger K means more change. - // In the "original" Elo, K is constant, but it's common for K to - // get smaller as your rating goes up - let K = 50; - - // dynamic K-scaling (optional) - if (previousUserElo < 1100) { - if (score < 0.5) { - K = 20 + (previousUserElo - 1000) * 30 / 100; - } else if (score > 0.5) { - K = 80 - (previousUserElo - 1000) * 30 / 100; - } - } else if (previousUserElo > 1300) { - K = 40; - } else if (previousUserElo > 1600) { - K = 32; - } - - // main Elo formula - const E = 1 / (1 + Math.pow(10, (foeElo - previousUserElo) / 400)); - - const newElo = previousUserElo + K * (score - E); - - return Math.max(newElo, 1000); - } }