This document details the end-to-end process of how a chess game is analyzed in Chess Analyzer Pro, from the moment a game is loaded to the final display of Move Classifications and Accuracy.
1. Game Ingestion (Loading & Parsing)
The process begins when a user loads a game via PGN File, API (Chess.com/Lichess), or text/link.
The PGN Parser
The PGNParser (src/backend/pgn_parser.py) converts raw game data into our internal format.
- Reading: Uses
python-chessto read the game structure. - Conversion: Converts PGN nodes into
GameAnalysisandMoveAnalysisobjects. - Initialization: Captures FEN, UCI, and basic metadata.
Data Snapshot: Parsed Game
Right after parsing, the data looks like this structure (simplified):
GameAnalysis(
game_id="uuid-1234...",
metadata={
"White": "Magnus Carlsen",
"Black": "Hikaru Nakamura",
"Result": "1-0",
"Opening": "Sicilian Defense"
},
moves=[
# Move 1 (White)
MoveAnalysis(
san="e4",
uci="e2e4",
fen_before="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
classification=None, # Not yet analyzed
eval_before_cp=None
),
# Move 1 (Black)
MoveAnalysis(
san="c5",
uci="c7c5",
fen_before="rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
...
)
]
)
2. The Analysis Worker Pipeline
The AnalysisWorker (src/gui/analysis_worker.py) runs the analysis in a background thread to keep the UI responsive. It iterates through the moves and feeds them to the Analyzer.
3. Stockfish Engine Interaction
For each move, Analyzer (src/backend/analyzer.py) asks Stockfish for the "truth" of the position.
Engine Query
We call engine.analyse(board, limit=Depth 18, multipv=3).
Data Snapshot: Engine Output
The engine returns a list of dictionaries (one for each "PV" or principal variation asked for):
[
# PV 1 (The Best Move)
{
"pv": [Move.from_uci('f3e5'), Move.from_uci('d6e5')],
"score": <PovScore: Cp(+35) white> # 0.35 advantage for White
},
# PV 2 (Second Best)
{
"pv": [Move.from_uci('d2d4'), Move.from_uci('c5d4')],
"score": <PovScore: Cp(+10) white>
},
# PV 3 (Third Best)
{
"pv": [Move.from_uci('b1c3')],
"score": <PovScore: Cp(-15) white> # Slight disadvantage
}
]
4. Evaluation to Probability
We convert raw Centipawn (CP) scores into Win Probability (0.0 - 1.0) using a logistic regression formula to normalize human error perception.
$Win% = 50 + 50 \times (2 / (1 + e^{-0.00368208 \times CP}) - 1)$
Data Snapshot: Probability Conversion
For a move with Cp(+150) (White is better):
- Input:
+150CP - Calculation: Logisitic curve result.
- Output:
0.635(63.5% Win Probability for White)
For a mate score #M3:
- Input:
Mate(+3) - Output:
1.0(100% Win Probability)
5. Move Classification
We look at the change in Win Probability (WPL) from Before the move to After the move.
The Algorithm
AnalysisWorker compares the state before the move (where we could have played the 'Best' move) vs after the move (what we actually played).
Data Snapshot: Classification Example
Let's say White is winning (0.90 / 90%).
White plays a bad move, and the evaluation drops to equal (0.50 / 50%).
MoveAnalysis(
san="Bl5?",
uci="b2b4",
# 1. We had a winning position
eval_before_cp=450,
win_chance_before=0.90,
# 2. We played a bad move, now it's even
eval_after_cp=0,
win_chance_after=0.50,
# 3. The Loss
wpl=0.40, # 40% loss in win probability
# 4. Resulting Classification
classification="Blunder",
explanation="Win chance dropped by 40.0%"
)
Common Classifications:
- Best: Played the top engine move.
- Great: Played the only good move (Win Prob difference > 15% to next best).
- Excl:
WPL < 1% - Good:
WPL < 4% - Inac:
WPL < 9% - Mist:
WPL < 20% - Blund:
WPL >= 20%
6. Accuracy Calculation
We calculate an aggregate "Accuracy Score" (0-100) based on Average Centipawn Loss (ACPL).
Data Snapshot: Final Summary
After all moves are processed, game_analysis.summary is populated:
{
"white": {
"acpl": 15.4, # Average CP Loss per move
"accuracy": 92.5, # 100 * exp(-0.004 * 15.4)
"Brilliant": 1,
"Great": 2,
"Best": 25,
"Excellent": 5,
"Good": 3,
"Inaccuracy": 2,
"Mistake": 1,
"Blunder": 0,
"Miss": 0,
"Book": 5
},
"black": {
"acpl": 45.2,
"accuracy": 78.1,
# ... counts ...
}
}
7. Persistence & UI
Finally, the fully populated GameAnalysis object is sent to the UI.
- Move List: Renders the moves, adding
??for Blunders (Red) and!!for Brilliants (Teal) based on theclassificationfield. - Graph: Plots the
eval_before_cpfor each move index. - Stats: Displays the Accuracy % from the summary dictionary.