fix: Undo button now properly truncates move history
- Added UNDO_TO_MOVE action that both navigates AND truncates history - Added undoToMove() function to OpeningTrainingContext - Updated handleUndoDeviation to use undoToMove instead of navigate Previously, the Undo button only navigated back but didn't remove the deviation moves, so the session remained in off-book state.
This commit is contained in:
@@ -52,6 +52,7 @@ export default function OpeningTrainer({
|
|||||||
currentFeedback,
|
currentFeedback,
|
||||||
initializeSession,
|
initializeSession,
|
||||||
makeMove,
|
makeMove,
|
||||||
|
undoToMove,
|
||||||
navigateToMove,
|
navigateToMove,
|
||||||
} = useOpeningTraining();
|
} = useOpeningTraining();
|
||||||
|
|
||||||
@@ -225,12 +226,12 @@ export default function OpeningTrainer({
|
|||||||
const handleUndoDeviation = () => {
|
const handleUndoDeviation = () => {
|
||||||
if (!session || session.deviationMoveIndex === null) return;
|
if (!session || session.deviationMoveIndex === null) return;
|
||||||
|
|
||||||
// Navigate back to the move before deviation
|
// Undo to the move before deviation (this truncates move history)
|
||||||
navigateToMove(session.deviationMoveIndex - 1);
|
const targetIndex = session.deviationMoveIndex - 1;
|
||||||
setShowDeviationDialog(false);
|
undoToMove(targetIndex);
|
||||||
|
|
||||||
// After a brief delay, make another legal move to continue in theory
|
// Close the dialog
|
||||||
// This allows the player to try again
|
setShowDeviationDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartGameFromPosition = () => {
|
const handleStartGameFromPosition = () => {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ interface OpeningTrainingContextType {
|
|||||||
// Actions
|
// Actions
|
||||||
initializeSession: (opening: OpeningMetadata, forceNew?: boolean) => Promise<void>;
|
initializeSession: (opening: OpeningMetadata, forceNew?: boolean) => Promise<void>;
|
||||||
makeMove: (san: string) => Promise<void>;
|
makeMove: (san: string) => Promise<void>;
|
||||||
|
undoToMove: (index: number) => void;
|
||||||
navigateToMove: (index: number) => void;
|
navigateToMove: (index: number) => void;
|
||||||
resetSession: () => void;
|
resetSession: () => void;
|
||||||
}
|
}
|
||||||
@@ -294,6 +295,17 @@ export function OpeningTrainingProvider({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const undoToMove = (index: number) => {
|
||||||
|
if (!session) return;
|
||||||
|
|
||||||
|
console.log('[OpeningTraining] Undoing to move index:', index);
|
||||||
|
|
||||||
|
dispatch({ type: 'UNDO_TO_MOVE', index });
|
||||||
|
|
||||||
|
// Clear feedback when undoing
|
||||||
|
setCurrentFeedback(null);
|
||||||
|
};
|
||||||
|
|
||||||
const navigateToMove = (index: number) => {
|
const navigateToMove = (index: number) => {
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
|
|
||||||
@@ -355,6 +367,7 @@ export function OpeningTrainingProvider({
|
|||||||
currentFeedback,
|
currentFeedback,
|
||||||
initializeSession,
|
initializeSession,
|
||||||
makeMove,
|
makeMove,
|
||||||
|
undoToMove,
|
||||||
navigateToMove,
|
navigateToMove,
|
||||||
resetSession,
|
resetSession,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export type SessionAction =
|
|||||||
| { type: 'NAVIGATE_PREVIOUS' }
|
| { type: 'NAVIGATE_PREVIOUS' }
|
||||||
| { type: 'NAVIGATE_NEXT' }
|
| { type: 'NAVIGATE_NEXT' }
|
||||||
| { type: 'NAVIGATE_TO_CURRENT' } // Jump back to end of history
|
| { type: 'NAVIGATE_TO_CURRENT' } // Jump back to end of history
|
||||||
|
| { type: 'UNDO_TO_MOVE'; index: number } // Navigate and truncate history
|
||||||
|
|
||||||
// Theory tracking
|
// Theory tracking
|
||||||
| { type: 'DEVIATION_DETECTED'; moveIndex: number }
|
| { type: 'DEVIATION_DETECTED'; moveIndex: number }
|
||||||
@@ -348,6 +349,47 @@ export function sessionReducer(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'UNDO_TO_MOVE': {
|
||||||
|
const { index } = action;
|
||||||
|
|
||||||
|
// Clamp index to valid range (-1 means start position, truncate all moves)
|
||||||
|
const clampedIndex = Math.max(-1, Math.min(index, state.moveHistory.length - 1));
|
||||||
|
|
||||||
|
// Truncate move history to this point
|
||||||
|
const newHistory = clampedIndex < 0 ? [] : state.moveHistory.slice(0, clampedIndex + 1);
|
||||||
|
|
||||||
|
// Build FEN at target index
|
||||||
|
const newFEN = buildFenAtIndex(newHistory, newHistory.length);
|
||||||
|
|
||||||
|
// Reset deviation if we're now back in theory
|
||||||
|
const isNowInTheory = isInTheory(state.opening, newHistory);
|
||||||
|
const newDeviationIndex = isNowInTheory ? null : state.deviationMoveIndex;
|
||||||
|
|
||||||
|
// Determine phase
|
||||||
|
let phase: SessionPhase;
|
||||||
|
if (newDeviationIndex !== null) {
|
||||||
|
phase = 'off_book';
|
||||||
|
} else if (isEndOfRepertoire(state.opening, newHistory.length)) {
|
||||||
|
phase = 'end_of_repertoire';
|
||||||
|
} else if (isOpponentTurn(state.opening, newHistory)) {
|
||||||
|
phase = 'opponent_turn';
|
||||||
|
} else {
|
||||||
|
phase = 'user_turn';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
moveHistory: newHistory,
|
||||||
|
currentMoveIndex: newHistory.length,
|
||||||
|
currentFEN: newFEN,
|
||||||
|
deviationMoveIndex: newDeviationIndex,
|
||||||
|
isInTheory: isNowInTheory,
|
||||||
|
phase,
|
||||||
|
pendingOpponentMove: null,
|
||||||
|
lastUpdatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'NAVIGATE_TO_CURRENT': {
|
case 'NAVIGATE_TO_CURRENT': {
|
||||||
// Jump to the end of history (current position)
|
// Jump to the end of history (current position)
|
||||||
const atEnd = state.currentMoveIndex === state.moveHistory.length;
|
const atEnd = state.currentMoveIndex === state.moveHistory.length;
|
||||||
|
|||||||
Reference in New Issue
Block a user