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:
@@ -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
|
||||||
|
|||||||
59
js/app.js
59
js/app.js
@@ -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);
|
||||||
@@ -508,12 +522,23 @@ function updateAllGamesList() {
|
|||||||
entry.className = 'game-entry';
|
entry.className = 'game-entry';
|
||||||
if (currentGame && game === currentGame) entry.classList.add('active');
|
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 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);
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user