Skip to content

Commit 156390e

Browse files
Merge pull request #132 from CSSLab/fix-move-classification-discrepancy
Fix move classification discrepancy
2 parents ee55afb + 0eb76a7 commit 156390e

File tree

2 files changed

+119
-29
lines changed

2 files changed

+119
-29
lines changed

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>

src/components/Openings/OpeningSelectionModal.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ const PreviewPanel: React.FC<{
479479
<p className="text-xs text-secondary">Configure your drill settings</p>
480480
</div>
481481

482-
<div className="flex flex-1 flex-col gap-4 overflow-hidden p-3 md:p-4">
482+
<div className="red-scrollbar flex flex-1 flex-col gap-4 overflow-y-scroll p-3 md:p-4">
483483
<div className="flex flex-col gap-1">
484484
<p className="text-sm font-medium md:text-base">
485485
{previewOpening.name}
@@ -598,7 +598,7 @@ const SelectedPanel: React.FC<{
598598
<h2 className="text-xl font-bold">
599599
Selected Openings ({selections.length})
600600
</h2>
601-
<p className="text-xs text-secondary">Click to remove an opening</p>
601+
<p className="text-xs text-secondary">Click X remove an opening</p>
602602
</div>
603603

604604
{/* Mobile header */}
@@ -621,16 +621,8 @@ const SelectedPanel: React.FC<{
621621
<div className="flex w-full flex-col">
622622
{selections.map((selection) => (
623623
<div
624-
tabIndex={0}
625-
role="button"
626624
key={selection.id}
627-
onKeyDown={(e) => {
628-
if (e.key === 'Enter' || e.key === ' ') {
629-
removeSelection(selection.id)
630-
}
631-
}}
632-
onClick={() => removeSelection(selection.id)}
633-
className="group flex cursor-pointer items-center justify-between border-b border-white/5 p-3 transition-colors hover:bg-human-2/10 md:px-4"
625+
className="flex items-center justify-between border-b border-white/5 p-3 transition-colors md:px-4"
634626
>
635627
<div className="min-w-0 flex-1">
636628
<div className="flex items-center gap-2">
@@ -657,8 +649,16 @@ const SelectedPanel: React.FC<{
657649
</div>
658650
</div>
659651
</div>
660-
<button className="ml-2 text-secondary transition-colors group-hover:text-human-4">
661-
<span className="material-symbols-outlined text-sm">
652+
<button
653+
onKeyDown={(e) => {
654+
if (e.key === 'Enter' || e.key === ' ') {
655+
removeSelection(selection.id)
656+
}
657+
}}
658+
onClick={() => removeSelection(selection.id)}
659+
className="ml-2 text-secondary transition-colors hover:text-human-4"
660+
>
661+
<span className="material-symbols-outlined !text-lg">
662662
close
663663
</span>
664664
</button>

0 commit comments

Comments
 (0)