Skip to content

Commit 7df77f9

Browse files
DylanSpQuanyails
authored andcommitted
Calculate and display Elo change on server (smogon#7960)
1 parent fc77260 commit 7df77f9

File tree

2 files changed

+93
-64
lines changed

2 files changed

+93
-64
lines changed

server/ladders-local.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -172,30 +172,7 @@ export class LadderStore {
172172
updateRow(row: LadderRow, score: number, foeElo: number) {
173173
let elo = row[1];
174174

175-
// The K factor determines how much your Elo changes when you win or
176-
// lose games. Larger K means more change.
177-
// In the "original" Elo, K is constant, but it's common for K to
178-
// get smaller as your rating goes up
179-
let K = 50;
180-
181-
// dynamic K-scaling (optional)
182-
if (elo < 1200) {
183-
if (score < 0.5) {
184-
K = 10 + (elo - 1000) * 40 / 200;
185-
} else if (score > 0.5) {
186-
K = 90 - (elo - 1000) * 40 / 200;
187-
}
188-
} else if (elo > 1350 && elo <= 1600) {
189-
K = 40;
190-
} else {
191-
K = 32;
192-
}
193-
194-
// main Elo formula
195-
const E = 1 / (1 + Math.pow(10, (foeElo - elo) / 400));
196-
elo += K * (score - E);
197-
198-
if (elo < 1000) elo = 1000;
175+
elo = this.calculateElo(elo, score, foeElo);
199176

200177
row[1] = elo;
201178
if (score > 0.6) {
@@ -334,4 +311,35 @@ export class LadderStore {
334311
}
335312
return Promise.all(ratings);
336313
}
314+
315+
/**
316+
* Calculates Elo based on a match result
317+
*/
318+
private calculateElo(previousUserElo: number, score: number, foeElo: number): number {
319+
// The K factor determines how much your Elo changes when you win or
320+
// lose games. Larger K means more change.
321+
// In the "original" Elo, K is constant, but it's common for K to
322+
// get smaller as your rating goes up
323+
let K = 50;
324+
325+
// dynamic K-scaling (optional)
326+
if (previousUserElo < 1200) {
327+
if (score < 0.5) {
328+
K = 10 + (previousUserElo - 1000) * 40 / 200;
329+
} else if (score > 0.5) {
330+
K = 90 - (previousUserElo - 1000) * 40 / 200;
331+
}
332+
} else if (previousUserElo > 1350 && previousUserElo <= 1600) {
333+
K = 40;
334+
} else {
335+
K = 32;
336+
}
337+
338+
// main Elo formula
339+
const E = 1 / (1 + Math.pow(10, (foeElo - previousUserElo) / 400));
340+
341+
const newElo = previousUserElo + K * (score - E);
342+
343+
return Math.max(newElo, 1000);
344+
}
337345
}

server/ladders-remote.ts

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -70,25 +70,43 @@ export class LadderStore {
7070
const formatid = this.formatid;
7171
const p1 = Users.getExact(p1name);
7272
const p2 = Users.getExact(p2name);
73-
room.update();
74-
room.send(`||Ladder updating...`);
75-
const [data, error] = await LoginServer.request('ladderupdate', {
73+
74+
const ladderUpdatePromise = LoginServer.request('ladderupdate', {
7675
p1: p1name,
7776
p2: p2name,
7877
score: p1score,
7978
format: formatid,
8079
});
80+
81+
// calculate new Elo scores and display to room while loginserver updates the ladder
82+
const [p1OldElo, p2OldElo] = (await Promise.all([this.getRating(p1!.id), this.getRating(p2!.id)])).map(Math.round);
83+
const p1NewElo = Math.round(this.calculateElo(p1OldElo, p1score, p2OldElo));
84+
const p2NewElo = Math.round(this.calculateElo(p2OldElo, 1 - p1score, p1OldElo));
85+
86+
const p1Act = (p1score > 0.9 ? `winning` : (p1score < 0.1 ? `losing` : `tying`));
87+
let p1Reasons = `${p1NewElo - p1OldElo} for ${p1Act}`;
88+
if (!p1Reasons.startsWith('-')) p1Reasons = '+' + p1Reasons;
89+
room.addRaw(Utils.html`${p1name}'s rating: ${p1OldElo} &rarr; <strong>${p1NewElo}</strong><br />(${p1Reasons})`);
90+
91+
const p2Act = (p1score > 0.9 || p1score < 0 ? `losing` : (p1score < 0.1 ? `winning` : `tying`));
92+
let p2Reasons = `${p2NewElo - p2OldElo} for ${p2Act}`;
93+
if (!p2Reasons.startsWith('-')) p2Reasons = '+' + p2Reasons;
94+
room.addRaw(Utils.html`${p2name}'s rating: ${p2OldElo} &rarr; <strong>${p2NewElo}</strong><br />(${p2Reasons})`);
95+
96+
room.rated = Math.min(p1NewElo, p2NewElo);
97+
98+
if (p1) p1.mmrCache[formatid] = +p1NewElo;
99+
if (p2) p2.mmrCache[formatid] = +p2NewElo;
100+
101+
room.update();
102+
103+
104+
const [data, error] = await ladderUpdatePromise;
81105
let problem = false;
82106

83107
if (error) {
84-
if (error.message === 'stream interrupt') {
85-
room.add(`||Ladder updated, but score could not be retrieved.`);
86-
} else {
87-
room.add(`||Ladder (probably) updated, but score could not be retrieved (${error.message}).`);
88-
}
89108
problem = true;
90109
} else if (!room.battle) {
91-
Monitor.warn(`room expired before ladder update was received`);
92110
problem = true;
93111
} else if (!data) {
94112
room.add(`|error|Unexpected response ${data} from ladder server.`);
@@ -108,37 +126,7 @@ export class LadderStore {
108126
return [p1score, null, null];
109127
}
110128

111-
let p1rating;
112-
let p2rating;
113-
try {
114-
p1rating = data!.p1rating;
115-
p2rating = data!.p2rating;
116-
117-
let oldelo = Math.round(p1rating.oldelo);
118-
let elo = Math.round(p1rating.elo);
119-
let act = (p1score > 0.9 ? `winning` : (p1score < 0.1 ? `losing` : `tying`));
120-
let reasons = `${elo - oldelo} for ${act}`;
121-
if (!reasons.startsWith('-')) reasons = '+' + reasons;
122-
room.addRaw(Utils.html`${p1name}'s rating: ${oldelo} &rarr; <strong>${elo}</strong><br />(${reasons})`);
123-
let minElo = elo;
124-
125-
oldelo = Math.round(p2rating.oldelo);
126-
elo = Math.round(p2rating.elo);
127-
act = (p1score > 0.9 || p1score < 0 ? `losing` : (p1score < 0.1 ? `winning` : `tying`));
128-
reasons = `${elo - oldelo} for ${act}`;
129-
if (!reasons.startsWith('-')) reasons = '+' + reasons;
130-
room.addRaw(Utils.html`${p2name}'s rating: ${oldelo} &rarr; <strong>${elo}</strong><br />(${reasons})`);
131-
if (elo < minElo) minElo = elo;
132-
room.rated = minElo;
133-
134-
if (p1) p1.mmrCache[formatid] = +p1rating.elo;
135-
if (p2) p2.mmrCache[formatid] = +p2rating.elo;
136-
room.update();
137-
} catch (e) {
138-
room.addRaw(`There was an error calculating rating changes.`);
139-
room.update();
140-
}
141-
return [p1score, p1rating, p2rating];
129+
return [p1score, data!.p1rating, data!.p2rating];
142130
}
143131

144132
/**
@@ -149,4 +137,37 @@ export class LadderStore {
149137
static async visualizeAll(username: string) {
150138
return [`<tr><td><strong>Please use the official client at play.pokemonshowdown.com</strong></td></tr>`];
151139
}
140+
141+
/**
142+
* Calculates Elo for quick display, matching the formula on loginserver
143+
*/
144+
// see lib/ntbb-ladder.lib.php in the pokemon-showdown-client repo for the login server implementation
145+
// *intentionally* different from calculation in ladders-local, due to the high activity on the main server
146+
private calculateElo(previousUserElo: number, score: number, foeElo: number): number {
147+
// The K factor determines how much your Elo changes when you win or
148+
// lose games. Larger K means more change.
149+
// In the "original" Elo, K is constant, but it's common for K to
150+
// get smaller as your rating goes up
151+
let K = 50;
152+
153+
// dynamic K-scaling (optional)
154+
if (previousUserElo < 1100) {
155+
if (score < 0.5) {
156+
K = 20 + (previousUserElo - 1000) * 30 / 100;
157+
} else if (score > 0.5) {
158+
K = 80 - (previousUserElo - 1000) * 30 / 100;
159+
}
160+
} else if (previousUserElo > 1300) {
161+
K = 40;
162+
} else if (previousUserElo > 1600) {
163+
K = 32;
164+
}
165+
166+
// main Elo formula
167+
const E = 1 / (1 + Math.pow(10, (foeElo - previousUserElo) / 400));
168+
169+
const newElo = previousUserElo + K * (score - E);
170+
171+
return Math.max(newElo, 1000);
172+
}
152173
}

0 commit comments

Comments
 (0)