diff --git a/backend/dao/user.js b/backend/dao/user.js index 3ee867726..7e8a78513 100644 --- a/backend/dao/user.js +++ b/backend/dao/user.js @@ -167,19 +167,7 @@ class UsersDAO { } static async checkIfPb(uid, user, result) { - const { - mode, - mode2, - acc, - consistency, - difficulty, - lazyMode, - language, - punctuation, - rawWpm, - wpm, - funbox, - } = result; + const { mode, funbox } = result; if (funbox !== "none" && funbox !== "plus_one" && funbox !== "plus_two") { return false; @@ -192,20 +180,7 @@ class UsersDAO { let lbpb = user.lbPersonalBests; if (!lbpb) lbpb = {}; - let pb = checkAndUpdatePb( - user.personalBests, - lbpb, - mode, - mode2, - acc, - consistency, - difficulty, - lazyMode, - language, - punctuation, - rawWpm, - wpm - ); + let pb = checkAndUpdatePb(user.personalBests, lbpb, result); if (pb.isPb) { await db @@ -227,20 +202,7 @@ class UsersDAO { return []; } - const { - mode, - mode2, - acc, - consistency, - difficulty, - lazyMode, - language, - punctuation, - rawWpm, - wpm, - tags, - funbox, - } = result; + const { mode, tags, funbox } = result; if (funbox !== "none" && funbox !== "plus_one" && funbox !== "plus_two") { return []; @@ -262,20 +224,7 @@ class UsersDAO { let ret = []; tagsToCheck.forEach(async (tag) => { - let tagpb = checkAndUpdatePb( - tag.personalBests, - undefined, - mode, - mode2, - acc, - consistency, - difficulty, - lazyMode, - language, - punctuation, - rawWpm, - wpm - ); + let tagpb = checkAndUpdatePb(tag.personalBests, undefined, result); if (tagpb.isPb) { ret.push(tag._id); await db diff --git a/backend/utils/pb.js b/backend/utils/pb.js deleted file mode 100644 index b54bc73d1..000000000 --- a/backend/utils/pb.js +++ /dev/null @@ -1,148 +0,0 @@ -/* - - -obj structure - -time: { - 10: [ - this is a list because there can be - different personal bests for different difficulties, languages and punctuation - { - acc, - consistency, - difficulty, - language, - punctuation, - raw, - timestamp, - wpm - } - ] -}, -words: { - 10: [ - {} - ] -}, -zen: { - zen: [ - {} - ] -}, -custom: { - custom: { - [] - } -} - - - - - -*/ - -export function checkAndUpdatePb( - obj, - lbObj, - mode, - mode2, - acc, - consistency, - difficulty, - lazyMode = false, - language, - punctuation, - raw, - wpm -) { - //verify structure first - if (obj === undefined) obj = {}; - if (obj[mode] === undefined) obj[mode] = {}; - if (obj[mode][mode2] === undefined) obj[mode][mode2] = []; - - let isPb = false; - let found = false; - //find a pb - obj[mode][mode2].forEach((pb) => { - //check if we should compare first - if ( - (pb.lazyMode === lazyMode || - (pb.lazyMode === undefined && lazyMode === false)) && - pb.difficulty === difficulty && - pb.language === language && - pb.punctuation === punctuation - ) { - found = true; - //compare - if (pb.wpm < wpm) { - //update - isPb = true; - pb.acc = acc; - pb.consistency = consistency; - pb.difficulty = difficulty; - pb.language = language; - pb.punctuation = punctuation; - pb.lazyMode = lazyMode; - pb.raw = raw; - pb.wpm = wpm; - pb.timestamp = Date.now(); - } - } - }); - //if not found push a new one - if (!found) { - isPb = true; - obj[mode][mode2].push({ - acc, - consistency, - difficulty, - lazyMode, - language, - punctuation, - raw, - wpm, - timestamp: Date.now(), - }); - } - - if ( - lbObj && - mode === "time" && - (mode2 == "15" || mode2 == "60") && - !lazyMode - ) { - //updating lbpersonalbests object - //verify structure first - if (lbObj[mode] === undefined) lbObj[mode] = {}; - if (lbObj[mode][mode2] === undefined || Array.isArray(lbObj[mode][mode2])) - lbObj[mode][mode2] = {}; - - let bestForEveryLanguage = {}; - if (obj?.[mode]?.[mode2]) { - obj[mode][mode2].forEach((pb) => { - if (!bestForEveryLanguage[pb.language]) { - bestForEveryLanguage[pb.language] = pb; - } else { - if (bestForEveryLanguage[pb.language].wpm < pb.wpm) { - bestForEveryLanguage[pb.language] = pb; - } - } - }); - Object.keys(bestForEveryLanguage).forEach((key) => { - if (lbObj[mode][mode2][key] === undefined) { - lbObj[mode][mode2][key] = bestForEveryLanguage[key]; - } else { - if (lbObj[mode][mode2][key].wpm < bestForEveryLanguage[key].wpm) { - lbObj[mode][mode2][key] = bestForEveryLanguage[key]; - } - } - }); - bestForEveryLanguage = {}; - } - } - - return { - isPb, - obj, - lbObj, - }; -} diff --git a/backend/utils/pb.ts b/backend/utils/pb.ts new file mode 100644 index 000000000..0ff3af98e --- /dev/null +++ b/backend/utils/pb.ts @@ -0,0 +1,146 @@ +import _ from "lodash"; + +interface CheckAndUpdatePbResult { + isPb: boolean; + obj: object; + lbObj: object; +} + +type Result = MonkeyTypes.Result; + +export function checkAndUpdatePb( + userPersonalBests: MonkeyTypes.User["personalBests"], + lbPersonalBests: MonkeyTypes.User["lbPersonalBests"], + result: Result +): CheckAndUpdatePbResult { + const { mode, mode2 } = result; + + const userPb = userPersonalBests ?? {}; + userPb[mode] = userPb[mode] ?? {}; + userPb[mode][mode2] = userPb[mode][mode2] ?? []; + + const personalBestMatch: MonkeyTypes.PersonalBest = userPb[mode][mode2].find( + (pb: MonkeyTypes.PersonalBest) => { + return matchesPersonalBest(result, pb); + } + ); + + let isPb = true; + + if (personalBestMatch) { + const didUpdate = updatePersonalBest(personalBestMatch, result); + isPb = didUpdate; + } else { + userPb[mode][mode2].push(buildPersonalBest(result)); + } + + updateLeaderboardPersonalBests(userPb, lbPersonalBests, result); + + return { + isPb, + obj: userPb, + lbObj: lbPersonalBests, + }; +} + +function matchesPersonalBest( + result: Result, + personalBest: MonkeyTypes.PersonalBest +): boolean { + const sameLazyMode = + result.lazyMode === personalBest.lazyMode || + (!result.lazyMode && !personalBest.lazyMode); + const samePunctuation = result.punctuation === personalBest.punctuation; + const sameDifficulty = result.difficulty === personalBest.difficulty; + const sameLanguage = result.language === personalBest.language; + + return sameLazyMode && samePunctuation && sameDifficulty && sameLanguage; +} + +function updatePersonalBest( + personalBest: MonkeyTypes.PersonalBest, + result: Result +): boolean { + if (personalBest.wpm > result.wpm) { + return false; + } + + personalBest.acc = result.acc; + personalBest.consistency = result.consistency; + personalBest.difficulty = result.difficulty; + personalBest.language = result.language; + personalBest.punctuation = result.punctuation; + personalBest.lazyMode = result.lazyMode; + personalBest.raw = result.rawWpm; + personalBest.wpm = result.wpm; + personalBest.timestamp = Date.now(); + + return true; +} + +function buildPersonalBest(result: Result): MonkeyTypes.PersonalBest { + return { + acc: result.acc, + consistency: result.consistency, + difficulty: result.difficulty, + lazyMode: result.lazyMode, + language: result.language, + punctuation: result.punctuation, + raw: result.rawWpm, + wpm: result.wpm, + timestamp: Date.now(), + }; +} + +function updateLeaderboardPersonalBests( + userPersonalBests: MonkeyTypes.User["personalBests"], + lbPersonalBests: MonkeyTypes.User["lbPersonalBests"], + result: Result +): void { + if (!shouldUpdateLeaderboardPersonalBests(lbPersonalBests, result)) { + return; + } + + const { mode, mode2 } = result; + + lbPersonalBests[mode] = lbPersonalBests[mode] ?? {}; + const lbMode2 = lbPersonalBests[mode][mode2]; + if (!lbMode2 || Array.isArray(lbMode2)) { + lbPersonalBests[mode][mode2] = {}; + } + + const bestForEveryLanguage = {}; + + userPersonalBests[mode][mode2].forEach((pb: MonkeyTypes.PersonalBest) => { + const language = pb.language; + if ( + !bestForEveryLanguage[language] || + bestForEveryLanguage[language].wpm < pb.wpm + ) { + bestForEveryLanguage[language] = pb; + } + }); + + _.each( + bestForEveryLanguage, + (pb: MonkeyTypes.PersonalBest, language: string) => { + const languageDoesNotExist = !lbPersonalBests[mode][mode2][language]; + + if ( + languageDoesNotExist || + lbPersonalBests[mode][mode2][language].wpm < pb.wpm + ) { + lbPersonalBests[mode][mode2][language] = pb; + } + } + ); +} + +function shouldUpdateLeaderboardPersonalBests( + lbPersonalBests: MonkeyTypes.User["lbPersonalBests"], + result: Result +): boolean { + const isValidTimeMode = + result.mode === "time" && (result.mode2 === "15" || result.mode2 === "60"); + return lbPersonalBests && isValidTimeMode && !result.lazyMode; +}