115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
import { Triangle, Diamond, Circle, Square } from 'lucide-react';
|
|
import type { GameConfig, Player, PointsBreakdown } from './types';
|
|
|
|
export const COLORS = {
|
|
red: 'bg-red-600',
|
|
blue: 'bg-blue-600',
|
|
yellow: 'bg-yellow-600',
|
|
green: 'bg-green-600',
|
|
purple: 'bg-theme-primary',
|
|
purpleLight: 'bg-theme-primary/70',
|
|
};
|
|
|
|
export const SHAPES = {
|
|
triangle: Triangle,
|
|
diamond: Diamond,
|
|
circle: Circle,
|
|
square: Square,
|
|
};
|
|
|
|
export const BOT_NAMES = [
|
|
"QuizWiz", "FastFinger", "Brainiac", "KnowItAll", "Guesser",
|
|
"LuckyStrike", "OwlFan", "Kahootie", "ZigZag", "Pixel"
|
|
];
|
|
|
|
export const QUESTION_TIME = 20; // seconds
|
|
export const QUESTION_TIME_MS = 20000; // milliseconds
|
|
export const POINTS_PER_QUESTION = 1000;
|
|
|
|
export const calculateBasePoints = (timeLeftMs: number, questionTimeMs: number, maxPoints: number = POINTS_PER_QUESTION): number => {
|
|
const responseTimeMs = questionTimeMs - timeLeftMs;
|
|
const responseTimeSec = responseTimeMs / 1000;
|
|
const questionTimeSec = questionTimeMs / 1000;
|
|
|
|
if (responseTimeSec < 0.5) {
|
|
return maxPoints;
|
|
}
|
|
|
|
return Math.round((1 - (responseTimeSec / questionTimeSec) / 2) * maxPoints);
|
|
};
|
|
|
|
interface PointsCalculationParams {
|
|
isCorrect: boolean;
|
|
timeLeftMs: number;
|
|
questionTimeMs: number;
|
|
streak: number;
|
|
playerRank: number;
|
|
isFirstCorrect: boolean;
|
|
config: GameConfig;
|
|
}
|
|
|
|
export const calculatePointsWithBreakdown = (params: PointsCalculationParams): PointsBreakdown => {
|
|
const { isCorrect, timeLeftMs, questionTimeMs, streak, playerRank, isFirstCorrect, config } = params;
|
|
|
|
const breakdown: PointsBreakdown = {
|
|
basePoints: 0,
|
|
streakBonus: 0,
|
|
comebackBonus: 0,
|
|
firstCorrectBonus: 0,
|
|
penalty: 0,
|
|
total: 0,
|
|
};
|
|
|
|
if (!isCorrect) {
|
|
if (config.penaltyForWrongAnswer) {
|
|
breakdown.penalty = Math.round(POINTS_PER_QUESTION * (config.penaltyPercent / 100));
|
|
breakdown.total = -breakdown.penalty;
|
|
}
|
|
return breakdown;
|
|
}
|
|
|
|
breakdown.basePoints = calculateBasePoints(timeLeftMs, questionTimeMs);
|
|
let pointsAfterStreak = breakdown.basePoints;
|
|
|
|
if (config.streakBonusEnabled && streak >= config.streakThreshold) {
|
|
const streakCount = streak - config.streakThreshold;
|
|
const multiplier = config.streakMultiplier + (streakCount * (config.streakMultiplier - 1));
|
|
pointsAfterStreak = Math.round(breakdown.basePoints * multiplier);
|
|
breakdown.streakBonus = pointsAfterStreak - breakdown.basePoints;
|
|
}
|
|
|
|
if (config.comebackBonusEnabled && playerRank > 3) {
|
|
breakdown.comebackBonus = config.comebackBonusPoints;
|
|
}
|
|
|
|
if (config.firstCorrectBonusEnabled && isFirstCorrect) {
|
|
breakdown.firstCorrectBonus = config.firstCorrectBonusPoints;
|
|
}
|
|
|
|
breakdown.total = pointsAfterStreak + breakdown.comebackBonus + breakdown.firstCorrectBonus;
|
|
return breakdown;
|
|
};
|
|
|
|
export const calculatePoints = (params: PointsCalculationParams): number => {
|
|
return calculatePointsWithBreakdown(params).total;
|
|
};
|
|
|
|
export const getPlayerRank = (playerId: string, players: Player[]): number => {
|
|
const sorted = [...players].sort((a, b) => b.score - a.score);
|
|
return sorted.findIndex(p => p.id === playerId) + 1;
|
|
};
|
|
|
|
export const PLAYER_COLORS = [
|
|
'#2563eb',
|
|
'#e21b3c',
|
|
'#26890c',
|
|
'#ffa602',
|
|
'#d89e00',
|
|
'#0aa3a3',
|
|
'#b8008a',
|
|
'#6a4c93',
|
|
'#ff6b6b',
|
|
'#4ecdc4',
|
|
'#45b7d1',
|
|
'#8b5cf6',
|
|
];
|