Skip to content

Commit e5da95a

Browse files
Merge pull request #133 from CSSLab/dev
Bug fixes within move list + openings
2 parents f2aa4c5 + 2acd7e6 commit e5da95a

File tree

5 files changed

+165
-52
lines changed

5 files changed

+165
-52
lines changed

src/components/Board/MovesContainer.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable jsx-a11y/no-static-element-interactions */
22
/* eslint-disable jsx-a11y/click-events-have-key-events */
3-
import React, { useContext, useMemo, Fragment, useEffect } from 'react'
3+
import React, { useContext, useMemo, Fragment, useEffect, useRef } from 'react'
44
import { WindowSizeContext } from 'src/contexts'
55
import { GameNode, AnalyzedGame, Termination, BaseGame } from 'src/types'
66
import { TuringGame } from 'src/types/turing'
@@ -68,6 +68,8 @@ export const MovesContainer: React.FC<Props> = (props) => {
6868
showVariations = true,
6969
} = props
7070
const { isMobile } = useContext(WindowSizeContext)
71+
const containerRef = useRef<HTMLDivElement>(null)
72+
const currentMoveRef = useRef<HTMLDivElement>(null)
7173

7274
// Helper function to determine if move indicators should be shown
7375
const shouldShowIndicators = (node: GameNode | null) => {
@@ -114,6 +116,17 @@ export const MovesContainer: React.FC<Props> = (props) => {
114116
return () => window.removeEventListener('keydown', handleKeyDown)
115117
}, [baseController.currentNode, baseController.goToNode])
116118

119+
// Auto-scroll to current move
120+
useEffect(() => {
121+
if (currentMoveRef.current && containerRef.current) {
122+
currentMoveRef.current.scrollIntoView({
123+
behavior: 'smooth',
124+
block: 'nearest',
125+
inline: 'nearest',
126+
})
127+
}
128+
}, [baseController.currentNode])
129+
117130
const moves = useMemo(() => {
118131
const nodes = mainLineNodes.slice(1)
119132
const rows: (GameNode | null)[][] = []
@@ -187,7 +200,7 @@ export const MovesContainer: React.FC<Props> = (props) => {
187200

188201
if (isMobile) {
189202
return (
190-
<div className="w-full overflow-x-auto px-2">
203+
<div ref={containerRef} className="w-full overflow-x-auto px-2">
191204
<div className="flex flex-row items-center gap-1">
192205
{mobileMovePairs.map((pair, pairIndex) => (
193206
<React.Fragment key={pairIndex}>
@@ -196,6 +209,11 @@ export const MovesContainer: React.FC<Props> = (props) => {
196209
</div>
197210
{pair.whiteMove && (
198211
<div
212+
ref={
213+
baseController.currentNode === pair.whiteMove
214+
? currentMoveRef
215+
: null
216+
}
199217
onClick={() =>
200218
baseController.goToNode(pair.whiteMove as GameNode)
201219
}
@@ -224,6 +242,11 @@ export const MovesContainer: React.FC<Props> = (props) => {
224242
)}
225243
{pair.blackMove && (
226244
<div
245+
ref={
246+
baseController.currentNode === pair.blackMove
247+
? currentMoveRef
248+
: null
249+
}
227250
onClick={() =>
228251
baseController.goToNode(pair.blackMove as GameNode)
229252
}
@@ -272,14 +295,20 @@ export const MovesContainer: React.FC<Props> = (props) => {
272295
}
273296

274297
return (
275-
<div className="red-scrollbar grid h-48 auto-rows-min grid-cols-5 overflow-y-auto overflow-x-hidden whitespace-nowrap rounded-sm bg-background-1/60 md:h-full md:w-full">
298+
<div
299+
ref={containerRef}
300+
className="red-scrollbar grid h-48 auto-rows-min grid-cols-5 overflow-y-auto overflow-x-hidden whitespace-nowrap rounded-sm bg-background-1/60 md:h-full md:w-full"
301+
>
276302
{moves.map(([whiteNode, blackNode], index) => {
277303
return (
278304
<>
279305
<span className="flex h-7 items-center justify-center bg-background-2 text-sm text-secondary">
280306
{(whiteNode || blackNode)?.moveNumber}
281307
</span>
282308
<div
309+
ref={
310+
baseController.currentNode === whiteNode ? currentMoveRef : null
311+
}
283312
onClick={() => {
284313
if (whiteNode) baseController.goToNode(whiteNode)
285314
}}
@@ -312,6 +341,9 @@ export const MovesContainer: React.FC<Props> = (props) => {
312341
/>
313342
) : null}
314343
<div
344+
ref={
345+
baseController.currentNode === blackNode ? currentMoveRef : null
346+
}
315347
onClick={() => {
316348
if (blackNode) baseController.goToNode(blackNode)
317349
}}

src/components/Home/Sections/AnalysisSection.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ const AnalysisChessboard = ({ forceKey }: { forceKey?: number }) => {
7070
ref={containerRef}
7171
className="relative w-full"
7272
style={{
73-
transform: 'translateZ(0)',
7473
aspectRatio: '1/1',
7574
}}
7675
>
@@ -129,16 +128,12 @@ export const AnalysisSection = ({ id }: AnalysisSectionProps) => {
129128
})
130129

131130
const [renderKey, setRenderKey] = useState(0)
132-
const [windowSize, setWindowSize] = useState({ width: 0, height: 0 })
131+
133132
const [currentMaiaModel, setCurrentMaiaModel] = useState('maia_kdd_1500')
134133
const [hoverArrow, setHoverArrow] = useState<DrawShape | null>(null)
135134

136135
useEffect(() => {
137136
const handleResize = () => {
138-
setWindowSize({
139-
width: window.innerWidth,
140-
height: window.innerHeight,
141-
})
142137
setRenderKey((prev) => prev + 1)
143138
}
144139

@@ -239,20 +234,8 @@ export const AnalysisSection = ({ id }: AnalysisSectionProps) => {
239234
<div className="w-full rounded-t-sm bg-background-1/60 p-2 text-left text-sm font-medium text-primary/80">
240235
Spassky, Boris V.
241236
</div>
242-
<div
243-
className="relative w-full"
244-
style={{
245-
aspectRatio: '1/1',
246-
transform: 'translateZ(0)',
247-
}}
248-
>
249-
<div
250-
className="h-full w-full"
251-
style={{
252-
position: 'relative',
253-
transform: 'translateZ(0)',
254-
}}
255-
>
237+
<div className="relative aspect-square w-full">
238+
<div className="aspect-square h-full w-full">
256239
<AnalysisChessboard forceKey={renderKey} />
257240
</div>
258241
</div>

src/components/Openings/DrillPerformanceModal.tsx

Lines changed: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ const CustomTooltip: React.FC<{
375375
: 'text-yellow-400'
376376
}`}
377377
>
378-
{data.classification} {data.isPlayerMove ? '(You)' : '(Maia)'}
378+
{data.classification}
379379
</p>
380380
)}
381381
</div>
@@ -390,13 +390,20 @@ const EvaluationChart: React.FC<{
390390
onHoverMove?: (moveIndex: number) => void
391391
playerColor: 'white' | 'black'
392392
drill: DrillPerformanceData['drill']
393+
gameNodesMap: Map<string, GameNode>
394+
getChartClassification: (
395+
analysis: MoveAnalysis,
396+
gameNodesMap: Map<string, GameNode>,
397+
) => 'excellent' | 'good' | 'inaccuracy' | 'mistake' | 'blunder'
393398
}> = ({
394399
evaluationChart,
395400
moveAnalyses,
396401
currentMoveIndex = -1,
397402
onHoverMove,
398403
playerColor,
399404
drill,
405+
gameNodesMap,
406+
getChartClassification,
400407
}) => {
401408
if (evaluationChart.length === 0) {
402409
return (
@@ -406,11 +413,6 @@ const EvaluationChart: React.FC<{
406413
)
407414
}
408415

409-
// Helper function to get move classification (use analysis data for compatibility)
410-
const getChartClassification = (analysis: MoveAnalysis): string => {
411-
return analysis.classification || ''
412-
}
413-
414416
// Generate move numbers for chartData - create pairs first then extract chart data
415417
const movePairs = (() => {
416418
const pairs: Array<{
@@ -486,7 +488,7 @@ const EvaluationChart: React.FC<{
486488
}
487489

488490
const dynamicClassification = moveAnalysis
489-
? getChartClassification(moveAnalysis)
491+
? getChartClassification(moveAnalysis, gameNodesMap)
490492
: ''
491493

492494
// Get Stockfish depth by finding the corresponding game node
@@ -658,6 +660,11 @@ const DesktopLayout: React.FC<{
658660
gameTree: GameTree
659661
playerMoveCount: number
660662
treeController: ReturnType<typeof useTreeController>
663+
gameNodesMap: Map<string, GameNode>
664+
getChartClassification: (
665+
analysis: MoveAnalysis,
666+
gameNodesMap: Map<string, GameNode>,
667+
) => 'excellent' | 'good' | 'inaccuracy' | 'mistake' | 'blunder'
661668
}> = ({
662669
drill,
663670
filteredEvaluationChart,
@@ -669,6 +676,8 @@ const DesktopLayout: React.FC<{
669676
gameTree,
670677
playerMoveCount,
671678
treeController,
679+
gameNodesMap,
680+
getChartClassification,
672681
}) => (
673682
<div className="relative flex h-[90vh] max-h-[800px] w-[95vw] max-w-[1200px] flex-col overflow-hidden rounded-lg bg-background-1 shadow-2xl">
674683
{/* Header */}
@@ -753,6 +762,8 @@ const DesktopLayout: React.FC<{
753762
}}
754763
playerColor={drill.selection.playerColor}
755764
drill={drill}
765+
gameNodesMap={gameNodesMap}
766+
getChartClassification={getChartClassification}
756767
/>
757768

758769
{/* Critical Decisions Section */}
@@ -765,15 +776,44 @@ const DesktopLayout: React.FC<{
765776
</div>
766777
<div className="flex flex-col gap-2 px-3 pb-3">
767778
{(() => {
768-
// Get critical moves (blunders, excellent moves, inaccuracies)
769-
const criticalMoves = performanceData.moveAnalyses
770-
.filter((move) => move.isPlayerMove)
771-
.filter(
772-
(move) =>
773-
move.classification === 'blunder' ||
774-
move.classification === 'excellent' ||
775-
move.classification === 'inaccuracy',
776-
)
779+
// Get critical moves directly from game tree (like MovesContainer)
780+
const mainLineNodes = gameTree.getMainLine().slice(1) // Skip root
781+
const criticalMoves = mainLineNodes
782+
.filter((node, index) => {
783+
// Determine if this is a player move
784+
const chess = new Chess(node.fen)
785+
const isPlayerMove =
786+
drill.selection.playerColor === 'white'
787+
? chess.turn() === 'b' // If black to move, white just played
788+
: chess.turn() === 'w' // If white to move, black just played
789+
return isPlayerMove
790+
})
791+
.filter((node) => {
792+
// Filter for critical moves (same logic as MovesContainer)
793+
return node.blunder || node.inaccuracy || node.excellentMove
794+
})
795+
.map((node) => {
796+
// Convert to MoveAnalysis format for display
797+
let classification: 'blunder' | 'inaccuracy' | 'excellent' =
798+
'excellent'
799+
if (node.blunder) {
800+
classification = 'blunder'
801+
} else if (node.inaccuracy) {
802+
classification = 'inaccuracy'
803+
}
804+
805+
return {
806+
move: node.move || '',
807+
san: node.san || '',
808+
fen: node.fen,
809+
fenBeforeMove: node.parent?.fen,
810+
moveNumber: node.moveNumber,
811+
isPlayerMove: true,
812+
evaluation: 0, // Will be filled if needed
813+
classification,
814+
evaluationLoss: 0,
815+
}
816+
})
777817
.sort((a, b) => {
778818
// Sort by move number (chronological order)
779819
return a.moveNumber - b.moveNumber
@@ -919,6 +959,11 @@ const MobileLayout: React.FC<{
919959
gameTree: GameTree
920960
playerMoveCount: number
921961
treeController: ReturnType<typeof useTreeController>
962+
gameNodesMap: Map<string, GameNode>
963+
getChartClassification: (
964+
analysis: MoveAnalysis,
965+
gameNodesMap: Map<string, GameNode>,
966+
) => 'excellent' | 'good' | 'inaccuracy' | 'mistake' | 'blunder'
922967
}> = ({
923968
drill,
924969
filteredEvaluationChart,
@@ -932,6 +977,8 @@ const MobileLayout: React.FC<{
932977
gameTree,
933978
playerMoveCount,
934979
treeController,
980+
gameNodesMap,
981+
getChartClassification,
935982
}) => (
936983
<div className="relative flex h-[95vh] w-[95vw] flex-col overflow-hidden rounded-lg bg-background-1 shadow-2xl">
937984
{/* Header */}
@@ -1046,6 +1093,8 @@ const MobileLayout: React.FC<{
10461093
}}
10471094
playerColor={drill.selection.playerColor}
10481095
drill={drill}
1096+
gameNodesMap={gameNodesMap}
1097+
getChartClassification={getChartClassification}
10491098
/>
10501099
</div>
10511100
)}
@@ -1156,6 +1205,43 @@ export const DrillPerformanceModal: React.FC<Props> = ({
11561205
drill.selection.playerColor,
11571206
)
11581207

1208+
// Create a map of FEN -> GameNode for consistent classification
1209+
const gameNodesMap = useMemo(() => {
1210+
const map = new Map<string, GameNode>()
1211+
const collectNodes = (node: GameNode): void => {
1212+
map.set(node.fen, node)
1213+
node.children.forEach(collectNodes)
1214+
}
1215+
collectNodes(gameTree.getRoot())
1216+
return map
1217+
}, [gameTree])
1218+
1219+
// Helper function to get move classification from GameNode (same as MovesContainer)
1220+
const getChartClassification = useCallback(
1221+
(
1222+
analysis: MoveAnalysis,
1223+
gameNodesMap: Map<string, GameNode>,
1224+
): 'excellent' | 'good' | 'inaccuracy' | 'mistake' | 'blunder' => {
1225+
// Get the GameNode for the position after the move
1226+
const moveNode = gameNodesMap.get(analysis.fen)
1227+
if (!moveNode) {
1228+
return analysis.classification || 'good'
1229+
}
1230+
1231+
// Use the same logic as MovesContainer
1232+
if (moveNode.blunder) {
1233+
return 'blunder'
1234+
} else if (moveNode.inaccuracy) {
1235+
return 'inaccuracy'
1236+
} else if (moveNode.excellentMove) {
1237+
return 'excellent'
1238+
}
1239+
1240+
return 'good'
1241+
},
1242+
[],
1243+
)
1244+
11591245
// Count player moves from move analyses for display purposes
11601246
const playerMoveCount = useMemo(() => {
11611247
return moveAnalyses.filter((move) => move.isPlayerMove).length
@@ -1196,6 +1282,8 @@ export const DrillPerformanceModal: React.FC<Props> = ({
11961282
gameTree={gameTree}
11971283
playerMoveCount={playerMoveCount}
11981284
treeController={treeController}
1285+
gameNodesMap={gameNodesMap}
1286+
getChartClassification={getChartClassification}
11991287
/>
12001288
) : (
12011289
<DesktopLayout
@@ -1209,6 +1297,8 @@ export const DrillPerformanceModal: React.FC<Props> = ({
12091297
gameTree={gameTree}
12101298
playerMoveCount={playerMoveCount}
12111299
treeController={treeController}
1300+
gameNodesMap={gameNodesMap}
1301+
getChartClassification={getChartClassification}
12121302
/>
12131303
)}
12141304
</ModalContainer>

0 commit comments

Comments
 (0)