feat: Add lookupPossibleOpenings and PGN parsing helpers
- Add lookupPossibleOpenings() to find multiple openings from move sequence - Add extractMoveSequenceFromPGN() to parse PGN strings - Add buildMoveSequenceFromSteps() for analysis page - Import moveIndex.json for fast lookup - Returns up to 5 possible openings per sequence
This commit is contained in:
@@ -3,6 +3,7 @@ import ecoB from '../../public/openings/ecoB.json';
|
||||
import ecoC from '../../public/openings/ecoC.json';
|
||||
import ecoD from '../../public/openings/ecoD.json';
|
||||
import ecoE from '../../public/openings/ecoE.json';
|
||||
import moveIndex from '../../public/openings/moveIndex.json';
|
||||
|
||||
export interface OpeningMetadata {
|
||||
src: string;
|
||||
@@ -34,3 +35,111 @@ export function lookupOpening(fen: string): OpeningMetadata | null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup possible openings from a move sequence.
|
||||
* Returns up to maxResults openings that match the given move sequence.
|
||||
*
|
||||
* @param moveSequence - The move sequence in SAN notation (e.g., "1. e4 c5 2. Nf3")
|
||||
* @param maxResults - Maximum number of results to return (default: 5)
|
||||
* @returns Array of opening metadata objects
|
||||
*
|
||||
* @example
|
||||
* // After 1.e4
|
||||
* const openings = lookupPossibleOpenings("1. e4", 5);
|
||||
* // Returns: [{ eco: "B00", name: "King's Pawn Game", ... }]
|
||||
*
|
||||
* @example
|
||||
* // After 1.e4 c5
|
||||
* const openings = lookupPossibleOpenings("1. e4 c5", 5);
|
||||
* // Returns: [{ eco: "B20", name: "Sicilian Defense", ... }, ...]
|
||||
*/
|
||||
export function lookupPossibleOpenings(
|
||||
moveSequence: string,
|
||||
maxResults: number = 5
|
||||
): OpeningMetadata[] {
|
||||
const normalized = moveSequence.trim();
|
||||
const matches = (moveIndex as Record<string, OpeningMetadata[]>)[normalized] || [];
|
||||
return matches.slice(0, maxResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract move sequence from PGN string.
|
||||
* Removes headers and metadata, keeping only the move sequence.
|
||||
*
|
||||
* @param pgn - PGN string (may include headers)
|
||||
* @returns Move sequence in SAN notation (e.g., "1. e4 c5 2. Nf3")
|
||||
*
|
||||
* @example
|
||||
* const pgn = '[Event "Game"]\n[Site "..."]\n\n1. e4 c5 2. Nf3';
|
||||
* const moves = extractMoveSequenceFromPGN(pgn);
|
||||
* // Returns: "1. e4 c5 2. Nf3"
|
||||
*/
|
||||
export function extractMoveSequenceFromPGN(pgn: string): string {
|
||||
// Remove headers (lines starting with '[')
|
||||
const lines = pgn.split('\n');
|
||||
const moveLines = lines.filter(line => !line.trim().startsWith('['));
|
||||
|
||||
// Join and clean up
|
||||
let moveSequence = moveLines.join(' ').trim();
|
||||
|
||||
// Remove result markers (1-0, 0-1, 1/2-1/2, *)
|
||||
moveSequence = moveSequence.replace(/\s+(1-0|0-1|1\/2-1\/2|\*)\s*$/, '');
|
||||
|
||||
// Remove comments in curly braces
|
||||
moveSequence = moveSequence.replace(/\{[^}]*\}/g, '');
|
||||
|
||||
// Remove variations in parentheses
|
||||
moveSequence = moveSequence.replace(/\([^)]*\)/g, '');
|
||||
|
||||
// Remove annotations ($1, $2, etc.)
|
||||
moveSequence = moveSequence.replace(/\$\d+/g, '');
|
||||
|
||||
// Remove NAG symbols (!, ?, !!, ??, !?, ?!)
|
||||
moveSequence = moveSequence.replace(/[!?]+/g, '');
|
||||
|
||||
// Normalize whitespace
|
||||
moveSequence = moveSequence.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return moveSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build move sequence from an array of move steps.
|
||||
* Used in the analysis page to construct the move sequence up to a given index.
|
||||
*
|
||||
* @param steps - Array of move steps with san and color properties
|
||||
* @param upToIndex - Build sequence up to this index (exclusive)
|
||||
* @returns Move sequence in SAN notation
|
||||
*
|
||||
* @example
|
||||
* const steps = [
|
||||
* { san: "e4", color: "white", moveNumber: 1, ... },
|
||||
* { san: "c5", color: "black", moveNumber: 1, ... },
|
||||
* { san: "Nf3", color: "white", moveNumber: 2, ... }
|
||||
* ];
|
||||
* const sequence = buildMoveSequenceFromSteps(steps, 3);
|
||||
* // Returns: "1. e4 c5 2. Nf3"
|
||||
*/
|
||||
export function buildMoveSequenceFromSteps(
|
||||
steps: Array<{ san: string; color: 'white' | 'black'; moveNumber: number }>,
|
||||
upToIndex: number
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
let currentMoveNumber = 0;
|
||||
|
||||
for (let i = 0; i < upToIndex && i < steps.length; i++) {
|
||||
const step = steps[i];
|
||||
|
||||
if (step.color === 'white') {
|
||||
// White's move - include move number
|
||||
parts.push(`${step.moveNumber}. ${step.san}`);
|
||||
currentMoveNumber = step.moveNumber;
|
||||
} else {
|
||||
// Black's move - no move number prefix
|
||||
parts.push(step.san);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user