/** * 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]; } window.parsePGN = parsePGN; window.filterLaraGames = filterLaraGames; window.getLiveGame = getLiveGame; window.getLatestGame = getLatestGame;