From 46cd03f44015edcf333b9b84c311137c16b8ac2a Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Fri, 15 Nov 2024 15:34:46 -0500 Subject: [PATCH 01/16] base rating system --- package.json | 3 ++ packages/server/src/games.ts | 9 ++++- packages/server/src/ratings.ts | 64 ++++++++++++++++++++++++++++++++ packages/server/src/users.ts | 17 ++++++--- packages/shared/src/api_types.ts | 10 ++++- yarn.lock | 9 +++++ 6 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 packages/server/src/ratings.ts diff --git a/package.json b/package.json index 6ac73184..856a7876 100644 --- a/package.json +++ b/package.json @@ -24,5 +24,8 @@ "prettier": { "trailingComma": "all", "endOfLine": "lf" + }, + "dependencies": { + "glicko2": "^1.2.1" } } diff --git a/packages/server/src/games.ts b/packages/server/src/games.ts index 104ca405..f082381b 100644 --- a/packages/server/src/games.ts +++ b/packages/server/src/games.ts @@ -21,6 +21,7 @@ import { } from "./time-control/time-control"; import { timeControlHandlerMap } from "./time-control/time-handler-map"; import { Clock } from "./time-control/clock"; +import { updateRatings } from './ratings' export function gamesCollection() { return getDb().db().collection("games"); @@ -28,7 +29,7 @@ export function gamesCollection() { /** * @param count number of games to return (default = 10, max = 100) - * @param offset number of games to skip (default = 0) + * @param offset number of games to skip (default = 0)gamesCollection * @param filter filter settings for the query */ export async function getGames( @@ -69,6 +70,7 @@ export async function getGame(id: string): Promise { throw new Error("Game not found"); } + const game = outwardFacingGame(db_game); // Legacy games don't have a players field // TODO: remove this code after doing proper db migration @@ -198,6 +200,11 @@ export async function handleMoveAndTime( game.moves.push(moves); emitGame(game.id, game.players?.length ?? 0, game_obj, timeControl); + + if(game_obj.phase == "gameover" && game.variant == "quantum"){ + // user ranking value is only for quantum right now + await updateRatings(game, game_obj); + } return game; } diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts new file mode 100644 index 00000000..b25e63cb --- /dev/null +++ b/packages/server/src/ratings.ts @@ -0,0 +1,64 @@ +import { GameResponse, AbstractGame, UserRanking } from "@ogfcommunity/variants-shared"; +import { Glicko2, Player } from 'glicko2'; +import { updateUserRating, getUser } from './users' + + +const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + +const matches: [Player, Player, number][] = []; + +export async function updateRatings(game: GameResponse, game_obj: AbstractGame){ + + // Rating system currently only for two player games + const winner = game_obj.result[0] === "B" ? 1 : 0; + + const player_black_id = game.players[0].id; + const player_white_id = game.players[1].id; + + const db_player_black_ranking = (await getUser(player_black_id)).ranking; + const db_player_white_ranking = (await getUser(player_white_id)).ranking; + + var glicko_player_black; + var glicko_player_white; + + if (db_player_black_ranking == undefined) { + glicko_player_black = ranking.makePlayer(1500, 350, 0.06); + } else { + glicko_player_black = ranking.makePlayer(db_player_black_ranking.rating, db_player_black_ranking.rd, db_player_black_ranking.vol); + } + + if (db_player_white_ranking == undefined) { + glicko_player_white = ranking.makePlayer(1500, 350, 0.06); + } else { + glicko_player_white = ranking.makePlayer(db_player_white_ranking.rating, db_player_white_ranking.rd, db_player_white_ranking.vol); + } + + matches.push([glicko_player_black, glicko_player_white, winner]); + + ranking.updateRatings(matches); + + const player_black_new_ranking: UserRanking = { + rating: glicko_player_black.getRating(), + rd: glicko_player_black.getRd(), + vol: glicko_player_black.getVol(), + } + + const player_white_new_ranking: UserRanking = { + rating: glicko_player_white.getRating(), + rd: glicko_player_white.getRd(), + vol: glicko_player_white.getVol(), + } + + await updateUserRating(player_black_id, player_black_new_ranking); + await updateUserRating(player_white_id, player_white_new_ranking); + + //any return values? + +} + + diff --git a/packages/server/src/users.ts b/packages/server/src/users.ts index af855380..1fda9b9b 100644 --- a/packages/server/src/users.ts +++ b/packages/server/src/users.ts @@ -1,12 +1,15 @@ import { getDb } from "./db"; -import { UserResponse } from "@ogfcommunity/variants-shared"; +import { UserResponse, UserRanking } from "@ogfcommunity/variants-shared"; import { Collection, WithId, ObjectId } from "mongodb"; import { randomBytes, scrypt } from "node:crypto"; + + + export interface GuestUser extends UserResponse { token: string; login_type: "guest"; - rating?: number; + ranking?: UserRanking; } // Not currently used, but the plan is to use LocalStrategy from Password.js @@ -15,16 +18,16 @@ export interface PersistentUser extends UserResponse { username: string; password_hash: string; login_type: "persistent"; - rating?: number; + ranking?: UserRanking; } export async function updateUserRating( user_id: string, - new_rating: number, + new_ranking: UserRanking, ): Promise { const update_result = await usersCollection().updateOne( { _id: new ObjectId(user_id) }, - { $set: { rating: new_rating } }, + { $set: { ranking: new_ranking } }, ); if (update_result.matchedCount == 0) { throw new Error("User not found"); @@ -49,6 +52,7 @@ export async function getUserByName(username: string): Promise { username: db_user.username, password_hash: db_user.password_hash, login_type: db_user.login_type, + ranking: db_user.ranking, }; } @@ -134,6 +138,7 @@ export async function createUserWithUsernameAndPassword( username, password_hash, login_type: "persistent", + ranking: {rating: 1500, rd: 350, vol: 0.06} }; const result = await usersCollection().insertOne(user); @@ -188,7 +193,7 @@ function outwardFacingUser( id: db_user._id.toString(), login_type: db_user.login_type, ...(db_user.login_type === "persistent" && { username: db_user.username }), - rating: db_user.rating, + ranking: db_user.ranking, }; } diff --git a/packages/shared/src/api_types.ts b/packages/shared/src/api_types.ts index ea3ff21e..2d39fb99 100644 --- a/packages/shared/src/api_types.ts +++ b/packages/shared/src/api_types.ts @@ -4,10 +4,16 @@ import { ITimeControlConfig, } from "./time_control/time_control.types"; +export interface UserRanking { + rating: number + rd: number + vol: number +} + export interface User { username?: string; id: string; - rating?: number; + ranking?: UserRanking; } export interface GameResponse { id: string; @@ -22,7 +28,7 @@ export interface UserResponse { id?: string; login_type: "guest" | "persistent"; username?: string; - rating?: number; + ranking?: UserRanking; } export type GamesFilter = { diff --git a/yarn.lock b/yarn.lock index 9eab34f6..191d479e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1094,6 +1094,8 @@ __metadata: "@ogfcommunity/variants-monorepo@workspace:.": version: 0.0.0-use.local resolution: "@ogfcommunity/variants-monorepo@workspace:." + dependencies: + glicko2: ^1.2.1 languageName: unknown linkType: soft @@ -4248,6 +4250,13 @@ __metadata: languageName: node linkType: hard +"glicko2@npm:^1.2.1": + version: 1.2.1 + resolution: "glicko2@npm:1.2.1" + checksum: f0c5c15aec1d43daca49cb7b75d49346d36e2f2f239738cdf7d77e1e4459ed98b7638c8f0eba8b112def8e56937eea3eda17fbd71e42996b35e2adddc4efd08b + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" From 295c1772277e2f86ff69f751fe1728f0f5b30ede Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Fri, 15 Nov 2024 15:40:06 -0500 Subject: [PATCH 02/16] manual yarn lint changes --- packages/server/src/games.ts | 7 ++- packages/server/src/ratings.ts | 91 ++++++++++++++++++-------------- packages/server/src/users.ts | 5 +- packages/shared/src/api_types.ts | 6 +-- 4 files changed, 58 insertions(+), 51 deletions(-) diff --git a/packages/server/src/games.ts b/packages/server/src/games.ts index f082381b..9625c270 100644 --- a/packages/server/src/games.ts +++ b/packages/server/src/games.ts @@ -21,7 +21,7 @@ import { } from "./time-control/time-control"; import { timeControlHandlerMap } from "./time-control/time-handler-map"; import { Clock } from "./time-control/clock"; -import { updateRatings } from './ratings' +import { updateRatings } from "./ratings"; export function gamesCollection() { return getDb().db().collection("games"); @@ -70,7 +70,6 @@ export async function getGame(id: string): Promise { throw new Error("Game not found"); } - const game = outwardFacingGame(db_game); // Legacy games don't have a players field // TODO: remove this code after doing proper db migration @@ -200,8 +199,8 @@ export async function handleMoveAndTime( game.moves.push(moves); emitGame(game.id, game.players?.length ?? 0, game_obj, timeControl); - - if(game_obj.phase == "gameover" && game.variant == "quantum"){ + + if (game_obj.phase == "gameover" && game.variant == "quantum") { // user ranking value is only for quantum right now await updateRatings(game, game_obj); } diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index b25e63cb..233c8783 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -1,64 +1,75 @@ -import { GameResponse, AbstractGame, UserRanking } from "@ogfcommunity/variants-shared"; -import { Glicko2, Player } from 'glicko2'; -import { updateUserRating, getUser } from './users' - - -const ranking = new Glicko2({ +import { + GameResponse, + AbstractGame, + UserRanking, + } from "@ogfcommunity/variants-shared"; + import { Glicko2, Player } from "glicko2"; + import { updateUserRating, getUser } from "./users"; + + const ranking = new Glicko2({ tau: 0.5, rating: 1500, rd: 350, vol: 0.06, }); - -const matches: [Player, Player, number][] = []; - -export async function updateRatings(game: GameResponse, game_obj: AbstractGame){ - + + const matches: [Player, Player, number][] = []; + + export async function updateRatings( + game: GameResponse, + game_obj: AbstractGame, + ) { // Rating system currently only for two player games const winner = game_obj.result[0] === "B" ? 1 : 0; - + const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; - + const db_player_black_ranking = (await getUser(player_black_id)).ranking; const db_player_white_ranking = (await getUser(player_white_id)).ranking; - - var glicko_player_black; - var glicko_player_white; - + + let glicko_player_black; + let glicko_player_white; + if (db_player_black_ranking == undefined) { - glicko_player_black = ranking.makePlayer(1500, 350, 0.06); + glicko_player_black = ranking.makePlayer(1500, 350, 0.06); } else { - glicko_player_black = ranking.makePlayer(db_player_black_ranking.rating, db_player_black_ranking.rd, db_player_black_ranking.vol); + glicko_player_black = ranking.makePlayer( + db_player_black_ranking.rating, + db_player_black_ranking.rd, + db_player_black_ranking.vol, + ); } - + if (db_player_white_ranking == undefined) { - glicko_player_white = ranking.makePlayer(1500, 350, 0.06); + glicko_player_white = ranking.makePlayer(1500, 350, 0.06); } else { - glicko_player_white = ranking.makePlayer(db_player_white_ranking.rating, db_player_white_ranking.rd, db_player_white_ranking.vol); + glicko_player_white = ranking.makePlayer( + db_player_white_ranking.rating, + db_player_white_ranking.rd, + db_player_white_ranking.vol, + ); } - + matches.push([glicko_player_black, glicko_player_white, winner]); - + ranking.updateRatings(matches); - + const player_black_new_ranking: UserRanking = { - rating: glicko_player_black.getRating(), - rd: glicko_player_black.getRd(), - vol: glicko_player_black.getVol(), - } - + rating: glicko_player_black.getRating(), + rd: glicko_player_black.getRd(), + vol: glicko_player_black.getVol(), + }; + const player_white_new_ranking: UserRanking = { - rating: glicko_player_white.getRating(), - rd: glicko_player_white.getRd(), - vol: glicko_player_white.getVol(), - } - + rating: glicko_player_white.getRating(), + rd: glicko_player_white.getRd(), + vol: glicko_player_white.getVol(), + }; + await updateUserRating(player_black_id, player_black_new_ranking); await updateUserRating(player_white_id, player_white_new_ranking); - + //any return values? - -} - - + } + \ No newline at end of file diff --git a/packages/server/src/users.ts b/packages/server/src/users.ts index 1fda9b9b..098f9032 100644 --- a/packages/server/src/users.ts +++ b/packages/server/src/users.ts @@ -3,9 +3,6 @@ import { UserResponse, UserRanking } from "@ogfcommunity/variants-shared"; import { Collection, WithId, ObjectId } from "mongodb"; import { randomBytes, scrypt } from "node:crypto"; - - - export interface GuestUser extends UserResponse { token: string; login_type: "guest"; @@ -138,7 +135,7 @@ export async function createUserWithUsernameAndPassword( username, password_hash, login_type: "persistent", - ranking: {rating: 1500, rd: 350, vol: 0.06} + ranking: { rating: 1500, rd: 350, vol: 0.06 }, }; const result = await usersCollection().insertOne(user); diff --git a/packages/shared/src/api_types.ts b/packages/shared/src/api_types.ts index 2d39fb99..977e018a 100644 --- a/packages/shared/src/api_types.ts +++ b/packages/shared/src/api_types.ts @@ -5,9 +5,9 @@ import { } from "./time_control/time_control.types"; export interface UserRanking { - rating: number - rd: number - vol: number + rating: number; + rd: number; + vol: number; } export interface User { From 4f156aec80d78d1ff10d54feddc6bb8fec8137e1 Mon Sep 17 00:00:00 2001 From: Sameer Dalal <85125585+SameerDalal@users.noreply.github.com> Date: Sat, 16 Nov 2024 08:47:14 -0500 Subject: [PATCH 03/16] Update packages/server/src/ratings.ts Co-authored-by: Benjamin Jones --- packages/server/src/ratings.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index 233c8783..d12693ee 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -67,8 +67,10 @@ import { vol: glicko_player_white.getVol(), }; - await updateUserRating(player_black_id, player_black_new_ranking); - await updateUserRating(player_white_id, player_white_new_ranking); + await Promise.all( + updateUserRating(player_black_id, player_black_new_ranking), + updateUserRating(player_white_id, player_white_new_ranking), + ) //any return values? } From 4ca2d1f941bedba9c2e78807f79de3e42a48a84a Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Sat, 16 Nov 2024 08:49:17 -0500 Subject: [PATCH 04/16] created component functions for rating system --- packages/server/src/games.ts | 2 +- packages/server/src/ratings.ts | 65 +++++++++++++------------------- packages/shared/src/api_types.ts | 14 +++++++ 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/packages/server/src/games.ts b/packages/server/src/games.ts index 9625c270..ede38df7 100644 --- a/packages/server/src/games.ts +++ b/packages/server/src/games.ts @@ -29,7 +29,7 @@ export function gamesCollection() { /** * @param count number of games to return (default = 10, max = 100) - * @param offset number of games to skip (default = 0)gamesCollection + * @param offset number of games to skip (default = 0) * @param filter filter settings for the query */ export async function getGames( diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index 233c8783..f10bcc70 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -25,51 +25,40 @@ import { const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; - const db_player_black_ranking = (await getUser(player_black_id)).ranking; - const db_player_white_ranking = (await getUser(player_white_id)).ranking; - - let glicko_player_black; - let glicko_player_white; - - if (db_player_black_ranking == undefined) { - glicko_player_black = ranking.makePlayer(1500, 350, 0.06); - } else { - glicko_player_black = ranking.makePlayer( - db_player_black_ranking.rating, - db_player_black_ranking.rd, - db_player_black_ranking.vol, - ); - } - - if (db_player_white_ranking == undefined) { - glicko_player_white = ranking.makePlayer(1500, 350, 0.06); - } else { - glicko_player_white = ranking.makePlayer( - db_player_white_ranking.rating, - db_player_white_ranking.rd, - db_player_white_ranking.vol, - ); - } - + const glicko_player_black = await getGlickoPlayer(player_black_id); + const glicko_player_white = await getGlickoPlayer(player_white_id); + matches.push([glicko_player_black, glicko_player_white, winner]); ranking.updateRatings(matches); - const player_black_new_ranking: UserRanking = { - rating: glicko_player_black.getRating(), - rd: glicko_player_black.getRd(), - vol: glicko_player_black.getVol(), - }; - - const player_white_new_ranking: UserRanking = { - rating: glicko_player_white.getRating(), - rd: glicko_player_white.getRd(), - vol: glicko_player_white.getVol(), - }; + const player_black_new_ranking = userRatingFromGlickoPlayer(glicko_player_black); + const player_white_new_ranking = userRatingFromGlickoPlayer(glicko_player_white); await updateUserRating(player_black_id, player_black_new_ranking); await updateUserRating(player_white_id, player_white_new_ranking); //any return values? } - \ No newline at end of file + +async function getGlickoPlayer(user_id: string): Promise { + + const db_player_ranking = (await getUser(user_id)).ranking; + + if (db_player_ranking == undefined) { + return ranking.makePlayer(1500, 350, 0.06); + } + return ranking.makePlayer( + db_player_ranking.rating, + db_player_ranking.rd, + db_player_ranking.vol, + ); +} + +function userRatingFromGlickoPlayer(glicko_player: Player): UserRanking { + return { + rating: glicko_player.getRating(), + rd: glicko_player.getRd(), + vol: glicko_player.getVol(), + }; +} \ No newline at end of file diff --git a/packages/shared/src/api_types.ts b/packages/shared/src/api_types.ts index 977e018a..3fae4319 100644 --- a/packages/shared/src/api_types.ts +++ b/packages/shared/src/api_types.ts @@ -4,16 +4,29 @@ import { ITimeControlConfig, } from "./time_control/time_control.types"; + + + export interface UserRanking { rating: number; rd: number; vol: number; } +export interface GameResult { + opponentRating: UserRanking; + result: "win" | "loss"; +} + +export interface GameResults { + [variant: string]: GameResult[] +} + export interface User { username?: string; id: string; ranking?: UserRanking; + gameHistory?: GameResults } export interface GameResponse { id: string; @@ -29,6 +42,7 @@ export interface UserResponse { login_type: "guest" | "persistent"; username?: string; ranking?: UserRanking; + gameHistory?: GameResults } export type GamesFilter = { From 3b7088d9a3d5c1ff38e4012128bb6b28918813ff Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Sat, 16 Nov 2024 16:56:11 -0500 Subject: [PATCH 05/16] created per-variant rating system and match history for players --- packages/server/src/ratings.ts | 103 +++++++++++++++++++++++++------ packages/server/src/users.ts | 29 +++++++-- packages/shared/src/api_types.ts | 24 +++---- 3 files changed, 119 insertions(+), 37 deletions(-) diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index 39aef96c..24743fb4 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -2,9 +2,13 @@ import { GameResponse, AbstractGame, UserRanking, + UserRankings, + UserResponse, + GameResult, + GameResults, } from "@ogfcommunity/variants-shared"; import { Glicko2, Player } from "glicko2"; - import { updateUserRating, getUser } from "./users"; + import { updateUserRanking, getUser, updateUserGameHistory } from "./users"; const ranking = new Glicko2({ tau: 0.5, @@ -20,32 +24,66 @@ import { game_obj: AbstractGame, ) { // Rating system currently only for two player games - const winner = game_obj.result[0] === "B" ? 1 : 0; - + + const variant = game.variant + const result = game_obj.result[0] + + var glicko_outcome; + var outcome_player_black; + var outcome_player_white; + + if (result === "B") { + glicko_outcome = 1 + outcome_player_black = "Win" + outcome_player_white = "Loss" + } else if (result === "W") { + glicko_outcome = 0 + outcome_player_black = "Loss" + outcome_player_white = "Win" + } else { + glicko_outcome = .5 + outcome_player_black = "Tie" + outcome_player_white = "Tie" + } + const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; - - const glicko_player_black = await getGlickoPlayer(player_black_id); - const glicko_player_white = await getGlickoPlayer(player_white_id); - matches.push([glicko_player_black, glicko_player_white, winner]); + const db_player_black = await getUser(player_black_id); + const db_player_white = await getUser(player_white_id); + + if(db_player_black.ranking == undefined){ + db_player_black.ranking = {} + } + if(db_player_white.ranking == undefined){ + db_player_white.ranking = {} + } + + const new_game_history_player_black = updatedGameHistory(db_player_black, db_player_white.ranking[variant], outcome_player_black, variant); + const new_game_history_player_white = updatedGameHistory(db_player_white, db_player_black.ranking[variant], outcome_player_white, variant); + + await Promise.all([ + updateUserGameHistory(player_black_id, new_game_history_player_black), + updateUserGameHistory(player_white_id, new_game_history_player_white), + ]); + + const glicko_player_black = await getGlickoPlayer(db_player_black.ranking[variant]); + const glicko_player_white = await getGlickoPlayer(db_player_white.ranking[variant]); + + matches.push([glicko_player_black, glicko_player_white, glicko_outcome]); ranking.updateRatings(matches); - const player_black_new_ranking = userRatingFromGlickoPlayer(glicko_player_black); - const player_white_new_ranking = userRatingFromGlickoPlayer(glicko_player_white); - - await Promise.all( - updateUserRating(player_black_id, player_black_new_ranking), - updateUserRating(player_white_id, player_white_new_ranking), - ) + const player_black_new_ranking = userRankingFromGlickoPlayer(db_player_black, glicko_player_black, variant); + const player_white_new_ranking = userRankingFromGlickoPlayer(db_player_white, glicko_player_white, variant); - //any return values? + await Promise.all([ + updateUserRanking(player_black_id, player_black_new_ranking), + updateUserRanking(player_white_id, player_white_new_ranking), + ]); } -async function getGlickoPlayer(user_id: string): Promise { - - const db_player_ranking = (await getUser(user_id)).ranking; +async function getGlickoPlayer(db_player_ranking: UserRanking): Promise { if (db_player_ranking == undefined) { return ranking.makePlayer(1500, 350, 0.06); @@ -57,10 +95,35 @@ async function getGlickoPlayer(user_id: string): Promise { ); } -function userRatingFromGlickoPlayer(glicko_player: Player): UserRanking { - return { +function userRankingFromGlickoPlayer(player: UserResponse, glicko_player: Player, variant: string): UserRankings { + var player_ranking = player.ranking; + + const ranking: UserRanking = { rating: glicko_player.getRating(), rd: glicko_player.getRd(), vol: glicko_player.getVol(), }; + + player_ranking[variant] = ranking; + return player_ranking; +} + +function updatedGameHistory(player: UserResponse, opponentRating: UserRanking, game_result: string, variant: string): GameResults { + + var player_game_history = player.gameHistory + + if(player_game_history == undefined){ + player_game_history = {} + } + if(player_game_history[variant] == undefined){ + player_game_history[variant] = []; + } + + const new_game_history: GameResult = { + opponentRating: opponentRating, + result: game_result as "Win" | "Loss" | "Tie", + } + + player_game_history[variant].push(new_game_history) + return player_game_history } \ No newline at end of file diff --git a/packages/server/src/users.ts b/packages/server/src/users.ts index 098f9032..0bbbc52c 100644 --- a/packages/server/src/users.ts +++ b/packages/server/src/users.ts @@ -1,12 +1,13 @@ import { getDb } from "./db"; -import { UserResponse, UserRanking } from "@ogfcommunity/variants-shared"; +import { UserResponse, UserRanking, UserRankings, GameResults } from "@ogfcommunity/variants-shared"; import { Collection, WithId, ObjectId } from "mongodb"; import { randomBytes, scrypt } from "node:crypto"; export interface GuestUser extends UserResponse { token: string; login_type: "guest"; - ranking?: UserRanking; + ranking?: UserRankings + gameHistory?: GameResults; } // Not currently used, but the plan is to use LocalStrategy from Password.js @@ -15,12 +16,13 @@ export interface PersistentUser extends UserResponse { username: string; password_hash: string; login_type: "persistent"; - ranking?: UserRanking; + ranking?: UserRankings + gameHistory?: GameResults; } -export async function updateUserRating( +export async function updateUserRanking( user_id: string, - new_ranking: UserRanking, + new_ranking: UserRankings ): Promise { const update_result = await usersCollection().updateOne( { _id: new ObjectId(user_id) }, @@ -31,6 +33,19 @@ export async function updateUserRating( } } +export async function updateUserGameHistory( + user_id: string, + game_results: GameResults, +): Promise { + const update_result = await usersCollection().updateOne( + { _id: new ObjectId(user_id) }, + { $set: { gameHistory: game_results } }, + ); + if (update_result.matchedCount == 0) { + throw new Error("Game history not found"); + } +} + function usersCollection(): Collection { return getDb().db().collection("users"); } @@ -135,7 +150,8 @@ export async function createUserWithUsernameAndPassword( username, password_hash, login_type: "persistent", - ranking: { rating: 1500, rd: 350, vol: 0.06 }, + ranking: {}, + gameHistory: {}, }; const result = await usersCollection().insertOne(user); @@ -191,6 +207,7 @@ function outwardFacingUser( login_type: db_user.login_type, ...(db_user.login_type === "persistent" && { username: db_user.username }), ranking: db_user.ranking, + gameHistory: db_user.gameHistory, }; } diff --git a/packages/shared/src/api_types.ts b/packages/shared/src/api_types.ts index 3fae4319..d775f796 100644 --- a/packages/shared/src/api_types.ts +++ b/packages/shared/src/api_types.ts @@ -5,27 +5,29 @@ import { } from "./time_control/time_control.types"; - - -export interface UserRanking { - rating: number; - rd: number; - vol: number; +export interface GameResults { + [variant: string]: GameResult[] } export interface GameResult { opponentRating: UserRanking; - result: "win" | "loss"; + result: "Win" | "Loss" | "Tie"; } -export interface GameResults { - [variant: string]: GameResult[] +export interface UserRankings { + [variant: string]: UserRanking +} + +export interface UserRanking { + rating: number; + rd: number; + vol: number; } export interface User { username?: string; id: string; - ranking?: UserRanking; + ranking?: UserRankings gameHistory?: GameResults } export interface GameResponse { @@ -41,7 +43,7 @@ export interface UserResponse { id?: string; login_type: "guest" | "persistent"; username?: string; - ranking?: UserRanking; + ranking?: UserRankings gameHistory?: GameResults } From d7d19837c19b45894f6ac9b0253ab62537c5fe99 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Sat, 16 Nov 2024 17:05:44 -0500 Subject: [PATCH 06/16] glicko dependency change --- package.json | 3 --- packages/server/package.json | 1 + yarn.lock | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 856a7876..6ac73184 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,5 @@ "prettier": { "trailingComma": "all", "endOfLine": "lf" - }, - "dependencies": { - "glicko2": "^1.2.1" } } diff --git a/packages/server/package.json b/packages/server/package.json index 506ee4c4..f91eb62a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -22,6 +22,7 @@ "connect-mongo": "^5.0.0", "express": "^4.18.2", "express-session": "^1.17.3", + "glicko2": "^1.2.1", "mongodb": "^5.6.0", "nodemon": "^3.1.0", "passport": "^0.6.0", diff --git a/yarn.lock b/yarn.lock index 191d479e..496dd792 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1094,8 +1094,6 @@ __metadata: "@ogfcommunity/variants-monorepo@workspace:.": version: 0.0.0-use.local resolution: "@ogfcommunity/variants-monorepo@workspace:." - dependencies: - glicko2: ^1.2.1 languageName: unknown linkType: soft @@ -1118,6 +1116,7 @@ __metadata: eslint: ^8.44.0 express: ^4.18.2 express-session: ^1.17.3 + glicko2: ^1.2.1 jest: ^29.7.0 mongodb: ^5.6.0 nodemon: ^3.1.0 From aadae5ac6dd3066a2bb49ffcbec54c22a15156f1 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Sat, 16 Nov 2024 17:08:15 -0500 Subject: [PATCH 07/16] yarn lint fix --- packages/server/src/ratings.ts | 225 ++++++++++++++++++------------- packages/server/src/users.ts | 12 +- packages/shared/src/api_types.ts | 15 +-- 3 files changed, 143 insertions(+), 109 deletions(-) diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index 24743fb4..942029f7 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -1,90 +1,113 @@ import { - GameResponse, - AbstractGame, - UserRanking, - UserRankings, - UserResponse, - GameResult, - GameResults, - } from "@ogfcommunity/variants-shared"; - import { Glicko2, Player } from "glicko2"; - import { updateUserRanking, getUser, updateUserGameHistory } from "./users"; - - const ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); - - const matches: [Player, Player, number][] = []; - - export async function updateRatings( - game: GameResponse, - game_obj: AbstractGame, - ) { - // Rating system currently only for two player games - - const variant = game.variant - const result = game_obj.result[0] - - var glicko_outcome; - var outcome_player_black; - var outcome_player_white; - - if (result === "B") { - glicko_outcome = 1 - outcome_player_black = "Win" - outcome_player_white = "Loss" - } else if (result === "W") { - glicko_outcome = 0 - outcome_player_black = "Loss" - outcome_player_white = "Win" - } else { - glicko_outcome = .5 - outcome_player_black = "Tie" - outcome_player_white = "Tie" - } - - const player_black_id = game.players[0].id; - const player_white_id = game.players[1].id; - - const db_player_black = await getUser(player_black_id); - const db_player_white = await getUser(player_white_id); - - if(db_player_black.ranking == undefined){ - db_player_black.ranking = {} - } - if(db_player_white.ranking == undefined){ - db_player_white.ranking = {} - } - - const new_game_history_player_black = updatedGameHistory(db_player_black, db_player_white.ranking[variant], outcome_player_black, variant); - const new_game_history_player_white = updatedGameHistory(db_player_white, db_player_black.ranking[variant], outcome_player_white, variant); - - await Promise.all([ - updateUserGameHistory(player_black_id, new_game_history_player_black), - updateUserGameHistory(player_white_id, new_game_history_player_white), - ]); - - const glicko_player_black = await getGlickoPlayer(db_player_black.ranking[variant]); - const glicko_player_white = await getGlickoPlayer(db_player_white.ranking[variant]); - - matches.push([glicko_player_black, glicko_player_white, glicko_outcome]); - - ranking.updateRatings(matches); - - const player_black_new_ranking = userRankingFromGlickoPlayer(db_player_black, glicko_player_black, variant); - const player_white_new_ranking = userRankingFromGlickoPlayer(db_player_white, glicko_player_white, variant); - - await Promise.all([ - updateUserRanking(player_black_id, player_black_new_ranking), - updateUserRanking(player_white_id, player_white_new_ranking), - ]); + GameResponse, + AbstractGame, + UserRanking, + UserRankings, + UserResponse, + GameResult, + GameResults, +} from "@ogfcommunity/variants-shared"; +import { Glicko2, Player } from "glicko2"; +import { updateUserRanking, getUser, updateUserGameHistory } from "./users"; + +const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, +}); + +const matches: [Player, Player, number][] = []; + +export async function updateRatings( + game: GameResponse, + game_obj: AbstractGame, +) { + // Rating system currently only for two player games + + const variant = game.variant; + const result = game_obj.result[0]; + + let glicko_outcome; + let outcome_player_black; + let outcome_player_white; + + if (result === "B") { + glicko_outcome = 1; + outcome_player_black = "Win"; + outcome_player_white = "Loss"; + } else if (result === "W") { + glicko_outcome = 0; + outcome_player_black = "Loss"; + outcome_player_white = "Win"; + } else { + glicko_outcome = 0.5; + outcome_player_black = "Tie"; + outcome_player_white = "Tie"; } - -async function getGlickoPlayer(db_player_ranking: UserRanking): Promise { + const player_black_id = game.players[0].id; + const player_white_id = game.players[1].id; + + const db_player_black = await getUser(player_black_id); + const db_player_white = await getUser(player_white_id); + + if (db_player_black.ranking == undefined) { + db_player_black.ranking = {}; + } + if (db_player_white.ranking == undefined) { + db_player_white.ranking = {}; + } + + const new_game_history_player_black = updatedGameHistory( + db_player_black, + db_player_white.ranking[variant], + outcome_player_black, + variant, + ); + const new_game_history_player_white = updatedGameHistory( + db_player_white, + db_player_black.ranking[variant], + outcome_player_white, + variant, + ); + + await Promise.all([ + updateUserGameHistory(player_black_id, new_game_history_player_black), + updateUserGameHistory(player_white_id, new_game_history_player_white), + ]); + + const glicko_player_black = await getGlickoPlayer( + db_player_black.ranking[variant], + ); + const glicko_player_white = await getGlickoPlayer( + db_player_white.ranking[variant], + ); + + matches.push([glicko_player_black, glicko_player_white, glicko_outcome]); + + ranking.updateRatings(matches); + + const player_black_new_ranking = userRankingFromGlickoPlayer( + db_player_black, + glicko_player_black, + variant, + ); + const player_white_new_ranking = userRankingFromGlickoPlayer( + db_player_white, + glicko_player_white, + variant, + ); + + await Promise.all([ + updateUserRanking(player_black_id, player_black_new_ranking), + updateUserRanking(player_white_id, player_white_new_ranking), + ]); +} + +async function getGlickoPlayer( + db_player_ranking: UserRanking, +): Promise { if (db_player_ranking == undefined) { return ranking.makePlayer(1500, 350, 0.06); } @@ -95,8 +118,12 @@ async function getGlickoPlayer(db_player_ranking: UserRanking): Promise ); } -function userRankingFromGlickoPlayer(player: UserResponse, glicko_player: Player, variant: string): UserRankings { - var player_ranking = player.ranking; +function userRankingFromGlickoPlayer( + player: UserResponse, + glicko_player: Player, + variant: string, +): UserRankings { + const player_ranking = player.ranking; const ranking: UserRanking = { rating: glicko_player.getRating(), @@ -108,22 +135,26 @@ function userRankingFromGlickoPlayer(player: UserResponse, glicko_player: Player return player_ranking; } -function updatedGameHistory(player: UserResponse, opponentRating: UserRanking, game_result: string, variant: string): GameResults { - - var player_game_history = player.gameHistory +function updatedGameHistory( + player: UserResponse, + opponentRating: UserRanking, + game_result: string, + variant: string, +): GameResults { + let player_game_history = player.gameHistory; - if(player_game_history == undefined){ - player_game_history = {} + if (player_game_history == undefined) { + player_game_history = {}; } - if(player_game_history[variant] == undefined){ + if (player_game_history[variant] == undefined) { player_game_history[variant] = []; } const new_game_history: GameResult = { opponentRating: opponentRating, result: game_result as "Win" | "Loss" | "Tie", - } - - player_game_history[variant].push(new_game_history) - return player_game_history -} \ No newline at end of file + }; + + player_game_history[variant].push(new_game_history); + return player_game_history; +} diff --git a/packages/server/src/users.ts b/packages/server/src/users.ts index 0bbbc52c..a9ba9ed1 100644 --- a/packages/server/src/users.ts +++ b/packages/server/src/users.ts @@ -1,12 +1,16 @@ import { getDb } from "./db"; -import { UserResponse, UserRanking, UserRankings, GameResults } from "@ogfcommunity/variants-shared"; +import { + UserResponse, + UserRankings, + GameResults, +} from "@ogfcommunity/variants-shared"; import { Collection, WithId, ObjectId } from "mongodb"; import { randomBytes, scrypt } from "node:crypto"; export interface GuestUser extends UserResponse { token: string; login_type: "guest"; - ranking?: UserRankings + ranking?: UserRankings; gameHistory?: GameResults; } @@ -16,13 +20,13 @@ export interface PersistentUser extends UserResponse { username: string; password_hash: string; login_type: "persistent"; - ranking?: UserRankings + ranking?: UserRankings; gameHistory?: GameResults; } export async function updateUserRanking( user_id: string, - new_ranking: UserRankings + new_ranking: UserRankings, ): Promise { const update_result = await usersCollection().updateOne( { _id: new ObjectId(user_id) }, diff --git a/packages/shared/src/api_types.ts b/packages/shared/src/api_types.ts index d775f796..f2a843ff 100644 --- a/packages/shared/src/api_types.ts +++ b/packages/shared/src/api_types.ts @@ -4,9 +4,8 @@ import { ITimeControlConfig, } from "./time_control/time_control.types"; - -export interface GameResults { - [variant: string]: GameResult[] +export interface GameResults { + [variant: string]: GameResult[]; } export interface GameResult { @@ -15,7 +14,7 @@ export interface GameResult { } export interface UserRankings { - [variant: string]: UserRanking + [variant: string]: UserRanking; } export interface UserRanking { @@ -27,8 +26,8 @@ export interface UserRanking { export interface User { username?: string; id: string; - ranking?: UserRankings - gameHistory?: GameResults + ranking?: UserRankings; + gameHistory?: GameResults; } export interface GameResponse { id: string; @@ -43,8 +42,8 @@ export interface UserResponse { id?: string; login_type: "guest" | "persistent"; username?: string; - ranking?: UserRankings - gameHistory?: GameResults + ranking?: UserRankings; + gameHistory?: GameResults; } export type GamesFilter = { From 7ac2667bfbd7f9bc0609d25506edb24cf2b88f67 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Sat, 16 Nov 2024 17:34:00 -0500 Subject: [PATCH 08/16] fixed small bug encountered when player resigns --- packages/server/src/games.ts | 4 ++-- packages/server/src/ratings.ts | 43 +++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/packages/server/src/games.ts b/packages/server/src/games.ts index ede38df7..7a7f04db 100644 --- a/packages/server/src/games.ts +++ b/packages/server/src/games.ts @@ -200,8 +200,8 @@ export async function handleMoveAndTime( emitGame(game.id, game.players?.length ?? 0, game_obj, timeControl); - if (game_obj.phase == "gameover" && game.variant == "quantum") { - // user ranking value is only for quantum right now + if (game_obj.phase == "gameover" && game.players.length == 2) { + // user ranking value is only for two player games await updateRatings(game, game_obj); } diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index 942029f7..4e1c509c 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -23,29 +23,44 @@ export async function updateRatings( game: GameResponse, game_obj: AbstractGame, ) { - // Rating system currently only for two player games - const variant = game.variant; - const result = game_obj.result[0]; let glicko_outcome; let outcome_player_black; let outcome_player_white; - if (result === "B") { - glicko_outcome = 1; - outcome_player_black = "Win"; - outcome_player_white = "Loss"; - } else if (result === "W") { - glicko_outcome = 0; - outcome_player_black = "Loss"; - outcome_player_white = "Win"; + if(game_obj.result.endsWith("R")) { + + if (game_obj.result[0] === "B") { + glicko_outcome = 0; + outcome_player_black = "Loss"; + outcome_player_white = "Win"; + } else if (game_obj.result[0] === "W") { + glicko_outcome = 1; + outcome_player_black = "Win"; + outcome_player_white = "Loss"; + } + } else { - glicko_outcome = 0.5; - outcome_player_black = "Tie"; - outcome_player_white = "Tie"; + + if (game_obj.result[0] === "B") { + glicko_outcome = 1; + outcome_player_black = "Win"; + outcome_player_white = "Loss"; + } else if (game_obj.result[0] === "W") { + glicko_outcome = 0; + outcome_player_black = "Loss"; + outcome_player_white = "Win"; + } else { + glicko_outcome = 0.5; + outcome_player_black = "Tie"; + outcome_player_white = "Tie"; + } + } + + const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; From 22d2a0b82bf408802b7b76a908e5ca31644a7189 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Sat, 16 Nov 2024 17:36:54 -0500 Subject: [PATCH 09/16] yarn lint fix --- packages/server/src/ratings.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index 4e1c509c..65ce3555 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -29,8 +29,7 @@ export async function updateRatings( let outcome_player_black; let outcome_player_white; - if(game_obj.result.endsWith("R")) { - + if (game_obj.result.endsWith("R")) { if (game_obj.result[0] === "B") { glicko_outcome = 0; outcome_player_black = "Loss"; @@ -40,9 +39,7 @@ export async function updateRatings( outcome_player_black = "Win"; outcome_player_white = "Loss"; } - } else { - if (game_obj.result[0] === "B") { glicko_outcome = 1; outcome_player_black = "Win"; @@ -56,11 +53,8 @@ export async function updateRatings( outcome_player_black = "Tie"; outcome_player_white = "Tie"; } - } - - const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; From 2156503543df2fbea4f65e014bcfa1097e8db138 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Mon, 18 Nov 2024 14:59:46 -0500 Subject: [PATCH 10/16] rating system fixes --- packages/server/src/games.ts | 6 +- packages/server/src/ratings.ts | 139 +++++++++++-------------------- packages/server/src/users.ts | 20 +---- packages/shared/src/api_types.ts | 11 --- 4 files changed, 54 insertions(+), 122 deletions(-) diff --git a/packages/server/src/games.ts b/packages/server/src/games.ts index 7a7f04db..77ce3e83 100644 --- a/packages/server/src/games.ts +++ b/packages/server/src/games.ts @@ -21,7 +21,7 @@ import { } from "./time-control/time-control"; import { timeControlHandlerMap } from "./time-control/time-handler-map"; import { Clock } from "./time-control/clock"; -import { updateRatings } from "./ratings"; +import { updateRatings, supportsRatings } from "./ratings"; export function gamesCollection() { return getDb().db().collection("games"); @@ -200,14 +200,14 @@ export async function handleMoveAndTime( emitGame(game.id, game.players?.length ?? 0, game_obj, timeControl); - if (game_obj.phase == "gameover" && game.players.length == 2) { - // user ranking value is only for two player games + if (game_obj.phase == "gameover" && game.players.length == 2 && supportsRatings(game.variant)) { await updateRatings(game, game_obj); } return game; } + function emitGame( game_id: string, num_players: number, diff --git a/packages/server/src/ratings.ts b/packages/server/src/ratings.ts index 65ce3555..d94645b8 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/ratings.ts @@ -4,93 +4,59 @@ import { UserRanking, UserRankings, UserResponse, - GameResult, - GameResults, } from "@ogfcommunity/variants-shared"; import { Glicko2, Player } from "glicko2"; -import { updateUserRanking, getUser, updateUserGameHistory } from "./users"; +import { updateUserRanking, getUser } from "./users"; -const ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, -}); -const matches: [Player, Player, number][] = []; export async function updateRatings( game: GameResponse, game_obj: AbstractGame, ) { + + const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + + const matches: [Player, Player, number][] = []; + const variant = game.variant; let glicko_outcome; - let outcome_player_black; - let outcome_player_white; - - if (game_obj.result.endsWith("R")) { - if (game_obj.result[0] === "B") { - glicko_outcome = 0; - outcome_player_black = "Loss"; - outcome_player_white = "Win"; - } else if (game_obj.result[0] === "W") { - glicko_outcome = 1; - outcome_player_black = "Win"; - outcome_player_white = "Loss"; - } + let outcome_player_black: "Win" | "Loss" | "Tie" + let outcome_player_white: "Win" | "Loss" | "Tie" + + if (game_obj.result[0] === "B") { + glicko_outcome = 1; + outcome_player_black = "Win"; + outcome_player_white = "Loss"; + } else if (game_obj.result[0] === "W") { + glicko_outcome = 0; + outcome_player_black = "Loss"; + outcome_player_white = "Win"; } else { - if (game_obj.result[0] === "B") { - glicko_outcome = 1; - outcome_player_black = "Win"; - outcome_player_white = "Loss"; - } else if (game_obj.result[0] === "W") { - glicko_outcome = 0; - outcome_player_black = "Loss"; - outcome_player_white = "Win"; - } else { - glicko_outcome = 0.5; - outcome_player_black = "Tie"; - outcome_player_white = "Tie"; - } + glicko_outcome = 0.5; + outcome_player_black = "Tie"; + outcome_player_white = "Tie"; } - + const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; const db_player_black = await getUser(player_black_id); const db_player_white = await getUser(player_white_id); - if (db_player_black.ranking == undefined) { - db_player_black.ranking = {}; - } - if (db_player_white.ranking == undefined) { - db_player_white.ranking = {}; - } - - const new_game_history_player_black = updatedGameHistory( - db_player_black, - db_player_white.ranking[variant], - outcome_player_black, - variant, - ); - const new_game_history_player_white = updatedGameHistory( - db_player_white, - db_player_black.ranking[variant], - outcome_player_white, - variant, - ); - - await Promise.all([ - updateUserGameHistory(player_black_id, new_game_history_player_black), - updateUserGameHistory(player_white_id, new_game_history_player_white), - ]); - - const glicko_player_black = await getGlickoPlayer( + const glicko_player_black = getGlickoPlayer( db_player_black.ranking[variant], + ranking ); - const glicko_player_white = await getGlickoPlayer( + const glicko_player_white = getGlickoPlayer( db_player_white.ranking[variant], + ranking ); matches.push([glicko_player_black, glicko_player_white, glicko_outcome]); @@ -114,9 +80,10 @@ export async function updateRatings( ]); } -async function getGlickoPlayer( +function getGlickoPlayer( db_player_ranking: UserRanking, -): Promise { + ranking: Glicko2 +): Player { if (db_player_ranking == undefined) { return ranking.makePlayer(1500, 350, 0.06); } @@ -144,26 +111,20 @@ function userRankingFromGlickoPlayer( return player_ranking; } -function updatedGameHistory( - player: UserResponse, - opponentRating: UserRanking, - game_result: string, - variant: string, -): GameResults { - let player_game_history = player.gameHistory; - - if (player_game_history == undefined) { - player_game_history = {}; - } - if (player_game_history[variant] == undefined) { - player_game_history[variant] = []; - } - - const new_game_history: GameResult = { - opponentRating: opponentRating, - result: game_result as "Win" | "Loss" | "Tie", - }; - - player_game_history[variant].push(new_game_history); - return player_game_history; -} +export function supportsRatings(variant: string) { + return [ + "baduk", + "phantom", + "capture", + "tetris", + "pyramid", + "thue-morse", + "freeze", + "fractional", + "keima", + "one color", + "drift", + "quantum", + "sfractional" + ].includes(variant); +} \ No newline at end of file diff --git a/packages/server/src/users.ts b/packages/server/src/users.ts index a9ba9ed1..fc932d91 100644 --- a/packages/server/src/users.ts +++ b/packages/server/src/users.ts @@ -2,7 +2,6 @@ import { getDb } from "./db"; import { UserResponse, UserRankings, - GameResults, } from "@ogfcommunity/variants-shared"; import { Collection, WithId, ObjectId } from "mongodb"; import { randomBytes, scrypt } from "node:crypto"; @@ -11,7 +10,6 @@ export interface GuestUser extends UserResponse { token: string; login_type: "guest"; ranking?: UserRankings; - gameHistory?: GameResults; } // Not currently used, but the plan is to use LocalStrategy from Password.js @@ -21,7 +19,6 @@ export interface PersistentUser extends UserResponse { password_hash: string; login_type: "persistent"; ranking?: UserRankings; - gameHistory?: GameResults; } export async function updateUserRanking( @@ -37,19 +34,6 @@ export async function updateUserRanking( } } -export async function updateUserGameHistory( - user_id: string, - game_results: GameResults, -): Promise { - const update_result = await usersCollection().updateOne( - { _id: new ObjectId(user_id) }, - { $set: { gameHistory: game_results } }, - ); - if (update_result.matchedCount == 0) { - throw new Error("Game history not found"); - } -} - function usersCollection(): Collection { return getDb().db().collection("users"); } @@ -155,7 +139,6 @@ export async function createUserWithUsernameAndPassword( password_hash, login_type: "persistent", ranking: {}, - gameHistory: {}, }; const result = await usersCollection().insertOne(user); @@ -210,8 +193,7 @@ function outwardFacingUser( id: db_user._id.toString(), login_type: db_user.login_type, ...(db_user.login_type === "persistent" && { username: db_user.username }), - ranking: db_user.ranking, - gameHistory: db_user.gameHistory, + ranking: db_user.ranking || {}, }; } diff --git a/packages/shared/src/api_types.ts b/packages/shared/src/api_types.ts index f2a843ff..cf586ac2 100644 --- a/packages/shared/src/api_types.ts +++ b/packages/shared/src/api_types.ts @@ -4,15 +4,6 @@ import { ITimeControlConfig, } from "./time_control/time_control.types"; -export interface GameResults { - [variant: string]: GameResult[]; -} - -export interface GameResult { - opponentRating: UserRanking; - result: "Win" | "Loss" | "Tie"; -} - export interface UserRankings { [variant: string]: UserRanking; } @@ -27,7 +18,6 @@ export interface User { username?: string; id: string; ranking?: UserRankings; - gameHistory?: GameResults; } export interface GameResponse { id: string; @@ -43,7 +33,6 @@ export interface UserResponse { login_type: "guest" | "persistent"; username?: string; ranking?: UserRankings; - gameHistory?: GameResults; } export type GamesFilter = { From c66c77b1fbedc208a17022d05f6b8c7b16d38350 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Mon, 25 Nov 2024 15:06:53 -0500 Subject: [PATCH 11/16] added unit tests for rating system --- packages/server/src/games.ts | 2 +- .../src/rating/__tests__/rating.test.ts | 155 ++++++++++++++++++ .../src/{ratings.ts => rating/rating.ts} | 39 ++--- 3 files changed, 173 insertions(+), 23 deletions(-) create mode 100644 packages/server/src/rating/__tests__/rating.test.ts rename packages/server/src/{ratings.ts => rating/rating.ts} (79%) diff --git a/packages/server/src/games.ts b/packages/server/src/games.ts index 77ce3e83..919ecf0d 100644 --- a/packages/server/src/games.ts +++ b/packages/server/src/games.ts @@ -21,7 +21,7 @@ import { } from "./time-control/time-control"; import { timeControlHandlerMap } from "./time-control/time-handler-map"; import { Clock } from "./time-control/clock"; -import { updateRatings, supportsRatings } from "./ratings"; +import { updateRatings, supportsRatings } from "./rating/rating"; export function gamesCollection() { return getDb().db().collection("games"); diff --git a/packages/server/src/rating/__tests__/rating.test.ts b/packages/server/src/rating/__tests__/rating.test.ts new file mode 100644 index 00000000..ba9e52c7 --- /dev/null +++ b/packages/server/src/rating/__tests__/rating.test.ts @@ -0,0 +1,155 @@ +import { getGlickoPlayer, userRankingFromGlickoPlayer, supportsRatings, getGlickoResult } from '../rating' +import { Glicko2 } from 'glicko2'; +import { UserRanking, UserResponse } from "@ogfcommunity/variants-shared"; + +test("supportsRatings", () => { + const supportsRating = supportsRatings("chess"); + expect(supportsRating).toEqual(false); + + const new_supportsRating = supportsRatings("quantum"); + expect(new_supportsRating).toEqual(true); +}); + +test("getGlickoPlayer - with typical player value", () => { + const db_player = + { + id: '672844d2254d76r4387b8488', + login_type: 'persistent', + username: 'testUser', + ranking: { + quantum: { + rating: 1337.6891060937023, + rd: 290.31896371798047, + vol: 0.05999967537233814 + }, + baduk: { + rating: 1623.3256984571523, + rd: 290.31896371798047, + vol: 0.05999967537233814 + }, + phantom: { + rating: 1762.3697989563215, + rd: 290.31896371798047, + vol: 0.05999967537233814 + } + } + } + + const db_player_ranking_quantum = db_player.ranking["quantum"] + + const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + + const new_ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + + expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual(new_ranking.makePlayer(db_player_ranking_quantum.rating, db_player_ranking_quantum.rd, db_player_ranking_quantum.vol)); +}); + +test("getGlickoPlayer - when player does not have any rating", () => { + + const db_player_ranking_quantum:undefined = undefined + + const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + + const new_ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + + expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual(new_ranking.makePlayer(1500, 350, 0.06)); + }); + +// do we need to run tests for very simple functions? +test("getGlickoResult", () => { + + expect(getGlickoResult("B")).toBe(1); + expect(getGlickoResult("W")).toBe(0); + expect(getGlickoResult("T")).toBe(0.5); + +}); + +test("userRankingFromGlickoPlayer", () => { + + const variant = "quantum"; + const glicko_ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + const db_player_initial: UserResponse = + { + id: '672844d2254d76r4387b8488', + login_type: 'persistent', + username: 'testUser', + ranking: { + quantum: { + rating: 1337.6891060937023, + rd: 290.31896371798047, + vol: 0.05999967537233814 + }, + baduk: { + rating: 1623.3256984571523, + rd: 290.31896371798047, + vol: 0.05999967537233814 + }, + phantom: { + rating: 1762.3697989563215, + rd: 290.31896371798047, + vol: 0.05999967537233814 + } + } + } + + const new_quantum_ranking: UserRanking = + { + rating: 1667.6891060937023, + rd: 295.31896371798047, + vol: 0.06999967537233814 + } + + const glicko_player = getGlickoPlayer(new_quantum_ranking, glicko_ranking); + + const expected_player: UserResponse = + { + id: '672844d2254d76r4387b8488', + login_type: 'persistent', + username: 'testUser', + ranking: { + quantum: { + rating: 1667.6891060937023, + rd: 295.31896371798047, + vol: 0.06999967537233814 + }, + baduk: { + rating: 1623.3256984571523, + rd: 290.31896371798047, + vol: 0.05999967537233814 + }, + phantom: { + rating: 1762.3697989563215, + rd: 290.31896371798047, + vol: 0.05999967537233814 + } + } + } + + expect(userRankingFromGlickoPlayer(db_player_initial, glicko_player, variant)).toEqual(expected_player.ranking) + +}); diff --git a/packages/server/src/ratings.ts b/packages/server/src/rating/rating.ts similarity index 79% rename from packages/server/src/ratings.ts rename to packages/server/src/rating/rating.ts index d94645b8..8bdcf2c9 100644 --- a/packages/server/src/ratings.ts +++ b/packages/server/src/rating/rating.ts @@ -6,8 +6,7 @@ import { UserResponse, } from "@ogfcommunity/variants-shared"; import { Glicko2, Player } from "glicko2"; -import { updateUserRanking, getUser } from "./users"; - +import { updateUserRanking, getUser } from "../users"; export async function updateRatings( @@ -26,23 +25,7 @@ export async function updateRatings( const variant = game.variant; - let glicko_outcome; - let outcome_player_black: "Win" | "Loss" | "Tie" - let outcome_player_white: "Win" | "Loss" | "Tie" - - if (game_obj.result[0] === "B") { - glicko_outcome = 1; - outcome_player_black = "Win"; - outcome_player_white = "Loss"; - } else if (game_obj.result[0] === "W") { - glicko_outcome = 0; - outcome_player_black = "Loss"; - outcome_player_white = "Win"; - } else { - glicko_outcome = 0.5; - outcome_player_black = "Tie"; - outcome_player_white = "Tie"; - } + const glicko_outcome = getGlickoResult(game_obj.result[0]); const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; @@ -80,7 +63,19 @@ export async function updateRatings( ]); } -function getGlickoPlayer( +export function getGlickoResult(game_result: string): number { + + if (game_result === "B") { + return 1 + } + if (game_result === "W") { + return 0 + } + return 0.5 + +} + +export function getGlickoPlayer( db_player_ranking: UserRanking, ranking: Glicko2 ): Player { @@ -94,12 +89,11 @@ function getGlickoPlayer( ); } -function userRankingFromGlickoPlayer( +export function userRankingFromGlickoPlayer( player: UserResponse, glicko_player: Player, variant: string, ): UserRankings { - const player_ranking = player.ranking; const ranking: UserRanking = { rating: glicko_player.getRating(), @@ -107,6 +101,7 @@ function userRankingFromGlickoPlayer( vol: glicko_player.getVol(), }; + const player_ranking = player.ranking; player_ranking[variant] = ranking; return player_ranking; } From 6331c0ddf8b979b1b7c407b2b1305d732c956088 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Mon, 25 Nov 2024 15:15:21 -0500 Subject: [PATCH 12/16] fixed issue of rating numbers too long for tests --- .../src/rating/__tests__/rating.test.ts | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/server/src/rating/__tests__/rating.test.ts b/packages/server/src/rating/__tests__/rating.test.ts index ba9e52c7..a3e2638a 100644 --- a/packages/server/src/rating/__tests__/rating.test.ts +++ b/packages/server/src/rating/__tests__/rating.test.ts @@ -18,19 +18,19 @@ test("getGlickoPlayer - with typical player value", () => { username: 'testUser', ranking: { quantum: { - rating: 1337.6891060937023, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1337, + rd: 290, + vol: 0.0599 }, baduk: { - rating: 1623.3256984571523, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1623, + rd: 290, + vol: 0.0599 }, phantom: { - rating: 1762.3697989563215, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1762, + rd: 290, + vol: 0.0599 } } } @@ -100,28 +100,28 @@ test("userRankingFromGlickoPlayer", () => { username: 'testUser', ranking: { quantum: { - rating: 1337.6891060937023, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1337, + rd: 290, + vol: 0.0599 }, baduk: { - rating: 1623.3256984571523, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1623, + rd: 290, + vol: 0.0599 }, phantom: { - rating: 1762.3697989563215, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1762, + rd: 290, + vol: 0.0599 } } } const new_quantum_ranking: UserRanking = { - rating: 1667.6891060937023, - rd: 295.31896371798047, - vol: 0.06999967537233814 + rating: 1667, + rd: 295, + vol: 0.0699 } const glicko_player = getGlickoPlayer(new_quantum_ranking, glicko_ranking); @@ -133,19 +133,19 @@ test("userRankingFromGlickoPlayer", () => { username: 'testUser', ranking: { quantum: { - rating: 1667.6891060937023, - rd: 295.31896371798047, - vol: 0.06999967537233814 + rating: 1667, + rd: 295, + vol: 0.0699 }, baduk: { - rating: 1623.3256984571523, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1623, + rd: 290, + vol: 0.0599 }, phantom: { - rating: 1762.3697989563215, - rd: 290.31896371798047, - vol: 0.05999967537233814 + rating: 1762, + rd: 290, + vol: 0.0599 } } } From 67d4378af6752079a5478c8efd79af2b6c62abc2 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Mon, 25 Nov 2024 15:18:02 -0500 Subject: [PATCH 13/16] yarn lint changes --- packages/server/src/games.ts | 7 +- .../src/rating/__tests__/rating.test.ts | 281 +++++++++--------- packages/server/src/rating/rating.ts | 27 +- packages/server/src/users.ts | 5 +- 4 files changed, 161 insertions(+), 159 deletions(-) diff --git a/packages/server/src/games.ts b/packages/server/src/games.ts index 919ecf0d..2918d493 100644 --- a/packages/server/src/games.ts +++ b/packages/server/src/games.ts @@ -200,14 +200,17 @@ export async function handleMoveAndTime( emitGame(game.id, game.players?.length ?? 0, game_obj, timeControl); - if (game_obj.phase == "gameover" && game.players.length == 2 && supportsRatings(game.variant)) { + if ( + game_obj.phase == "gameover" && + game.players.length == 2 && + supportsRatings(game.variant) + ) { await updateRatings(game, game_obj); } return game; } - function emitGame( game_id: string, num_players: number, diff --git a/packages/server/src/rating/__tests__/rating.test.ts b/packages/server/src/rating/__tests__/rating.test.ts index a3e2638a..a699f6d3 100644 --- a/packages/server/src/rating/__tests__/rating.test.ts +++ b/packages/server/src/rating/__tests__/rating.test.ts @@ -1,155 +1,162 @@ -import { getGlickoPlayer, userRankingFromGlickoPlayer, supportsRatings, getGlickoResult } from '../rating' -import { Glicko2 } from 'glicko2'; -import { UserRanking, UserResponse } from "@ogfcommunity/variants-shared"; - -test("supportsRatings", () => { +import { + getGlickoPlayer, + userRankingFromGlickoPlayer, + supportsRatings, + getGlickoResult, + } from "../rating"; + import { Glicko2 } from "glicko2"; + import { UserRanking, UserResponse } from "@ogfcommunity/variants-shared"; + + test("supportsRatings", () => { const supportsRating = supportsRatings("chess"); expect(supportsRating).toEqual(false); - + const new_supportsRating = supportsRatings("quantum"); expect(new_supportsRating).toEqual(true); -}); - -test("getGlickoPlayer - with typical player value", () => { - const db_player = - { - id: '672844d2254d76r4387b8488', - login_type: 'persistent', - username: 'testUser', - ranking: { - quantum: { - rating: 1337, - rd: 290, - vol: 0.0599 - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599 - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599 - } - } - } - - const db_player_ranking_quantum = db_player.ranking["quantum"] - - const ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, + }); + + test("getGlickoPlayer - with typical player value", () => { + const db_player = { + id: "672844d2254d76r4387b8488", + login_type: "persistent", + username: "testUser", + ranking: { + quantum: { + rating: 1337, + rd: 290, + vol: 0.0599, + }, + baduk: { + rating: 1623, + rd: 290, + vol: 0.0599, + }, + phantom: { + rating: 1762, + rd: 290, + vol: 0.0599, + }, + }, + }; + + const db_player_ranking_quantum = db_player.ranking["quantum"]; + + const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - + const new_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - - expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual(new_ranking.makePlayer(db_player_ranking_quantum.rating, db_player_ranking_quantum.rd, db_player_ranking_quantum.vol)); -}); - -test("getGlickoPlayer - when player does not have any rating", () => { - - const db_player_ranking_quantum:undefined = undefined - + + expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( + new_ranking.makePlayer( + db_player_ranking_quantum.rating, + db_player_ranking_quantum.rd, + db_player_ranking_quantum.vol, + ), + ); + }); + + test("getGlickoPlayer - when player does not have any rating", () => { + const db_player_ranking_quantum: undefined = undefined; + const ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - + const new_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - - expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual(new_ranking.makePlayer(1500, 350, 0.06)); - }); - -// do we need to run tests for very simple functions? -test("getGlickoResult", () => { - + + expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( + new_ranking.makePlayer(1500, 350, 0.06), + ); + }); + + // do we need to run tests for very simple functions? + test("getGlickoResult", () => { expect(getGlickoResult("B")).toBe(1); expect(getGlickoResult("W")).toBe(0); expect(getGlickoResult("T")).toBe(0.5); - -}); - -test("userRankingFromGlickoPlayer", () => { - + }); + + test("userRankingFromGlickoPlayer", () => { const variant = "quantum"; const glicko_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - const db_player_initial: UserResponse = - { - id: '672844d2254d76r4387b8488', - login_type: 'persistent', - username: 'testUser', - ranking: { - quantum: { - rating: 1337, - rd: 290, - vol: 0.0599 - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599 - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599 - } - } - } - - const new_quantum_ranking: UserRanking = - { - rating: 1667, - rd: 295, - vol: 0.0699 - } - + const db_player_initial: UserResponse = { + id: "672844d2254d76r4387b8488", + login_type: "persistent", + username: "testUser", + ranking: { + quantum: { + rating: 1337, + rd: 290, + vol: 0.0599, + }, + baduk: { + rating: 1623, + rd: 290, + vol: 0.0599, + }, + phantom: { + rating: 1762, + rd: 290, + vol: 0.0599, + }, + }, + }; + + const new_quantum_ranking: UserRanking = { + rating: 1667, + rd: 295, + vol: 0.0699, + }; + const glicko_player = getGlickoPlayer(new_quantum_ranking, glicko_ranking); - - const expected_player: UserResponse = - { - id: '672844d2254d76r4387b8488', - login_type: 'persistent', - username: 'testUser', - ranking: { - quantum: { - rating: 1667, - rd: 295, - vol: 0.0699 - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599 - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599 - } - } - } - - expect(userRankingFromGlickoPlayer(db_player_initial, glicko_player, variant)).toEqual(expected_player.ranking) - -}); + + const expected_player: UserResponse = { + id: "672844d2254d76r4387b8488", + login_type: "persistent", + username: "testUser", + ranking: { + quantum: { + rating: 1667, + rd: 295, + vol: 0.0699, + }, + baduk: { + rating: 1623, + rd: 290, + vol: 0.0599, + }, + phantom: { + rating: 1762, + rd: 290, + vol: 0.0599, + }, + }, + }; + + expect( + userRankingFromGlickoPlayer(db_player_initial, glicko_player, variant), + ).toEqual(expected_player.ranking); + }); + \ No newline at end of file diff --git a/packages/server/src/rating/rating.ts b/packages/server/src/rating/rating.ts index 8bdcf2c9..afce5da1 100644 --- a/packages/server/src/rating/rating.ts +++ b/packages/server/src/rating/rating.ts @@ -8,12 +8,10 @@ import { import { Glicko2, Player } from "glicko2"; import { updateUserRanking, getUser } from "../users"; - export async function updateRatings( game: GameResponse, game_obj: AbstractGame, ) { - const ranking = new Glicko2({ tau: 0.5, rating: 1500, @@ -26,7 +24,7 @@ export async function updateRatings( const variant = game.variant; const glicko_outcome = getGlickoResult(game_obj.result[0]); - + const player_black_id = game.players[0].id; const player_white_id = game.players[1].id; @@ -35,11 +33,11 @@ export async function updateRatings( const glicko_player_black = getGlickoPlayer( db_player_black.ranking[variant], - ranking + ranking, ); const glicko_player_white = getGlickoPlayer( db_player_white.ranking[variant], - ranking + ranking, ); matches.push([glicko_player_black, glicko_player_white, glicko_outcome]); @@ -64,20 +62,18 @@ export async function updateRatings( } export function getGlickoResult(game_result: string): number { - - if (game_result === "B") { - return 1 + if (game_result === "B") { + return 1; } - if (game_result === "W") { - return 0 + if (game_result === "W") { + return 0; } - return 0.5 - + return 0.5; } export function getGlickoPlayer( db_player_ranking: UserRanking, - ranking: Glicko2 + ranking: Glicko2, ): Player { if (db_player_ranking == undefined) { return ranking.makePlayer(1500, 350, 0.06); @@ -94,7 +90,6 @@ export function userRankingFromGlickoPlayer( glicko_player: Player, variant: string, ): UserRankings { - const ranking: UserRanking = { rating: glicko_player.getRating(), rd: glicko_player.getRd(), @@ -120,6 +115,6 @@ export function supportsRatings(variant: string) { "one color", "drift", "quantum", - "sfractional" + "sfractional", ].includes(variant); -} \ No newline at end of file +} diff --git a/packages/server/src/users.ts b/packages/server/src/users.ts index fc932d91..d251ccfd 100644 --- a/packages/server/src/users.ts +++ b/packages/server/src/users.ts @@ -1,8 +1,5 @@ import { getDb } from "./db"; -import { - UserResponse, - UserRankings, -} from "@ogfcommunity/variants-shared"; +import { UserResponse, UserRankings } from "@ogfcommunity/variants-shared"; import { Collection, WithId, ObjectId } from "mongodb"; import { randomBytes, scrypt } from "node:crypto"; From 17783ec6798164d1dd0bd8f90c94d012884a1ee4 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Mon, 25 Nov 2024 15:28:30 -0500 Subject: [PATCH 14/16] yarn lint fix for rating test file --- .../src/rating/__tests__/rating.test.ts | 309 +++++++++--------- 1 file changed, 154 insertions(+), 155 deletions(-) diff --git a/packages/server/src/rating/__tests__/rating.test.ts b/packages/server/src/rating/__tests__/rating.test.ts index a699f6d3..78a65f81 100644 --- a/packages/server/src/rating/__tests__/rating.test.ts +++ b/packages/server/src/rating/__tests__/rating.test.ts @@ -1,162 +1,161 @@ import { - getGlickoPlayer, - userRankingFromGlickoPlayer, - supportsRatings, - getGlickoResult, - } from "../rating"; - import { Glicko2 } from "glicko2"; - import { UserRanking, UserResponse } from "@ogfcommunity/variants-shared"; - - test("supportsRatings", () => { - const supportsRating = supportsRatings("chess"); - expect(supportsRating).toEqual(false); - - const new_supportsRating = supportsRatings("quantum"); - expect(new_supportsRating).toEqual(true); - }); - - test("getGlickoPlayer - with typical player value", () => { - const db_player = { - id: "672844d2254d76r4387b8488", - login_type: "persistent", - username: "testUser", - ranking: { - quantum: { - rating: 1337, - rd: 290, - vol: 0.0599, - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599, - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599, - }, + getGlickoPlayer, + userRankingFromGlickoPlayer, + supportsRatings, + getGlickoResult, +} from "../rating"; +import { Glicko2 } from "glicko2"; +import { UserRanking, UserResponse } from "@ogfcommunity/variants-shared"; + +test("supportsRatings", () => { + const supportsRating = supportsRatings("chess"); + expect(supportsRating).toEqual(false); + + const new_supportsRating = supportsRatings("quantum"); + expect(new_supportsRating).toEqual(true); +}); + +test("getGlickoPlayer - with typical player value", () => { + const db_player = { + id: "672844d2254d76r4387b8488", + login_type: "persistent", + username: "testUser", + ranking: { + quantum: { + rating: 1337, + rd: 290, + vol: 0.0599, + }, + baduk: { + rating: 1623, + rd: 290, + vol: 0.0599, + }, + phantom: { + rating: 1762, + rd: 290, + vol: 0.0599, }, - }; - - const db_player_ranking_quantum = db_player.ranking["quantum"]; - - const ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); - - const new_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); - - expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( - new_ranking.makePlayer( - db_player_ranking_quantum.rating, - db_player_ranking_quantum.rd, - db_player_ranking_quantum.vol, - ), - ); + }, + }; + + const db_player_ranking_quantum = db_player.ranking["quantum"]; + + const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + + const new_ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, + }); + + expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( + new_ranking.makePlayer( + db_player_ranking_quantum.rating, + db_player_ranking_quantum.rd, + db_player_ranking_quantum.vol, + ), + ); +}); + +test("getGlickoPlayer - when player does not have any rating", () => { + const db_player_ranking_quantum: undefined = undefined; + + const ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - - test("getGlickoPlayer - when player does not have any rating", () => { - const db_player_ranking_quantum: undefined = undefined; - - const ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); - - const new_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); - - expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( - new_ranking.makePlayer(1500, 350, 0.06), - ); + + const new_ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - - // do we need to run tests for very simple functions? - test("getGlickoResult", () => { - expect(getGlickoResult("B")).toBe(1); - expect(getGlickoResult("W")).toBe(0); - expect(getGlickoResult("T")).toBe(0.5); + + expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( + new_ranking.makePlayer(1500, 350, 0.06), + ); +}); + +// do we need to run tests for very simple functions? +test("getGlickoResult", () => { + expect(getGlickoResult("B")).toBe(1); + expect(getGlickoResult("W")).toBe(0); + expect(getGlickoResult("T")).toBe(0.5); +}); + +test("userRankingFromGlickoPlayer", () => { + const variant = "quantum"; + const glicko_ranking = new Glicko2({ + tau: 0.5, + rating: 1500, + rd: 350, + vol: 0.06, }); - - test("userRankingFromGlickoPlayer", () => { - const variant = "quantum"; - const glicko_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); - const db_player_initial: UserResponse = { - id: "672844d2254d76r4387b8488", - login_type: "persistent", - username: "testUser", - ranking: { - quantum: { - rating: 1337, - rd: 290, - vol: 0.0599, - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599, - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599, - }, + const db_player_initial: UserResponse = { + id: "672844d2254d76r4387b8488", + login_type: "persistent", + username: "testUser", + ranking: { + quantum: { + rating: 1337, + rd: 290, + vol: 0.0599, }, - }; - - const new_quantum_ranking: UserRanking = { - rating: 1667, - rd: 295, - vol: 0.0699, - }; - - const glicko_player = getGlickoPlayer(new_quantum_ranking, glicko_ranking); - - const expected_player: UserResponse = { - id: "672844d2254d76r4387b8488", - login_type: "persistent", - username: "testUser", - ranking: { - quantum: { - rating: 1667, - rd: 295, - vol: 0.0699, - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599, - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599, - }, + baduk: { + rating: 1623, + rd: 290, + vol: 0.0599, }, - }; - - expect( - userRankingFromGlickoPlayer(db_player_initial, glicko_player, variant), - ).toEqual(expected_player.ranking); - }); - \ No newline at end of file + phantom: { + rating: 1762, + rd: 290, + vol: 0.0599, + }, + }, + }; + + const new_quantum_ranking: UserRanking = { + rating: 1667, + rd: 295, + vol: 0.0699, + }; + + const glicko_player = getGlickoPlayer(new_quantum_ranking, glicko_ranking); + + const expected_player: UserResponse = { + id: "672844d2254d76r4387b8488", + login_type: "persistent", + username: "testUser", + ranking: { + quantum: { + rating: 1667, + rd: 295, + vol: 0.0699, + }, + baduk: { + rating: 1623, + rd: 290, + vol: 0.0599, + }, + phantom: { + rating: 1762, + rd: 290, + vol: 0.0599, + }, + }, + }; + + expect( + userRankingFromGlickoPlayer(db_player_initial, glicko_player, variant), + ).toEqual(expected_player.ranking); +}); From f1a3f124bccac1cfc9261daaf1226c5f3442a2a6 Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Tue, 26 Nov 2024 13:13:02 -0500 Subject: [PATCH 15/16] fixes on the ratings unit tests --- .../src/rating/__tests__/rating.test.ts | 107 ++++++------------ packages/server/src/rating/rating.ts | 13 +-- 2 files changed, 41 insertions(+), 79 deletions(-) diff --git a/packages/server/src/rating/__tests__/rating.test.ts b/packages/server/src/rating/__tests__/rating.test.ts index 78a65f81..60669e0c 100644 --- a/packages/server/src/rating/__tests__/rating.test.ts +++ b/packages/server/src/rating/__tests__/rating.test.ts @@ -1,11 +1,11 @@ import { getGlickoPlayer, - userRankingFromGlickoPlayer, + applyPlayerRankingToUserResponse, supportsRatings, getGlickoResult, } from "../rating"; import { Glicko2 } from "glicko2"; -import { UserRanking, UserResponse } from "@ogfcommunity/variants-shared"; +import { UserRanking, UserRankings, UserResponse } from "@ogfcommunity/variants-shared"; test("supportsRatings", () => { const supportsRating = supportsRatings("chess"); @@ -16,30 +16,12 @@ test("supportsRatings", () => { }); test("getGlickoPlayer - with typical player value", () => { - const db_player = { - id: "672844d2254d76r4387b8488", - login_type: "persistent", - username: "testUser", - ranking: { - quantum: { - rating: 1337, - rd: 290, - vol: 0.0599, - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599, - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599, - }, - }, - }; - const db_player_ranking_quantum = db_player.ranking["quantum"]; + const db_player_ranking_quantum = { + rating: 1337, + rd: 290, + vol: 0.0599, + } const ranking = new Glicko2({ tau: 0.5, @@ -48,20 +30,11 @@ test("getGlickoPlayer - with typical player value", () => { vol: 0.06, }); - const new_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); + const player = getGlickoPlayer(db_player_ranking_quantum, ranking); - expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( - new_ranking.makePlayer( - db_player_ranking_quantum.rating, - db_player_ranking_quantum.rd, - db_player_ranking_quantum.vol, - ), - ); + expect(player.getRating()).toBe(1337); + expect(player.getRd()).toBe(290); + expect(player.getVol()).toBe(0.0599); }); test("getGlickoPlayer - when player does not have any rating", () => { @@ -74,26 +47,21 @@ test("getGlickoPlayer - when player does not have any rating", () => { vol: 0.06, }); - const new_ranking = new Glicko2({ - tau: 0.5, - rating: 1500, - rd: 350, - vol: 0.06, - }); + const player = getGlickoPlayer(db_player_ranking_quantum, ranking) - expect(getGlickoPlayer(db_player_ranking_quantum, ranking)).toEqual( - new_ranking.makePlayer(1500, 350, 0.06), - ); + expect(player.getRating()).toBe(1500); + expect(player.getRd()).toBe(350); + expect(player.getVol()).toBe(0.06); }); -// do we need to run tests for very simple functions? + test("getGlickoResult", () => { expect(getGlickoResult("B")).toBe(1); expect(getGlickoResult("W")).toBe(0); expect(getGlickoResult("T")).toBe(0.5); }); -test("userRankingFromGlickoPlayer", () => { +test("applyPlayerRankingToUserResponse", () => { const variant = "quantum"; const glicko_ranking = new Glicko2({ tau: 0.5, @@ -132,30 +100,25 @@ test("userRankingFromGlickoPlayer", () => { const glicko_player = getGlickoPlayer(new_quantum_ranking, glicko_ranking); - const expected_player: UserResponse = { - id: "672844d2254d76r4387b8488", - login_type: "persistent", - username: "testUser", - ranking: { - quantum: { - rating: 1667, - rd: 295, - vol: 0.0699, - }, - baduk: { - rating: 1623, - rd: 290, - vol: 0.0599, - }, - phantom: { - rating: 1762, - rd: 290, - vol: 0.0599, - }, + const expected_player_rankings: UserRankings = { + quantum: { + rating: 1667, + rd: 295, + vol: 0.0699, }, - }; + baduk: { + rating: 1623, + rd: 290, + vol: 0.0599, + }, + phantom: { + rating: 1762, + rd: 290, + vol: 0.0599, + }, + } expect( - userRankingFromGlickoPlayer(db_player_initial, glicko_player, variant), - ).toEqual(expected_player.ranking); + applyPlayerRankingToUserResponse(db_player_initial, glicko_player, variant), + ).toEqual(expected_player_rankings); }); diff --git a/packages/server/src/rating/rating.ts b/packages/server/src/rating/rating.ts index afce5da1..20279102 100644 --- a/packages/server/src/rating/rating.ts +++ b/packages/server/src/rating/rating.ts @@ -19,8 +19,6 @@ export async function updateRatings( vol: 0.06, }); - const matches: [Player, Player, number][] = []; - const variant = game.variant; const glicko_outcome = getGlickoResult(game_obj.result[0]); @@ -40,16 +38,16 @@ export async function updateRatings( ranking, ); - matches.push([glicko_player_black, glicko_player_white, glicko_outcome]); + ranking.addResult(glicko_player_black, glicko_player_white, glicko_outcome) - ranking.updateRatings(matches); + ranking.calculatePlayersRatings() - const player_black_new_ranking = userRankingFromGlickoPlayer( + const player_black_new_ranking = applyPlayerRankingToUserResponse( db_player_black, glicko_player_black, variant, ); - const player_white_new_ranking = userRankingFromGlickoPlayer( + const player_white_new_ranking = applyPlayerRankingToUserResponse( db_player_white, glicko_player_white, variant, @@ -85,7 +83,8 @@ export function getGlickoPlayer( ); } -export function userRankingFromGlickoPlayer( +// this function updates player's UserRanking with new ratings +export function applyPlayerRankingToUserResponse( player: UserResponse, glicko_player: Player, variant: string, From c13ab6dc423298d19840b5d085561fc2374c9d2c Mon Sep 17 00:00:00 2001 From: SameerDalal Date: Tue, 26 Nov 2024 13:15:57 -0500 Subject: [PATCH 16/16] yarn lint changes --- .../server/src/rating/__tests__/rating.test.ts | 14 ++++++++------ packages/server/src/rating/rating.ts | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/server/src/rating/__tests__/rating.test.ts b/packages/server/src/rating/__tests__/rating.test.ts index 60669e0c..2006a156 100644 --- a/packages/server/src/rating/__tests__/rating.test.ts +++ b/packages/server/src/rating/__tests__/rating.test.ts @@ -5,7 +5,11 @@ import { getGlickoResult, } from "../rating"; import { Glicko2 } from "glicko2"; -import { UserRanking, UserRankings, UserResponse } from "@ogfcommunity/variants-shared"; +import { + UserRanking, + UserRankings, + UserResponse, +} from "@ogfcommunity/variants-shared"; test("supportsRatings", () => { const supportsRating = supportsRatings("chess"); @@ -16,12 +20,11 @@ test("supportsRatings", () => { }); test("getGlickoPlayer - with typical player value", () => { - const db_player_ranking_quantum = { rating: 1337, rd: 290, vol: 0.0599, - } + }; const ranking = new Glicko2({ tau: 0.5, @@ -47,14 +50,13 @@ test("getGlickoPlayer - when player does not have any rating", () => { vol: 0.06, }); - const player = getGlickoPlayer(db_player_ranking_quantum, ranking) + const player = getGlickoPlayer(db_player_ranking_quantum, ranking); expect(player.getRating()).toBe(1500); expect(player.getRd()).toBe(350); expect(player.getVol()).toBe(0.06); }); - test("getGlickoResult", () => { expect(getGlickoResult("B")).toBe(1); expect(getGlickoResult("W")).toBe(0); @@ -116,7 +118,7 @@ test("applyPlayerRankingToUserResponse", () => { rd: 290, vol: 0.0599, }, - } + }; expect( applyPlayerRankingToUserResponse(db_player_initial, glicko_player, variant), diff --git a/packages/server/src/rating/rating.ts b/packages/server/src/rating/rating.ts index 20279102..38ed73c9 100644 --- a/packages/server/src/rating/rating.ts +++ b/packages/server/src/rating/rating.ts @@ -38,9 +38,9 @@ export async function updateRatings( ranking, ); - ranking.addResult(glicko_player_black, glicko_player_white, glicko_outcome) + ranking.addResult(glicko_player_black, glicko_player_white, glicko_outcome); - ranking.calculatePlayersRatings() + ranking.calculatePlayersRatings(); const player_black_new_ranking = applyPlayerRankingToUserResponse( db_player_black,