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