Split app.js into modular components (state, evaluation, ui, board, data)
This commit is contained in:
@@ -22,7 +22,12 @@ Live-Überwachung von Lara Kiesewetters Partien bei der **ODJM (Offene Deutsche
|
|||||||
├── index.html # HTML-Grundgerüst
|
├── index.html # HTML-Grundgerüst
|
||||||
├── style.css # Dark-Theme-Styling (responsive)
|
├── style.css # Dark-Theme-Styling (responsive)
|
||||||
├── js/
|
├── js/
|
||||||
│ ├── app.js # Hauptlogik (Brett, UI, Auto-Refresh, Eval-Bar)
|
│ ├── app.js # Event-Listener und Initialisierung
|
||||||
|
│ ├── state.js # Globaler Zustand (Variablen)
|
||||||
|
│ ├── board.js # Schachbrett-Rendering und Zug-Navigation
|
||||||
|
│ ├── data.js # PGN-Laden, Turniertabelle, Auto-Refresh
|
||||||
|
│ ├── evaluation.js # Stockfish-Analyse und Eval-Bar
|
||||||
|
│ ├── ui.js # UI-Rendering (Spielerinfo, Züge, PGN)
|
||||||
│ └── pgn-parser.js # PGN-Parser (Header + Züge extrahieren)
|
│ └── pgn-parser.js # PGN-Parser (Header + Züge extrahieren)
|
||||||
├── server.py # Lokaler Proxy-Server + Stockfish-Engine (Python 3)
|
├── server.py # Lokaler Proxy-Server + Stockfish-Engine (Python 3)
|
||||||
├── cache/ # PGN- und Standings-Cache
|
├── cache/ # PGN- und Standings-Cache
|
||||||
|
|||||||
@@ -122,6 +122,11 @@
|
|||||||
<script src="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"></script>
|
<script src="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js"></script>
|
||||||
<script src="js/pgn-parser.js"></script>
|
<script src="js/pgn-parser.js"></script>
|
||||||
|
<script src="js/state.js"></script>
|
||||||
|
<script src="js/evaluation.js"></script>
|
||||||
|
<script src="js/ui.js"></script>
|
||||||
|
<script src="js/board.js"></script>
|
||||||
|
<script src="js/data.js"></script>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
693
js/app.js
693
js/app.js
@@ -1,691 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* Lara Kiesewetter – Live Schachturnier
|
* Lara Kiesewetter – Live Schachturnier
|
||||||
* Haupt-Application
|
* Event listeners and initialization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global $, parsePGN, filterLaraGames, getLiveGame, getLatestGame, Chess, Chessboard */
|
/* global $, Chess, Chessboard */
|
||||||
|
|
||||||
let board = null;
|
|
||||||
let chess = null;
|
|
||||||
let currentGame = null;
|
|
||||||
let allLaraGames = [];
|
|
||||||
let currentMoveIndex = -1;
|
|
||||||
let userSelectedGame = false;
|
|
||||||
let evalAbortController = null;
|
|
||||||
let lastEvalFen = null;
|
|
||||||
let currentRound = 0;
|
|
||||||
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()}`);
|
|
||||||
if (!res.ok) return null;
|
|
||||||
const buf = await res.arrayBuffer();
|
|
||||||
// DSJ liefert ISO-8859-1; versuche UTF-8, fallback auf Latin-1
|
|
||||||
let text = new TextDecoder('utf-8').decode(buf);
|
|
||||||
if (text.includes('\uFFFD')) {
|
|
||||||
text = new TextDecoder('iso-8859-1').decode(buf);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadPGN(showOverlay = true) {
|
|
||||||
const currentPollId = pollId;
|
|
||||||
if (showOverlay) showLoading(true);
|
|
||||||
hideError();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await updateStandings();
|
|
||||||
|
|
||||||
if (currentRound === 0) {
|
|
||||||
if (showOverlay) showLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nur aktuelle/live Runden neu laden, historische bleiben gecached
|
|
||||||
const maxRound = currentRound + 1;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alle PGNs kombinieren
|
|
||||||
const combinedPgn = Object.values(roundPgns).join('\n\n');
|
|
||||||
const allGames = parsePGN(combinedPgn);
|
|
||||||
allLaraGames = filterLaraGames(allGames);
|
|
||||||
|
|
||||||
if (allLaraGames.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userSelectedGame) {
|
|
||||||
const liveGame = getLiveGame(allLaraGames);
|
|
||||||
currentGame = liveGame || getLatestGame(allLaraGames);
|
|
||||||
}
|
|
||||||
updateBoard();
|
|
||||||
updatePlayerInfo();
|
|
||||||
updateMovesList();
|
|
||||||
updateAllGamesList();
|
|
||||||
updatePGNDisplay();
|
|
||||||
updateTimestamp();
|
|
||||||
|
|
||||||
showLoading(false);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
if (currentPollId !== pollId) return;
|
|
||||||
console.error('Fehler beim Laden:', error);
|
|
||||||
showLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktualisiert das Schachbrett
|
|
||||||
*/
|
|
||||||
function updateBoard() {
|
|
||||||
if (!currentGame) return;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
|
||||||
|
|
||||||
// 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++) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (board) {
|
|
||||||
board.position(chess.fen(), true);
|
|
||||||
board.orientation(orientation);
|
|
||||||
board.resize();
|
|
||||||
highlightLastMove();
|
|
||||||
} 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();
|
|
||||||
|
|
||||||
syncEvalBarHeight();
|
|
||||||
updateEvaluation();
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncEvalBarHeight() {
|
|
||||||
const boardEl = document.getElementById('board');
|
|
||||||
const evalContainer = document.getElementById('eval-bar-container');
|
|
||||||
if (boardEl && evalContainer) {
|
|
||||||
const h = boardEl.offsetHeight;
|
|
||||||
if (h > 100) evalContainer.style.minHeight = h + 'px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sendet FEN an Stockfish und aktualisiert die Eval-Bar
|
|
||||||
*/
|
|
||||||
async function updateEvaluation() {
|
|
||||||
if (!chess) return;
|
|
||||||
const fen = chess.fen();
|
|
||||||
|
|
||||||
if (lastEvalFen === fen && evalAbortController) return;
|
|
||||||
lastEvalFen = fen;
|
|
||||||
|
|
||||||
if (evalAbortController) {
|
|
||||||
evalAbortController.abort();
|
|
||||||
}
|
|
||||||
evalAbortController = new AbortController();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch('/evaluate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ fen }),
|
|
||||||
signal: evalAbortController.signal,
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Eval fehlgeschlagen');
|
|
||||||
|
|
||||||
const reader = res.body.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = '';
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split('\n');
|
|
||||||
buffer = lines.pop();
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (!line.trim()) continue;
|
|
||||||
try {
|
|
||||||
displayEval(JSON.parse(line));
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer.trim()) {
|
|
||||||
try { displayEval(JSON.parse(buffer)); } catch {}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e.name === 'AbortError') return;
|
|
||||||
const el = document.getElementById('eval-score');
|
|
||||||
if (el) el.textContent = '?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayEval(data) {
|
|
||||||
const evalScore = document.getElementById('eval-score');
|
|
||||||
const barFill = document.getElementById('eval-bar-fill');
|
|
||||||
const barMarker = document.getElementById('eval-bar-marker');
|
|
||||||
|
|
||||||
let scoreText = '0.00';
|
|
||||||
let fillPercent = 50;
|
|
||||||
|
|
||||||
if (data.scoreMate !== null && data.scoreMate !== undefined && data.scoreMate !== 0) {
|
|
||||||
const mate = parseInt(data.scoreMate);
|
|
||||||
const whiteMate = chess && chess.turn() === 'w' ? mate : -mate;
|
|
||||||
if (whiteMate > 0) {
|
|
||||||
scoreText = '#' + whiteMate;
|
|
||||||
fillPercent = 100;
|
|
||||||
} else {
|
|
||||||
scoreText = '#' + Math.abs(whiteMate);
|
|
||||||
fillPercent = 0;
|
|
||||||
}
|
|
||||||
} else if (data.scoreCp !== null && data.scoreCp !== undefined) {
|
|
||||||
const cp = parseInt(data.scoreCp);
|
|
||||||
const whiteCp = chess && chess.turn() === 'w' ? cp : -cp;
|
|
||||||
const pawns = (whiteCp / 100).toFixed(2);
|
|
||||||
scoreText = (whiteCp > 0 ? '+' : '') + pawns;
|
|
||||||
fillPercent = 50 + whiteCp / 14;
|
|
||||||
fillPercent = Math.max(0, Math.min(100, fillPercent));
|
|
||||||
}
|
|
||||||
|
|
||||||
evalScore.textContent = scoreText;
|
|
||||||
|
|
||||||
if (barFill) barFill.style.height = fillPercent + '%';
|
|
||||||
if (barMarker) barMarker.style.top = (100 - fillPercent) + '%';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gehe zu einem bestimmten Zug (Index)
|
|
||||||
*/
|
|
||||||
function goToMove(index) {
|
|
||||||
if (!currentGame) return;
|
|
||||||
|
|
||||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
|
||||||
|
|
||||||
if (index < -1) index = -1;
|
|
||||||
if (index >= nonResultMoves.length) index = nonResultMoves.length - 1;
|
|
||||||
|
|
||||||
currentMoveIndex = index;
|
|
||||||
|
|
||||||
chess = new Chess();
|
|
||||||
for (let i = 0; i <= index; i++) {
|
|
||||||
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);
|
|
||||||
highlightActivePlayer();
|
|
||||||
highlightLastMove();
|
|
||||||
updateClocks(index);
|
|
||||||
syncEvalBarHeight();
|
|
||||||
updateEvaluation();
|
|
||||||
|
|
||||||
document.querySelectorAll('#moves-list .move').forEach(el => el.classList.remove('current'));
|
|
||||||
if (index >= 0) {
|
|
||||||
const moveEl = document.querySelector(`#moves-list [data-index="${index}"]`);
|
|
||||||
if (moveEl) moveEl.classList.add('current');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktualisiert die Spielerinformationen
|
|
||||||
*/
|
|
||||||
function updatePlayerInfo() {
|
|
||||||
if (!currentGame) return;
|
|
||||||
|
|
||||||
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
|
||||||
|
|
||||||
const laraPlayer = laraIsWhite ? currentGame.white : currentGame.black;
|
|
||||||
const laraElo = laraIsWhite ? currentGame.whiteElo : currentGame.blackElo;
|
|
||||||
const laraEmoji = laraIsWhite ? '⬜' : '⬛';
|
|
||||||
|
|
||||||
const oppPlayer = laraIsWhite ? currentGame.black : currentGame.white;
|
|
||||||
const oppElo = laraIsWhite ? currentGame.blackElo : currentGame.whiteElo;
|
|
||||||
const oppEmoji = laraIsWhite ? '⬛' : '⬜';
|
|
||||||
|
|
||||||
// Top-Panel (player-black) = Gegner
|
|
||||||
document.getElementById('black-name').textContent = oppPlayer;
|
|
||||||
document.getElementById('black-elo').textContent = oppElo ? `(ELO: ${oppElo})` : '';
|
|
||||||
document.querySelector('#player-black .player-avatar').textContent = oppEmoji;
|
|
||||||
|
|
||||||
// Bottom-Panel (player-white) = Lara
|
|
||||||
document.getElementById('white-name').textContent = laraPlayer;
|
|
||||||
document.getElementById('white-elo').textContent = laraElo ? `(ELO: ${laraElo})` : '';
|
|
||||||
document.querySelector('#player-white .player-avatar').textContent = laraEmoji;
|
|
||||||
|
|
||||||
updateClocks(currentMoveIndex);
|
|
||||||
|
|
||||||
// Round info
|
|
||||||
const roundInfo = `Runde ${currentGame.round} – ${currentGame.event || 'Turnier'}`;
|
|
||||||
document.getElementById('round-info').textContent = roundInfo;
|
|
||||||
const mobileRound = document.getElementById('round-info-mobile');
|
|
||||||
if (mobileRound) mobileRound.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}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktualisiert die Uhrenanzeige basierend auf dem aktuellen Zugindex
|
|
||||||
*/
|
|
||||||
function updateClocks(moveIndex) {
|
|
||||||
if (!currentGame) return;
|
|
||||||
|
|
||||||
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
|
||||||
|
|
||||||
let whiteClock, blackClock;
|
|
||||||
|
|
||||||
if (moveIndex < 0) {
|
|
||||||
whiteClock = currentGame.whiteClock;
|
|
||||||
blackClock = currentGame.blackClock;
|
|
||||||
} else {
|
|
||||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
|
||||||
if (moveIndex < nonResultMoves.length) {
|
|
||||||
whiteClock = nonResultMoves[moveIndex].whiteClock;
|
|
||||||
blackClock = nonResultMoves[moveIndex].blackClock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const laraClock = laraIsWhite ? whiteClock : blackClock;
|
|
||||||
const oppClock = laraIsWhite ? blackClock : whiteClock;
|
|
||||||
|
|
||||||
document.getElementById('black-clock').textContent = oppClock || '--:--:--';
|
|
||||||
document.getElementById('white-clock').textContent = laraClock || '--:--:--';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Highlight den Spieler, der gerade am Zug ist
|
|
||||||
*/
|
|
||||||
function highlightActivePlayer() {
|
|
||||||
if (!chess || !currentGame) return;
|
|
||||||
|
|
||||||
const laraPanel = document.getElementById('player-white');
|
|
||||||
const oppPanel = document.getElementById('player-black');
|
|
||||||
|
|
||||||
laraPanel.classList.remove('active');
|
|
||||||
oppPanel.classList.remove('active');
|
|
||||||
|
|
||||||
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
|
||||||
const isLaraTurn = (chess.turn() === 'w' && laraIsWhite) || (chess.turn() === 'b' && !laraIsWhite);
|
|
||||||
|
|
||||||
if (isLaraTurn) {
|
|
||||||
laraPanel.classList.add('active');
|
|
||||||
} else {
|
|
||||||
oppPanel.classList.add('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Highlight den letzten Zug auf dem Brett
|
|
||||||
*/
|
|
||||||
function highlightLastMove() {
|
|
||||||
if (!board || !chess || !currentGame) return;
|
|
||||||
|
|
||||||
$('#board [data-square]').removeClass('last-move-highlight');
|
|
||||||
|
|
||||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
|
||||||
|
|
||||||
if (currentMoveIndex >= 0 && currentMoveIndex < nonResultMoves.length) {
|
|
||||||
const moves = chess.history({ verbose: true });
|
|
||||||
if (moves.length > 0) {
|
|
||||||
const lastMoveData = moves[moves.length - 1];
|
|
||||||
$(`#board [data-square="${lastMoveData.from}"]`).addClass('last-move-highlight');
|
|
||||||
$(`#board [data-square="${lastMoveData.to}"]`).addClass('last-move-highlight');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generiert PGN-Text aus einem Spiel-Objekt
|
|
||||||
*/
|
|
||||||
function generatePGN(game) {
|
|
||||||
if (!game) return '';
|
|
||||||
|
|
||||||
let pgn = '';
|
|
||||||
if (game.event) pgn += `[Event "${game.event}"]\n`;
|
|
||||||
if (game.site) pgn += `[Site "${game.site}"]\n`;
|
|
||||||
if (game.date) pgn += `[Date "${game.date}"]\n`;
|
|
||||||
if (game.round) pgn += `[Round "${game.round}"]\n`;
|
|
||||||
if (game.white) pgn += `[White "${game.white}"]\n`;
|
|
||||||
if (game.black) pgn += `[Black "${game.black}"]\n`;
|
|
||||||
if (game.result) pgn += `[Result "${game.result}"]\n`;
|
|
||||||
if (game.whiteElo) pgn += `[WhiteElo "${game.whiteElo}"]\n`;
|
|
||||||
if (game.blackElo) pgn += `[BlackElo "${game.blackElo}"]\n`;
|
|
||||||
if (game.termination) pgn += `[Termination "${game.termination}"]\n`;
|
|
||||||
|
|
||||||
pgn += '\n';
|
|
||||||
|
|
||||||
const nonResultMoves = game.moves.filter(m => !m.isResult);
|
|
||||||
for (let i = 0; i < nonResultMoves.length; i += 2) {
|
|
||||||
const moveNumber = Math.floor(i / 2) + 1;
|
|
||||||
pgn += `${moveNumber}. ${nonResultMoves[i].san}`;
|
|
||||||
if (i + 1 < nonResultMoves.length) {
|
|
||||||
pgn += ` ${nonResultMoves[i + 1].san} `;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resultMove = game.moves.find(m => m.isResult);
|
|
||||||
if (resultMove) {
|
|
||||||
pgn += ` ${resultMove.san}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pgn.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePGNDisplay() {
|
|
||||||
if (!currentGame) return;
|
|
||||||
const text = generatePGN(currentGame);
|
|
||||||
document.querySelectorAll('.pgn-text').forEach(el => el.textContent = text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktualisiert die Zugliste
|
|
||||||
*/
|
|
||||||
function updateMovesList() {
|
|
||||||
if (!currentGame) return;
|
|
||||||
|
|
||||||
const movesList = document.getElementById('moves-list');
|
|
||||||
movesList.innerHTML = '';
|
|
||||||
|
|
||||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
|
||||||
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
|
||||||
|
|
||||||
for (let i = 0; i < nonResultMoves.length; i += 2) {
|
|
||||||
const moveNumber = Math.floor(i / 2) + 1;
|
|
||||||
|
|
||||||
const numSpan = document.createElement('span');
|
|
||||||
numSpan.className = 'move-number';
|
|
||||||
numSpan.textContent = `${moveNumber}.`;
|
|
||||||
movesList.appendChild(numSpan);
|
|
||||||
|
|
||||||
if (laraIsWhite) {
|
|
||||||
// Lara (Weiß) zuerst
|
|
||||||
const laraMove = document.createElement('span');
|
|
||||||
laraMove.className = 'move lara-move';
|
|
||||||
laraMove.textContent = nonResultMoves[i].san;
|
|
||||||
laraMove.dataset.index = i;
|
|
||||||
movesList.appendChild(laraMove);
|
|
||||||
|
|
||||||
if (i + 1 < nonResultMoves.length) {
|
|
||||||
const oppMove = document.createElement('span');
|
|
||||||
oppMove.className = 'move opp-move';
|
|
||||||
oppMove.textContent = nonResultMoves[i + 1].san;
|
|
||||||
oppMove.dataset.index = i + 1;
|
|
||||||
movesList.appendChild(oppMove);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Lara (Schwarz) – Gegner zuerst, dann Lara
|
|
||||||
const oppMove = document.createElement('span');
|
|
||||||
oppMove.className = 'move opp-move';
|
|
||||||
oppMove.textContent = nonResultMoves[i].san;
|
|
||||||
oppMove.dataset.index = i;
|
|
||||||
movesList.appendChild(oppMove);
|
|
||||||
|
|
||||||
if (i + 1 < nonResultMoves.length) {
|
|
||||||
const laraMove = document.createElement('span');
|
|
||||||
laraMove.className = 'move lara-move';
|
|
||||||
laraMove.textContent = nonResultMoves[i + 1].san;
|
|
||||||
laraMove.dataset.index = i + 1;
|
|
||||||
movesList.appendChild(laraMove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark current move
|
|
||||||
if (currentMoveIndex >= 0) {
|
|
||||||
const curMove = movesList.querySelector(`[data-index="${currentMoveIndex}"]`);
|
|
||||||
if (curMove) curMove.classList.add('current');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Klick auf einen Zug in der Liste
|
|
||||||
*/
|
|
||||||
function handleMoveClick(e) {
|
|
||||||
if (!e.target.classList.contains('move') || !currentGame) return;
|
|
||||||
goToMove(parseInt(e.target.dataset.index));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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';
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<div class="game-round">${resultIcon} 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', () => {
|
|
||||||
userSelectedGame = true;
|
|
||||||
currentGame = game;
|
|
||||||
previousMoveCount = -1;
|
|
||||||
currentMoveIndex = Number.MAX_SAFE_INTEGER;
|
|
||||||
updateBoard();
|
|
||||||
updatePlayerInfo();
|
|
||||||
updateMovesList();
|
|
||||||
updateAllGamesList();
|
|
||||||
updatePGNDisplay();
|
|
||||||
});
|
|
||||||
|
|
||||||
list.appendChild(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt die Turniertabelle vom DSJ und zeigt Laras Platzierung an
|
|
||||||
*/
|
|
||||||
async function updateStandings() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/tabelle/?t=${Date.now()}`);
|
|
||||||
if (!res.ok) throw new Error('Fehler beim Laden');
|
|
||||||
const html = await res.text();
|
|
||||||
|
|
||||||
const roundMatch = html.match(/Tabellenstand\s+nach\s+der\s+(\d+)\.\s*Runde/);
|
|
||||||
if (roundMatch) {
|
|
||||||
currentRound = parseInt(roundMatch[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = html.matchAll(/<tr[^>]*>(.*?)<\/tr>/gs);
|
|
||||||
for (const row of rows) {
|
|
||||||
if (!row[1].includes('Lara Kiesewetter')) continue;
|
|
||||||
|
|
||||||
const cells = row[1].matchAll(/<td[^>]*>(.*?)<\/td>/gs);
|
|
||||||
const clean = [];
|
|
||||||
for (const cell of cells) {
|
|
||||||
clean.push(cell[1].replace(/<[^>]+>/g, '').trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clean.length >= 9) {
|
|
||||||
const data = {
|
|
||||||
rank: clean[0],
|
|
||||||
player: 'Lara Kiesewetter',
|
|
||||||
wins: clean[5],
|
|
||||||
draws: clean[6],
|
|
||||||
losses: clean[7],
|
|
||||||
points: clean[8],
|
|
||||||
round_info: roundMatch ? `nach der ${roundMatch[1]}. Runde` : '',
|
|
||||||
round: currentRound,
|
|
||||||
};
|
|
||||||
const container = document.getElementById('standings-content');
|
|
||||||
container.innerHTML = `
|
|
||||||
<div class="standings-rank">${data.rank}.</div>
|
|
||||||
<div class="standings-rank-label">Tabellenplatz</div>
|
|
||||||
<div class="standings-header">${data.round_info || 'nach Runde 1'}</div>
|
|
||||||
<div class="standings-row">
|
|
||||||
<span class="standings-label">Punkte</span>
|
|
||||||
<span class="standings-value">${data.points}</span>
|
|
||||||
</div>
|
|
||||||
<div class="standings-row">
|
|
||||||
<span class="standings-label">Siege</span>
|
|
||||||
<span class="standings-value">${data.wins}</span>
|
|
||||||
</div>
|
|
||||||
<div class="standings-row">
|
|
||||||
<span class="standings-label">Unentschieden</span>
|
|
||||||
<span class="standings-value">${data.draws}</span>
|
|
||||||
</div>
|
|
||||||
<div class="standings-row">
|
|
||||||
<span class="standings-label">Niederlagen</span>
|
|
||||||
<span class="standings-value">${data.losses}</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.getElementById('standings-content').innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
|
||||||
} catch {
|
|
||||||
document.getElementById('standings-content').innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update timestamp
|
|
||||||
*/
|
|
||||||
function updateTimestamp() {
|
|
||||||
const time = new Date();
|
|
||||||
document.getElementById('last-update').textContent =
|
|
||||||
`Letztes Update: ${time.toLocaleTimeString('de-DE')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Startet Auto-Refresh alle 15 Sekunden
|
|
||||||
*/
|
|
||||||
function startAutoRefresh() {
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
clearInterval(updateTimer);
|
|
||||||
|
|
||||||
const myId = ++pollId;
|
|
||||||
let lastUpdate = Date.now();
|
|
||||||
|
|
||||||
document.getElementById('refresh-timer').textContent = '0s';
|
|
||||||
document.getElementById('refresh-timer').style.color = '#4ade80';
|
|
||||||
|
|
||||||
loadPGN(true);
|
|
||||||
|
|
||||||
pollInterval = setInterval(() => {
|
|
||||||
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; }
|
|
||||||
loadPGN(false);
|
|
||||||
lastUpdate = Date.now();
|
|
||||||
}, 15000);
|
|
||||||
|
|
||||||
updateTimer = setInterval(() => {
|
|
||||||
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); return; }
|
|
||||||
const elapsed = Date.now() - lastUpdate;
|
|
||||||
const remaining = Math.max(0, 15000 - elapsed);
|
|
||||||
const s = Math.floor(remaining / 1000);
|
|
||||||
document.getElementById('refresh-timer').textContent = `${s}s`;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UI Helpers
|
|
||||||
*/
|
|
||||||
function showLoading(show) {
|
|
||||||
document.getElementById('loading-overlay').style.display = show ? 'flex' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideError() {
|
|
||||||
document.getElementById('error-overlay').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manual refresh button – startet neuen Long-Poll-Zyklus
|
|
||||||
*/
|
|
||||||
document.getElementById('refresh-btn').addEventListener('click', () => {
|
document.getElementById('refresh-btn').addEventListener('click', () => {
|
||||||
pollId++;
|
pollId++;
|
||||||
startAutoRefresh();
|
startAutoRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* PGN in Zwischenablage kopieren
|
|
||||||
*/
|
|
||||||
document.querySelectorAll('.copy-pgn-btn').forEach(btn => {
|
document.querySelectorAll('.copy-pgn-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', async () => {
|
btn.addEventListener('click', async () => {
|
||||||
const panel = btn.closest('[class*="pgn-panel"]');
|
const panel = btn.closest('[class*="pgn-panel"]');
|
||||||
@@ -706,9 +30,6 @@ document.querySelectorAll('.copy-pgn-btn').forEach(btn => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Pfeiltasten-Navigation
|
|
||||||
*/
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (!currentGame) return;
|
if (!currentGame) return;
|
||||||
|
|
||||||
@@ -721,9 +42,6 @@ document.addEventListener('keydown', (e) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Board-Resize bei Fenster- und Container-Änderungen
|
|
||||||
*/
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
if (board) board.resize();
|
if (board) board.resize();
|
||||||
});
|
});
|
||||||
@@ -735,9 +53,6 @@ if (window.ResizeObserver) {
|
|||||||
ro.observe(document.getElementById('board'));
|
ro.observe(document.getElementById('board'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Page Visibility API – Timer pausieren wenn Tab unsichtbar
|
|
||||||
*/
|
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
clearInterval(pollInterval);
|
clearInterval(pollInterval);
|
||||||
@@ -747,12 +62,8 @@ document.addEventListener('visibilitychange', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Init
|
|
||||||
*/
|
|
||||||
loadPGN();
|
loadPGN();
|
||||||
startAutoRefresh();
|
startAutoRefresh();
|
||||||
|
|
||||||
// Eval-Bar initialisieren
|
|
||||||
chess = new Chess();
|
chess = new Chess();
|
||||||
updateEvaluation();
|
updateEvaluation();
|
||||||
97
js/board.js
Normal file
97
js/board.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Lara Kiesewetter – Live Schachturnier
|
||||||
|
* Board rendering and move navigation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global $, Chess, Chessboard */
|
||||||
|
|
||||||
|
function handleMoveClick(e) {
|
||||||
|
if (!e.target.classList.contains('move') || !currentGame) return;
|
||||||
|
goToMove(parseInt(e.target.dataset.index));
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToMove(index) {
|
||||||
|
if (!currentGame) return;
|
||||||
|
|
||||||
|
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||||
|
|
||||||
|
if (index < -1) index = -1;
|
||||||
|
if (index >= nonResultMoves.length) index = nonResultMoves.length - 1;
|
||||||
|
|
||||||
|
currentMoveIndex = index;
|
||||||
|
|
||||||
|
chess = new Chess();
|
||||||
|
for (let i = 0; i <= index; i++) {
|
||||||
|
const san = nonResultMoves[i].san;
|
||||||
|
let result = chess.move(san);
|
||||||
|
if (!result) {
|
||||||
|
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);
|
||||||
|
highlightActivePlayer();
|
||||||
|
highlightLastMove();
|
||||||
|
updateClocks(index);
|
||||||
|
syncEvalBarHeight();
|
||||||
|
updateEvaluation();
|
||||||
|
|
||||||
|
document.querySelectorAll('#moves-list .move').forEach(el => el.classList.remove('current'));
|
||||||
|
if (index >= 0) {
|
||||||
|
const moveEl = document.querySelector(`#moves-list [data-index="${index}"]`);
|
||||||
|
if (moveEl) moveEl.classList.add('current');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBoard() {
|
||||||
|
if (!currentGame) return;
|
||||||
|
|
||||||
|
const laraIsBlack = currentGame.black.toLowerCase().includes('kiesewetter');
|
||||||
|
const orientation = laraIsBlack ? 'black' : 'white';
|
||||||
|
|
||||||
|
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||||
|
|
||||||
|
if (currentMoveIndex >= nonResultMoves.length || currentMoveIndex < -1) {
|
||||||
|
currentMoveIndex = nonResultMoves.length - 1;
|
||||||
|
} else if (previousMoveCount >= 0 && currentMoveIndex === previousMoveCount - 1 && nonResultMoves.length > previousMoveCount) {
|
||||||
|
currentMoveIndex = nonResultMoves.length - 1;
|
||||||
|
}
|
||||||
|
previousMoveCount = nonResultMoves.length;
|
||||||
|
|
||||||
|
chess = new Chess();
|
||||||
|
if (currentMoveIndex >= 0) {
|
||||||
|
for (let i = 0; i <= currentMoveIndex && i < nonResultMoves.length; i++) {
|
||||||
|
const san = nonResultMoves[i].san;
|
||||||
|
let result = chess.move(san);
|
||||||
|
if (!result) {
|
||||||
|
const cleanSan = san.replace(/^([NBRQK])([a-h])?([1-8])?([a-h][1-8].*)$/, '$1$4');
|
||||||
|
result = chess.move(cleanSan);
|
||||||
|
}
|
||||||
|
if (!result) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (board) {
|
||||||
|
board.position(chess.fen(), true);
|
||||||
|
board.orientation(orientation);
|
||||||
|
board.resize();
|
||||||
|
highlightLastMove();
|
||||||
|
} else {
|
||||||
|
board = Chessboard('board', {
|
||||||
|
position: chess.fen(),
|
||||||
|
orientation: orientation,
|
||||||
|
pieceTheme: 'https://chessboardjs.com/img/chesspieces/wikipedia/{piece}.png',
|
||||||
|
draggable: false,
|
||||||
|
spawnMoveError: false
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('moves-list').addEventListener('click', handleMoveClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightActivePlayer();
|
||||||
|
|
||||||
|
syncEvalBarHeight();
|
||||||
|
updateEvaluation();
|
||||||
|
}
|
||||||
169
js/data.js
Normal file
169
js/data.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/**
|
||||||
|
* Lara Kiesewetter – Live Schachturnier
|
||||||
|
* Data fetching, PGN loading, standings, auto-refresh
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global parsePGN, filterLaraGames, getLiveGame, getTodaysGames, getLatestGame */
|
||||||
|
|
||||||
|
async function fetchRoundPGN(round) {
|
||||||
|
const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/partien/${round}.pgn?t=${Date.now()}`);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const buf = await res.arrayBuffer();
|
||||||
|
let text = new TextDecoder('utf-8').decode(buf);
|
||||||
|
if (text.includes('\uFFFD')) {
|
||||||
|
text = new TextDecoder('iso-8859-1').decode(buf);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPGN(showOverlay = true) {
|
||||||
|
const currentPollId = pollId;
|
||||||
|
if (showOverlay) showLoading(true);
|
||||||
|
hideError();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (currentRound === 0) {
|
||||||
|
if (showOverlay) showLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxRound = currentRound + 1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedPgn = Object.values(roundPgns).join('\n\n');
|
||||||
|
const allGames = parsePGN(combinedPgn);
|
||||||
|
allLaraGames = filterLaraGames(allGames);
|
||||||
|
|
||||||
|
if (allLaraGames.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userSelectedGame) {
|
||||||
|
const liveGame = getLiveGame(allLaraGames);
|
||||||
|
const todaysGames = getTodaysGames(allLaraGames);
|
||||||
|
currentGame = liveGame || (todaysGames.length > 0 ? getLatestGame(todaysGames) : getLatestGame(allLaraGames));
|
||||||
|
}
|
||||||
|
updateBoard();
|
||||||
|
updatePlayerInfo();
|
||||||
|
updateMovesList();
|
||||||
|
updateAllGamesList();
|
||||||
|
updatePGNDisplay();
|
||||||
|
updateTimestamp();
|
||||||
|
|
||||||
|
showLoading(false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (currentPollId !== pollId) return;
|
||||||
|
console.error('Fehler beim Laden:', error);
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateStandings() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/tabelle/?t=${Date.now()}`);
|
||||||
|
if (!res.ok) throw new Error('Fehler beim Laden');
|
||||||
|
const html = await res.text();
|
||||||
|
|
||||||
|
const roundMatch = html.match(/Tabellenstand\s+nach\s+der\s+(\d+)\.\s*Runde/);
|
||||||
|
if (roundMatch) {
|
||||||
|
currentRound = parseInt(roundMatch[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = html.matchAll(/<tr[^>]*>(.*?)<\/tr>/gs);
|
||||||
|
for (const row of rows) {
|
||||||
|
if (!row[1].includes('Lara Kiesewetter')) continue;
|
||||||
|
|
||||||
|
const cells = row[1].matchAll(/<td[^>]*>(.*?)<\/td>/gs);
|
||||||
|
const clean = [];
|
||||||
|
for (const cell of cells) {
|
||||||
|
clean.push(cell[1].replace(/<[^>]+>/g, '').trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clean.length >= 9) {
|
||||||
|
const data = {
|
||||||
|
rank: clean[0],
|
||||||
|
player: 'Lara Kiesewetter',
|
||||||
|
wins: clean[5],
|
||||||
|
draws: clean[6],
|
||||||
|
losses: clean[7],
|
||||||
|
points: clean[8],
|
||||||
|
round_info: roundMatch ? `nach der ${roundMatch[1]}. Runde` : '',
|
||||||
|
round: currentRound,
|
||||||
|
};
|
||||||
|
const container = document.getElementById('standings-content');
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="standings-rank">${data.rank}.</div>
|
||||||
|
<div class="standings-rank-label">Tabellenplatz</div>
|
||||||
|
<div class="standings-header">${data.round_info || 'nach Runde 1'}</div>
|
||||||
|
<div class="standings-row">
|
||||||
|
<span class="standings-label">Punkte</span>
|
||||||
|
<span class="standings-value">${data.points}</span>
|
||||||
|
</div>
|
||||||
|
<div class="standings-row">
|
||||||
|
<span class="standings-label">Siege</span>
|
||||||
|
<span class="standings-value">${data.wins}</span>
|
||||||
|
</div>
|
||||||
|
<div class="standings-row">
|
||||||
|
<span class="standings-label">Unentschieden</span>
|
||||||
|
<span class="standings-value">${data.draws}</span>
|
||||||
|
</div>
|
||||||
|
<div class="standings-row">
|
||||||
|
<span class="standings-label">Niederlagen</span>
|
||||||
|
<span class="standings-value">${data.losses}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById('standings-content').innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
||||||
|
} catch {
|
||||||
|
document.getElementById('standings-content').innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimestamp() {
|
||||||
|
const time = new Date();
|
||||||
|
document.getElementById('last-update').textContent =
|
||||||
|
`Letztes Update: ${time.toLocaleTimeString('de-DE')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAutoRefresh() {
|
||||||
|
clearInterval(pollInterval);
|
||||||
|
clearInterval(updateTimer);
|
||||||
|
clearInterval(standingsInterval);
|
||||||
|
|
||||||
|
const myId = ++pollId;
|
||||||
|
let lastUpdate = Date.now();
|
||||||
|
|
||||||
|
document.getElementById('refresh-timer').textContent = '0s';
|
||||||
|
document.getElementById('refresh-timer').style.color = '#4ade80';
|
||||||
|
|
||||||
|
loadPGN(true);
|
||||||
|
updateStandings();
|
||||||
|
|
||||||
|
pollInterval = setInterval(() => {
|
||||||
|
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); clearInterval(standingsInterval); return; }
|
||||||
|
loadPGN(false);
|
||||||
|
lastUpdate = Date.now();
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
updateTimer = setInterval(() => {
|
||||||
|
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); clearInterval(standingsInterval); return; }
|
||||||
|
const elapsed = Date.now() - lastUpdate;
|
||||||
|
const remaining = Math.max(0, 15000 - elapsed);
|
||||||
|
const s = Math.floor(remaining / 1000);
|
||||||
|
document.getElementById('refresh-timer').textContent = `${s}s`;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
standingsInterval = setInterval(() => {
|
||||||
|
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(updateTimer); clearInterval(standingsInterval); return; }
|
||||||
|
updateStandings();
|
||||||
|
}, 1800000);
|
||||||
|
}
|
||||||
99
js/evaluation.js
Normal file
99
js/evaluation.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Lara Kiesewetter – Live Schachturnier
|
||||||
|
* Stockfish evaluation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global Chess */
|
||||||
|
|
||||||
|
function syncEvalBarHeight() {
|
||||||
|
const boardEl = document.getElementById('board');
|
||||||
|
const evalContainer = document.getElementById('eval-bar-container');
|
||||||
|
if (boardEl && evalContainer) {
|
||||||
|
const h = boardEl.offsetHeight;
|
||||||
|
if (h > 100) evalContainer.style.minHeight = h + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateEvaluation() {
|
||||||
|
if (!chess) return;
|
||||||
|
const fen = chess.fen();
|
||||||
|
|
||||||
|
if (lastEvalFen === fen && evalAbortController) return;
|
||||||
|
lastEvalFen = fen;
|
||||||
|
|
||||||
|
if (evalAbortController) {
|
||||||
|
evalAbortController.abort();
|
||||||
|
}
|
||||||
|
evalAbortController = new AbortController();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/evaluate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ fen }),
|
||||||
|
signal: evalAbortController.signal,
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error('Eval fehlgeschlagen');
|
||||||
|
|
||||||
|
const reader = res.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = '';
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true });
|
||||||
|
const lines = buffer.split('\n');
|
||||||
|
buffer = lines.pop();
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!line.trim()) continue;
|
||||||
|
try {
|
||||||
|
displayEval(JSON.parse(line));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.trim()) {
|
||||||
|
try { displayEval(JSON.parse(buffer)); } catch {}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name === 'AbortError') return;
|
||||||
|
const el = document.getElementById('eval-score');
|
||||||
|
if (el) el.textContent = '?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayEval(data) {
|
||||||
|
const evalScore = document.getElementById('eval-score');
|
||||||
|
const barFill = document.getElementById('eval-bar-fill');
|
||||||
|
const barMarker = document.getElementById('eval-bar-marker');
|
||||||
|
|
||||||
|
let scoreText = '0.00';
|
||||||
|
let fillPercent = 50;
|
||||||
|
|
||||||
|
if (data.scoreMate !== null && data.scoreMate !== undefined && data.scoreMate !== 0) {
|
||||||
|
const mate = parseInt(data.scoreMate);
|
||||||
|
const whiteMate = chess && chess.turn() === 'w' ? mate : -mate;
|
||||||
|
if (whiteMate > 0) {
|
||||||
|
scoreText = '#' + whiteMate;
|
||||||
|
fillPercent = 100;
|
||||||
|
} else {
|
||||||
|
scoreText = '#' + Math.abs(whiteMate);
|
||||||
|
fillPercent = 0;
|
||||||
|
}
|
||||||
|
} else if (data.scoreCp !== null && data.scoreCp !== undefined) {
|
||||||
|
const cp = parseInt(data.scoreCp);
|
||||||
|
const whiteCp = chess && chess.turn() === 'w' ? cp : -cp;
|
||||||
|
const pawns = (whiteCp / 100).toFixed(2);
|
||||||
|
scoreText = (whiteCp > 0 ? '+' : '') + pawns;
|
||||||
|
fillPercent = 50 + whiteCp / 14;
|
||||||
|
fillPercent = Math.max(0, Math.min(100, fillPercent));
|
||||||
|
}
|
||||||
|
|
||||||
|
evalScore.textContent = scoreText;
|
||||||
|
|
||||||
|
if (barFill) barFill.style.height = fillPercent + '%';
|
||||||
|
if (barMarker) barMarker.style.top = (100 - fillPercent) + '%';
|
||||||
|
}
|
||||||
@@ -120,9 +120,17 @@ function getLiveGame(laraGames) {
|
|||||||
return laraGames.find(game => game.isLive) || null;
|
return laraGames.find(game => game.isLive) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTodaysGames(laraGames) {
|
||||||
|
const today = new Date();
|
||||||
|
const yyyy = today.getFullYear();
|
||||||
|
const mm = String(today.getMonth() + 1).padStart(2, '0');
|
||||||
|
const dd = String(today.getDate()).padStart(2, '0');
|
||||||
|
const todayStr = `${yyyy}.${mm}.${dd}`;
|
||||||
|
return laraGames.filter(game => game.date === todayStr);
|
||||||
|
}
|
||||||
|
|
||||||
function getLatestGame(laraGames) {
|
function getLatestGame(laraGames) {
|
||||||
if (laraGames.length === 0) return null;
|
if (laraGames.length === 0) return null;
|
||||||
// Sort by round number, return the highest round
|
|
||||||
const sorted = [...laraGames].sort((a, b) => {
|
const sorted = [...laraGames].sort((a, b) => {
|
||||||
const roundA = parseInt(a.round) || 0;
|
const roundA = parseInt(a.round) || 0;
|
||||||
const roundB = parseInt(b.round) || 0;
|
const roundB = parseInt(b.round) || 0;
|
||||||
@@ -134,4 +142,5 @@ function getLatestGame(laraGames) {
|
|||||||
window.parsePGN = parsePGN;
|
window.parsePGN = parsePGN;
|
||||||
window.filterLaraGames = filterLaraGames;
|
window.filterLaraGames = filterLaraGames;
|
||||||
window.getLiveGame = getLiveGame;
|
window.getLiveGame = getLiveGame;
|
||||||
|
window.getTodaysGames = getTodaysGames;
|
||||||
window.getLatestGame = getLatestGame;
|
window.getLatestGame = getLatestGame;
|
||||||
|
|||||||
22
js/state.js
Normal file
22
js/state.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Lara Kiesewetter – Live Schachturnier
|
||||||
|
* Global state
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global Chess */
|
||||||
|
|
||||||
|
let board = null;
|
||||||
|
let chess = null;
|
||||||
|
let currentGame = null;
|
||||||
|
let allLaraGames = [];
|
||||||
|
let currentMoveIndex = -1;
|
||||||
|
let userSelectedGame = false;
|
||||||
|
let evalAbortController = null;
|
||||||
|
let lastEvalFen = null;
|
||||||
|
let currentRound = 0;
|
||||||
|
let roundPgns = {};
|
||||||
|
let pollId = 0;
|
||||||
|
let pollInterval = null;
|
||||||
|
let updateTimer = null;
|
||||||
|
let standingsInterval = null;
|
||||||
|
let previousMoveCount = -1;
|
||||||
255
js/ui.js
Normal file
255
js/ui.js
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
/**
|
||||||
|
* Lara Kiesewetter – Live Schachturnier
|
||||||
|
* UI rendering: player info, clocks, highlights, moves list, games list, PGN display
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global $, Chess */
|
||||||
|
|
||||||
|
function showLoading(show) {
|
||||||
|
document.getElementById('loading-overlay').style.display = show ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideError() {
|
||||||
|
document.getElementById('error-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePlayerInfo() {
|
||||||
|
if (!currentGame) return;
|
||||||
|
|
||||||
|
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
||||||
|
|
||||||
|
const laraPlayer = laraIsWhite ? currentGame.white : currentGame.black;
|
||||||
|
const laraElo = laraIsWhite ? currentGame.whiteElo : currentGame.blackElo;
|
||||||
|
const laraEmoji = laraIsWhite ? '⬜' : '⬛';
|
||||||
|
|
||||||
|
const oppPlayer = laraIsWhite ? currentGame.black : currentGame.white;
|
||||||
|
const oppElo = laraIsWhite ? currentGame.blackElo : currentGame.whiteElo;
|
||||||
|
const oppEmoji = laraIsWhite ? '⬛' : '⬜';
|
||||||
|
|
||||||
|
document.getElementById('black-name').textContent = oppPlayer;
|
||||||
|
document.getElementById('black-elo').textContent = oppElo ? `(ELO: ${oppElo})` : '';
|
||||||
|
document.querySelector('#player-black .player-avatar').textContent = oppEmoji;
|
||||||
|
|
||||||
|
document.getElementById('white-name').textContent = laraPlayer;
|
||||||
|
document.getElementById('white-elo').textContent = laraElo ? `(ELO: ${laraElo})` : '';
|
||||||
|
document.querySelector('#player-white .player-avatar').textContent = laraEmoji;
|
||||||
|
|
||||||
|
updateClocks(currentMoveIndex);
|
||||||
|
|
||||||
|
const roundInfo = `Runde ${currentGame.round} – ${currentGame.event || 'Turnier'}`;
|
||||||
|
document.getElementById('round-info').textContent = roundInfo;
|
||||||
|
const mobileRound = document.getElementById('round-info-mobile');
|
||||||
|
if (mobileRound) mobileRound.textContent = roundInfo;
|
||||||
|
|
||||||
|
const resultEl = document.getElementById('result-info');
|
||||||
|
if (currentGame.isLive) {
|
||||||
|
resultEl.innerHTML = '<span style="color: #4ade80;">● Laufen</span>';
|
||||||
|
} else {
|
||||||
|
resultEl.textContent = `Ergebnis: ${currentGame.result}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateClocks(moveIndex) {
|
||||||
|
if (!currentGame) return;
|
||||||
|
|
||||||
|
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
||||||
|
|
||||||
|
let whiteClock, blackClock;
|
||||||
|
|
||||||
|
if (moveIndex < 0) {
|
||||||
|
whiteClock = currentGame.whiteClock;
|
||||||
|
blackClock = currentGame.blackClock;
|
||||||
|
} else {
|
||||||
|
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||||
|
if (moveIndex < nonResultMoves.length) {
|
||||||
|
whiteClock = nonResultMoves[moveIndex].whiteClock;
|
||||||
|
blackClock = nonResultMoves[moveIndex].blackClock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const laraClock = laraIsWhite ? whiteClock : blackClock;
|
||||||
|
const oppClock = laraIsWhite ? blackClock : whiteClock;
|
||||||
|
|
||||||
|
document.getElementById('black-clock').textContent = oppClock || '--:--:--';
|
||||||
|
document.getElementById('white-clock').textContent = laraClock || '--:--:--';
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightActivePlayer() {
|
||||||
|
if (!chess || !currentGame) return;
|
||||||
|
|
||||||
|
const laraPanel = document.getElementById('player-white');
|
||||||
|
const oppPanel = document.getElementById('player-black');
|
||||||
|
|
||||||
|
laraPanel.classList.remove('active');
|
||||||
|
oppPanel.classList.remove('active');
|
||||||
|
|
||||||
|
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
||||||
|
const isLaraTurn = (chess.turn() === 'w' && laraIsWhite) || (chess.turn() === 'b' && !laraIsWhite);
|
||||||
|
|
||||||
|
if (isLaraTurn) {
|
||||||
|
laraPanel.classList.add('active');
|
||||||
|
} else {
|
||||||
|
oppPanel.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightLastMove() {
|
||||||
|
if (!board || !chess || !currentGame) return;
|
||||||
|
|
||||||
|
$('#board [data-square]').removeClass('last-move-highlight');
|
||||||
|
|
||||||
|
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||||
|
|
||||||
|
if (currentMoveIndex >= 0 && currentMoveIndex < nonResultMoves.length) {
|
||||||
|
const moves = chess.history({ verbose: true });
|
||||||
|
if (moves.length > 0) {
|
||||||
|
const lastMoveData = moves[moves.length - 1];
|
||||||
|
$(`#board [data-square="${lastMoveData.from}"]`).addClass('last-move-highlight');
|
||||||
|
$(`#board [data-square="${lastMoveData.to}"]`).addClass('last-move-highlight');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePGN(game) {
|
||||||
|
if (!game) return '';
|
||||||
|
|
||||||
|
let pgn = '';
|
||||||
|
if (game.event) pgn += `[Event "${game.event}"]\n`;
|
||||||
|
if (game.site) pgn += `[Site "${game.site}"]\n`;
|
||||||
|
if (game.date) pgn += `[Date "${game.date}"]\n`;
|
||||||
|
if (game.round) pgn += `[Round "${game.round}"]\n`;
|
||||||
|
if (game.white) pgn += `[White "${game.white}"]\n`;
|
||||||
|
if (game.black) pgn += `[Black "${game.black}"]\n`;
|
||||||
|
if (game.result) pgn += `[Result "${game.result}"]\n`;
|
||||||
|
if (game.whiteElo) pgn += `[WhiteElo "${game.whiteElo}"]\n`;
|
||||||
|
if (game.blackElo) pgn += `[BlackElo "${game.blackElo}"]\n`;
|
||||||
|
if (game.termination) pgn += `[Termination "${game.termination}"]\n`;
|
||||||
|
|
||||||
|
pgn += '\n';
|
||||||
|
|
||||||
|
const nonResultMoves = game.moves.filter(m => !m.isResult);
|
||||||
|
for (let i = 0; i < nonResultMoves.length; i += 2) {
|
||||||
|
const moveNumber = Math.floor(i / 2) + 1;
|
||||||
|
pgn += `${moveNumber}. ${nonResultMoves[i].san}`;
|
||||||
|
if (i + 1 < nonResultMoves.length) {
|
||||||
|
pgn += ` ${nonResultMoves[i + 1].san} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultMove = game.moves.find(m => m.isResult);
|
||||||
|
if (resultMove) {
|
||||||
|
pgn += ` ${resultMove.san}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pgn.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePGNDisplay() {
|
||||||
|
if (!currentGame) return;
|
||||||
|
const text = generatePGN(currentGame);
|
||||||
|
document.querySelectorAll('.pgn-text').forEach(el => el.textContent = text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMovesList() {
|
||||||
|
if (!currentGame) return;
|
||||||
|
|
||||||
|
const movesList = document.getElementById('moves-list');
|
||||||
|
movesList.innerHTML = '';
|
||||||
|
|
||||||
|
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||||
|
const laraIsWhite = currentGame.white.toLowerCase().includes('kiesewetter');
|
||||||
|
|
||||||
|
for (let i = 0; i < nonResultMoves.length; i += 2) {
|
||||||
|
const moveNumber = Math.floor(i / 2) + 1;
|
||||||
|
|
||||||
|
const numSpan = document.createElement('span');
|
||||||
|
numSpan.className = 'move-number';
|
||||||
|
numSpan.textContent = `${moveNumber}.`;
|
||||||
|
movesList.appendChild(numSpan);
|
||||||
|
|
||||||
|
if (laraIsWhite) {
|
||||||
|
const laraMove = document.createElement('span');
|
||||||
|
laraMove.className = 'move lara-move';
|
||||||
|
laraMove.textContent = nonResultMoves[i].san;
|
||||||
|
laraMove.dataset.index = i;
|
||||||
|
movesList.appendChild(laraMove);
|
||||||
|
|
||||||
|
if (i + 1 < nonResultMoves.length) {
|
||||||
|
const oppMove = document.createElement('span');
|
||||||
|
oppMove.className = 'move opp-move';
|
||||||
|
oppMove.textContent = nonResultMoves[i + 1].san;
|
||||||
|
oppMove.dataset.index = i + 1;
|
||||||
|
movesList.appendChild(oppMove);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const oppMove = document.createElement('span');
|
||||||
|
oppMove.className = 'move opp-move';
|
||||||
|
oppMove.textContent = nonResultMoves[i].san;
|
||||||
|
oppMove.dataset.index = i;
|
||||||
|
movesList.appendChild(oppMove);
|
||||||
|
|
||||||
|
if (i + 1 < nonResultMoves.length) {
|
||||||
|
const laraMove = document.createElement('span');
|
||||||
|
laraMove.className = 'move lara-move';
|
||||||
|
laraMove.textContent = nonResultMoves[i + 1].san;
|
||||||
|
laraMove.dataset.index = i + 1;
|
||||||
|
movesList.appendChild(laraMove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentMoveIndex >= 0) {
|
||||||
|
const curMove = movesList.querySelector(`[data-index="${currentMoveIndex}"]`);
|
||||||
|
if (curMove) curMove.classList.add('current');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAllGamesList() {
|
||||||
|
const list = document.getElementById('all-games-list');
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="game-round">${resultIcon} 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', () => {
|
||||||
|
userSelectedGame = true;
|
||||||
|
currentGame = game;
|
||||||
|
previousMoveCount = -1;
|
||||||
|
currentMoveIndex = Number.MAX_SAFE_INTEGER;
|
||||||
|
updateBoard();
|
||||||
|
updatePlayerInfo();
|
||||||
|
updateMovesList();
|
||||||
|
updateAllGamesList();
|
||||||
|
updatePGNDisplay();
|
||||||
|
});
|
||||||
|
|
||||||
|
list.appendChild(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user