all repos — emchess @ be8e1da8b28b364ae535696c302f3e41d52448c0

src/game/coach.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
 74
 75
 76
 77
import { threats, exchangeLoss, pieceValue, PIECE_NAME } from "./analysis";
// Warn before a move that drops material the opponent can simply take. Returns the
// piece at risk, or null if the move is fine. Checks are skipped — a check usually
// changes the tactics.
export function blunderCheck(game, from, to, promotion, human) {
    const test = game.clone();
    const move = test.play(from, to, promotion);
    if (!move || test.inCheck())
        return null;
    const gained = move.captured ? pieceValue(move.captured) : 0;
    const worst = threats(test, human)[0];
    if (worst && worst.loss - gained >= 2)
        return worst;
    return null;
}
export function assess(cp, mate) {
    if (mate !== null) {
        return mate > 0 ? `You have a forced mate in ${mate}.` : `You are getting mated in ${Math.abs(mate)}.`;
    }
    if (cp === null)
        return "The position is unclear.";
    const p = cp / 100;
    if (p >= 2.5)
        return "You have a winning advantage.";
    if (p >= 0.9)
        return "You stand a little better.";
    if (p > -0.9)
        return "The position is about equal.";
    if (p > -2.5)
        return "You stand worse.";
    return "You are in serious trouble.";
}
export function explainPosition(game, human, cp, mate) {
    const enemy = human === "w" ? "b" : "w";
    const mine = threats(game, human);
    const theirs = threats(game, enemy);
    let detail;
    if (game.inCheck() && game.turn === human) {
        detail = "Your king is in check, so answer that first.";
    }
    else if (mine[0] && mine[0].loss >= 2) {
        detail = `Your ${PIECE_NAME[mine[0].type]} on ${mine[0].square} is hanging.`;
    }
    else if (theirs[0] && theirs[0].loss >= 2) {
        detail = `You can win the ${PIECE_NAME[theirs[0].type]} on ${theirs[0].square}.`;
    }
    else {
        detail = developmentHint(game, human);
    }
    return assess(cp, mate) + " " + detail;
}
// Explain, in plain terms, why the last move lost ground. Returns null when there is
// nothing concrete to say.
export function explainMistake(before, after, move, human) {
    const beforeHanging = new Set(threats(before, human).map((t) => t.square));
    const newHang = threats(after, human).find((t) => !beforeHanging.has(t.square) && t.loss >= 2);
    if (newHang) {
        return `That left your ${PIECE_NAME[newHang.type]} on ${newHang.square} attacked and underdefended.`;
    }
    const movedLoss = exchangeLoss(after, move.to);
    if (movedLoss >= 2) {
        return `Your ${PIECE_NAME[move.piece]} walked into a loss on ${move.to}.`;
    }
    return null;
}
function developmentHint(game, human) {
    const rank = human === "w" ? "1" : "8";
    const home = game
        .pieces(human)
        .filter((p) => (p.type === "n" || p.type === "b") && p.square[1] === rank);
    if (home.length >= 2)
        return "Bring your knights and bishops into the game.";
    const enemy = human === "w" ? "b" : "w";
    if (game.inCheck() && game.turn === enemy)
        return "You are giving check.";
    return "Nothing is loose. Look for a plan and improve your worst piece.";
}