Made app more modular.
Fixed some bugs. Added some functionality.
This commit is contained in:
@@ -1,33 +1,37 @@
|
||||
|
||||
/**
|
||||
* Dynamic Scoring Algorithm
|
||||
* Logic:
|
||||
* 1. Base Score starts at initialPoints.
|
||||
* 2. As solves increase, the score for EVERYONE who solved it decreases.
|
||||
* 3. We use a decay function: currentPoints = max(min_points, initial * decay_factor ^ (solves - 1))
|
||||
* 4. Plus a "First Blood" bonus: 1st solver gets 10% extra, 2nd 5%, 3rd 2%.
|
||||
* Dynamic Scoring Algorithm (Parabolic Decay)
|
||||
* Logic:
|
||||
* 1. Base Value = ((minimum - initial) / (decay^2)) * (solve_count^2) + initial
|
||||
* 2. If solve_count >= decay, return minimum.
|
||||
* 3. Fixed Bonuses (based on initial points): 1st: 15%, 2nd: 10%, 3rd: 5%.
|
||||
*/
|
||||
|
||||
const MIN_POINTS_PERCENTAGE = 0.2; // Points won't drop below 20% of initial
|
||||
const DECAY_CONSTANT = 0.92; // Aggressive decay per solve
|
||||
export const calculateChallengeValue = (
|
||||
initial: number,
|
||||
minimum: number,
|
||||
decay: number,
|
||||
solveCount: number
|
||||
): number => {
|
||||
if (solveCount === 0) return initial;
|
||||
if (decay <= 0 || solveCount >= decay) return minimum;
|
||||
|
||||
export const calculateChallengeValue = (initialPoints: number, solveCount: number): number => {
|
||||
if (solveCount === 0) return initialPoints;
|
||||
const minPoints = Math.floor(initialPoints * MIN_POINTS_PERCENTAGE);
|
||||
const decayedPoints = Math.floor(initialPoints * Math.pow(DECAY_CONSTANT, solveCount - 1));
|
||||
return Math.max(minPoints, decayedPoints);
|
||||
// Parabolic formula: ((min - init) / decay^2) * solveCount^2 + init
|
||||
const value = ((minimum - initial) / (decay * decay)) * (solveCount * solveCount) + initial;
|
||||
|
||||
return Math.ceil(value);
|
||||
};
|
||||
|
||||
export const getFirstBloodBonus = (rank: number): number => {
|
||||
if (rank === 0) return 0.10; // 1st
|
||||
if (rank === 1) return 0.05; // 2nd
|
||||
if (rank === 2) return 0.02; // 3rd
|
||||
export const getFirstBloodBonusFactor = (rank: number): number => {
|
||||
if (rank === 0) return 0.15; // 1st
|
||||
if (rank === 1) return 0.10; // 2nd
|
||||
if (rank === 2) return 0.05; // 3rd
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const calculateTeamTotalScore = (
|
||||
teamId: string,
|
||||
challenges: { id: string, initialPoints: number, solves: string[] }[],
|
||||
challenges: any[],
|
||||
solves: { teamId: string, challengeId: string, timestamp: number }[]
|
||||
): number => {
|
||||
let total = 0;
|
||||
@@ -39,17 +43,21 @@ export const calculateTeamTotalScore = (
|
||||
const challenge = challenges.find(c => c.id === solve.challengeId);
|
||||
if (!challenge) return;
|
||||
|
||||
// Current value of challenge (shared by all)
|
||||
const baseValue = calculateChallengeValue(challenge.initialPoints, challenge.solves.length);
|
||||
// Current dynamic base value (retroactive for everyone)
|
||||
const baseValue = calculateChallengeValue(
|
||||
challenge.initialPoints,
|
||||
challenge.minimumPoints || 0,
|
||||
challenge.decaySolves || 1,
|
||||
challenge.solves.length
|
||||
);
|
||||
|
||||
// Calculate rank for bonus
|
||||
// Find all solves for this challenge sorted by time
|
||||
// Calculate rank for fixed bonus based on initial value
|
||||
const challengeSolves = solves
|
||||
.filter(s => s.challengeId === solve.challengeId)
|
||||
.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
const rank = challengeSolves.findIndex(s => s.teamId === teamId);
|
||||
const bonus = Math.floor(baseValue * getFirstBloodBonus(rank));
|
||||
const bonus = Math.floor(challenge.initialPoints * getFirstBloodBonusFactor(rank));
|
||||
|
||||
total += (baseValue + bonus);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user