Skip to content

Commit b8f5f4d

Browse files
fix: prevent rebuilding of game node/tree on new move
1 parent 706714f commit b8f5f4d

2 files changed

Lines changed: 142 additions & 53 deletions

File tree

src/hooks/useBroadcastController.ts

Lines changed: 111 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ export const useBroadcastController = (): BroadcastStreamController => {
193193
if (roundData) {
194194
const game = roundData.games.get(gameId)
195195
if (game) {
196+
console.log(
197+
'Manual game selection:',
198+
game.white + ' vs ' + game.black,
199+
)
196200
setCurrentGame(game)
197201
}
198202
}
@@ -226,46 +230,99 @@ export const useBroadcastController = (): BroadcastStreamController => {
226230
const startingFen =
227231
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
228232

229-
// Build game tree from moves
230-
const tree = new GameTree(startingFen)
231-
const chess = new Chess(startingFen)
232-
let currentNode = tree.getRoot()
233-
234-
const gameStates = [
235-
{
236-
board: startingFen,
237-
lastMove: undefined as [string, string] | undefined,
238-
san: undefined as string | undefined,
239-
check: false,
240-
maia_values: {},
241-
},
242-
]
233+
// Check if we have an existing game state to build upon
234+
const existingLiveGame = gameStates.current.get(broadcastGame.id)
235+
236+
let tree: GameTree
237+
let movesList: any[]
238+
let existingMoveCount = 0
239+
240+
if (existingLiveGame && existingLiveGame.tree) {
241+
// Reuse existing tree and states - preserve all analysis and variations
242+
tree = existingLiveGame.tree
243+
movesList = [...existingLiveGame.moves]
244+
existingMoveCount = movesList.length - 1 // Subtract 1 for initial position
245+
console.log(
246+
`Reusing existing tree with ${existingMoveCount} moves, adding ${broadcastGame.moves.length - existingMoveCount} new moves`,
247+
)
248+
} else {
249+
// Create new tree only for new games
250+
tree = new GameTree(startingFen)
251+
movesList = [
252+
{
253+
board: startingFen,
254+
lastMove: undefined as [string, string] | undefined,
255+
san: undefined as string | undefined,
256+
check: false,
257+
maia_values: {},
258+
},
259+
]
260+
console.log(
261+
`Creating new tree for ${broadcastGame.white} vs ${broadcastGame.black}`,
262+
)
263+
}
243264

244-
// Process each move
245-
for (const moveStr of broadcastGame.moves) {
246-
try {
247-
const move = chess.move(moveStr)
248-
if (move) {
249-
const newFen = chess.fen()
250-
const uci =
251-
move.from + move.to + (move.promotion ? move.promotion : '')
252-
253-
gameStates.push({
254-
board: newFen,
255-
lastMove: [move.from, move.to],
256-
san: move.san,
257-
check: chess.inCheck(),
258-
maia_values: {},
259-
})
260-
261-
currentNode = tree.addMainMove(currentNode, newFen, uci, move.san)
265+
// Only process new moves that we don't already have
266+
if (broadcastGame.moves.length > existingMoveCount) {
267+
const chess = new Chess(startingFen)
268+
let currentNode = tree.getRoot()
269+
270+
// Replay existing moves to get to the current position
271+
for (let i = 0; i < existingMoveCount; i++) {
272+
try {
273+
const move = chess.move(broadcastGame.moves[i])
274+
if (move && currentNode.mainChild) {
275+
currentNode = currentNode.mainChild
276+
}
277+
} catch (error) {
278+
console.warn(
279+
`Error replaying existing move ${broadcastGame.moves[i]}:`,
280+
error,
281+
)
282+
break
283+
}
284+
}
285+
286+
// Add only the new moves
287+
for (let i = existingMoveCount; i < broadcastGame.moves.length; i++) {
288+
try {
289+
const moveStr = broadcastGame.moves[i]
290+
const move = chess.move(moveStr)
291+
if (move) {
292+
const newFen = chess.fen()
293+
const uci =
294+
move.from + move.to + (move.promotion ? move.promotion : '')
295+
296+
movesList.push({
297+
board: newFen,
298+
lastMove: [move.from, move.to],
299+
san: move.san,
300+
check: chess.inCheck(),
301+
maia_values: {},
302+
})
303+
304+
currentNode = tree.addMainMove(currentNode, newFen, uci, move.san)
305+
console.log(`Added new move: ${move.san}`)
306+
}
307+
} catch (error) {
308+
console.warn(
309+
`Error processing new move ${broadcastGame.moves[i]}:`,
310+
error,
311+
)
312+
break
262313
}
263-
} catch (error) {
264-
console.warn(`Error processing move ${moveStr}:`, error)
265-
break
266314
}
267315
}
268316

317+
// Preserve existing availableMoves array (legacy) and extend if needed
318+
const availableMoves =
319+
existingLiveGame?.availableMoves || new Array(movesList.length).fill({})
320+
321+
// Extend availableMoves array if we have new moves
322+
while (availableMoves.length < movesList.length) {
323+
availableMoves.push({})
324+
}
325+
269326
return {
270327
id: broadcastGame.id,
271328
blackPlayer: {
@@ -278,10 +335,8 @@ export const useBroadcastController = (): BroadcastStreamController => {
278335
},
279336
gameType: 'broadcast',
280337
type: 'stream' as const,
281-
moves: gameStates,
282-
availableMoves: new Array(gameStates.length).fill(
283-
{},
284-
) as AvailableMoves[],
338+
moves: movesList,
339+
availableMoves: availableMoves as AvailableMoves[],
285340
termination:
286341
broadcastGame.result === '*'
287342
? undefined
@@ -294,8 +349,8 @@ export const useBroadcastController = (): BroadcastStreamController => {
294349
? 'black'
295350
: 'none',
296351
},
297-
maiaEvaluations: [],
298-
stockfishEvaluations: [],
352+
maiaEvaluations: existingLiveGame?.maiaEvaluations || [],
353+
stockfishEvaluations: existingLiveGame?.stockfishEvaluations || [],
299354
loadedFen: broadcastGame.fen,
300355
loaded: true,
301356
tree,
@@ -366,15 +421,30 @@ export const useBroadcastController = (): BroadcastStreamController => {
366421

367422
// Update current game data if it's in the update, but don't switch to a different game
368423
if (currentGame) {
424+
console.log(
425+
'Current game selected:',
426+
currentGame.white + ' vs ' + currentGame.black,
427+
)
369428
const updatedCurrentGame = parseResult.games.find(
370429
(g) => g.id === currentGame.id,
371430
)
372431
if (updatedCurrentGame) {
432+
console.log('Updating current game with new data')
373433
// Update the currently selected game with new data (including clocks)
374434
setCurrentGame(updatedCurrentGame)
435+
} else {
436+
console.log(
437+
'Current game not in update - keeping selection unchanged',
438+
)
375439
}
440+
// Important: Do NOT change game selection if current game is not in the update
376441
} else if (parseResult.games.length > 0) {
377442
// Auto-select first game only if no game is currently selected
443+
console.log('No game selected - auto-selecting first game')
444+
console.log(
445+
'Auto-selecting:',
446+
parseResult.games[0].white + ' vs ' + parseResult.games[0].black,
447+
)
378448
setCurrentGame(parseResult.games[0])
379449
}
380450

src/pages/broadcast/[broadcastId]/[roundId].tsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,31 +121,45 @@ const BroadcastAnalysisPage: NextPage = () => {
121121

122122
useEffect(() => {
123123
const currentLiveGame = (broadcastController as any).currentLiveGame
124-
if (currentLiveGame?.tree && analysisController) {
124+
if (
125+
currentLiveGame?.tree &&
126+
analysisController &&
127+
analysisController.currentNode
128+
) {
125129
try {
126130
const mainLine = currentLiveGame.tree.getMainLine()
127131
const currentMoveCount = mainLine.length
128132

129133
// If new moves have been added to the game
130134
if (currentMoveCount > lastGameMoveCount.current) {
131-
lastGameMoveCount.current = currentMoveCount
135+
console.log(
136+
`New move detected: ${lastGameMoveCount.current} -> ${currentMoveCount}`,
137+
)
132138

133139
// Find the last node in the main line
134-
let lastNode = currentLiveGame.tree.getRoot()
135-
while (lastNode.mainChild) {
136-
lastNode = lastNode.mainChild
137-
}
140+
const lastNode = mainLine[mainLine.length - 1]
138141

139-
// Only auto-follow if user is currently at the previous last node
140-
if (
141-
analysisController.currentNode &&
142-
lastNode.parent === analysisController.currentNode
143-
) {
142+
// Only auto-follow if user is currently at the previous last node (or close to it)
143+
const isAtLatestPosition =
144+
lastNode.parent === analysisController.currentNode ||
145+
lastNode === analysisController.currentNode
146+
147+
console.log('Auto-follow check:', {
148+
isAtLatestPosition,
149+
currentNodeId: analysisController.currentNode.id,
150+
lastNodeParentId: lastNode.parent?.id,
151+
lastNodeId: lastNode.id,
152+
})
153+
154+
if (isAtLatestPosition) {
155+
console.log('Auto-following to new move')
144156
analysisController.setCurrentNode(lastNode)
145157
}
158+
159+
lastGameMoveCount.current = currentMoveCount
146160
}
147161
} catch (error) {
148-
console.error('Error setting current node:', error)
162+
console.error('Error in auto-follow logic:', error)
149163
}
150164
}
151165
}, [(broadcastController as any).currentLiveGame, analysisController])
@@ -157,6 +171,11 @@ const BroadcastAnalysisPage: NextPage = () => {
157171
const mainLine = currentLiveGame.tree.getMainLine()
158172
if (mainLine.length > 0) {
159173
analysisController.setCurrentNode(mainLine[mainLine.length - 1])
174+
// Update the move count tracker for the new game
175+
lastGameMoveCount.current = mainLine.length
176+
} else {
177+
// Reset move count for games with no moves
178+
lastGameMoveCount.current = 0
160179
}
161180
}
162181
}, [broadcastController.currentGame?.id])

0 commit comments

Comments
 (0)