Spaces:
Running
Running
File size: 5,412 Bytes
f7cecf3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | // 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,
};
}; |