/** * 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 > 0) { headers.Event = block.substring(0, 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 = []; // Remove comments in curly braces movesText = movesText.replace(/\{[^}]*\}/g, ''); // Remove move numbers movesText = movesText.replace(/\d+\.\s*/g, ''); // Split into individual moves const tokens = movesText.split(/\s+/).filter(t => t.trim()); for (const token of tokens) { if (['1-0', '0-1', '1/2-1/2', '*'].includes(token)) { moves.push({ san: token, isResult: true }); } else if (token.length > 0) { moves.push({ san: token, isResult: false }); } } 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]; }