Skip to content

Commit d5f0b76

Browse files
fix: remove code duplication and make depth configurable in engine analysis
- Make useEngineAnalysis accept configurable target depth instead of hardcoded 18 - Remove duplicate useGameAnalysis hook to eliminate code duplication - Create simple batch analysis that reuses existing useEngineAnalysis infrastructure - Fix depth configuration bug where analysis always used depth 18 - Ensure both single-position and batch analysis use same underlying logic Co-authored-by: kevinjosethomas <46242684+kevinjosethomas@users.noreply.github.com>
1 parent d1c70ee commit d5f0b76

6 files changed

Lines changed: 184 additions & 315 deletions

File tree

src/components/Analysis/AnalysisProgressOverlay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import { motion } from 'framer-motion'
3-
import { GameAnalysisProgress } from 'src/hooks/useAnalysisController/useGameAnalysis'
3+
import { GameAnalysisProgress } from 'src/hooks/useAnalysisController/useAnalysisController'
44

55
interface Props {
66
progress: GameAnalysisProgress

src/hooks/useAnalysisController/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ export * from './useEngineAnalysis'
33
export * from './useAnalysisController'
44
export * from './useMoveRecommendations'
55
export * from './useBoardDescription'
6-
export * from './useGameAnalysis'

src/hooks/useAnalysisController/useAnalysisController.ts

Lines changed: 178 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
11
import { Chess } from 'chess.ts'
2-
import { Key, useContext, useEffect, useMemo, useState } from 'react'
2+
import {
3+
Key,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
useCallback,
9+
useRef,
10+
} from 'react'
311

4-
import { AnalyzedGame } from 'src/types'
12+
import { AnalyzedGame, GameNode } from 'src/types'
513
import type { DrawShape } from 'chessground/draw'
614
import { MAIA_MODELS } from 'src/constants/common'
715
import { useTreeController, useLocalStorage } from '..'
816
import { useEngineAnalysis } from './useEngineAnalysis'
9-
import { useGameAnalysis } from './useGameAnalysis'
1017
import { useBoardDescription } from './useBoardDescription'
1118
import { useMoveRecommendations } from './useMoveRecommendations'
1219
import { MaiaEngineContext } from 'src/contexts/MaiaEngineContext'
1320
import { generateColorSanMapping, calculateBlunderMeter } from './utils'
1421
import { StockfishEngineContext } from 'src/contexts/StockfishEngineContext'
1522

23+
export interface GameAnalysisProgress {
24+
currentMoveIndex: number
25+
totalMoves: number
26+
currentMove: string
27+
isAnalyzing: boolean
28+
isComplete: boolean
29+
isCancelled: boolean
30+
}
31+
32+
export interface GameAnalysisConfig {
33+
targetDepth: number
34+
}
35+
1636
export const useAnalysisController = (
1737
game: AnalyzedGame,
1838
initialOrientation?: 'white' | 'black',
@@ -30,6 +50,151 @@ export const useAnalysisController = (
3050
const [analysisState, setAnalysisState] = useState(0)
3151
const inProgressAnalyses = useMemo(() => new Set<string>(), [])
3252

53+
// Game analysis state
54+
const [gameAnalysisConfig, setGameAnalysisConfig] =
55+
useState<GameAnalysisConfig>({
56+
targetDepth: 18,
57+
})
58+
59+
const [gameAnalysisProgress, setGameAnalysisProgress] =
60+
useState<GameAnalysisProgress>({
61+
currentMoveIndex: 0,
62+
totalMoves: 0,
63+
currentMove: '',
64+
isAnalyzing: false,
65+
isComplete: false,
66+
isCancelled: false,
67+
})
68+
69+
const gameAnalysisController = useRef<{
70+
cancelled: boolean
71+
currentNode: GameNode | null
72+
}>({
73+
cancelled: false,
74+
currentNode: null,
75+
})
76+
77+
// Simple batch analysis functions that reuse existing analysis infrastructure
78+
const startGameAnalysis = useCallback(
79+
async (targetDepth: number) => {
80+
// If already analyzing, cancel the current analysis first
81+
if (gameAnalysisProgress.isAnalyzing) {
82+
gameAnalysisController.current.cancelled = true
83+
stockfish.stopEvaluation()
84+
}
85+
86+
// Reset state
87+
gameAnalysisController.current.cancelled = false
88+
gameAnalysisController.current.currentNode = null
89+
90+
const mainLine = game.tree.getMainLine()
91+
92+
setGameAnalysisConfig({ targetDepth })
93+
setGameAnalysisProgress({
94+
currentMoveIndex: 0,
95+
totalMoves: mainLine.length,
96+
currentMove: '',
97+
isAnalyzing: true,
98+
isComplete: false,
99+
isCancelled: false,
100+
})
101+
102+
// Wait for engines to be ready
103+
let retries = 0
104+
const maxRetries = 50 // 5 seconds
105+
106+
while (
107+
retries < maxRetries &&
108+
(!stockfish.isReady() || maia.status !== 'ready') &&
109+
!gameAnalysisController.current.cancelled
110+
) {
111+
await new Promise((resolve) => setTimeout(resolve, 100))
112+
retries++
113+
}
114+
115+
if (gameAnalysisController.current.cancelled) {
116+
setGameAnalysisProgress((prev) => ({
117+
...prev,
118+
isAnalyzing: false,
119+
isCancelled: true,
120+
}))
121+
return
122+
}
123+
124+
// Analyze each position in the main line
125+
for (let i = 0; i < mainLine.length; i++) {
126+
if (gameAnalysisController.current.cancelled) break
127+
128+
const node = mainLine[i]
129+
gameAnalysisController.current.currentNode = node
130+
131+
// Update the UI to show the current node being analyzed (live update)
132+
controller.setCurrentNode(node)
133+
134+
const moveDisplay = node.san || node.move || `Position ${i + 1}`
135+
136+
setGameAnalysisProgress((prev) => ({
137+
...prev,
138+
currentMoveIndex: i + 1,
139+
currentMove: moveDisplay,
140+
}))
141+
142+
// Wait for analysis to reach target depth (the useEngineAnalysis will handle this)
143+
let analysisRetries = 0
144+
const maxAnalysisRetries = 600 // 60 seconds max per position
145+
146+
while (
147+
analysisRetries < maxAnalysisRetries &&
148+
!gameAnalysisController.current.cancelled &&
149+
(!node.analysis.stockfish ||
150+
node.analysis.stockfish.depth < targetDepth)
151+
) {
152+
await new Promise((resolve) => setTimeout(resolve, 100))
153+
analysisRetries++
154+
}
155+
}
156+
157+
// Analysis complete
158+
setGameAnalysisProgress((prev) => ({
159+
...prev,
160+
isAnalyzing: false,
161+
isComplete: !gameAnalysisController.current.cancelled,
162+
isCancelled: gameAnalysisController.current.cancelled,
163+
}))
164+
165+
gameAnalysisController.current.currentNode = null
166+
},
167+
[
168+
game.tree,
169+
gameAnalysisProgress.isAnalyzing,
170+
stockfish,
171+
maia.status,
172+
controller.setCurrentNode,
173+
],
174+
)
175+
176+
const cancelGameAnalysis = useCallback(() => {
177+
gameAnalysisController.current.cancelled = true
178+
stockfish.stopEvaluation()
179+
180+
setGameAnalysisProgress((prev) => ({
181+
...prev,
182+
isAnalyzing: false,
183+
isCancelled: true,
184+
}))
185+
}, [stockfish])
186+
187+
const resetGameAnalysisProgress = useCallback(() => {
188+
setGameAnalysisProgress({
189+
currentMoveIndex: 0,
190+
totalMoves: 0,
191+
currentMove: '',
192+
isAnalyzing: false,
193+
isComplete: false,
194+
isCancelled: false,
195+
})
196+
}, [])
197+
33198
const [currentMove, setCurrentMove] = useState<[string, string] | null>()
34199
const [currentMaiaModel, setCurrentMaiaModel] = useLocalStorage(
35200
'currentMaiaModel',
@@ -47,13 +212,7 @@ export const useAnalysisController = (
47212
inProgressAnalyses,
48213
currentMaiaModel,
49214
setAnalysisState,
50-
)
51-
52-
const gameAnalysis = useGameAnalysis(
53-
game.tree,
54-
currentMaiaModel,
55-
setAnalysisState,
56-
controller.setCurrentNode,
215+
gameAnalysisProgress.isAnalyzing ? gameAnalysisConfig.targetDepth : 18,
57216
)
58217

59218
const availableMoves = useMemo(() => {
@@ -187,6 +346,14 @@ export const useAnalysisController = (
187346
arrows,
188347
stockfish: stockfish,
189348
maia: maia,
190-
gameAnalysis,
349+
gameAnalysis: {
350+
progress: gameAnalysisProgress,
351+
config: gameAnalysisConfig,
352+
setConfig: setGameAnalysisConfig,
353+
startAnalysis: startGameAnalysis,
354+
cancelAnalysis: cancelGameAnalysis,
355+
resetProgress: resetGameAnalysisProgress,
356+
isEnginesReady: stockfish.isReady() && maia.status === 'ready',
357+
},
191358
}
192359
}

src/hooks/useAnalysisController/useEngineAnalysis.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import { MaiaEngineContext, StockfishEngineContext } from 'src/contexts'
88
export const useEngineAnalysis = (
99
currentNode: GameNode | null,
1010
inProgressAnalyses: Set<string>,
11-
1211
currentMaiaModel: string,
1312
setAnalysisState: React.Dispatch<React.SetStateAction<number>>,
13+
targetDepth = 18,
1414
) => {
1515
const maia = useContext(MaiaEngineContext)
1616
const stockfish = useContext(StockfishEngineContext)
@@ -132,7 +132,7 @@ export const useEngineAnalysis = (
132132
if (!currentNode) return
133133
if (
134134
currentNode.analysis.stockfish &&
135-
currentNode.analysis.stockfish?.depth >= 18
135+
currentNode.analysis.stockfish?.depth >= targetDepth
136136
)
137137
return
138138

@@ -160,6 +160,7 @@ export const useEngineAnalysis = (
160160
const evaluationStream = stockfish.streamEvaluations(
161161
chess.fen(),
162162
chess.moves().length,
163+
targetDepth,
163164
)
164165

165166
if (evaluationStream && !cancelled) {
@@ -194,5 +195,5 @@ export const useEngineAnalysis = (
194195
cancelled = true
195196
clearTimeout(timeoutId)
196197
}
197-
}, [currentNode, stockfish, currentMaiaModel, setAnalysisState])
198+
}, [currentNode, stockfish, currentMaiaModel, setAnalysisState, targetDepth])
198199
}

0 commit comments

Comments
 (0)