import React, { useState } from 'react'; import Papa from 'papaparse'; const SeasonDFS = () => { // Original DFS states const [availablePlayers, setAvailablePlayers] = useState([]); const [userRoster, setUserRoster] = useState([]); const [currentSalary, setCurrentSalary] = useState(0); const [currentScore, setCurrentScore] = useState(0); const [loadingStats, setLoadingStats] = useState(false); const [error, setError] = useState(''); const [gameLocked, setGameLocked] = useState(false); const [difficulty, setDifficulty] = useState('balanced'); const [aiTeams, setAiTeams] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [winner, setWinner] = useState(null); const [positionFilter, setPositionFilter] = useState('ALL'); // Season-specific states const [seasonWeek, setSeasonWeek] = useState(1); const [seasonPoints, setSeasonPoints] = useState(0); const [seasonHistory, setSeasonHistory] = useState([]); const [streaks, setStreaks] = useState({ userStreak: 0, weeklyRanks: [] }); // Constants const SALARY_CAP = 50000; const MIN_SALARY = 3000; const POSITIONS = ['ALL', 'SP', 'CL', 'RP', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF']; const ROSTER_REQUIREMENTS = { 'SP': { min: 1, max: 2 }, 'CL': { min: 0, max: 1 }, // Allow 0-1 closers 'RP': { min: 1, max: 2 }, 'C': { min: 1, max: 1 }, '1B': { min: 1, max: 1 }, '2B': { min: 1, max: 1 }, '3B': { min: 1, max: 1 }, 'SS': { min: 1, max: 1 }, 'LF': { min: 1, max: 1 }, 'CF': { min: 1, max: 1 }, 'RF': { min: 1, max: 1 }, 'UTIL': { min: 0, max: 1 } }; // Username generation for AI opponents const generateRandomUser = () => { const prefixes = [ 'DFS', 'Fantasy', 'Ball', 'Base', 'Diamond', 'Stats', 'Money', 'Pro', 'Grinder', 'Daily', 'MLB', 'Pitcher', 'Slugger', 'Hitter', 'Closer', 'Ace', 'Heater', 'Knuckle', 'Curveball', 'LaunchAngle', 'ExitVelo', 'xStats', 'WARlord', 'Sabermetric', 'BigFly', 'Moonshot', 'Roto', 'CashGame', 'Steals', 'OBP', 'OPS', 'Barrels', 'NoHitter', 'PowerBat', 'GPP', 'SpinRate', 'OutfieldCannon', 'DingerDealer', 'WalkOff', 'CyYoung', 'MVP', 'GoldGlove' ]; const suffixes = [ 'King', 'Queen', 'Champ', 'Master', 'Guru', 'Wizard', 'Beast', 'Expert', 'Boss', 'Shark', 'Warrior', 'Destroyer', 'Legend', 'Savage', 'Clutch', 'GOAT', 'Cannon', 'Swingman', 'Whiff', 'Slugger', 'Killer', 'Dinger', 'Velocity', 'Cleanup', 'HotStreak', 'Stealer', 'RBI', 'Boomer', 'Splitter', 'Heater', 'Cutter', 'Sinker', 'Slurve', 'Perfecto', 'ClutchHit', 'TripleCrown', 'LaunchPad', 'HOF', 'Fastball', 'OverTheFence', 'WarningTrack', 'Lumberjack' ]; const symbols = ['_', '', '.', '-', '#']; return `${prefixes[Math.floor(Math.random() * prefixes.length)]}${symbols[Math.floor(Math.random() * symbols.length)]}${suffixes[Math.floor(Math.random() * suffixes.length)]}${Math.floor(Math.random() * 999).toString().padStart(2, '0')}`; }; const generateCompetitorPool = (count = 20) => { return Array(count).fill(null).map(() => ({ name: generateRandomUser(), isRegular: false })); }; const regularCompetitors = [ { name: 'NikkiKnuckles', isRegular: true, strategy: { powerFocus: 0.7, contactFocus: 0.4, pitchingFocus: 0.8, riskTolerance: 0.6, valueHunting: true } }, { name: 'MarioTheBarber', isRegular: true, strategy: { powerFocus: 0.3, contactFocus: 0.9, pitchingFocus: 0.6, riskTolerance: 0.4, valueHunting: false } }, { name: 'LahdeeenLegend', isRegular: true, strategy: { powerFocus: 0.9, contactFocus: 0.3, pitchingFocus: 0.5, riskTolerance: 0.8, valueHunting: true } }, { name: 'DionGiantsBucs', isRegular: true, strategy: { powerFocus: 0.6, contactFocus: 0.6, pitchingFocus: 0.7, riskTolerance: 0.5, valueHunting: true } }, { name: 'xWARlord420', isRegular: true, strategy: { powerFocus: 0.8, contactFocus: 0.5, pitchingFocus: 0.4, riskTolerance: 0.7, valueHunting: false } }, { name: 'ChaosTheory', isRegular: true, strategy: { powerFocus: () => Math.random() * 2, contactFocus: () => Math.random() * 2, pitchingFocus: () => Math.random() * 2, riskTolerance: 1.0, valueHunting: () => Math.random() > 0.5, stackingPreference: () => Math.random() > 0.3, maxSalaryPerPlayer: () => 3000 + Math.random() * 47000, favorPosition: () => { const positions = ['SP', 'RP', 'C', '1B', '2B', '3B', 'SS', 'OF']; return positions[Math.floor(Math.random() * positions.length)]; }, willingToOverpay: () => Math.random() > 0.7 } } ]; const calculateWeeklyBonuses = (rank, score) => { let bonus = 0; if (rank === 1) bonus += 1000; else if (rank <= 3) bonus += 500; else if (rank <= 10) bonus += 250; if (streaks.userStreak >= 3) bonus *= 1.1; if (streaks.userStreak >= 5) bonus *= 1.25; return bonus; }; const processFile = async (file) => { setLoadingStats(true); setError(''); try { const text = await file.text(); Papa.parse(text, { header: true, dynamicTyping: true, skipEmptyLines: true, complete: (results) => { if (results.errors.length > 0) { setError('Error parsing CSV file'); return; } const processedPlayers = results.data.map(player => { const singles = (player.H || 0) - ((player['2B'] || 0) + (player['3B'] || 0) + (player.HR || 0)); const points = calculateFantasyPoints(player); return { ...player, '1B': singles, points }; }); // Calculate salaries const playersWithSalaries = generateSalary(processedPlayers); setAvailablePlayers(playersWithSalaries); setLoadingStats(false); }, error: (error) => { setError(`Error processing file: ${error.message}`); setLoadingStats(false); } }); } catch (error) { setError(`Error reading file: ${error.message}`); setLoadingStats(false); } }; const calculateFantasyPoints = (player) => { const scoring = { '1B': 3, '2B': 5, '3B': 8, 'HR': 10, 'R': 2, 'RBI': 2, 'BB': 2, 'SB': 5, 'CS': -2, 'HBP': 2, 'IP': 2.25, 'K': 2, 'W': 4, 'ER': -2, 'H': -0.6, 'BB': -0.6 }; let totalPoints = 0; for (const [stat, points] of Object.entries(scoring)) { const value = parseFloat(player[stat]) || 0; // Ensure valid number totalPoints += value * points; } return isNaN(totalPoints) ? 0 : totalPoints; // Prevent NaN }; const generateSalary = (players) => { const positionMultipliers = { 'SP': 1.4, 'CL': 1.35, 'RP': 1.3, 'C': 1.2, '1B': 1.1, '2B': 1.1, '3B': 1.1, 'SS': 1.15, 'LF': 0.9, 'CF': 0.9, 'RF': 0.9 }; // Extract valid WPA values const wpaValues = players .map(player => parseFloat(player.WPA)) // Ensure WPA is a number .filter(wpa => !isNaN(wpa)); const maxWPA = Math.max(...wpaValues); const minWPA = Math.min(...wpaValues); const wpaRange = maxWPA - minWPA || 1; // Prevent division by zero let totalSalary = 0; let scaledSalaries = players.map(player => { const multiplier = positionMultipliers[player.POS] || 1; // Normalize WPA for scaling (0 to 1 range) const wpaNormalized = !isNaN(parseFloat(player.WPA)) ? (parseFloat(player.WPA) - minWPA) / wpaRange : 0.5; // Default if WPA is missing let baseSalary = ((player.points || 0) * 100 * multiplier) + (wpaNormalized * 5000); // Ensure salary is a number let salary = Math.max(MIN_SALARY, Math.round(baseSalary)); totalSalary += salary; return { ...player, salary }; }); // Scale salaries down **only if they exceed cap** if (totalSalary > SALARY_CAP * players.length) { const scaleFactor = (SALARY_CAP * players.length) / totalSalary; scaledSalaries = scaledSalaries.map(player => ({ ...player, salary: Math.max(MIN_SALARY, Math.round(player.salary * scaleFactor)) })); } return scaledSalaries; }; const validateRoster = (roster) => { const positionCounts = roster.reduce((counts, player) => { const pos = player.POS; counts[pos] = (counts[pos] || 0) + 1; return counts; }, {}); const totalPitchers = (positionCounts['SP'] || 0) + (positionCounts['RP'] || 0); if (totalPitchers < 2) { return `Need 2 pitchers (SP/RP). Currently have ${totalPitchers}`; } for (const [pos, requirement] of Object.entries(ROSTER_REQUIREMENTS)) { if (pos === 'UTIL') { // UTIL can be any non-pitcher position const nonPitchers = roster.filter(p => !['SP', 'RP', 'CL'].includes(p.POS)); if (nonPitchers.length < 1) { return `UTIL must be filled with a non-pitcher`; } continue; } const count = positionCounts[pos] || 0; if (count < requirement.min) { return `Need at least ${requirement.min} ${pos} player${requirement.min > 1 ? 's' : ''}. Have ${count}`; } if (count > requirement.max) { return `Maximum ${requirement.max} ${pos} players allowed. Have ${count}`; } } return null; }; const addToRoster = (player) => { if (userRoster.length >= 11) { setError('Roster is full (11 players maximum)'); return; } const newSalary = currentSalary + player.salary; if (newSalary > SALARY_CAP) { setError(`Cannot add ${player.Name} - would exceed salary cap by $${(newSalary - SALARY_CAP).toLocaleString()}`); return; } const updatedRoster = [...userRoster, player]; const positionCounts = updatedRoster.reduce((counts, p) => { const pos = p.POS; counts[pos] = (counts[pos] || 0) + 1; return counts; }, {}); const requirement = ROSTER_REQUIREMENTS[player.POS]; if (requirement && positionCounts[player.POS] > requirement.max) { setError(`Maximum ${requirement.max} ${player.POS} players allowed`); return; } setUserRoster(updatedRoster); setCurrentSalary(newSalary); updateScore(updatedRoster); }; const removeFromRoster = (playerIndex) => { const updatedRoster = userRoster.filter((_, index) => index !== playerIndex); const newSalary = updatedRoster.reduce((total, player) => total + player.salary, 0); setUserRoster(updatedRoster); setCurrentSalary(newSalary); updateScore(updatedRoster); }; const updateScore = (roster) => { const score = roster.reduce((total, player) => total + player.points, 0); setCurrentScore(score); }; const generateAiTeam = (competitor) => { const strategy = competitor.strategy || { powerFocus: Math.random(), contactFocus: Math.random(), pitchingFocus: Math.random(), stackingPreference: Math.random() > 0.7, valueHunting: Math.random() > 0.5, riskTolerance: Math.random() }; const evaluatePlayer = (player) => { let value = player.points; const getStrategyValue = (strategyProp) => { return typeof strategyProp === 'function' ? strategyProp() : strategyProp; }; if (player.POS?.includes('SP') || player.POS?.includes('RP')) { value *= (1 + getStrategyValue(strategy.pitchingFocus)); } else { value *= (1 + (player.HR || 0) * getStrategyValue(strategy.powerFocus)); value *= (1 + (player.AVG || 0) * getStrategyValue(strategy.contactFocus)); } if (strategy.favorPosition && getStrategyValue(strategy.favorPosition) === player.POS) { value *= 1.5; } if (strategy.willingToOverpay && getStrategyValue(strategy.willingToOverpay)) { value *= 1 + Math.random(); } if (getStrategyValue(strategy.valueHunting)) { value = value / player.salary; } const riskTolerance = getStrategyValue(strategy.riskTolerance); value *= (1 - riskTolerance/2 + Math.random() * riskTolerance); return value; }; let roster = []; let totalSalary = 0; let availablePool = [...availablePlayers]; // Fill required positions for (const [pos, requirement] of Object.entries(ROSTER_REQUIREMENTS)) { if (pos === 'UTIL') continue; const count = requirement.min; const positionPlayers = availablePool .filter(p => p.POS === pos) .filter(p => p.salary <= SALARY_CAP - totalSalary) .sort((a, b) => evaluatePlayer(b) - evaluatePlayer(a)); for (let i = 0; i < count && i < positionPlayers.length; i++) { const player = positionPlayers[i]; roster.push(player); totalSalary += player.salary; availablePool = availablePool.filter(p => p !== player); } } // Add UTIL player const utilPlayers = availablePool .filter(p => !p.POS?.includes('SP') && !p.POS?.includes('RP')) .filter(p => p.salary <= SALARY_CAP - totalSalary) .sort((a, b) => evaluatePlayer(b) - evaluatePlayer(a)); if (utilPlayers.length > 0) { roster.push(utilPlayers[0]); totalSalary += utilPlayers[0].salary; } const totalPoints = roster.reduce((sum, player) => sum + player.points, 0); let adjustedPoints = totalPoints; if (difficulty === 'hard') { adjustedPoints *= 1.1; } else if (difficulty === 'easy') { adjustedPoints *= 0.9; } return { name: competitor.name, score: adjustedPoints, roster: roster, salary: totalSalary, remainingSalary: SALARY_CAP - totalSalary }; }; const lockGame = () => { if (userRoster.length !== 11) { // Keep as 11 if that's what you want setError('Must have exactly 11 players (including UTIL) to lock lineup'); return; } const positionError = validateRoster(userRoster); if (positionError) { setError(positionError); return; } const competitors = [ ...regularCompetitors, ...generateCompetitorPool(15 - regularCompetitors.length) ].sort(() => Math.random() - 0.5).slice(0, 15); const aiResults = competitors.map(competitor => generateAiTeam(competitor)); const allResults = [ { name: 'You', score: currentScore, roster: userRoster, salary: currentSalary }, ...aiResults ].sort((a, b) => b.score - a.score); const userRank = allResults.findIndex(result => result.name === 'You') + 1; const weekResult = { week: seasonWeek, score: currentScore, rank: userRank, earnings: calculateWeeklyBonuses(userRank, currentScore) }; setSeasonHistory(prev => [...prev, weekResult]); setStreaks(prev => ({ userStreak: userRank <= 3 ? prev.userStreak + 1 : 0, weeklyRanks: [...prev.weeklyRanks, userRank] })); setSeasonPoints(prev => prev + currentScore); setAiTeams(allResults); setWinner(allResults[0]); setGameLocked(true); }; const advanceWeek = () => { setUserRoster([]); setCurrentSalary(0); setCurrentScore(0); setGameLocked(false); setSeasonWeek(prev => prev + 1); }; return (
Week {seasonWeek} of 26
Season Points: {seasonPoints}
Current Streak: {streaks.userStreak}
Base: ${SALARY_CAP.toLocaleString()}
Bonus: ${(seasonWeek * 5000).toLocaleString()}
Best Finish: {streaks.weeklyRanks.length > 0 ? Math.min(...streaks.weeklyRanks) : '-'}
Avg Finish: {streaks.weeklyRanks.length > 0 ? (streaks.weeklyRanks.reduce((a,b) => a+b, 0) / streaks.weeklyRanks.length).toFixed(1) : '-'}
| Name | POS | WAR | Salary | Points | Action |
|---|---|---|---|---|---|
| {player.Name} | {player.POS} | {player.WAR?.toFixed(1) || '0.0'} | ${player.salary?.toLocaleString()} | {player.points?.toFixed(1)} |
Current Score: {currentScore.toFixed(1)}
Total Salary: ${currentSalary.toLocaleString()}
| Name | POS | Salary | Points | Action |
|---|---|---|---|---|
| {player.Name} | {player.POS} | ${isNaN(player.salary) ? MIN_SALARY.toLocaleString() : player.salary.toLocaleString()} | {player.points?.toFixed(1)} |
| Rank | User | Score |
|---|---|---|
| {idx + 1} | {team.name} | {team.score.toFixed(1)} |
| Week | Score | Rank | Earnings |
|---|---|---|---|
| {week.week} | {week.score.toFixed(1)} | {week.rank} | ${week.earnings.toLocaleString()} |