Skip to content

Commit 029696c

Browse files
feat: add blundermeter, highlight, moves by rating legend, eval bars, player stats
1 parent dd1385f commit 029696c

File tree

11 files changed

+344
-250
lines changed

11 files changed

+344
-250
lines changed

src/components/Analysis/BlunderMeter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const BlunderMeter: React.FC<Props> = ({
1414
goodMoveChance,
1515
}: Props) => {
1616
return (
17-
<div className="flex flex-col gap-1 bg-background-1 p-4">
17+
<div className="flex flex-col gap-1 bg-background-1">
1818
<Tooltip id="probability" />
1919
<p className="text-sm">
2020
<span className="text-green-500">Good</span>{' '}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { MaiaEvaluation, StockfishEvaluation } from 'src/types'
2+
import { BlunderMeter } from './BlunderMeter'
3+
4+
interface Props {
5+
moveEvaluation: {
6+
maia?: MaiaEvaluation
7+
stockfish?: StockfishEvaluation
8+
}
9+
colorSanMapping: {
10+
[move: string]: {
11+
san: string
12+
color: string
13+
}
14+
}
15+
blunderMeter: {
16+
blunderMoveChance: number
17+
okMoveChance: number
18+
goodMoveChance: number
19+
}
20+
}
21+
22+
export const Highlight: React.FC<Props> = ({
23+
blunderMeter,
24+
moveEvaluation,
25+
colorSanMapping,
26+
}: Props) => {
27+
return (
28+
<div className="grid h-full max-h-full w-full grid-cols-3 flex-col overflow-hidden rounded border-[0.5px] border-white/40 bg-background-1">
29+
<div className="col-span-1 flex flex-col border-r-[0.5px] border-white/40">
30+
<div className="flex flex-col gap-1 p-4">
31+
<p className="text-xl font-semibold">Current Position</p>
32+
<p className="text-sm text-secondary">
33+
Maia predicts that Black will play{' '}
34+
{moveEvaluation.maia
35+
? colorSanMapping[Object.keys(moveEvaluation.maia.policy)[0]].san
36+
: '...'}{' '}
37+
next. This is a blunder.
38+
</p>
39+
</div>
40+
<div className="grid grid-cols-2">
41+
<div className="flex flex-col items-center justify-center bg-human-3/5 py-4">
42+
<p className="text-sm text-human-2">Maia White Win %</p>
43+
<p className="text-2xl font-bold text-human-1">
44+
{moveEvaluation.maia
45+
? `${Math.round(moveEvaluation.maia?.value * 1000) / 10}%`
46+
: '...'}
47+
</p>
48+
</div>
49+
<div className="flex flex-col items-center justify-center bg-engine-3/5 py-4">
50+
<p className="text-sm text-engine-2">
51+
SF Eval
52+
{moveEvaluation.stockfish?.depth
53+
? ` (D${moveEvaluation.stockfish?.depth})`
54+
: ''}
55+
</p>
56+
<p className="text-2xl font-bold text-engine-1">
57+
{moveEvaluation.stockfish
58+
? `${moveEvaluation.stockfish.model_optimal_cp / 100 > 0 ? '+' : ''}${moveEvaluation.stockfish.model_optimal_cp / 100}`
59+
: '...'}
60+
</p>
61+
</div>
62+
</div>
63+
<div className="flex flex-col p-4">
64+
<BlunderMeter {...blunderMeter} />
65+
</div>
66+
</div>
67+
<div className="col-span-2 flex flex-col"></div>
68+
</div>
69+
)
70+
}

src/components/Analysis/HorizontalEvaluationBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ export const HorizontalEvaluationBar: React.FC<Props> = ({
1515
width = Math.max(0, Math.min(100, width))
1616

1717
return (
18-
<div className="relative flex h-6 w-[60vh] max-w-[70vw] flex-col justify-center overflow-hidden rounded-sm bg-engine-3/30">
18+
<div className="relative flex h-6 w-[calc(60vh-0.25rem)] max-w-[70vw] flex-col justify-center overflow-hidden bg-engine-3/30">
1919
<p className="z-10 ml-2 whitespace-nowrap text-xs">{label}</p>
2020
<div
21-
className="absolute bottom-0 left-0 z-0 h-full w-full transform rounded-r-sm bg-engine-3 duration-300"
21+
className="absolute left-0 top-0 z-0 h-full w-full transform bg-engine-3 duration-300"
2222
style={{ width: `${width}%` }}
2323
/>
2424
</div>

src/components/Analysis/MoveMap.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
CartesianGrid,
1010
ResponsiveContainer,
1111
} from 'recharts'
12+
import type { DrawShape } from 'chessground/draw'
1213

1314
interface Props {
1415
moveMap?: { move: string; x: number; y: number }[]
@@ -18,13 +19,26 @@ interface Props {
1819
color: string
1920
}
2021
}
22+
23+
setHoverArrow: React.Dispatch<React.SetStateAction<DrawShape | null>>
2124
}
2225

2326
export const MoveMap: React.FC<Props> = ({
2427
moveMap,
2528
colorSanMapping,
29+
setHoverArrow,
2630
}: Props) => {
27-
console.log(moveMap)
31+
const onMouseEnter = (move: string) => {
32+
setHoverArrow({
33+
orig: move.slice(0, 2) as any,
34+
dest: move.slice(2, 4) as any,
35+
brush: 'green',
36+
modifiers: {
37+
lineWidth: 10,
38+
},
39+
})
40+
}
41+
2842
return (
2943
<div className="flex h-full max-h-full flex-col overflow-hidden rounded bg-background-1/60">
3044
<p className="p-4 text-lg text-white">Move Map</p>
@@ -143,6 +157,11 @@ export const MoveMap: React.FC<Props> = ({
143157
<Cell
144158
key={`cell-${entry.move}${index}`}
145159
fill={colorSanMapping[entry.move].color || '#fff'}
160+
onMouseEnter={() => onMouseEnter(entry.move)}
161+
onMouseOutCapture={() => {
162+
console.log('beep')
163+
setHoverArrow(null)
164+
}}
146165
/>
147166
))}
148167
</Scatter>

src/components/Analysis/MoveRecommendations.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { DrawShape } from 'chessground/draw'
12
interface Props {
23
recommendations: {
34
maia?: { move: string; prob: number }[]
@@ -9,12 +10,26 @@ interface Props {
910
color: string
1011
}
1112
}
13+
14+
setHoverArrow: React.Dispatch<React.SetStateAction<DrawShape | null>>
1215
}
1316

1417
export const MoveRecommendations: React.FC<Props> = ({
1518
recommendations,
1619
colorSanMapping,
20+
setHoverArrow,
1721
}: Props) => {
22+
const onMouseEnter = (move: string) => {
23+
setHoverArrow({
24+
orig: move.slice(0, 2) as any,
25+
dest: move.slice(2, 4) as any,
26+
brush: 'green',
27+
modifiers: {
28+
lineWidth: 10,
29+
},
30+
})
31+
}
32+
1833
return (
1934
<div className="col-span-2 grid h-full max-h-full grid-cols-2 flex-col overflow-hidden rounded">
2035
<div className="flex flex-col gap-2 bg-background-1 p-5">
@@ -36,7 +51,13 @@ export const MoveRecommendations: React.FC<Props> = ({
3651
color: colorSanMapping[move].color,
3752
}}
3853
>
39-
<p className="font-mono">{colorSanMapping[move].san}</p>
54+
<p
55+
className="cursor-default font-mono hover:underline"
56+
onMouseEnter={() => onMouseEnter(move)}
57+
onMouseOutCapture={() => setHoverArrow(null)}
58+
>
59+
{colorSanMapping[move].san}
60+
</p>
4061
<p className="font-mono text-sm">
4162
{Math.round(prob * 1000) / 10}%
4263
</p>
@@ -63,7 +84,13 @@ export const MoveRecommendations: React.FC<Props> = ({
6384
color: colorSanMapping[move].color,
6485
}}
6586
>
66-
<p className="font-mono">{colorSanMapping[move].san}</p>
87+
<p
88+
className="cursor-default font-mono hover:underline"
89+
onMouseEnter={() => onMouseEnter(move)}
90+
onMouseOutCapture={() => setHoverArrow(null)}
91+
>
92+
{colorSanMapping[move].san}
93+
</p>
6794
<p className="font-mono text-sm">{cp / 100}</p>
6895
</div>
6996
))}

src/components/Analysis/MovesByRating.tsx

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ResponsiveContainer,
77
XAxis,
88
YAxis,
9+
Tooltip,
910
} from 'recharts'
1011

1112
interface Props {
@@ -77,6 +78,34 @@ export const MovesByRating: React.FC<Props> = ({
7778
tickLine={false}
7879
tickFormatter={(value) => `${value}%`}
7980
/>
81+
<defs>
82+
{moves &&
83+
Object.keys(moves[0]).map((move, i) => {
84+
if (move === 'rating') {
85+
return null
86+
}
87+
return (
88+
<linearGradient
89+
id={`color${move}`}
90+
x1="0"
91+
y1="0"
92+
x2="0"
93+
y2="1"
94+
>
95+
<stop
96+
offset="5%"
97+
stopColor={colorSanMapping[move].color}
98+
stopOpacity={0.5}
99+
/>
100+
<stop
101+
offset="95%"
102+
stopColor={colorSanMapping[move].color}
103+
stopOpacity={0}
104+
/>
105+
</linearGradient>
106+
)
107+
})}
108+
</defs>
80109
{moves &&
81110
Object.keys(moves[0]).map((move, i) => {
82111
if (move === 'rating') {
@@ -88,21 +117,47 @@ export const MovesByRating: React.FC<Props> = ({
88117
yAxisId="left"
89118
dataKey={move}
90119
dot={{
120+
r: 3,
91121
stroke: colorSanMapping[move].color,
92-
strokeWidth: 1,
122+
strokeWidth: 3,
93123
}}
94124
stroke={colorSanMapping[move].color}
95-
fill={colorSanMapping[move].color}
96-
fillOpacity={0.1}
125+
fill={`url(#color${move})`}
97126
strokeWidth={3}
98127
/>
99128
)
100129
})}
130+
<Tooltip
131+
content={({ payload }) => {
132+
return (
133+
<div className="flex w-32 flex-col rounded border border-white/10 bg-background-1 pb-2">
134+
<div className="flex px-3 py-2">
135+
{payload ? (
136+
<p className="text-sm">{payload[0]?.payload.rating}</p>
137+
) : null}
138+
</div>
139+
{payload?.map((point) => {
140+
const san = colorSanMapping[point.name as string].san
141+
const prob = Math.round((point.value as number) * 10) / 10
142+
return (
143+
<div className="flex items-center justify-between px-3">
144+
<p className="text-xs">{san}</p>
145+
<p className="font-mono text-xs">{prob}%</p>
146+
</div>
147+
)
148+
})}
149+
</div>
150+
)
151+
}}
152+
/>
101153
<Legend
102-
verticalAlign="top"
103154
align="right"
155+
verticalAlign="top"
104156
wrapperStyle={{ top: -14, right: 20, fontSize: 14 }}
105157
iconSize={0}
158+
formatter={(value) => {
159+
return colorSanMapping[value as string].san
160+
}}
106161
/>
107162
</AreaChart>
108163
</ResponsiveContainer>

src/components/Analysis/VerticalEvaluationBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const VerticalEvaluationBar: React.FC<Props> = ({
1414
const height = ((value ?? min - min) / (max - min)) * 100
1515

1616
return (
17-
<div className="relative flex h-[60vh] max-h-[70vw] w-6 flex-col justify-end overflow-hidden rounded-sm bg-human-3/30">
17+
<div className="relative flex h-[calc(60vh-0.25rem)] max-h-[70vw] w-6 flex-col justify-end overflow-hidden bg-human-3/30">
1818
<p className="z-10 mb-3 -rotate-90 whitespace-nowrap text-xs">{label}</p>
1919
<div
2020
className="absolute bottom-0 left-0 z-0 h-full w-full transform rounded-t-sm bg-human-3 duration-300"

src/components/Analysis/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
export * from './MoveMap'
22
export * from './MovePlot'
3+
export * from './Highlight'
34
export * from './Tournament'
45
export * from './BlunderMeter'
56
export * from './UserGameList'
67
export * from './AnalysisGameList'
78
export * from './HorizontalEvaluationBar'
89
export * from './PositionEvaluationContainer'
910
export * from './VerticalEvaluationBar'
10-
export * from './MovesByRating'
1111
export * from './MoveRecommendations'
12+
export * from './MovesByRating'

src/components/Board/GameBoard.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { defaults } from 'chessground/state'
23
import { useCallback, useContext } from 'react'
3-
import type { DrawShape } from 'chessground/draw'
44
import Chessground from '@react-chess/chessground'
5+
import type { DrawBrush, DrawBrushes, DrawShape } from 'chessground/draw'
56

67
import { BaseGame, Check } from 'src/types'
78
import { GameControllerContext } from 'src/contexts'
@@ -17,6 +18,7 @@ interface Props {
1718
check?: Check
1819
}
1920
shapes?: DrawShape[]
21+
brushes?: DrawBrushes
2022
}
2123

2224
export const GameBoard: React.FC<Props> = ({
@@ -26,6 +28,7 @@ export const GameBoard: React.FC<Props> = ({
2628
setCurrentMove,
2729
setCurrentSquare,
2830
shapes,
31+
brushes,
2932
}: Props) => {
3033
const { currentIndex, orientation } = useContext(GameControllerContext)
3134

@@ -56,6 +59,7 @@ export const GameBoard: React.FC<Props> = ({
5659
},
5760
drawable: {
5861
autoShapes: shapes || [],
62+
brushes: { ...defaults().drawable.brushes, ...brushes },
5963
},
6064
fen: move ? move.fen : game.moves[currentIndex]?.board,
6165
lastMove: move

0 commit comments

Comments
 (0)