openskill.lua is an implementation of the Weng-Lin Bayesian ranking, a better, license-free alternative to the TrueSkill ranking system.
It is a Luau port of the amazing openskill.js module, designed specifically for Roblox game development.
Get the module here and insert it into your game (preferably in ServerStorage).
Alternatively, you can paste this directly into your Roblox Studio command bar:
game:GetObjects("rbxassetid://8134663273")[1].Parent=game.ServerStorageCreate a script and require the module:
local OpenSkill = require(game.ServerStorage.OpenSkill)You can create a rating for every player to describe their skill. Ratings are represented as a Gaussian curve with two properties:
- mu: The average skill of the player.
- sigma: The degree of uncertainty in the player's skill.
Maintaining an uncertainty (sigma) allows the system to make large changes to skill estimates early on, but smaller, more stable changes after a series of consistent games.
local a1 = OpenSkill.Rating() --> {mu = 25, sigma = 8.333}
local a2 = OpenSkill.Rating(32.444) --> {mu = 32.444, sigma = 10.814}
local b1 = OpenSkill.Rating(nil, 2.421) --> {mu = 25, sigma = 2.421}
local b2 = OpenSkill.Rating(25.188, 6.211) --> {mu = 25.188, sigma = 6.211}If a1 and a2 form a team and win against a team of b1 and b2, you can update their skill ratings:
OpenSkill.Rate({{a1, a2}, {b1, b2}})When displaying a rating or sorting a leaderboard, use Ordinal. By default, this returns mu - 3 * sigma, showing a rating for which there is a 99.7% likelihood the player's true rating is higher. In early games, a player's ordinal rating will usually go up, even if they lose!
OpenSkill.Ordinal(a1) --> 0 (before rating)
OpenSkill.Ordinal(a1) --> 2.3245624871094 (after winning)If your teams are listed in one order but your ranking is in a different order, you can specify a rank option. Lower ranks are considered better.
local a = OpenSkill.Rating()
local b = OpenSkill.Rating()
local c = OpenSkill.Rating()
local d = OpenSkill.Rating()
OpenSkill.Rate({{a}, {b}, {c}, {d}}, { --4 teams consisting of 1 player
rank = {4, 1, 3, 2}
})In this example, team b placed 1st, d placed 2nd, c placed 3rd, and a placed 4th.
You can also provide a score instead, where higher is better. These can just be raw scores from the game.
OpenSkill.Rate({{a}, {b}, {c}, {d}}, {
score = {37, 19, 37, 42}
})Note: Ties should have either an equivalent rank or score.
openskill.lua provides two rating models: PlackettLuce and ThurstoneMosteller.
- Plackett-Luce (Default): A generalized Bradley-Terry model for k ≥ 3 teams which scales best. It follows a logistic distribution over a player's skill, similar to Glicko.
- Thurstone-Mosteller: Follows a Gaussian distribution, similar to TrueSkill. Accuracy is usually slightly lower than Plackett-Luce, but can be tuned with an alternative gamma function.
Note: openskill.lua uses full pairing which yields highly accurate ratings. However, in games with an extremely high number of teams (100+), calculations become computationally expensive due to joint probability integration.
You can change the global default model or pass it per-match:
-- Global
OpenSkill.Settings.DefaultModel = "ThurstoneMosteller"
-- Per-match
OpenSkill.Rate({{a}, {b}, {c}, {d}}, {
model = "ThurstoneMosteller"
})OpenSkill.DefaultModel : stringDetermines the model which is used by default.
OpenSkill.Rating(mu : number?, sigma : number?, options : any?): ratingCreates a rating object, which describes a player's skill. Ratings are kept as an object which represent a gaussian curve, with properties where mu represents the mean, and sigma represents the spread or standard deviation. mu is the average skill of the player and sigma is the degree of uncertainty in the player's skill. Maintaining an uncertainty allows the system to make big changes to the skill estimates early on but small changes after a series of consistent games has been played. If omitted, mu defaults to 25 and sigma defaults to 25 / 3.
OpenSkill.Ordinal(rating : rating, options : any?): numberRepresents a player's skill estimate as a single number. By default, this returns mu - 3 * sigma, showing a rating for which there's a 99.7% likelihood the player's true rating is higher. So in early games, a player's ordinal rating will usually go up and could go up even if that player loses.
OpenSkill.Rate(teams : {{rating}}, options : any?): {{{number}}}Takes an array of teams (which are arrays of ratings) and updates their values based on the outcome of the match. Returns the new rating values in identically structured arrays.
OpenSkill.WinProbability(teams : {{rating}}, options : any?): {number}Calculates the probability of each team winning the match.
OpenSkill.DrawProbability(teams : {{rating}}, options : any?): numberCalculates the probability of a draw between the teams. This is extremely useful for determining fair team compositions in matchmaking.
Contributions, issues, and feature requests are greatly appreciated!