@quenty/elo 7.19.0 → 7.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [7.19.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/elo@7.19.0...@quenty/elo@7.19.1) (2025-04-05)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Add types to packages ([2374fb2](https://github.com/Quenty/NevermoreEngine/commit/2374fb2b043cfbe0e9b507b3316eec46a4e353a0))
12
+
13
+
14
+
15
+
16
+
6
17
  # [7.19.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/elo@7.18.2...@quenty/elo@7.19.0) (2025-04-02)
7
18
 
8
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/elo",
3
- "version": "7.19.0",
3
+ "version": "7.19.1",
4
4
  "description": "Elo rating utility library.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -29,12 +29,12 @@
29
29
  "access": "public"
30
30
  },
31
31
  "devDependencies": {
32
- "@quenty/blend": "^12.18.0",
33
- "@quenty/loader": "^10.8.0",
34
- "@quenty/maid": "^3.4.0"
32
+ "@quenty/blend": "^12.18.1",
33
+ "@quenty/loader": "^10.8.1",
34
+ "@quenty/maid": "^3.4.1"
35
35
  },
36
36
  "dependencies": {
37
37
  "@quenty/probability": "^2.3.1"
38
38
  },
39
- "gitHead": "e8ea56930e65322fcffc05a1556d5df988068f0b"
39
+ "gitHead": "78c3ac0ab08dd18085b6e6e6e4f745e76ed99f68"
40
40
  }
@@ -1,9 +1,8 @@
1
+ --!strict
1
2
  --[=[
2
3
  @class EloMatchResult
3
4
  ]=]
4
5
 
5
- local require = require(script.Parent.loader).load(script)
6
-
7
6
  return table.freeze(setmetatable({
8
7
  PLAYER_ONE_WIN = 1;
9
8
  DRAW = 0.5;
@@ -1,3 +1,4 @@
1
+ --!strict
1
2
  --[=[
2
3
  @class EloMatchResultUtils
3
4
  ]=]
@@ -8,18 +9,30 @@ local EloMatchResult = require("EloMatchResult")
8
9
 
9
10
  local EloMatchResultUtils = {}
10
11
 
11
- function EloMatchResultUtils.isEloMatchResult(matchResult)
12
+ --[=[
13
+ Checks if the given match result is a valid EloMatchResult.
14
+
15
+ @param matchResult any
16
+ @return boolean
17
+ ]=]
18
+ function EloMatchResultUtils.isEloMatchResult(matchResult: any): boolean
12
19
  return matchResult == EloMatchResult.PLAYER_ONE_WIN
13
20
  or matchResult == EloMatchResult.PLAYER_TWO_WIN
14
21
  or matchResult == EloMatchResult.DRAW
15
22
  end
16
23
 
17
- function EloMatchResultUtils.isEloMatchResultList(eloMatchResultList)
24
+ --[=[
25
+ Checks if the given match result is a valid EloMatchResult list
26
+
27
+ @param eloMatchResultList any
28
+ @return boolean
29
+ ]=]
30
+ function EloMatchResultUtils.isEloMatchResultList(eloMatchResultList: {number }): boolean
18
31
  if type(eloMatchResultList) ~= "table" then
19
32
  return false
20
33
  end
21
34
 
22
- for _, eloMatchResult in pairs(eloMatchResultList) do
35
+ for _, eloMatchResult in eloMatchResultList do
23
36
  if not EloMatchResultUtils.isEloMatchResult(eloMatchResult) then
24
37
  return false
25
38
  end
@@ -1,3 +1,4 @@
1
+ --!strict
1
2
  --[=[
2
3
  Utilities to compute elo scores for players
3
4
  @class EloUtils
@@ -30,6 +31,8 @@ local Probability = require("Probability")
30
31
 
31
32
  local EloUtils = {}
32
33
 
34
+ export type EloMatchResultList = { number }
35
+
33
36
  --[=[
34
37
  @interface EloConfig
35
38
  .factor number
@@ -39,21 +42,36 @@ local EloUtils = {}
39
42
  .groupMultipleResultAsOne boolean
40
43
  @within EloUtils
41
44
  ]=]
45
+ export type EloConfig = {
46
+ factor: number,
47
+ kfactor: number | (rating: number) -> number,
48
+ initial: number,
49
+ ratingFloor: number,
50
+ groupMultipleResultAsOne: boolean,
51
+ }
52
+
53
+ export type PartialEloConfig = {
54
+ factor: number?,
55
+ kfactor: (number | (rating: number) -> number)?,
56
+ initial: number?,
57
+ ratingFloor: number?,
58
+ groupMultipleResultAsOne: boolean?,
59
+ }
42
60
 
43
61
  --[=[
44
62
  Creates a new elo config.
45
63
  @param config table? -- Optional table with defaults
46
64
  @return EloConfig
47
65
  ]=]
48
- function EloUtils.createConfig(config)
49
- config = config or {}
66
+ function EloUtils.createConfig(config: PartialEloConfig?): EloConfig
67
+ local partial: PartialEloConfig = config or {}
50
68
 
51
69
  return {
52
- factor = config.factor or 400;
53
- kfactor = config.kfactor or EloUtils.standardKFactorFormula;
54
- initial = config.initial or 1400;
55
- ratingFloor = config.ratingFloor or 100;
56
- groupMultipleResultAsOne = false;
70
+ factor = partial.factor or 400,
71
+ kfactor = partial.kfactor or EloUtils.standardKFactorFormula,
72
+ initial = partial.initial or 1400,
73
+ ratingFloor = partial.ratingFloor or 100,
74
+ groupMultipleResultAsOne = false,
57
75
  }
58
76
  end
59
77
 
@@ -63,7 +81,7 @@ end
63
81
  @param config any
64
82
  @return boolean
65
83
  ]=]
66
- function EloUtils.isEloConfig(config)
84
+ function EloUtils.isEloConfig(config: EloConfig): boolean
67
85
  return type(config) == "table"
68
86
  and type(config.factor) == "number"
69
87
  and (type(config.kfactor) == "number" or type(config.kfactor) == "function")
@@ -78,10 +96,10 @@ end
78
96
  @param eloConfig EloConfig
79
97
  @return number
80
98
  ]=]
81
- function EloUtils.getStandardDeviation(eloConfig)
99
+ function EloUtils.getStandardDeviation(eloConfig: EloConfig): number
82
100
  assert(EloUtils.isEloConfig(eloConfig), "Bad eloConfig")
83
101
 
84
- return 0.5*eloConfig.factor*math.sqrt(2)
102
+ return 0.5 * eloConfig.factor * math.sqrt(2)
85
103
  end
86
104
 
87
105
  --[=[
@@ -91,13 +109,13 @@ end
91
109
  @param elo number
92
110
  @return number
93
111
  ]=]
94
- function EloUtils.getPercentile(eloConfig, elo)
112
+ function EloUtils.getPercentile(eloConfig: EloConfig, elo: number): number
95
113
  assert(EloUtils.isEloConfig(eloConfig), "Bad eloConfig")
96
114
 
97
115
  local standardDeviation = EloUtils.getStandardDeviation(eloConfig)
98
116
  local mean = eloConfig.initial
99
117
 
100
- local zScore = (elo - mean)/standardDeviation
118
+ local zScore = (elo - mean) / standardDeviation
101
119
  return Probability.cdf(zScore)
102
120
  end
103
121
 
@@ -108,14 +126,18 @@ end
108
126
  @param percentile number
109
127
  @return number
110
128
  ]=]
111
- function EloUtils.percentileToElo(eloConfig, percentile)
129
+ function EloUtils.percentileToElo(eloConfig: EloConfig, percentile: number): number?
112
130
  assert(EloUtils.isEloConfig(eloConfig), "Bad eloConfig")
113
131
 
114
132
  local standardDeviation = EloUtils.getStandardDeviation(eloConfig)
115
133
  local mean = eloConfig.initial
116
134
 
117
135
  local zScore = Probability.percentileToZScore(percentile)
118
- return mean + zScore*standardDeviation
136
+ if zScore == nil then
137
+ return nil
138
+ end
139
+
140
+ return mean + zScore * standardDeviation
119
141
  end
120
142
 
121
143
  --[=[
@@ -128,14 +150,25 @@ end
128
150
  @return number -- playerOneRating
129
151
  @return number -- playerTwoRating
130
152
  ]=]
131
- function EloUtils.getNewElo(config, playerOneRating, playerTwoRating, eloMatchResultList)
153
+ function EloUtils.getNewElo(
154
+ config: EloConfig,
155
+ playerOneRating: number,
156
+ playerTwoRating: number,
157
+ eloMatchResultList: EloMatchResultList
158
+ ): (number, number)
132
159
  assert(EloUtils.isEloConfig(config), "Bad config")
133
160
  assert(type(playerOneRating) == "number", "Bad playerOneRating")
134
161
  assert(type(playerTwoRating) == "number", "Bad playerTwoRating")
135
162
  assert(EloMatchResultUtils.isEloMatchResultList(eloMatchResultList), "Bad eloMatchResultList")
136
163
 
137
- local newPlayerOneRating = EloUtils.getNewPlayerOneScore(config, playerOneRating, playerTwoRating, eloMatchResultList)
138
- local newPlayerTwoRating = EloUtils.getNewPlayerOneScore(config, playerTwoRating, playerOneRating, EloUtils.fromOpponentPerspective(eloMatchResultList))
164
+ local newPlayerOneRating =
165
+ EloUtils.getNewPlayerOneScore(config, playerOneRating, playerTwoRating, eloMatchResultList)
166
+ local newPlayerTwoRating = EloUtils.getNewPlayerOneScore(
167
+ config,
168
+ playerTwoRating,
169
+ playerOneRating,
170
+ EloUtils.fromOpponentPerspective(eloMatchResultList)
171
+ )
139
172
  return newPlayerOneRating, newPlayerTwoRating
140
173
  end
141
174
 
@@ -149,13 +182,14 @@ end
149
182
  @return number -- playerOneRating
150
183
  @return number -- playerTwoRating
151
184
  ]=]
152
- function EloUtils.getEloChange(config, playerOneRating, playerTwoRating, eloMatchResultList)
185
+ function EloUtils.getEloChange(config: EloConfig, playerOneRating: number, playerTwoRating: number, eloMatchResultList)
153
186
  assert(EloUtils.isEloConfig(config), "Bad config")
154
187
  assert(type(playerOneRating) == "number", "Bad playerOneRating")
155
188
  assert(type(playerTwoRating) == "number", "Bad playerTwoRating")
156
189
  assert(EloMatchResultUtils.isEloMatchResultList(eloMatchResultList), "Bad eloMatchResultList")
157
190
 
158
- local newPlayerOneRating, newPlayerTwoRating = EloUtils.getNewElo(config, playerOneRating, playerTwoRating, eloMatchResultList)
191
+ local newPlayerOneRating, newPlayerTwoRating =
192
+ EloUtils.getNewElo(config, playerOneRating, playerTwoRating, eloMatchResultList)
159
193
  local playerOneChange = newPlayerOneRating - playerOneRating
160
194
  local playerTwoChange = newPlayerTwoRating - playerTwoRating
161
195
  return playerOneChange, playerTwoChange
@@ -169,13 +203,22 @@ end
169
203
  @param playerTwoRating number
170
204
  @param eloMatchResultList { EloMatchResult }
171
205
  ]=]
172
- function EloUtils.getNewPlayerOneScore(config, playerOneRating, playerTwoRating, eloMatchResultList)
206
+ function EloUtils.getNewPlayerOneScore(
207
+ config: EloConfig,
208
+ playerOneRating: number,
209
+ playerTwoRating: number,
210
+ eloMatchResultList: EloMatchResultList
211
+ )
173
212
  assert(EloUtils.isEloConfig(config), "Bad config")
174
213
  assert(type(playerOneRating) == "number", "Bad playerOneRating")
175
214
  assert(type(playerTwoRating) == "number", "Bad playerTwoRating")
176
215
  assert(EloMatchResultUtils.isEloMatchResultList(eloMatchResultList), "Bad eloMatchResultList")
177
216
 
178
- return math.max(config.ratingFloor, playerOneRating + EloUtils.getPlayerOneScoreAdjustment(config, playerOneRating, playerTwoRating, eloMatchResultList))
217
+ return math.max(
218
+ config.ratingFloor,
219
+ playerOneRating
220
+ + EloUtils.getPlayerOneScoreAdjustment(config, playerOneRating, playerTwoRating, eloMatchResultList)
221
+ )
179
222
  end
180
223
 
181
224
  --[=[
@@ -190,7 +233,7 @@ end
190
233
  @param playerTwoRating number
191
234
  @return number
192
235
  ]=]
193
- function EloUtils.getPlayerOneExpected(config, playerOneRating, playerTwoRating)
236
+ function EloUtils.getPlayerOneExpected(config: EloConfig, playerOneRating: number, playerTwoRating: number): number
194
237
  assert(EloUtils.isEloConfig(config), "Bad config")
195
238
  assert(type(playerOneRating) == "number", "Bad playerOneRating")
196
239
  assert(type(playerTwoRating) == "number", "Bad playerTwoRating")
@@ -208,7 +251,12 @@ end
208
251
  @param eloMatchResultList { EloMatchResult }
209
252
  @return number
210
253
  ]=]
211
- function EloUtils.getPlayerOneScoreAdjustment(config, playerOneRating, playerTwoRating, eloMatchResultList)
254
+ function EloUtils.getPlayerOneScoreAdjustment(
255
+ config: EloConfig,
256
+ playerOneRating: number,
257
+ playerTwoRating: number,
258
+ eloMatchResultList: EloMatchResultList
259
+ ): number
212
260
  assert(EloUtils.isEloConfig(config), "Bad config")
213
261
  assert(type(playerOneRating) == "number", "Bad playerOneRating")
214
262
  assert(type(playerTwoRating) == "number", "Bad playerTwoRating")
@@ -229,15 +277,15 @@ function EloUtils.getPlayerOneScoreAdjustment(config, playerOneRating, playerTwo
229
277
  multiplier = losses
230
278
  end
231
279
 
232
- adjustment = multiplier*(score - expected)
280
+ adjustment = multiplier * (score - expected)
233
281
  else
234
- for _, score in pairs(eloMatchResultList) do
282
+ for _, score in eloMatchResultList do
235
283
  adjustment = adjustment + (score - expected)
236
284
  end
237
285
  end
238
286
 
239
287
  local kfactor = EloUtils.extractKFactor(config, playerOneRating)
240
- return kfactor*adjustment
288
+ return kfactor * adjustment
241
289
  end
242
290
 
243
291
  --[=[
@@ -246,12 +294,12 @@ end
246
294
  @param eloMatchResultList { EloMatchResult }
247
295
  @return { number }
248
296
  ]=]
249
- function EloUtils.fromOpponentPerspective(eloMatchResultList)
297
+ function EloUtils.fromOpponentPerspective(eloMatchResultList: EloMatchResultList): EloMatchResultList
250
298
  assert(EloMatchResultUtils.isEloMatchResultList(eloMatchResultList), "Bad eloMatchResultList")
251
299
 
252
300
  local newScores = {}
253
301
 
254
- for index, score in pairs(eloMatchResultList) do
302
+ for index, score in eloMatchResultList do
255
303
  newScores[index] = 1 - score
256
304
  end
257
305
 
@@ -264,11 +312,11 @@ end
264
312
  @param eloMatchResultList { EloMatchResult }
265
313
  @return { number }
266
314
  ]=]
267
- function EloUtils.countPlayerOneWins(eloMatchResultList)
315
+ function EloUtils.countPlayerOneWins(eloMatchResultList: EloMatchResultList): number
268
316
  assert(EloMatchResultUtils.isEloMatchResultList(eloMatchResultList), "Bad eloMatchResultList")
269
317
 
270
318
  local count = 0
271
- for _, score in pairs(eloMatchResultList) do
319
+ for _, score in eloMatchResultList do
272
320
  if score == EloMatchResult.PLAYER_ONE_WIN then
273
321
  count = count + 1
274
322
  end
@@ -282,11 +330,11 @@ end
282
330
  @param eloMatchResultList { EloMatchResult }
283
331
  @return { number }
284
332
  ]=]
285
- function EloUtils.countPlayerTwoWins(eloMatchResultList)
333
+ function EloUtils.countPlayerTwoWins(eloMatchResultList: EloMatchResultList): number
286
334
  assert(EloMatchResultUtils.isEloMatchResultList(eloMatchResultList), "Bad eloMatchResultList")
287
335
 
288
336
  local count = 0
289
- for _, score in pairs(eloMatchResultList) do
337
+ for _, score in eloMatchResultList do
290
338
  if score == EloMatchResult.PLAYER_TWO_WIN then
291
339
  count = count + 1
292
340
  end
@@ -300,7 +348,7 @@ end
300
348
  @param rating number
301
349
  @return number
302
350
  ]=]
303
- function EloUtils.standardKFactorFormula(rating)
351
+ function EloUtils.standardKFactorFormula(rating: number): number
304
352
  if rating >= 2400 then
305
353
  return 16
306
354
  elseif rating >= 2100 then
@@ -317,7 +365,7 @@ end
317
365
  @param rating number
318
366
  @return number
319
367
  ]=]
320
- function EloUtils.extractKFactor(config, rating)
368
+ function EloUtils.extractKFactor(config: EloConfig, rating: number): number
321
369
  assert(EloUtils.isEloConfig(config), "Bad config")
322
370
  assert(type(rating) == "number", "Bad rating")
323
371
 
@@ -21,7 +21,7 @@ local function TextLabel(props)
21
21
  TextSize = props.TextSize or 20;
22
22
  Text = props.Text;
23
23
  RichText = props.RichText;
24
- };
24
+ }
25
25
  end
26
26
 
27
27
  local function MatchResultCard(props)
@@ -294,7 +294,7 @@ return function(target)
294
294
  };
295
295
  }
296
296
 
297
- for _, matchResultType in pairs(matchResultTypes) do
297
+ for _, matchResultType in matchResultTypes do
298
298
  if type(matchResultType) == "string" then
299
299
  table.insert(groupOptions, TextLabel({
300
300
  TextColor3 = Color3.new(1, 1, 1);