feat: WM2026 Tippspiel - Initial Backend + Frontend

This commit is contained in:
Ronny Müller
2026-04-03 21:41:19 +02:00
commit 1c685b90a0
2507 changed files with 997210 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
import { PointsCalculation, POINTS } from '../types';
interface Score {
home: number;
away: number;
}
/**
* Berechnet die Punkte für einen Tipp.
*
* Punkteregeln:
* - Exaktes Ergebnis: 3 Punkte
* - Richtige Tendenz: 1 Punkt (Sieg/Unentschieden/Niederlage korrekt)
* - Falsche Tendenz: 0 Punkte
*
* @param tip - Der abgegebene Tipp
* @param actual - Das tatsächliche Ergebnis
*/
export const calculatePoints = (
tip: Score,
actual: Score
): PointsCalculation => {
// Exaktes Ergebnis
if (tip.home === actual.home && tip.away === actual.away) {
return { result: 'exact', points: POINTS.EXACT };
}
// Tendenz prüfen (Sieg Heim / Unentschieden / Sieg Auswärts)
const tipTendency = getTendency(tip.home, tip.away);
const actualTendency = getTendency(actual.home, actual.away);
if (tipTendency === actualTendency) {
return { result: 'tendency', points: POINTS.TENDENCY };
}
return { result: 'wrong', points: POINTS.WRONG };
};
type Tendency = 'home' | 'draw' | 'away';
const getTendency = (home: number, away: number): Tendency => {
if (home > away) return 'home';
if (home < away) return 'away';
return 'draw';
};
/**
* Berechnet Punkte für die gesamte Tipp-History eines Users.
* Nützlich für Statistiken.
*/
export const calculateUserStats = (
tips: Array<{
tipHome: number;
tipAway: number;
actualHome: number | null;
actualAway: number | null;
points: number | null;
}>
) => {
const evaluated = tips.filter(
(t) => t.actualHome !== null && t.actualAway !== null
);
const exactCount = evaluated.filter((t) => t.points === POINTS.EXACT).length;
const tendencyCount = evaluated.filter(
(t) => t.points === POINTS.TENDENCY
).length;
const wrongCount = evaluated.filter((t) => t.points === POINTS.WRONG).length;
const totalPoints = evaluated.reduce((sum, t) => sum + (t.points ?? 0), 0);
const accuracy =
evaluated.length > 0
? Math.round(
((exactCount + tendencyCount) / evaluated.length) * 100
)
: 0;
return {
totalPoints,
exactCount,
tendencyCount,
wrongCount,
accuracy,
evaluatedCount: evaluated.length,
pendingCount: tips.length - evaluated.length,
};
};