Spaces:
Running
Running
| // src/utils/fplLogic.js | |
| export const CHIP_CONFIG = { | |
| wc: { | |
| label: "Wildcard", short: "WC", desc: "Unlimited free transfers for 1 GW. FTs reset to max after.", | |
| dot: "bg-yellow-400", text: "text-yellow-400", activeBg: "bg-yellow-500 text-slate-950", | |
| inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-yellow-700/40", badge: "bg-yellow-500/20 text-yellow-300", | |
| }, | |
| fh: { | |
| label: "Free Hit", short: "FH", desc: "Unlimited transfers for 1 GW. Squad reverts to original after.", | |
| dot: "bg-orange-400", text: "text-orange-400", activeBg: "bg-orange-500 text-slate-950", | |
| inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-orange-700/40", badge: "bg-orange-500/20 text-orange-300", | |
| }, | |
| bb: { | |
| label: "Bench Boost", short: "BB", desc: "All 15 squad players score points this GW.", | |
| dot: "bg-emerald-400", text: "text-emerald-400", activeBg: "bg-emerald-500 text-slate-950", | |
| inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-emerald-700/40", badge: "bg-emerald-500/20 text-emerald-300", | |
| }, | |
| tc: { | |
| label: "Triple Captain", short: "TC", desc: "Captain earns 3× points instead of 2× this GW.", | |
| dot: "bg-purple-400", text: "text-purple-400", activeBg: "bg-purple-500 text-white", | |
| inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-purple-700/40", badge: "bg-purple-500/20 text-purple-300", | |
| }, | |
| }; | |
| export const getPlayerPrice = (p) => { | |
| // 1. Exact FPL Calculation if we have both purchase_price and now_cost | |
| let pp = p.purchase_price !== undefined ? Number(p.purchase_price) : undefined; | |
| let nc = p.now_cost !== undefined ? Number(p.now_cost) : undefined; | |
| // FPL API sometimes sends prices multiplied by 10 (e.g., 43 for 4.3m) | |
| if (pp > 20) pp = pp / 10; | |
| if (nc > 20) nc = nc / 10; | |
| if (pp !== undefined && nc !== undefined) { | |
| if (nc > pp) { | |
| // Gain is 0.1m for every 0.2m increase. | |
| // To avoid floating point errors, multiply by 10, floor the difference/2, and divide by 10. | |
| const diff = Math.round((nc - pp) * 10); | |
| const gain = Math.floor(diff / 2); | |
| return (Math.round(pp * 10) + gain) / 10; | |
| } else { | |
| // FPL Rule: You take full losses on price drops. | |
| return nc; | |
| } | |
| } | |
| // 2. Fallbacks if purchase_price isn't explicitly available | |
| if (p.selling_price !== undefined && p.selling_price !== null) { | |
| const sp = Number(p.selling_price); | |
| return sp > 20 ? sp / 10 : sp; | |
| } | |
| if (p.sell_price !== undefined && p.sell_price !== null) { | |
| const sp = Number(p.sell_price); | |
| return sp > 20 ? sp / 10 : sp; | |
| } | |
| if (p.Price !== undefined) return Number(p.Price); | |
| if (nc !== undefined) return nc; | |
| return 0; | |
| }; | |
| export function normalizeBenchGkFirst(teamData, gw) { | |
| if (!teamData.length || teamData.length < 15 || !gw) return teamData; | |
| const starters = teamData.slice(0, 11); | |
| const bench = teamData.slice(11, 15); | |
| const getEV = (p) => Number(p[`${gw}_Pts`]) || 0; | |
| const nonBlank = bench.filter((p) => !p.isBlank); | |
| const blanks = bench.filter((p) => p.isBlank); | |
| const gk = nonBlank.find((p) => p.Pos === "G"); | |
| const outfield = nonBlank.filter((p) => p.Pos !== "G"); | |
| outfield.sort((a, b) => getEV(b) - getEV(a)); | |
| const ordered = gk ? [gk, ...outfield] : [...outfield]; | |
| const newBench = [...ordered, ...blanks]; | |
| while (newBench.length < 4) { | |
| newBench.push({ | |
| ID: `blank_pad_${Date.now()}_${newBench.length}`, | |
| isBlank: true, Pos: "M", Name: "", Team: "", Price: 0, | |
| }); | |
| } | |
| return [...starters, ...newBench.slice(0, 4)]; | |
| } | |
| export function countSquadByPos(players) { | |
| const c = { G: 0, D: 0, M: 0, F: 0 }; | |
| players.filter((p) => !p.isBlank && p.Pos).forEach((p) => { | |
| if (c[p.Pos] !== undefined) c[p.Pos] += 1; | |
| }); | |
| return c; | |
| } | |
| export function isValidFplSquad(players) { | |
| const c = countSquadByPos(players); | |
| return c.G === 2 && c.D === 5 && c.M === 5 && c.F === 3; | |
| } | |
| export const getOptimalLayout = (players, gw) => { | |
| if (!players.length || !gw || players.some((p) => p.isBlank)) return null; | |
| const getEV = (p) => Number(p[`${gw}_Pts`]) || 0; | |
| let gks = players.filter((p) => p.Pos === "G").sort((a, b) => getEV(b) - getEV(a)); | |
| let defs = players.filter((p) => p.Pos === "D").sort((a, b) => getEV(b) - getEV(a)); | |
| let mids = players.filter((p) => p.Pos === "M").sort((a, b) => getEV(b) - getEV(a)); | |
| let fwds = players.filter((p) => p.Pos === "F").sort((a, b) => getEV(b) - getEV(a)); | |
| const starters = []; | |
| if (gks.length) starters.push(gks.shift()); | |
| starters.push(...defs.splice(0, 3), ...mids.splice(0, 2), ...fwds.splice(0, 1)); | |
| const remaining = [...defs, ...mids, ...fwds].sort((a, b) => getEV(b) - getEV(a)); | |
| starters.push(...remaining.splice(0, 11 - starters.length)); | |
| const finalStarters = [ | |
| ...starters.filter((p) => p.Pos === "G"), | |
| ...starters.filter((p) => p.Pos === "D"), | |
| ...starters.filter((p) => p.Pos === "M"), | |
| ...starters.filter((p) => p.Pos === "F"), | |
| ]; | |
| const benchGk = gks.length ? gks[0] : null; | |
| const benchRest = remaining.sort((a, b) => getEV(b) - getEV(a)); | |
| const bench = benchGk ? [benchGk, ...benchRest] : benchRest; | |
| const topStarters = [...finalStarters].sort((a, b) => getEV(b) - getEV(a)); | |
| return { | |
| optimalArray: [...finalStarters, ...bench], | |
| cap: topStarters[0]?.ID, | |
| vice: topStarters[1]?.ID, | |
| }; | |
| }; |