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.";
}
|