Initial commit
This commit is contained in:
334
lara-chess/app.js
Normal file
334
lara-chess/app.js
Normal file
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* Lara Kiesewetter – Live Schachturnier
|
||||
* Haupt-Application
|
||||
*/
|
||||
|
||||
const PGN_URL = 'https://www.deutsche-schachjugend.de/2026/odjm-d/partien/gesamt-utf8.pgn';
|
||||
const REFRESH_INTERVAL = 60000; // 60 Sekunden
|
||||
const PLAYER_NAME = 'Kiesewetter, Lara';
|
||||
|
||||
let board = null;
|
||||
let chess = null;
|
||||
let currentGame = null;
|
||||
let allLaraGames = [];
|
||||
let refreshTimer = null;
|
||||
let countdown = 0;
|
||||
|
||||
/**
|
||||
* Lädt die PGN-Datei und aktualisiert die Anzeige
|
||||
*/
|
||||
async function loadPGN() {
|
||||
showLoading(true);
|
||||
hideError();
|
||||
|
||||
try {
|
||||
// Lokaler Proxy-Server (python server.py)
|
||||
const response = await fetch('http://localhost:8111/pgn');
|
||||
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const pgnText = await response.text();
|
||||
const allGames = parsePGN(pgnText);
|
||||
allLaraGames = filterLaraGames(allGames);
|
||||
|
||||
if (allLaraGames.length === 0) {
|
||||
showError('Keine Partien von Lara gefunden.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Finde die aktuelle/live Partie
|
||||
const liveGame = getLiveGame(allLaraGames);
|
||||
const targetGame = liveGame || getLatestGame(allLaraGames);
|
||||
|
||||
currentGame = targetGame;
|
||||
updateBoard();
|
||||
updatePlayerInfo();
|
||||
updateMovesList();
|
||||
updateAllGamesList();
|
||||
updateTimestamp();
|
||||
|
||||
showLoading(false);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden:', error);
|
||||
showError(`Fehler: ${error.message}`);
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert das Schachbrett
|
||||
*/
|
||||
function updateBoard() {
|
||||
if (!currentGame) return;
|
||||
|
||||
chess = new Chess();
|
||||
|
||||
// Spiegele das Brett, wenn Lara Schwarz hat
|
||||
const laraIsBlack = currentGame.black.toLowerCase().includes('kiesewetter');
|
||||
const orientation = laraIsBlack ? 'black' : 'white';
|
||||
|
||||
// Führe alle Züge aus
|
||||
for (const move of currentGame.moves) {
|
||||
if (move.isResult) break;
|
||||
try {
|
||||
chess.move(move.san);
|
||||
} catch (e) {
|
||||
// Ignoriere ungültige Züge
|
||||
}
|
||||
}
|
||||
|
||||
if (board) board.position(chess.fen(), true);
|
||||
else {
|
||||
board = Chessboard('board', {
|
||||
position: chess.fen(),
|
||||
orientation: orientation,
|
||||
pieceTheme: 'https://chessboardjs.com/img/chesspieces/wikipedia/{piece}.png',
|
||||
draggable: false,
|
||||
spawnMoveError: false
|
||||
});
|
||||
|
||||
// Click auf Züge
|
||||
document.getElementById('moves-list').addEventListener('click', handleMoveClick);
|
||||
}
|
||||
|
||||
// Highlight active player
|
||||
highlightActivePlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert die Spielerinformationen
|
||||
*/
|
||||
function updatePlayerInfo() {
|
||||
if (!currentGame) return;
|
||||
|
||||
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
||||
|
||||
if (laraIsWhite) {
|
||||
document.getElementById('white-name').textContent = currentGame.white;
|
||||
document.getElementById('white-elo').textContent = currentGame.whiteElo ? `(ELO: ${currentGame.whiteElo})` : '';
|
||||
document.getElementById('white-clock').textContent = formatClock(currentGame.whiteClock);
|
||||
|
||||
document.getElementById('black-name').textContent = currentGame.black;
|
||||
document.getElementById('black-elo').textContent = currentGame.blackElo ? `(ELO: ${currentGame.blackElo})` : '';
|
||||
document.getElementById('black-clock').textContent = formatClock(currentGame.blackClock);
|
||||
} else {
|
||||
document.getElementById('white-name').textContent = currentGame.white;
|
||||
document.getElementById('white-elo').textContent = currentGame.whiteElo ? `(ELO: ${currentGame.whiteElo})` : '';
|
||||
document.getElementById('white-clock').textContent = formatClock(currentGame.whiteClock);
|
||||
|
||||
document.getElementById('black-name').textContent = currentGame.black;
|
||||
document.getElementById('black-elo').textContent = currentGame.blackElo ? `(ELO: ${currentGame.blackElo})` : '';
|
||||
document.getElementById('black-clock').textContent = formatClock(currentGame.blackClock);
|
||||
}
|
||||
|
||||
// Round info
|
||||
const roundInfo = `Runde ${currentGame.round} – ${currentGame.event || 'Turnier'}`;
|
||||
document.getElementById('round-info').textContent = roundInfo;
|
||||
|
||||
// Result info
|
||||
const resultEl = document.getElementById('result-info');
|
||||
if (currentGame.isLive) {
|
||||
resultEl.innerHTML = '<span style="color: #4ade80;">● Laufen</span>';
|
||||
} else {
|
||||
resultEl.textContent = `Ergebnis: ${currentGame.result}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight den Spieler, der gerade am Zug ist
|
||||
*/
|
||||
function highlightActivePlayer() {
|
||||
if (!chess) return;
|
||||
|
||||
const whiteEl = document.getElementById('player-white');
|
||||
const blackEl = document.getElementById('player-black');
|
||||
|
||||
whiteEl.classList.remove('active');
|
||||
blackEl.classList.remove('active');
|
||||
|
||||
if (chess.turn() === 'w') {
|
||||
whiteEl.classList.add('active');
|
||||
} else {
|
||||
blackEl.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert die Zugliste
|
||||
*/
|
||||
function updateMovesList() {
|
||||
if (!currentGame) return;
|
||||
|
||||
const movesList = document.getElementById('moves-list');
|
||||
movesList.innerHTML = '';
|
||||
|
||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||
|
||||
for (let i = 0; i < nonResultMoves.length; i += 2) {
|
||||
const moveNumber = Math.floor(i / 2) + 1;
|
||||
|
||||
// Move number
|
||||
const numSpan = document.createElement('span');
|
||||
numSpan.className = 'move-number';
|
||||
numSpan.textContent = `${moveNumber}.`;
|
||||
movesList.appendChild(numSpan);
|
||||
|
||||
// White move
|
||||
const whiteMove = document.createElement('span');
|
||||
whiteMove.className = 'move';
|
||||
whiteMove.textContent = nonResultMoves[i].san;
|
||||
whiteMove.dataset.index = i;
|
||||
movesList.appendChild(whiteMove);
|
||||
|
||||
// Black move
|
||||
if (i + 1 < nonResultMoves.length) {
|
||||
const blackMove = document.createElement('span');
|
||||
blackMove.className = 'move';
|
||||
blackMove.textContent = nonResultMoves[i + 1].san;
|
||||
blackMove.dataset.index = i + 1;
|
||||
movesList.appendChild(blackMove);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark last move
|
||||
if (nonResultMoves.length > 0) {
|
||||
const lastMove = movesList.querySelector(`[data-index="${nonResultMoves.length - 1}"]`);
|
||||
if (lastMove) lastMove.classList.add('current');
|
||||
}
|
||||
|
||||
// Scroll to bottom
|
||||
movesList.scrollTop = movesList.scrollHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Klick auf einen Zug in der Liste
|
||||
*/
|
||||
function handleMoveClick(e) {
|
||||
if (!e.target.classList.contains('move') || !currentGame) return;
|
||||
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||
|
||||
chess = new Chess();
|
||||
for (let i = 0; i <= index; i++) {
|
||||
try {
|
||||
chess.move(nonResultMoves[i].san);
|
||||
} catch (err) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
board.position(chess.fen(), true);
|
||||
|
||||
// Update current highlight
|
||||
document.querySelectorAll('#moves-list .move').forEach(el => el.classList.remove('current'));
|
||||
e.target.classList.add('current');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert die Liste aller Partien
|
||||
*/
|
||||
function updateAllGamesList() {
|
||||
const list = document.getElementById('all-games-list');
|
||||
list.innerHTML = '';
|
||||
|
||||
// Sort by round
|
||||
const sorted = [...allLaraGames].sort((a, b) => {
|
||||
return (parseInt(a.round) || 0) - (parseInt(b.round) || 0);
|
||||
});
|
||||
|
||||
for (const game of sorted) {
|
||||
const entry = document.createElement('div');
|
||||
entry.className = 'game-entry';
|
||||
if (currentGame && game === currentGame) entry.classList.add('active');
|
||||
|
||||
const laraIsWhite = game.white.toLowerCase().includes('kiesewetter');
|
||||
const opponent = laraIsWhite ? game.black : game.white;
|
||||
const color = laraIsWhite ? '⬜ Weiß' : '⬛ Schwarz';
|
||||
|
||||
entry.innerHTML = `
|
||||
<div class="game-round">Runde ${game.round}</div>
|
||||
<div class="game-players">Lara ${color} vs ${opponent}</div>
|
||||
<div class="game-result">${game.isLive ? '● Laufen' : game.result}</div>
|
||||
`;
|
||||
|
||||
entry.addEventListener('click', () => {
|
||||
currentGame = game;
|
||||
updateBoard();
|
||||
updatePlayerInfo();
|
||||
updateMovesList();
|
||||
updateAllGamesList();
|
||||
});
|
||||
|
||||
list.appendChild(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format clock string
|
||||
*/
|
||||
function formatClock(clockStr) {
|
||||
if (!clockStr) return '--:--:--';
|
||||
// Format is HH:MM:SS
|
||||
return clockStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update timestamp
|
||||
*/
|
||||
function updateTimestamp() {
|
||||
const now = new Date();
|
||||
document.getElementById('last-update').textContent =
|
||||
`Letztes Update: ${now.toLocaleTimeString('de-DE')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start auto-refresh
|
||||
*/
|
||||
function startAutoRefresh() {
|
||||
countdown = REFRESH_INTERVAL / 1000;
|
||||
|
||||
if (refreshTimer) clearInterval(refreshTimer);
|
||||
|
||||
refreshTimer = setInterval(() => {
|
||||
countdown--;
|
||||
document.getElementById('refresh-timer').textContent =
|
||||
`Nächste Aktualisierung: ${countdown}s`;
|
||||
|
||||
if (countdown <= 0) {
|
||||
countdown = REFRESH_INTERVAL / 1000;
|
||||
loadPGN();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Helpers
|
||||
*/
|
||||
function showLoading(show) {
|
||||
document.getElementById('loading-overlay').style.display = show ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
document.getElementById('error-message').textContent = msg;
|
||||
document.getElementById('error-overlay').style.display = 'flex';
|
||||
}
|
||||
|
||||
function hideError() {
|
||||
document.getElementById('error-overlay').style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual refresh button
|
||||
*/
|
||||
document.getElementById('refresh-btn').addEventListener('click', () => {
|
||||
countdown = REFRESH_INTERVAL / 1000;
|
||||
loadPGN();
|
||||
});
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
loadPGN();
|
||||
startAutoRefresh();
|
||||
Reference in New Issue
Block a user