src/game/analysis.ts (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 74 75 76 77 78 79 80 |
import type { Game, Square, Color } from "./state";
const VALUE: Record<string, number> = { p: 1, n: 3, b: 3, r: 5, q: 9, k: 100 };
export function pieceValue(type: string): number {
return VALUE[type] ?? 0;
}
export const PIECE_NAME: Record<string, string> = {
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: Game, square: Square): number {
const victim = game.get(square);
if (!victim) return 0;
const owner = victim.color;
const enemy: Color = 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: Game, squares: Square[]): number[] {
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: number, attackers: number[], defenders: number[]): number {
const gains: number[] = [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];
}
export interface Threat {
square: Square;
type: string;
loss: number;
}
// Every piece of `color` the opponent can win material from, worst first.
export function threats(game: Game, color: Color): Threat[] {
const out: Threat[] = [];
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);
}
|