src/game/analysis.js (view raw)
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 |
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);
}
|