Skip to content

Commit

Permalink
feat: RatingCalculator
Browse files Browse the repository at this point in the history
Signed-off-by: Dup4 <lyuzhi.pan@gmail.com>
  • Loading branch information
Dup4 committed Jan 21, 2024
1 parent 2e53530 commit f9b9284
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/libs/core/src/index.ts
@@ -1,4 +1,5 @@
export * from "./export";
export * from "./rating";
export * from "./utils";

export * from "./award";
Expand Down
2 changes: 2 additions & 0 deletions packages/libs/core/src/rating/index.ts
@@ -0,0 +1,2 @@
export * from "./rating-calculator";
export * from "./rating-user";
94 changes: 94 additions & 0 deletions packages/libs/core/src/rating/rating-calculator.ts
@@ -0,0 +1,94 @@
import type { RatingUsers } from "./rating-user";
import { RatingUser } from "./rating-user";

// https://www.wikiwand.com/en/Elo_rating_system
export class RatingCalculator {
users: RatingUsers;

constructor() {
this.users = [];
}

calcP(userA: RatingUser, userB: RatingUser) {
return 1.0 / (1.0 + 10 ** ((userB.oldRating - userA.oldRating) / 400.0));
}

getExSeed(users: RatingUsers, rating: number, ownUser: RatingUser) {
const exUser = new RatingUser();
let res = 0;

users.forEach((user) => {
if (user.id !== ownUser.id) {
res += this.calcP(user, exUser);
}
});

return res;
}

calcRating(users: RatingUsers, rank: number, user: RatingUser) {
let left = 1;
let right = 8000;

while (right - left > 1) {
const mid = Math.floor((left + right) / 2);
if (this.getExSeed(users, mid, user) < rank) {
right = mid;
} else {
left = mid;
}
}

return left;
}

calculate() {
// Calculate seed
for (let i = 0; i < this.users.length; i++) {
const u = this.users[i];
u.seed = 1.0;
for (let j = 0; j < this.users.length; j++) {
if (i !== j) {
const otherUser = this.users[j];
u.seed += this.calcP(otherUser, u);
}
}
}

// Calculate initial delta and sumDelta
let sumDelta = 0;
for (let i = 0; i < this.users.length; i++) {
const u = this.users[i];
u.delta = Math.floor(
(this.calcRating(this.users, Math.sqrt(u.rank * u.seed), u)
- u.oldRating)
/ 2,
);
sumDelta += u.delta;
}

// Calculate first inc
let inc = Math.floor(-sumDelta / this.users.length) - 1;
for (let i = 0; i < this.users.length; i++) {
const u = this.users[i];
u.delta += inc;
}

// Calculate second inc
this.users = this.users.sort((a, b) => b.oldRating - a.oldRating);
const s = Math.min(this.users.length, Math.floor(4 * Math.round(Math.sqrt(this.users.length))));
let sumS = 0;
for (let i = 0; i < s; i++) {
sumS += this.users[i].delta;
}
inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);

// Calculate new rating
this.users.forEach((u) => {
u.delta += inc;
u.newRating = u.oldRating + u.delta;
});

this.users = this.users.sort((a, b) => a.rank - b.rank);
}
}
21 changes: 21 additions & 0 deletions packages/libs/core/src/rating/rating-user.ts
@@ -0,0 +1,21 @@
export class RatingUser {
id: string;

rank: number;
seed: number;
delta: number;
oldRating: number;
newRating: number;

constructor() {
this.id = "";

this.rank = 0;
this.seed = 1;
this.delta = 0;
this.oldRating = 0;
this.newRating = 0;
}
}

export type RatingUsers = Array<RatingUser>;

0 comments on commit f9b9284

Please sign in to comment.