diff --git a/README.md b/README.md index 06a3571..9c0f539 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Live-Überwachung von Lara Kiesewetters Partien bei der **ODJM (Offene Deutsche - **Alle Partien** – Übersicht aller Runden mit Lara; Klick zum Wechseln - **Live-Erkennung** – Automatische Erkennung laufender (unterminierter) Partien - **Turniertabelle** – Tabellenplatz, Punkte, Siege/Unentschieden/Niederlagen von Lara -- **Auto-Refresh** – Aktualisiert die Daten alle 30 Sekunden +- **Auto-Refresh** – Aktualisiert die Daten alle 15 Sekunden (nur aktuelle Runden) - **Proxy-Server** – Lokaler Python-Server mit Stockfish-Engine-Wrapper, PGN-Proxy und statischem File-Serving ## Projektstruktur diff --git a/js/app.js b/js/app.js index 19b0371..5c910b1 100644 --- a/js/app.js +++ b/js/app.js @@ -18,6 +18,7 @@ let roundPgns = {}; let pollId = 0; let pollInterval = null; let updateTimer = null; +let previousMoveCount = -1; async function fetchRoundPGN(round) { const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/partien/${round}.pgn?t=${Date.now()}`); @@ -44,9 +45,11 @@ async function loadPGN(showOverlay = true) { return; } - // Alle Runden immer frisch von der DSJ laden (mit Cache-Busting) + // Nur aktuelle/live Runden neu laden, historische bleiben gecached const maxRound = currentRound + 1; - for (let r = 1; r <= maxRound; r++) { + const isFirstLoad = Object.keys(roundPgns).length === 0; + const startRound = isFirstLoad ? 1 : currentRound; + for (let r = startRound; r <= maxRound; r++) { const text = await fetchRoundPGN(r); if (currentPollId !== pollId) return; if (text !== null) roundPgns[r] = text; @@ -94,19 +97,27 @@ function updateBoard() { // Führe alle Züge aus const nonResultMoves = currentGame.moves.filter(m => !m.isResult); - // Aktuellen Move beibehalten — nie automatisch zum letzten Zug springen + // Aktuellen Move beibehalten — nie automatisch zum letzten Zug springen, + // es sei denn der Nutzer war schon am Ende und neue Züge sind dazugekommen if (currentMoveIndex >= nonResultMoves.length || currentMoveIndex < -1) { currentMoveIndex = nonResultMoves.length - 1; + } else if (previousMoveCount >= 0 && currentMoveIndex === previousMoveCount - 1 && nonResultMoves.length > previousMoveCount) { + // Nutzer war am letzten Zug, neue Züge sind da → vorrücken + currentMoveIndex = nonResultMoves.length - 1; } + previousMoveCount = nonResultMoves.length; chess = new Chess(); if (currentMoveIndex >= 0) { for (let i = 0; i <= currentMoveIndex && i < nonResultMoves.length; i++) { - try { - chess.move(nonResultMoves[i].san); - } catch { - break; + const san = nonResultMoves[i].san; + let result = chess.move(san); + if (!result) { + // chess.js lehnt unnötige Disambiguierung ab (z.B. Nge7 wenn Nc6 gepinnt) + const cleanSan = san.replace(/^([NBRQK])([a-h])?([1-8])?([a-h][1-8].*)$/, '$1$4'); + result = chess.move(cleanSan); } + if (!result) break; } } @@ -246,11 +257,14 @@ function goToMove(index) { chess = new Chess(); for (let i = 0; i <= index; i++) { - try { - chess.move(nonResultMoves[i].san); - } catch { - break; + const san = nonResultMoves[i].san; + let result = chess.move(san); + if (!result) { + // chess.js lehnt unnötige Disambiguierung ab (z.B. Nge7 wenn Nc6 gepinnt) + const cleanSan = san.replace(/^([NBRQK])([a-h])?([1-8])?([a-h][1-8].*)$/, '$1$4'); + result = chess.move(cleanSan); } + if (!result) break; } board.position(chess.fen(), true); @@ -508,12 +522,23 @@ function updateAllGamesList() { entry.className = 'game-entry'; if (currentGame && game === currentGame) entry.classList.add('active'); - const laraIsWhite = game.white.toLowerCase().includes('kiesewetter'); +const laraIsWhite = game.white.toLowerCase().includes('kiesewetter'); const opponent = laraIsWhite ? game.black : game.white; const color = laraIsWhite ? '⬜ Weiß' : '⬛ Schwarz'; - + + let resultIcon = ''; + if (game.isLive) { + resultIcon = '⏳'; + } else if (game.result === '1/2-1/2') { + resultIcon = '🤝'; + } else if ((laraIsWhite && game.result === '1-0') || (!laraIsWhite && game.result === '0-1')) { + resultIcon = '✅'; + } else { + resultIcon = '❌'; + } + entry.innerHTML = ` -
Runde ${game.round}
+
${resultIcon} Runde ${game.round}
Lara ${color} vs ${opponent}
${game.isLive ? '● Laufen' : game.result}
`; @@ -521,6 +546,8 @@ function updateAllGamesList() { entry.addEventListener('click', () => { userSelectedGame = true; currentGame = game; + previousMoveCount = -1; + currentMoveIndex = Number.MAX_SAFE_INTEGER; updateBoard(); updatePlayerInfo(); updateMovesList(); @@ -608,7 +635,7 @@ function updateTimestamp() { } /** - * Startet Auto-Refresh alle 30 Sekunden + * Startet Auto-Refresh alle 15 Sekunden */ function startAutoRefresh() { clearInterval(pollInterval); @@ -626,12 +653,12 @@ function startAutoRefresh() { if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; } loadPGN(false); lastUpdate = Date.now(); - }, 30000); + }, 15000); updateTimer = setInterval(() => { if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; } const elapsed = Date.now() - lastUpdate; - const remaining = Math.max(0, 30000 - elapsed); + const remaining = Math.max(0, 15000 - elapsed); const s = Math.floor(remaining / 1000); document.getElementById('refresh-timer').textContent = `${s}s`; }, 1000); diff --git a/package.json b/package.json index bbf6cdb..052977e 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,8 @@ "@eslint/markdown": "^8.0.2", "eslint": "^10.4.0", "globals": "^17.6.0" + }, + "dependencies": { + "chess.js": "^0.10.3" } }