Skip to content

Commit 71ce043

Browse files
feat: condense summary + add stockfish evaluation graph
1 parent 99b0a90 commit 71ce043

File tree

1 file changed

+130
-128
lines changed

1 file changed

+130
-128
lines changed
Lines changed: 130 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useMemo, useEffect } from 'react'
22
import { motion } from 'framer-motion'
3+
import { LineChart, Line, XAxis, YAxis, ResponsiveContainer, ReferenceLine } from 'recharts'
34
import { AnalyzedGame } from 'src/types'
45
import { extractPlayerMistakes } from 'src/lib/analysis'
56

@@ -10,7 +11,6 @@ interface Props {
1011
}
1112

1213
interface GameSummary {
13-
totalMoves: number
1414
whiteMistakes: {
1515
total: number
1616
blunders: number
@@ -21,8 +21,11 @@ interface GameSummary {
2121
blunders: number
2222
inaccuracies: number
2323
}
24-
averageDepth: number
25-
positionsAnalyzed: number
24+
evaluationData: Array<{
25+
ply: number
26+
evaluation: number
27+
moveNumber: number
28+
}>
2629
}
2730

2831
export const AnalysisSummaryModal: React.FC<Props> = ({
@@ -47,87 +50,79 @@ export const AnalysisSummaryModal: React.FC<Props> = ({
4750
const whiteMistakes = extractPlayerMistakes(game.tree, 'white')
4851
const blackMistakes = extractPlayerMistakes(game.tree, 'black')
4952

50-
// Calculate analysis depth statistics
51-
let totalDepth = 0
52-
let positionsAnalyzed = 0
53-
54-
mainLine.forEach((node) => {
55-
if (node.analysis.stockfish && node.analysis.stockfish.depth > 0) {
56-
totalDepth += node.analysis.stockfish.depth
57-
positionsAnalyzed++
53+
// Generate evaluation data for the chart
54+
const evaluationData = mainLine.slice(1).map((node, index) => {
55+
const evaluation = node.analysis.stockfish?.model_optimal_cp || 0
56+
// Convert centipawns to evaluation (cap at +/- 5 for chart readability)
57+
const normalizedEval = Math.max(-5, Math.min(5, evaluation / 100))
58+
59+
return {
60+
ply: index + 1,
61+
evaluation: normalizedEval,
62+
moveNumber: Math.ceil((index + 1) / 2),
5863
}
5964
})
6065

61-
const averageDepth =
62-
positionsAnalyzed > 0 ? Math.round(totalDepth / positionsAnalyzed) : 0
63-
6466
return {
65-
totalMoves: Math.ceil((mainLine.length - 1) / 2), // Convert plies to moves
6667
whiteMistakes: {
6768
total: whiteMistakes.length,
6869
blunders: whiteMistakes.filter((m) => m.type === 'blunder').length,
69-
inaccuracies: whiteMistakes.filter((m) => m.type === 'inaccuracy')
70-
.length,
70+
inaccuracies: whiteMistakes.filter((m) => m.type === 'inaccuracy').length,
7171
},
7272
blackMistakes: {
7373
total: blackMistakes.length,
7474
blunders: blackMistakes.filter((m) => m.type === 'blunder').length,
75-
inaccuracies: blackMistakes.filter((m) => m.type === 'inaccuracy')
76-
.length,
75+
inaccuracies: blackMistakes.filter((m) => m.type === 'inaccuracy').length,
7776
},
78-
averageDepth,
79-
positionsAnalyzed,
77+
evaluationData,
8078
}
8179
}, [game.tree])
8280

8381
if (!isOpen) return null
8482

85-
const MistakeSection = ({
86-
title,
87-
color,
88-
mistakes,
89-
playerName,
90-
}: {
83+
const formatYAxisLabel = (value: number) => {
84+
if (value === 0) return '0.00'
85+
return value > 0 ? `+${value.toFixed(1)}` : `${value.toFixed(1)}`
86+
}
87+
88+
const PlayerPerformanceRow = ({
89+
title,
90+
color,
91+
mistakes,
92+
playerName
93+
}: {
9194
title: string
9295
color: string
9396
mistakes: { total: number; blunders: number; inaccuracies: number }
94-
playerName: string
97+
playerName: string
9598
}) => (
96-
<div className="flex flex-col gap-2 rounded-lg border border-white/10 bg-background-2/40 p-4">
97-
<div className="flex items-center gap-2">
98-
<div
99-
className={`h-3 w-3 rounded-full ${color === 'white' ? 'bg-white' : 'border border-white bg-black'}`}
100-
/>
101-
<h3 className="font-semibold">{title}</h3>
102-
<span className="text-sm text-secondary">({playerName})</span>
99+
<div className="flex items-center justify-between rounded border border-white/5 bg-background-2/30 p-3">
100+
<div className="flex items-center gap-3">
101+
<div className={`h-4 w-4 rounded-full ${color === 'white' ? 'bg-white' : 'border border-white bg-black'}`} />
102+
<div>
103+
<p className="text-sm font-medium text-primary">{playerName}</p>
104+
<p className="text-xs text-secondary">{title}</p>
105+
</div>
103106
</div>
104-
107+
105108
{mistakes.total === 0 ? (
106-
<div className="flex items-center gap-2 text-sm text-green-400">
107-
<span className="material-symbols-outlined !text-sm">
108-
check_circle
109-
</span>
110-
<span>No significant mistakes detected</span>
109+
<div className="flex items-center gap-2">
110+
<span className="material-symbols-outlined !text-sm text-green-400">check_circle</span>
111+
<span className="text-xs text-green-400">Clean game</span>
111112
</div>
112113
) : (
113-
<div className="grid grid-cols-3 gap-3 text-sm">
114-
<div className="flex flex-col items-center gap-1 rounded bg-background-3/50 p-2">
115-
<span className="text-lg font-bold text-red-400">
116-
{mistakes.blunders}
117-
</span>
118-
<span className="text-xs text-secondary">Blunders</span>
114+
<div className="flex items-center gap-3 text-xs">
115+
<div className="flex items-center gap-1">
116+
<span className="font-semibold text-red-400">{mistakes.blunders}</span>
117+
<span className="text-secondary">blunders</span>
119118
</div>
120-
<div className="flex flex-col items-center gap-1 rounded bg-background-3/50 p-2">
121-
<span className="text-lg font-bold text-orange-400">
122-
{mistakes.inaccuracies}
123-
</span>
124-
<span className="text-xs text-secondary">Inaccuracies</span>
119+
<div className="flex items-center gap-1">
120+
<span className="font-semibold text-orange-400">{mistakes.inaccuracies}</span>
121+
<span className="text-secondary">inaccuracies</span>
125122
</div>
126-
<div className="flex flex-col items-center gap-1 rounded bg-background-3/50 p-2">
127-
<span className="text-lg font-bold text-primary">
128-
{mistakes.total}
129-
</span>
130-
<span className="text-xs text-secondary">Total</span>
123+
<div className="flex items-center gap-1">
124+
<span className="font-semibold text-primary">{mistakes.total}</span>
125+
<span className="text-secondary">total</span>
131126
</div>
132127
</div>
133128
)}
@@ -136,7 +131,7 @@ export const AnalysisSummaryModal: React.FC<Props> = ({
136131

137132
return (
138133
<motion.div
139-
className="absolute left-0 top-0 z-20 flex h-screen w-screen flex-col items-center justify-center bg-black/70 px-4 backdrop-blur-sm md:px-0"
134+
className="fixed inset-0 z-20 flex items-center justify-center bg-black/70 px-4 backdrop-blur-sm md:px-0"
140135
initial={{ opacity: 0 }}
141136
animate={{ opacity: 1 }}
142137
exit={{ opacity: 0 }}
@@ -146,97 +141,104 @@ export const AnalysisSummaryModal: React.FC<Props> = ({
146141
}}
147142
>
148143
<motion.div
149-
className="flex w-full flex-col gap-5 rounded-md border border-white/10 bg-background-1 p-5 md:w-[min(600px,50vw)] md:p-6"
144+
className="flex w-full max-w-4xl flex-col gap-5 rounded-lg border border-white/10 bg-background-1 p-6 shadow-2xl"
150145
initial={{ y: 20, opacity: 0 }}
151146
animate={{ y: 0, opacity: 1 }}
152147
exit={{ y: 20, opacity: 0 }}
153148
transition={{ duration: 0.3 }}
154149
onClick={(e) => e.stopPropagation()}
155150
>
156-
<div className="flex items-center gap-2">
157-
<span className="material-symbols-outlined text-2xl text-human-3">
158-
analytics
159-
</span>
160-
<h2 className="text-xl font-semibold">Analysis Summary</h2>
161-
</div>
162-
163-
<div className="flex flex-col gap-4">
164-
{/* Game Overview */}
165-
<div className="flex flex-col gap-2">
166-
<h3 className="font-semibold text-primary/90">Game Overview</h3>
167-
<div className="grid grid-cols-2 gap-3 text-sm md:grid-cols-4">
168-
<div className="flex flex-col items-center gap-1 rounded bg-background-2/60 p-3">
169-
<span className="text-lg font-bold text-human-3">
170-
{summary.totalMoves}
171-
</span>
172-
<span className="text-xs text-secondary">Total Moves</span>
173-
</div>
174-
<div className="flex flex-col items-center gap-1 rounded bg-background-2/60 p-3">
175-
<span className="text-lg font-bold text-human-3">
176-
{summary.positionsAnalyzed}
177-
</span>
178-
<span className="text-xs text-secondary">Positions</span>
179-
</div>
180-
<div className="flex flex-col items-center gap-1 rounded bg-background-2/60 p-3">
181-
<span className="text-lg font-bold text-human-3">
182-
d{summary.averageDepth}
183-
</span>
184-
<span className="text-xs text-secondary">Avg Depth</span>
185-
</div>
186-
<div className="flex flex-col items-center gap-1 rounded bg-background-2/60 p-3">
187-
<span className="text-lg font-bold text-green-400">100%</span>
188-
<span className="text-xs text-secondary">Complete</span>
189-
</div>
190-
</div>
151+
{/* Header */}
152+
<div className="flex items-center justify-between border-b border-white/10 pb-4">
153+
<div className="flex items-center gap-3">
154+
<span className="material-symbols-outlined text-2xl text-human-4">
155+
analytics
156+
</span>
157+
<h2 className="text-xl font-bold text-primary">Analysis Complete</h2>
191158
</div>
159+
<button
160+
onClick={onClose}
161+
className="text-secondary transition-colors hover:text-primary"
162+
>
163+
<span className="material-symbols-outlined">close</span>
164+
</button>
165+
</div>
192166

167+
<div className="flex flex-col gap-5">
193168
{/* Player Performance */}
194169
<div className="flex flex-col gap-3">
195-
<h3 className="font-semibold text-primary/90">
196-
Player Performance
197-
</h3>
198-
199-
<MistakeSection
200-
title="White"
201-
color="white"
202-
mistakes={summary.whiteMistakes}
203-
playerName={game.whitePlayer.name}
204-
/>
205-
206-
<MistakeSection
207-
title="Black"
208-
color="black"
209-
mistakes={summary.blackMistakes}
210-
playerName={game.blackPlayer.name}
211-
/>
170+
<h3 className="text-sm font-semibold text-primary/90">Player Performance</h3>
171+
<div className="flex flex-col gap-2">
172+
<PlayerPerformanceRow
173+
title="White"
174+
color="white"
175+
mistakes={summary.whiteMistakes}
176+
playerName={game.whitePlayer.name}
177+
/>
178+
<PlayerPerformanceRow
179+
title="Black"
180+
color="black"
181+
mistakes={summary.blackMistakes}
182+
playerName={game.blackPlayer.name}
183+
/>
184+
</div>
212185
</div>
213186

214-
{/* Analysis Tips */}
215-
<div className="flex items-start gap-2 rounded bg-human-4/10 p-3">
216-
<span className="material-symbols-outlined !text-base text-human-3">
217-
lightbulb
218-
</span>
219-
<div className="flex flex-col gap-1">
220-
<p className="text-sm font-medium text-human-3">Next Steps</p>
221-
<p className="text-xs text-primary/80">
222-
Navigate through the game to review specific positions. Use the
223-
&quot;Learn from Mistakes&quot; feature to practice improving
224-
the identified errors.
225-
</p>
187+
{/* Evaluation Chart */}
188+
<div className="flex flex-col gap-3">
189+
<h3 className="text-sm font-semibold text-primary/90">Game Evaluation</h3>
190+
<div className="rounded border border-white/10 bg-background-2/20 p-4">
191+
<div className="h-48">
192+
<ResponsiveContainer width="100%" height="100%">
193+
<LineChart data={summary.evaluationData} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
194+
<XAxis
195+
dataKey="moveNumber"
196+
tick={{ fontSize: 12, fill: '#94a3b8' }}
197+
tickLine={{ stroke: '#334155' }}
198+
axisLine={{ stroke: '#334155' }}
199+
/>
200+
<YAxis
201+
domain={[-5, 5]}
202+
tickFormatter={formatYAxisLabel}
203+
tick={{ fontSize: 12, fill: '#94a3b8' }}
204+
tickLine={{ stroke: '#334155' }}
205+
axisLine={{ stroke: '#334155' }}
206+
/>
207+
<ReferenceLine y={0} stroke="#475569" strokeDasharray="2 2" />
208+
<Line
209+
type="monotone"
210+
dataKey="evaluation"
211+
stroke="#3b82f6"
212+
strokeWidth={2}
213+
dot={false}
214+
activeDot={{ r: 4, fill: '#3b82f6' }}
215+
/>
216+
</LineChart>
217+
</ResponsiveContainer>
218+
</div>
219+
<div className="mt-2 flex items-center justify-center gap-4 text-xs text-secondary">
220+
<div className="flex items-center gap-1">
221+
<div className="h-2 w-2 rounded-full bg-blue-500"></div>
222+
<span>Stockfish Evaluation</span>
223+
</div>
224+
<span></span>
225+
<span>Positive values favor White</span>
226+
</div>
226227
</div>
227228
</div>
228229
</div>
229230

230-
<div className="flex justify-end gap-2 pt-2">
231+
{/* Footer */}
232+
<div className="flex justify-end border-t border-white/10 pt-4">
231233
<button
232234
onClick={onClose}
233-
className="flex h-9 items-center gap-1 rounded bg-human-4 px-4 text-sm font-medium text-white transition duration-200 hover:bg-human-4/90"
235+
className="flex items-center gap-2 rounded bg-human-4 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-human-4/90"
234236
>
235-
<span className="material-symbols-outlined text-sm">check</span>
236-
Got it
237+
<span className="material-symbols-outlined !text-sm">check</span>
238+
Continue
237239
</button>
238240
</div>
239241
</motion.div>
240242
</motion.div>
241243
)
242-
}
244+
}

0 commit comments

Comments
 (0)