@@ -16,6 +16,15 @@ import {
1616 useLocalStorage ,
1717} from '..'
1818
19+ const COLORS = {
20+ good : [ '#238b45' , '#41ab5d' , '#74c476' , '#a1d99b' , '#c7e9c0' ] ,
21+ ok : [ '#ec7014' , '#feb24c' , '#fed976' , '#ffeda0' , '#ffffcc' ] . reverse ( ) ,
22+ blunder : [ '#cb181d' , '#ef3b2c' , '#fb6a4a' , '#fc9272' , '#fcbba1' ] . reverse ( ) ,
23+ }
24+
25+ // Constants for move classification based on winrate
26+ const BLUNDER_THRESHOLD = 0.1 // 10% winrate drop
27+ const INACCURACY_THRESHOLD = 0.05 // 5% winrate drop
1928const MAIA_MODELS = [
2029 'maia_kdd_1100' ,
2130 'maia_kdd_1200' ,
@@ -28,22 +37,6 @@ const MAIA_MODELS = [
2837 'maia_kdd_1900' ,
2938]
3039
31- // const MAIA_COLORS = ['#fe7f6d', '#f08a4c', '#ecaa4f', '#eccd4f']
32- const STOCKFISH_COLORS = [
33- '#1a9850' ,
34- '#91cf60' ,
35- '#d9ef8b' ,
36- '#fee08b' ,
37- '#fc8d59' ,
38- '#d73027' ,
39- ]
40-
41- const COLORS = {
42- good : [ '#238b45' , '#41ab5d' , '#74c476' , '#a1d99b' , '#c7e9c0' ] ,
43- ok : [ '#ec7014' , '#feb24c' , '#fed976' , '#ffeda0' , '#ffffcc' ] . reverse ( ) ,
44- blunder : [ '#cb181d' , '#ef3b2c' , '#fb6a4a' , '#fc9272' , '#fcbba1' ] . reverse ( ) ,
45- }
46-
4740export const useAnalysisController = ( game : AnalyzedGame ) => {
4841 const controller = useAnalysisGameController (
4942 game . tree as GameTree ,
@@ -67,7 +60,6 @@ export const useAnalysisController = (game: AnalyzedGame) => {
6760 MAIA_MODELS [ 0 ] ,
6861 )
6962
70- // Ensure the selected model is valid for the current context
7163 useEffect ( ( ) => {
7264 if ( ! MAIA_MODELS . includes ( currentMaiaModel ) ) {
7365 setCurrentMaiaModel ( MAIA_MODELS [ 0 ] )
@@ -268,20 +260,40 @@ export const useAnalysisController = (game: AnalyzedGame) => {
268260 okMoves : { probability : 0 , moves : [ ] } ,
269261 goodMoves : { probability : 0 , moves : [ ] } ,
270262 }
271- for ( const [ move , prob ] of Object . entries ( maia . policy ) ) {
272- const loss = stockfish . cp_relative_vec [ move ]
273- if ( loss === undefined ) continue
274- const probability = prob * 100
275-
276- if ( loss >= - 50 ) {
277- goodMoveProbability += probability
278- goodMoveChanceInfo . push ( { move, probability } )
279- } else if ( loss >= - 150 ) {
280- okMoveProbability += probability
281- okMoveChanceInfo . push ( { move, probability } )
282- } else {
283- blunderMoveProbability += probability
284- blunderMoveChanceInfo . push ( { move, probability } )
263+
264+ if ( stockfish . winrate_loss_vec ) {
265+ for ( const [ move , prob ] of Object . entries ( maia . policy ) ) {
266+ const winrate_loss = stockfish . winrate_loss_vec [ move ]
267+ if ( winrate_loss === undefined ) continue
268+ const probability = prob * 100
269+
270+ if ( winrate_loss >= - INACCURACY_THRESHOLD ) {
271+ goodMoveProbability += probability
272+ goodMoveChanceInfo . push ( { move, probability } )
273+ } else if ( winrate_loss >= - BLUNDER_THRESHOLD ) {
274+ okMoveProbability += probability
275+ okMoveChanceInfo . push ( { move, probability } )
276+ } else {
277+ blunderMoveProbability += probability
278+ blunderMoveChanceInfo . push ( { move, probability } )
279+ }
280+ }
281+ } else {
282+ for ( const [ move , prob ] of Object . entries ( maia . policy ) ) {
283+ const loss = stockfish . cp_relative_vec [ move ]
284+ if ( loss === undefined ) continue
285+ const probability = prob * 100
286+
287+ if ( loss >= - 50 ) {
288+ goodMoveProbability += probability
289+ goodMoveChanceInfo . push ( { move, probability } )
290+ } else if ( loss >= - 150 ) {
291+ okMoveProbability += probability
292+ okMoveChanceInfo . push ( { move, probability } )
293+ } else {
294+ blunderMoveProbability += probability
295+ blunderMoveChanceInfo . push ( { move, probability } )
296+ }
285297 }
286298 }
287299
@@ -350,10 +362,21 @@ export const useAnalysisController = (game: AnalyzedGame) => {
350362
351363 const moveRecommendations = useMemo ( ( ) => {
352364 if ( ! moveEvaluation ) return { }
365+
366+ const isBlackTurn = controller . currentNode ?. turn === 'b'
367+
353368 const recommendations : {
354369 maia ?: { move : string ; prob : number } [ ]
355- stockfish ?: { move : string ; cp : number } [ ]
356- } = { }
370+ stockfish ?: {
371+ move : string
372+ cp : number
373+ winrate ?: number
374+ winrate_loss ?: number
375+ } [ ]
376+ isBlackTurn ?: boolean
377+ } = {
378+ isBlackTurn,
379+ }
357380
358381 if ( moveEvaluation ?. maia ) {
359382 const policy = moveEvaluation . maia . policy
@@ -367,16 +390,21 @@ export const useAnalysisController = (game: AnalyzedGame) => {
367390
368391 if ( moveEvaluation ?. stockfish ) {
369392 const cp_vec = moveEvaluation . stockfish . cp_vec
393+ const winrate_vec = moveEvaluation . stockfish . winrate_vec || { }
394+ const winrate_loss_vec = moveEvaluation . stockfish . winrate_loss_vec || { }
395+
370396 const stockfish = Object . entries ( cp_vec ) . map ( ( [ move , cp ] ) => ( {
371397 move,
372398 cp,
399+ winrate : winrate_vec [ move ] || 0 ,
400+ winrate_loss : winrate_loss_vec [ move ] || 0 ,
373401 } ) )
374402
375403 recommendations . stockfish = stockfish
376404 }
377405
378406 return recommendations
379- } , [ moveEvaluation ] )
407+ } , [ moveEvaluation , controller . currentNode ] )
380408
381409 const moveMap = useMemo ( ( ) => {
382410 if ( ! moveEvaluation ?. maia || ! moveEvaluation ?. stockfish ) {
@@ -387,8 +415,13 @@ export const useAnalysisController = (game: AnalyzedGame) => {
387415 Object . entries ( moveEvaluation . maia . policy ) . slice ( 0 , 3 ) ,
388416 )
389417
418+ // Get the Stockfish moves in their sorted order (best to worst for the current player)
390419 const stockfishMoves = Object . entries ( moveEvaluation . stockfish . cp_vec )
420+
421+ // Top moves are the first 3 in the sorted order
391422 const topStockfish = Object . fromEntries ( stockfishMoves . slice ( 0 , 3 ) )
423+
424+ // Worst moves are the last 2 in the sorted order
392425 const worstStockfish = Object . fromEntries ( stockfishMoves . slice ( - 2 ) )
393426
394427 const moves = Array . from (
0 commit comments