diff --git a/backend/__tests__/utils/misc.spec.ts b/backend/__tests__/utils/misc.spec.ts index 299398d4b..d43988d10 100644 --- a/backend/__tests__/utils/misc.spec.ts +++ b/backend/__tests__/utils/misc.spec.ts @@ -37,4 +37,66 @@ describe("Misc Utils", () => { }); }); }); + + it("tensComplement", () => { + const testCases = [ + { + input: 123, + expected: 877, + }, + { + input: 877, + expected: 123, + }, + { + input: 0, + expected: 0, + }, + { + input: 86423, + expected: 13577, + }, + { + input: 10998739999, + expected: 89001260001, + }, + ]; + + _.each(testCases, ({ input, expected }) => { + expect(misc.tensComplement(input)).toBe(expected); + }); + }); + + it("kogascore", () => { + const testCases = [ + { + wpm: 214.8, + acc: 93.04, + timestamp: 1653586489000, + expectedScore: 1214800930436711, + }, + { + wpm: 214.8, + acc: 93.04, + timestamp: 1653601763000, + expectedScore: 1214800930421437, + }, + { + wpm: 199.37, + acc: 97.69, + timestamp: 1653588809000, + expectedScore: 1199370976934391, + }, + { + wpm: 196.2, + acc: 96.07, + timestamp: 1653591901000, + expectedScore: 1196200960731299, + }, + ]; + + _.each(testCases, ({ wpm, acc, timestamp, expectedScore }) => { + expect(misc.kogascore(wpm, acc, timestamp)).toBe(expectedScore); + }); + }); }); diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts index 480b1b8f7..24b71c4de 100644 --- a/backend/src/api/controllers/result.ts +++ b/backend/src/api/controllers/result.ts @@ -321,6 +321,7 @@ export async function addResult( const validResultCriteria = (funbox === "none" || funbox === "plus_one" || funbox === "plus_two") && !bailedOut && + !user.banned && (user.timeTyping ?? 0) > 7200; if (dailyLeaderboard && validResultCriteria) { diff --git a/backend/src/utils/daily-leaderboards.ts b/backend/src/utils/daily-leaderboards.ts index 0cacd6466..1e16dc23c 100644 --- a/backend/src/utils/daily-leaderboards.ts +++ b/backend/src/utils/daily-leaderboards.ts @@ -1,7 +1,7 @@ import _ from "lodash"; import LRUCache from "lru-cache"; import * as RedisClient from "../init/redis"; -import { getCurrentDayTimestamp, matchesAPattern } from "./misc"; +import { getCurrentDayTimestamp, matchesAPattern, kogascore } from "./misc"; interface DailyLeaderboardEntry { uid: string; @@ -19,21 +19,6 @@ const dailyLeaderboardNamespace = "monkeytypes:dailyleaderboard"; const scoresNamespace = `${dailyLeaderboardNamespace}:scores`; const resultsNamespace = `${dailyLeaderboardNamespace}:results`; -function compareDailyLeaderboardEntries( - a: DailyLeaderboardEntry, - b: DailyLeaderboardEntry -): number { - if (a.wpm !== b.wpm) { - return b.wpm - a.wpm; - } - - if (a.acc !== b.acc) { - return b.acc - a.acc; - } - - return a.timestamp - b.timestamp; -} - export class DailyLeaderboard { private leaderboardResultsKeyName: string; private leaderboardScoresKeyName: string; @@ -85,6 +70,8 @@ export class DailyLeaderboard { (currentDayTimestamp + leaderboardExpirationDurationInMilliseconds) / 1000 ); + const resultScore = kogascore(entry.wpm, entry.acc, entry.timestamp); + // @ts-ignore const rank = await connection.addResult( 2, @@ -93,7 +80,7 @@ export class DailyLeaderboard { maxResults, leaderboardExpirationTimeInSeconds, entry.uid, - entry.wpm, + resultScore, JSON.stringify(entry) ); @@ -126,13 +113,9 @@ export class DailyLeaderboard { maxRank ); - const normalizedResults: DailyLeaderboardEntry[] = results - .map((result) => JSON.parse(result)) - .sort(compareDailyLeaderboardEntries); - - const resultsWithRanks: DailyLeaderboardEntry[] = normalizedResults.map( - (result, index) => ({ - ...result, + const resultsWithRanks: DailyLeaderboardEntry[] = results.map( + (resultJSON, index) => ({ + ...JSON.parse(resultJSON), rank: minRank + index + 1, }) ); diff --git a/backend/src/utils/misc.ts b/backend/src/utils/misc.ts index 4c9c2583f..a0ee2604f 100644 --- a/backend/src/utils/misc.ts +++ b/backend/src/utils/misc.ts @@ -90,3 +90,34 @@ export function matchesAPattern(text: string, pattern: string): boolean { const regex = new RegExp(`^${pattern}$`); return regex.test(text); } + +export function tensComplement(num: number): number { + if (num === 0) { + return 0; + } + + let i = 0; + let temp = num; + + while (temp !== 0) { + ++i; + temp = Math.floor(temp / 10); + } + + return Math.pow(10, i) - num; +} + +export function kogascore(wpm: number, acc: number, timestamp: number): number { + const normalizedWpm = Math.floor(wpm * 100); + const normalizedAcc = Math.floor(acc * 100); + + const firstPart = (100000 + normalizedWpm) * 100000; + const secondPart = (firstPart + normalizedAcc) * 100000; + + const currentDayTimeMilliseconds = timestamp - (timestamp % 86400000); + const todaySeconds = Math.floor( + (timestamp - currentDayTimeMilliseconds) / 1000 + ); + + return secondPart + tensComplement(todaySeconds); +}