Skip to content

Commit

Permalink
fix: stockfish relative eval + move map + highlight eval precision
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinjosethomas committed Feb 10, 2025
1 parent 0ba7dd9 commit 8191748
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 19 deletions.
23 changes: 13 additions & 10 deletions src/api/analysis/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,16 +433,21 @@ function convertMoveMapToStockfishEval(

for (const move in cp_vec) {
const cp = moveMap[move]
cp_relative_vec[move] = model_optimal_cp - cp
cp_relative_vec[move] = cp - model_optimal_cp
}

const cp_vec_sorted = Object.fromEntries(
Object.entries(cp_vec).sort(([, a], [, b]) => b - a),
)

const cp_relative_vec_sorted = Object.fromEntries(
Object.entries(cp_relative_vec).sort(([, a], [, b]) => b - a),
)

if (turn === 'b') {
model_optimal_cp *= -1
for (const move in cp_vec) {
cp_vec[move] *= -1
}
for (const move in cp_relative_vec) {
cp_relative_vec[move] *= -1
for (const move in cp_vec_sorted) {
cp_vec_sorted[move] *= -1
}
}

Expand All @@ -451,8 +456,8 @@ function convertMoveMapToStockfishEval(
depth: 20,
model_move: model_move,
model_optimal_cp: model_optimal_cp,
cp_vec: cp_vec,
cp_relative_vec: cp_relative_vec,
cp_vec: cp_vec_sorted,
cp_relative_vec: cp_relative_vec_sorted,
}
}

Expand Down Expand Up @@ -533,8 +538,6 @@ export const getAnalyzedTournamentGame = async (gameId = ['FkgYSri1']) => {
break
}

// console.log(currentNode.fen === moves[i].board)

const stockfishEval = stockfishEvaluations[i]
? convertMoveMapToStockfishEval(
stockfishEvaluations[i],
Expand Down
10 changes: 5 additions & 5 deletions src/components/Analysis/Highlight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ export const Highlight: React.FC<Props> = ({
return (
<div
key={index}
className="flex cursor-default items-center justify-start gap-3 hover:underline"
className="grid cursor-default grid-cols-2 gap-3 hover:underline"
style={{
color: colorSanMapping[move]?.color ?? '#fff',
}}
>
<p className="w-[42px] text-right font-mono text-sm">
{Math.round(prob * 1000) / 10}%
<p className="text-right font-mono text-sm">
{(Math.round(prob * 1000) / 10).toFixed(1)}%
</p>
<p className="text-left font-mono text-sm">
{colorSanMapping[move]?.san ?? move}
Expand All @@ -80,14 +80,14 @@ export const Highlight: React.FC<Props> = ({
return (
<div
key={index}
className="flex cursor-default items-center justify-start gap-3 hover:underline"
className="grid cursor-default grid-cols-2 gap-3 hover:underline"
style={{
color: colorSanMapping[move]?.color ?? '#fff',
}}
>
<p className="w-[42px] text-right font-mono text-sm">
{cp > 0 ? '+' : null}
{cp / 100}
{`${(cp / 100).toFixed(2)}`}
</p>
<p className="font-mono text-sm">
{colorSanMapping[move]?.san ?? move}
Expand Down
32 changes: 28 additions & 4 deletions src/hooks/useStockfishEngine/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,42 @@ class Engine {
cp = mate > 0 ? 10000 : -10000
}

/*
The Stockfish engine, by default, reports centipawn (CP) scores from White's perspective.
This means a positive CP indicates an advantage for White, while a negative CP indicates
an advantage for Black.
However, when it's Black's turn to move, we want to interpret the CP score from Black's
perspective. To achieve this, we invert the sign of the CP score when it's Black's turn.
This ensures that a positive CP always represents an advantage for the player whose turn it is.
For example:
- If Stockfish reports CP = 100 (White's advantage) and it's White's turn, we keep CP = 100.
- If Stockfish reports CP = 100 (White's advantage) and it's Black's turn, we change CP to -100, indicating that Black is at a disadvantage.
*/
const board = new Chess(this.fen)
const isBlackTurn = board.turn() === 'b'
if (isBlackTurn) {
cp *= -1
}

if (this.store[depth]) {
/*
The cp_relative_vec (centipawn relative vector) is calculated to determine how much worse or better a given move is compared to the engine's "optimal" move (model_move) at the same depth.
Because the centipawn score (cp) has already been flipped to be relative to the current player's perspective (positive is good for the current player),
we need to ensure that the comparison to the optimal move (model_optimal_cp) is done in a consistent manner.
Therefore, we also flip the sign of model_optimal_cp when it is black's turn, so that the relative value is calculated correctly.
For example:
- If the engine evaluates the optimal move as CP = 50 when it's Black's turn, model_optimal_cp will be -50 after the initial flip.
- To calculate the relative value of another move with CP = 20, we use modelOptimalCp - cp, which is (-50) - (-20) = 30
- This indicates that the move with CP = 20 is significantly worse than the optimal move from Black's perspective.
*/
this.store[depth].cp_vec[move] = cp
this.store[depth].cp_relative_vec[move] =
this.store[depth].model_optimal_cp - cp
this.store[depth].cp_relative_vec[move] = isBlackTurn
? this.store[depth].model_optimal_cp - cp
: cp - this.store[depth].model_optimal_cp
} else {
this.store[depth] = {
depth: depth,
Expand All @@ -143,10 +169,8 @@ class Engine {
sent: false,
}
}

if (!this.store[depth].sent && multipv === this.legalMoveCount) {
this.store[depth].sent = true

if (this.evaluationResolver) {
this.evaluationResolver(this.store[depth])
this.evaluationResolver = null
Expand Down

0 comments on commit 8191748

Please sign in to comment.