all repos — emchess @ 582b0387fa0b9b6f7b51a8a13c4fee17764ffb39

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);
}