133 lines
4.1 KiB
JavaScript
133 lines
4.1 KiB
JavaScript
/**
|
||
* PGN Parser - Parses PGN files and extracts game data
|
||
*/
|
||
|
||
function parsePGN(pgnText) {
|
||
const games = [];
|
||
// Split by game boundaries - each game starts with [Event
|
||
const gameBlocks = pgnText.split(/\[\s*Event\s*"/);
|
||
|
||
for (let i = 1; i < gameBlocks.length; i++) {
|
||
const game = parseGameBlock(gameBlocks[i]);
|
||
if (game) games.push(game);
|
||
}
|
||
|
||
return games;
|
||
}
|
||
|
||
function parseGameBlock(block) {
|
||
try {
|
||
const headers = {};
|
||
|
||
// Der Event-Header fehlt, weil wir danach splitten – extrahiere ihn aus dem Blockanfang
|
||
const eventEnd = block.indexOf('"]');
|
||
if (eventEnd > 1) {
|
||
headers.Event = block.substring(1, eventEnd);
|
||
}
|
||
|
||
const headerRegex = /^\s*\[(\w+)\s+"([^"]*)"\]/gm;
|
||
let match;
|
||
|
||
// Extract all headers
|
||
let tempBlock = block;
|
||
while ((match = headerRegex.exec(tempBlock)) !== null) {
|
||
headers[match[1]] = match[2];
|
||
}
|
||
|
||
// Extract moves - everything after the last header
|
||
const lastHeaderEnd = block.lastIndexOf('"]');
|
||
let movesText = lastHeaderEnd > -1 ? block.substring(lastHeaderEnd + 2).trim() : '';
|
||
|
||
// Remove comments from moves for cleaner parsing
|
||
const moves = parseMoves(movesText);
|
||
|
||
return {
|
||
event: headers.Event || '',
|
||
site: headers.Site || '',
|
||
date: headers.Date || '',
|
||
round: headers.Round || '',
|
||
white: headers.White || '',
|
||
black: headers.Black || '',
|
||
result: headers.Result || '',
|
||
termination: headers.Termination || '',
|
||
whiteElo: headers.WhiteElo || '',
|
||
blackElo: headers.BlackElo || '',
|
||
whiteClock: headers.WhiteClock || '',
|
||
blackClock: headers.BlackClock || '',
|
||
moves: moves,
|
||
isLive: headers.Termination === 'unterminated' || headers.Result === '*'
|
||
};
|
||
} catch (e) {
|
||
console.error('Error parsing game:', e);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function parseMoves(movesText) {
|
||
const moves = [];
|
||
|
||
let clockWhite = null;
|
||
let clockBlack = null;
|
||
let color = 'w';
|
||
|
||
const tokenRegex = /\d+\.\s*|\{[^}]*\}|\S+/g;
|
||
const tokens = movesText.match(tokenRegex) || [];
|
||
|
||
for (let i = 0; i < tokens.length; i++) {
|
||
let token = tokens[i].trim();
|
||
|
||
if (['1-0', '0-1', '1/2-1/2', '*'].includes(token)) {
|
||
moves.push({ san: token, isResult: true, whiteClock: clockWhite, blackClock: clockBlack });
|
||
continue;
|
||
}
|
||
|
||
if (/^\d+\.$/.test(token)) continue;
|
||
|
||
if (token.startsWith('{')) {
|
||
const clkMatch = token.match(/\[%clk\s+([\d:]+)\]/);
|
||
if (clkMatch && moves.length > 0) {
|
||
const lastMove = moves[moves.length - 1];
|
||
if (!lastMove.isResult && lastMove.color) {
|
||
if (lastMove.color === 'w') {
|
||
clockWhite = clkMatch[1];
|
||
} else {
|
||
clockBlack = clkMatch[1];
|
||
}
|
||
lastMove.whiteClock = clockWhite;
|
||
lastMove.blackClock = clockBlack;
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
const move = { san: token, isResult: false, whiteClock: clockWhite, blackClock: clockBlack, color };
|
||
moves.push(move);
|
||
color = color === 'w' ? 'b' : 'w';
|
||
}
|
||
|
||
return moves;
|
||
}
|
||
|
||
function filterLaraGames(games) {
|
||
return games.filter(game =>
|
||
game.white.toLowerCase().includes('kiesewetter') ||
|
||
game.black.toLowerCase().includes('kiesewetter')
|
||
);
|
||
}
|
||
|
||
function getLiveGame(laraGames) {
|
||
// Return the game that is still in progress
|
||
return laraGames.find(game => game.isLive) || null;
|
||
}
|
||
|
||
function getLatestGame(laraGames) {
|
||
if (laraGames.length === 0) return null;
|
||
// Sort by round number, return the highest round
|
||
const sorted = [...laraGames].sort((a, b) => {
|
||
const roundA = parseInt(a.round) || 0;
|
||
const roundB = parseInt(b.round) || 0;
|
||
return roundB - roundA;
|
||
});
|
||
return sorted[0];
|
||
}
|