|
| 1 | +import { Chess } from 'chess.ts' |
1 | 2 | import { cpToWinrate } from 'src/lib' |
2 | 3 | import { MoveTooltip } from './MoveTooltip' |
3 | 4 | import { InteractiveDescription } from './InteractiveDescription' |
@@ -54,6 +55,35 @@ export const Highlight: React.FC<Props> = ({ |
54 | 55 | isHomePage = false, |
55 | 56 | }: Props) => { |
56 | 57 | const { isMobile } = useContext(WindowSizeContext) |
| 58 | + |
| 59 | + // Check if current position is checkmate (independent of Stockfish analysis) |
| 60 | + const isCurrentPositionCheckmate = currentNode |
| 61 | + ? (() => { |
| 62 | + try { |
| 63 | + const chess = new Chess(currentNode.fen) |
| 64 | + return chess.inCheckmate() |
| 65 | + } catch { |
| 66 | + return false |
| 67 | + } |
| 68 | + })() |
| 69 | + : false |
| 70 | + |
| 71 | + // Helper function to format evaluation values |
| 72 | + const formatEvaluation = ( |
| 73 | + cp: number, |
| 74 | + mateIn?: number, |
| 75 | + isCheckmate?: boolean, |
| 76 | + ) => { |
| 77 | + if (isCheckmate) { |
| 78 | + return 'Checkmate' |
| 79 | + } |
| 80 | + if (mateIn !== undefined) { |
| 81 | + // Show +M2/-M2 to indicate whose mate it is |
| 82 | + const sign = mateIn > 0 ? '+' : '-' |
| 83 | + return `${sign}M${Math.abs(mateIn)}` |
| 84 | + } |
| 85 | + return `${cp > 0 ? '+' : ''}${(cp / 100).toFixed(2)}` |
| 86 | + } |
57 | 87 | const [tooltipData, setTooltipData] = useState<{ |
58 | 88 | move: string |
59 | 89 | maiaProb?: number |
@@ -210,6 +240,26 @@ export const Highlight: React.FC<Props> = ({ |
210 | 240 |
|
211 | 241 | // Get the appropriate win rate |
212 | 242 | const getWhiteWinRate = () => { |
| 243 | + // Handle checkmate positions (check this first, even without Stockfish analysis) |
| 244 | + if (isCurrentPositionCheckmate) { |
| 245 | + // If it's checkmate, the current player has lost |
| 246 | + const currentTurn = currentNode?.turn || 'w' |
| 247 | + return currentTurn === 'w' ? '0.0%' : '100.0%' |
| 248 | + } |
| 249 | + |
| 250 | + // Handle checkmate positions detected by Stockfish |
| 251 | + if (moveEvaluation?.stockfish?.is_checkmate) { |
| 252 | + // If it's checkmate, the current player has lost |
| 253 | + const currentTurn = currentNode?.turn || 'w' |
| 254 | + return currentTurn === 'w' ? '0.0%' : '100.0%' |
| 255 | + } |
| 256 | + |
| 257 | + // Handle mate in X positions |
| 258 | + if (moveEvaluation?.stockfish?.mate_in !== undefined) { |
| 259 | + const mateIn = moveEvaluation.stockfish.mate_in |
| 260 | + return mateIn > 0 ? '100.0%' : '0.0%' |
| 261 | + } |
| 262 | + |
213 | 263 | if ( |
214 | 264 | isInFirst10Ply && |
215 | 265 | moveEvaluation?.stockfish?.model_optimal_cp !== undefined |
@@ -330,9 +380,15 @@ export const Highlight: React.FC<Props> = ({ |
330 | 380 | : ''} |
331 | 381 | </p> |
332 | 382 | <p className="text-base font-bold text-engine-1 md:text-sm lg:text-lg"> |
333 | | - {moveEvaluation?.stockfish |
334 | | - ? `${moveEvaluation.stockfish.model_optimal_cp > 0 ? '+' : ''}${moveEvaluation.stockfish.model_optimal_cp / 100}` |
335 | | - : '...'} |
| 383 | + {isCurrentPositionCheckmate |
| 384 | + ? 'Checkmate' |
| 385 | + : moveEvaluation?.stockfish |
| 386 | + ? formatEvaluation( |
| 387 | + moveEvaluation.stockfish.model_optimal_cp, |
| 388 | + moveEvaluation.stockfish.mate_in, |
| 389 | + moveEvaluation.stockfish.is_checkmate, |
| 390 | + ) |
| 391 | + : '...'} |
336 | 392 | </p> |
337 | 393 | </div> |
338 | 394 |
|
@@ -386,8 +442,9 @@ export const Highlight: React.FC<Props> = ({ |
386 | 442 | {colorSanMapping[move]?.san ?? move} |
387 | 443 | </p> |
388 | 444 | <p className="text-right font-mono text-sm md:text-xxs xl:text-xs"> |
389 | | - {cp > 0 ? '+' : null} |
390 | | - {`${(cp / 100).toFixed(2)}`} |
| 445 | + {Math.abs(cp) >= 10000 |
| 446 | + ? `${cp > 0 ? '+' : '-'}M${Math.max(1, Math.floor(Math.abs(10000 - Math.abs(cp)) / 100) + 1)}` |
| 447 | + : `${cp > 0 ? '+' : ''}${(cp / 100).toFixed(2)}`} |
391 | 448 | </p> |
392 | 449 | </button> |
393 | 450 | ) |
|
0 commit comments