@@ -70,25 +70,43 @@ export class LadderStore {
70
70
const formatid = this . formatid ;
71
71
const p1 = Users . getExact ( p1name ) ;
72
72
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' , {
76
75
p1 : p1name ,
77
76
p2 : p2name ,
78
77
score : p1score ,
79
78
format : formatid ,
80
79
} ) ;
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 } → < 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 } → < 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 ;
81
105
let problem = false ;
82
106
83
107
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
- }
89
108
problem = true ;
90
109
} else if ( ! room . battle ) {
91
- Monitor . warn ( `room expired before ladder update was received` ) ;
92
110
problem = true ;
93
111
} else if ( ! data ) {
94
112
room . add ( `|error|Unexpected response ${ data } from ladder server.` ) ;
@@ -108,37 +126,7 @@ export class LadderStore {
108
126
return [ p1score , null , null ] ;
109
127
}
110
128
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 } → < 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 } → < 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 ] ;
142
130
}
143
131
144
132
/**
@@ -149,4 +137,37 @@ export class LadderStore {
149
137
static async visualizeAll ( username : string ) {
150
138
return [ `<tr><td><strong>Please use the official client at play.pokemonshowdown.com</strong></td></tr>` ] ;
151
139
}
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
+ }
152
173
}
0 commit comments