55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
import { timingSafeEqual } from 'crypto';
|
|
|
|
export type Difficulty = 'LOW' | 'MEDIUM' | 'HIGH';
|
|
|
|
export const DIFFICULTY_RANK: Readonly<Record<Difficulty, number>> = {
|
|
LOW: 0,
|
|
MEDIUM: 1,
|
|
HIGH: 2,
|
|
};
|
|
|
|
export function compareDifficulty(a: Difficulty, b: Difficulty): number {
|
|
return DIFFICULTY_RANK[a] - DIFFICULTY_RANK[b];
|
|
}
|
|
|
|
export function computeLivePoints(
|
|
initial: number,
|
|
minimum: number,
|
|
decaySolves: number,
|
|
currentSolveCount: number,
|
|
): number {
|
|
if (!Number.isFinite(initial) || !Number.isFinite(minimum)) return minimum;
|
|
const i = Math.max(0, Math.floor(initial));
|
|
const m = Math.max(0, Math.floor(minimum));
|
|
const d = Math.max(0, Math.floor(decaySolves));
|
|
const c = Math.max(0, Math.floor(currentSolveCount));
|
|
if (i < m) return Math.max(0, m);
|
|
if (d <= 0) return i;
|
|
if (c >= d) return m;
|
|
const span = i - m;
|
|
const raw = i - span * (c / d);
|
|
return Math.max(m, Math.round(raw));
|
|
}
|
|
|
|
export function rankBonusForPosition(position: number): number {
|
|
if (!Number.isInteger(position) || position <= 0) return 0;
|
|
if (position === 1) return 15;
|
|
if (position === 2) return 10;
|
|
if (position === 3) return 5;
|
|
return 0;
|
|
}
|
|
|
|
export function safeEqualString(a: string, b: string): boolean {
|
|
const ab = Buffer.from(a, 'utf8');
|
|
const bb = Buffer.from(b, 'utf8');
|
|
if (ab.length !== bb.length) {
|
|
let diff = ab.length ^ bb.length;
|
|
const max = Math.max(ab.length, bb.length);
|
|
for (let i = 0; i < max; i += 1) {
|
|
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
}
|
|
return false;
|
|
}
|
|
return timingSafeEqual(ab, bb);
|
|
}
|