-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathuseMoveRecommendations.ts
More file actions
202 lines (170 loc) · 6.02 KB
/
useMoveRecommendations.ts
File metadata and controls
202 lines (170 loc) · 6.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import { useMemo } from 'react'
import { Chess } from 'chess.ts'
import { MAIA_MODELS } from 'src/constants/common'
import { GameNode, MaiaEvaluation, StockfishEvaluation } from 'src/types'
import { sortStockfishMoves } from './utils'
export const useMoveRecommendations = (
currentNode: GameNode | null,
moveEvaluation: {
maia?: MaiaEvaluation
stockfish?: StockfishEvaluation
} | null,
currentMaiaModel: string,
) => {
const recommendations = useMemo(() => {
if (!moveEvaluation) return {}
const isBlackTurn = currentNode?.turn === 'b'
const result: {
maia?: { move: string; prob: number }[]
stockfish?: {
move: string
cp: number
winrate?: number
winrate_loss?: number
cp_relative?: number
}[]
isBlackTurn?: boolean
} = {
isBlackTurn,
}
if (moveEvaluation?.maia) {
const policy = moveEvaluation.maia.policy
const maia = Object.entries(policy).map(([move, prob]) => ({
move,
prob,
}))
result.maia = maia
}
if (moveEvaluation?.stockfish) {
const cp_vec = moveEvaluation.stockfish.cp_vec
const cp_relative_vec = moveEvaluation.stockfish.cp_relative_vec || {}
const winrate_vec = moveEvaluation.stockfish.winrate_vec || {}
const winrate_loss_vec = moveEvaluation.stockfish.winrate_loss_vec || {}
const sortedMoves = sortStockfishMoves(
moveEvaluation.stockfish,
Object.keys(cp_vec),
)
const stockfish = sortedMoves.map((move) => ({
move,
cp: cp_vec[move] || 0,
winrate: winrate_vec[move] || 0,
winrate_loss: winrate_loss_vec[move] || 0,
cp_relative: cp_relative_vec[move] || 0,
}))
result.stockfish = stockfish
}
return result
}, [moveEvaluation, currentNode])
const movesByRating = useMemo(() => {
if (!currentNode) return
const maia = currentNode.analysis.maia
const stockfish = moveEvaluation?.stockfish
const candidates: string[][] = []
if (!maia) return
// Get top 3 Maia moves from selected rating level
if (maia[currentMaiaModel]?.policy) {
for (const move of Object.keys(maia[currentMaiaModel].policy).slice(
0,
3,
)) {
if (candidates.find((c) => c[0] === move)) continue
candidates.push([move, move])
}
}
// Get top 3 Stockfish moves
if (stockfish) {
for (const move of sortStockfishMoves(
stockfish,
Object.keys(stockfish.cp_vec),
).slice(0, 3)) {
if (candidates.find((c) => c[0] === move)) continue
candidates.push([move, move])
}
}
// Get top Maia move from each rating level
for (const rating of MAIA_MODELS) {
if (maia[rating]?.policy) {
const move = Object.keys(maia[rating].policy)[0]
if (move && !candidates.find((c) => c[0] === move)) {
candidates.push([move, move])
}
}
}
const data = []
for (const rating of MAIA_MODELS) {
const entry: { [key: string]: number } = {
rating: parseInt(rating.replace('maia_kdd_', '')),
}
for (const move of candidates) {
const probability = (maia[rating]?.policy?.[move[0]] || 0) * 100
entry[move[1]] = probability
}
data.push(entry)
}
return data
}, [currentMaiaModel, moveEvaluation, currentNode])
const moveMap = useMemo(() => {
if (!moveEvaluation?.maia || !moveEvaluation?.stockfish || !currentNode) {
return
}
// Get ALL legal moves from the current position
const chess = new Chess(currentNode.fen)
const legalMoves = chess.moves({ verbose: true })
// Create a set of all legal moves in UCI format
const allLegalMoves = new Set(
legalMoves.map((move) => `${move.from}${move.to}${move.promotion || ''}`),
)
const data = []
// Include all legal moves, not just top ones
for (const move of allLegalMoves) {
// Get Stockfish evaluation (default to worst possible if not evaluated)
const cp =
moveEvaluation.stockfish.cp_relative_vec[move] !== undefined
? Math.max(-3, moveEvaluation.stockfish.cp_relative_vec[move] / 100)
: -3 // Cap at -3 to avoid extreme values
// Get Maia probability (default to 0 if not in policy)
const prob = (moveEvaluation.maia.policy[move] || 0) * 100
// Calculate opacity based on position quality
// Moves in bottom-left (worse SF, less likely Maia) should be more transparent
// Normalize values: cp ranges from -3 to 0, prob ranges from 0 to ~100
const normalizedCp = (cp + 3) / 3 // 0 to 1 (0 = worst, 1 = best)
const normalizedProb = Math.min(prob / 50, 1) // 0 to 1 (capped at 50% for normalization)
// Combine both factors for opacity (minimum 0.2 for visibility, maximum 0.75)
const opacity = Math.max(
0.6,
Math.min(0.9, (normalizedCp + normalizedProb) / 2),
)
// Calculate importance score for sorting (higher = more important)
// This combines normalized Stockfish evaluation and Maia probability
const importance = normalizedCp + normalizedProb
// Calculate dynamic size based on importance (bigger = more important)
const size = 10
// Get additional data for comprehensive tooltip
const rawCp = moveEvaluation.stockfish.cp_vec[move] || 0
const mate = moveEvaluation.stockfish.mate_vec?.[move]
const winrate = moveEvaluation.stockfish.winrate_vec?.[move] || 0
const rawMaiaProb = moveEvaluation.maia.policy[move] || 0
data.push({
move,
x: cp,
y: prob,
z: 100,
opacity,
importance,
size,
rawCp,
winrate,
rawMaiaProb,
relativeCp: moveEvaluation.stockfish.cp_relative_vec[move],
mate,
})
}
// Sort by importance (ascending) so more important moves are rendered last (on top)
return data.sort((a, b) => a.importance - b.importance)
}, [moveEvaluation, currentNode])
return {
recommendations,
movesByRating,
moveMap,
}
}