|
1 | | -[Tutorial & Documentation](https://bstummer.github.io/openskill.lua) |
| 1 | +# openskill.lua |
| 2 | + |
| 3 | +openskill.lua is an implementation of the [Weng-Lin Bayesian ranking](https://www.csie.ntu.edu.tw/~cjlin/papers/online_ranking/online_journal.pdf), a better, license-free alternative to the [TrueSkill](https://www.microsoft.com/en-us/research/project/trueskill-ranking-system) ranking system. |
| 4 | + |
| 5 | +It is a Luau port of the amazing [openskill.js](https://github.com/philihp/openskill.js) module, designed specifically for Roblox game development. |
| 6 | + |
| 7 | +## Installation |
| 8 | + |
| 9 | +Get the module [here](https://www.roblox.com/library/8134663273) and insert it into your game (preferably in ServerStorage). |
| 10 | + |
| 11 | +Alternatively, you can paste this directly into your Roblox Studio command bar: |
| 12 | + |
| 13 | +```lua |
| 14 | +game:GetObjects("rbxassetid://8134663273")[1].Parent=game.ServerStorage |
| 15 | +``` |
| 16 | + |
| 17 | +## Quick Start |
| 18 | + |
| 19 | +### 1. Require the Module |
| 20 | + |
| 21 | +Create a script and require the module: |
| 22 | + |
| 23 | +```lua |
| 24 | +local OpenSkill = require(game.ServerStorage.OpenSkill) |
| 25 | +``` |
| 26 | + |
| 27 | +### 2. Create Ratings |
| 28 | + |
| 29 | +You can create a rating for every player to describe their skill. Ratings are represented as a Gaussian curve with two properties: |
| 30 | + |
| 31 | +- mu: The average skill of the player. |
| 32 | +- sigma: The degree of uncertainty in the player's skill. |
| 33 | + |
| 34 | +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. |
| 35 | + |
| 36 | +```lua |
| 37 | +local a1 = OpenSkill.Rating() --> {mu = 25, sigma = 8.333} |
| 38 | +local a2 = OpenSkill.Rating(32.444) --> {mu = 32.444, sigma = 10.814} |
| 39 | +local b1 = OpenSkill.Rating(nil, 2.421) --> {mu = 25, sigma = 2.421} |
| 40 | +local b2 = OpenSkill.Rating(25.188, 6.211) --> {mu = 25.188, sigma = 6.211} |
| 41 | +``` |
| 42 | + |
| 43 | +### 3. Rate a Match |
| 44 | + |
| 45 | +If `a1` and `a2` form a team and win against a team of `b1` and `b2`, you can update their skill ratings: |
| 46 | + |
| 47 | +```lua |
| 48 | +OpenSkill.Rate({{a1, a2}, {b1, b2}}) |
| 49 | +``` |
| 50 | + |
| 51 | +### 4. Displaying Ratings |
| 52 | + |
| 53 | +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! |
| 54 | +```lua |
| 55 | +OpenSkill.Ordinal(a1) --> 0 (before rating) |
| 56 | +OpenSkill.Ordinal(a1) --> 2.3245624871094 (after winning) |
| 57 | +``` |
| 58 | + |
| 59 | +## Advanced Match Results |
| 60 | + |
| 61 | +### Custom Ranks |
| 62 | + |
| 63 | +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. |
| 64 | + |
| 65 | +```lua |
| 66 | +local a = OpenSkill.Rating() |
| 67 | +local b = OpenSkill.Rating() |
| 68 | +local c = OpenSkill.Rating() |
| 69 | +local d = OpenSkill.Rating() |
| 70 | + |
| 71 | +OpenSkill.Rate({{a}, {b}, {c}, {d}}, { --4 teams consisting of 1 player |
| 72 | + rank = {4, 1, 3, 2} |
| 73 | +}) |
| 74 | +``` |
| 75 | +*In this example, team b placed 1st, d placed 2nd, c placed 3rd, and a placed 4th.* |
| 76 | + |
| 77 | +### Custom Scores |
| 78 | + |
| 79 | +You can also provide a score instead, where higher is better. These can just be raw scores from the game. |
| 80 | + |
| 81 | +```lua |
| 82 | +OpenSkill.Rate({{a}, {b}, {c}, {d}}, { |
| 83 | + score = {37, 19, 37, 42} |
| 84 | +}) |
| 85 | +``` |
| 86 | +*Note: Ties should have either an equivalent rank or score.* |
| 87 | + |
| 88 | +## Rating Models |
| 89 | + |
| 90 | +openskill.lua provides two rating models: `PlackettLuce` and `ThurstoneMosteller`. |
| 91 | +- 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. |
| 92 | +- 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. |
| 93 | + |
| 94 | +*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.* |
| 95 | + |
| 96 | +You can change the global default model or pass it per-match: |
| 97 | + |
| 98 | +```lua |
| 99 | +-- Global |
| 100 | +OpenSkill.Settings.DefaultModel = "ThurstoneMosteller" |
| 101 | + |
| 102 | +-- Per-match |
| 103 | +OpenSkill.Rate({{a}, {b}, {c}, {d}}, { |
| 104 | + model = "ThurstoneMosteller" |
| 105 | +}) |
| 106 | +``` |
| 107 | + |
| 108 | + |
| 109 | +## API Reference |
| 110 | + |
| 111 | +```lua |
| 112 | +OpenSkill.DefaultModel : string |
| 113 | +``` |
| 114 | +Determines the model which is used by default. |
| 115 | + |
| 116 | +```lua |
| 117 | +OpenSkill.Rating(mu : number?, sigma : number?, options : any?): rating |
| 118 | +``` |
| 119 | +Creates 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. |
| 120 | + |
| 121 | + |
| 122 | +```lua |
| 123 | +OpenSkill.Ordinal(rating : rating, options : any?): number |
| 124 | +``` |
| 125 | +Represents 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%](https://en.wikipedia.org/wiki/68–95–99.7_rule) 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. |
| 126 | + |
| 127 | + |
| 128 | +```lua |
| 129 | +OpenSkill.Rate(teams : {{rating}}, options : any?): {{{number}}} |
| 130 | +``` |
| 131 | +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. |
| 132 | + |
| 133 | + |
| 134 | +```lua |
| 135 | +OpenSkill.WinProbability(teams : {{rating}}, options : any?): {number} |
| 136 | +``` |
| 137 | +Calculates the probability of each team winning the match. |
| 138 | + |
| 139 | + |
| 140 | +```lua |
| 141 | +OpenSkill.DrawProbability(teams : {{rating}}, options : any?): number |
| 142 | +``` |
| 143 | +Calculates the probability of a draw between the teams. This is extremely useful for determining fair team compositions in matchmaking. |
| 144 | + |
| 145 | +## Contributing |
| 146 | + |
| 147 | +Contributions, issues, and feature requests are greatly appreciated! |
0 commit comments