Fix: Züge mit unnötiger SAN-Disambiguierung (Nge7) schlagen bei chess.js fehl; Auto-Advance bei neuen Zügen im Polling

This commit is contained in:
2026-05-27 10:22:52 +02:00
parent a42fe45812
commit 8d971dbef9
3 changed files with 48 additions and 18 deletions

View File

@@ -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 - **Alle Partien** Übersicht aller Runden mit Lara; Klick zum Wechseln
- **Live-Erkennung** Automatische Erkennung laufender (unterminierter) Partien - **Live-Erkennung** Automatische Erkennung laufender (unterminierter) Partien
- **Turniertabelle** Tabellenplatz, Punkte, Siege/Unentschieden/Niederlagen von Lara - **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 - **Proxy-Server** Lokaler Python-Server mit Stockfish-Engine-Wrapper, PGN-Proxy und statischem File-Serving
## Projektstruktur ## Projektstruktur

View File

@@ -18,6 +18,7 @@ let roundPgns = {};
let pollId = 0; let pollId = 0;
let pollInterval = null; let pollInterval = null;
let updateTimer = null; let updateTimer = null;
let previousMoveCount = -1;
async function fetchRoundPGN(round) { async function fetchRoundPGN(round) {
const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/partien/${round}.pgn?t=${Date.now()}`); 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; 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; 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); const text = await fetchRoundPGN(r);
if (currentPollId !== pollId) return; if (currentPollId !== pollId) return;
if (text !== null) roundPgns[r] = text; if (text !== null) roundPgns[r] = text;
@@ -94,19 +97,27 @@ function updateBoard() {
// Führe alle Züge aus // Führe alle Züge aus
const nonResultMoves = currentGame.moves.filter(m => !m.isResult); 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) { if (currentMoveIndex >= nonResultMoves.length || currentMoveIndex < -1) {
currentMoveIndex = nonResultMoves.length - 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(); chess = new Chess();
if (currentMoveIndex >= 0) { if (currentMoveIndex >= 0) {
for (let i = 0; i <= currentMoveIndex && i < nonResultMoves.length; i++) { for (let i = 0; i <= currentMoveIndex && i < nonResultMoves.length; i++) {
try { const san = nonResultMoves[i].san;
chess.move(nonResultMoves[i].san); let result = chess.move(san);
} catch { if (!result) {
break; // 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(); chess = new Chess();
for (let i = 0; i <= index; i++) { for (let i = 0; i <= index; i++) {
try { const san = nonResultMoves[i].san;
chess.move(nonResultMoves[i].san); let result = chess.move(san);
} catch { if (!result) {
break; // 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); board.position(chess.fen(), true);
@@ -512,8 +526,19 @@ function updateAllGamesList() {
const opponent = laraIsWhite ? game.black : game.white; const opponent = laraIsWhite ? game.black : game.white;
const color = laraIsWhite ? '⬜ Weiß' : '⬛ Schwarz'; 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 = ` entry.innerHTML = `
<div class="game-round">Runde ${game.round}</div> <div class="game-round">${resultIcon} Runde ${game.round}</div>
<div class="game-players">Lara ${color} vs ${opponent}</div> <div class="game-players">Lara ${color} vs ${opponent}</div>
<div class="game-result">${game.isLive ? '● Laufen' : game.result}</div> <div class="game-result">${game.isLive ? '● Laufen' : game.result}</div>
`; `;
@@ -521,6 +546,8 @@ function updateAllGamesList() {
entry.addEventListener('click', () => { entry.addEventListener('click', () => {
userSelectedGame = true; userSelectedGame = true;
currentGame = game; currentGame = game;
previousMoveCount = -1;
currentMoveIndex = Number.MAX_SAFE_INTEGER;
updateBoard(); updateBoard();
updatePlayerInfo(); updatePlayerInfo();
updateMovesList(); updateMovesList();
@@ -608,7 +635,7 @@ function updateTimestamp() {
} }
/** /**
* Startet Auto-Refresh alle 30 Sekunden * Startet Auto-Refresh alle 15 Sekunden
*/ */
function startAutoRefresh() { function startAutoRefresh() {
clearInterval(pollInterval); clearInterval(pollInterval);
@@ -626,12 +653,12 @@ function startAutoRefresh() {
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; } if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; }
loadPGN(false); loadPGN(false);
lastUpdate = Date.now(); lastUpdate = Date.now();
}, 30000); }, 15000);
updateTimer = setInterval(() => { updateTimer = setInterval(() => {
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; } if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; }
const elapsed = Date.now() - lastUpdate; 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); const s = Math.floor(remaining / 1000);
document.getElementById('refresh-timer').textContent = `${s}s`; document.getElementById('refresh-timer').textContent = `${s}s`;
}, 1000); }, 1000);

View File

@@ -7,5 +7,8 @@
"@eslint/markdown": "^8.0.2", "@eslint/markdown": "^8.0.2",
"eslint": "^10.4.0", "eslint": "^10.4.0",
"globals": "^17.6.0" "globals": "^17.6.0"
},
"dependencies": {
"chess.js": "^0.10.3"
} }
} }