const VALUE = { p: 1, n: 3, b: 3, r: 5, q: 9, k: 100 }; export function pieceValue(type) { return VALUE[type] ?? 0; } export const PIECE_NAME = { p: "pawn", n: "knight", b: "bishop", r: "rook", q: "queen", k: "king", }; // Static exchange evaluation on an occupied square. Returns the material the owner // stands to lose if the enemy starts capturing there (positive = a real loss). export function exchangeLoss(game, square) { const victim = game.get(square); if (!victim) return 0; const owner = victim.color; const enemy = owner === "w" ? "b" : "w"; const attackers = valuesOf(game, game.attackers(square, enemy)); const defenders = valuesOf(game, game.attackers(square, owner)); if (attackers.length === 0) return 0; return swap(pieceValue(victim.type), attackers, defenders); } function valuesOf(game, squares) { return squares .map((sq) => pieceValue(game.get(sq).type)) .sort((a, b) => a - b); } // Classic swap-off: least valuable piece captures first, each side may stop when // continuing would lose material. Returns net material for the attacking side. function swap(target, attackers, defenders) { const gains = [target]; let onSquare = attackers[0]; let a = 1; let d = 0; let depth = 1; let defenderToMove = true; while (true) { if (defenderToMove) { if (d >= defenders.length) break; gains[depth] = onSquare - gains[depth - 1]; onSquare = defenders[d++]; } else { if (a >= attackers.length) break; gains[depth] = onSquare - gains[depth - 1]; onSquare = attackers[a++]; } depth++; defenderToMove = !defenderToMove; } for (let i = depth - 1; i > 0; i--) { gains[i - 1] = -Math.max(-gains[i - 1], gains[i]); } return gains[0]; } // Every piece of `color` the opponent can win material from, worst first. export function threats(game, color) { const out = []; for (const { square, type } of game.pieces(color)) { if (type === "k") continue; const loss = exchangeLoss(game, square); if (loss > 0) out.push({ square, type, loss }); } return out.sort((a, b) => b.loss - a.loss); }