| |
| |
| |
| export class Clock { |
| |
| |
| |
| |
| |
| static updateClocks(node, isFlipped = false) { |
| |
| const whiteTime = Clock.getClockTime(node, 'white'); |
| const blackTime = Clock.getClockTime(node, 'black'); |
| |
| |
| Clock.displayClock('#white-clock .clock-time', whiteTime); |
| Clock.displayClock('#black-clock .clock-time', blackTime); |
| |
| |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| static getClockTime(node, color) { |
| |
| if (node.clock) { |
| |
| const isWhiteMove = node.move && node.move.color === 'w'; |
| const isBlackMove = node.move && node.move.color === 'b'; |
| |
| if ((color === 'white' && isWhiteMove) || (color === 'black' && isBlackMove)) { |
| return node.clock; |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| |
| |
| |
| |
| |
| static displayClock(selector, timeString, isActive = false) { |
| const element = document.querySelector(selector); |
| if (!element) return; |
| |
| |
| element.classList.remove('low-time', 'active'); |
| |
| if (timeString) { |
| const formattedTime = Clock.formatClockTime(timeString); |
| element.textContent = formattedTime; |
| |
| |
| const seconds = Clock.parseTimeToSeconds(timeString); |
| if (seconds !== null && seconds < 10) { |
| element.classList.add('low-time'); |
| } |
| } else { |
| element.textContent = '--:--'; |
| } |
| |
| |
| if (isActive) { |
| element.classList.add('active'); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| static formatClockTime(timeString) { |
| |
| if (timeString.includes(':')) { |
| const parts = timeString.split(':'); |
| if (parts.length >= 3) { |
| |
| const hours = parseInt(parts[0]); |
| const minutes = parseInt(parts[1]); |
| const seconds = parseInt(parts[2].split('.')[0]); |
| |
| if (hours > 0) { |
| return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; |
| } else { |
| return `${minutes}:${seconds.toString().padStart(2, '0')}`; |
| } |
| } else if (parts.length === 2) { |
| |
| const minutes = parseInt(parts[0]); |
| const seconds = parseInt(parts[1].split('.')[0]); |
| return `${minutes}:${seconds.toString().padStart(2, '0')}`; |
| } |
| } |
| |
| |
| return timeString; |
| } |
| |
| |
| |
| |
| |
| |
| static parseTimeToSeconds(timeString) { |
| try { |
| if (timeString.includes(':')) { |
| const parts = timeString.split(':'); |
| if (parts.length >= 3) { |
| |
| const hours = parseInt(parts[0]); |
| const minutes = parseInt(parts[1]); |
| const seconds = parseInt(parts[2].split('.')[0]); |
| return hours * 3600 + minutes * 60 + seconds; |
| } else if (parts.length === 2) { |
| |
| const minutes = parseInt(parts[0]); |
| const seconds = parseInt(parts[1].split('.')[0]); |
| return minutes * 60 + seconds; |
| } |
| } |
| } catch (e) { |
| console.warn('Failed to parse time string:', timeString); |
| } |
| |
| return null; |
| } |
| |
| |
| |
| |
| static resetClocks() { |
| Clock.displayClock('#white-clock .clock-time', null, false); |
| Clock.displayClock('#black-clock .clock-time', null, false); |
| } |
| |
| |
| |
| |
| |
| |
| |
| static updateFromMoveTree(moveTree, isFlipped = false, pgn = null) { |
| if (!moveTree || !moveTree.currentNode) { |
| Clock.resetClocks(); |
| return; |
| } |
| |
| const currentNode = moveTree.currentNode; |
| |
| |
| const getInitialTime = () => { |
| |
| if (pgn) { |
| const timeControlTime = Clock.getTimeControlFromPGN(pgn); |
| if (timeControlTime) return timeControlTime; |
| } |
| |
| |
| if (moveTree.mainline.length > 1) { |
| const firstMove = moveTree.mainline[1]; |
| if (firstMove.clock) { |
| return firstMove.clock; |
| } |
| } |
| return null; |
| }; |
| |
| |
| if (currentNode.id === 'root') { |
| const initialTime = getInitialTime(); |
| Clock.displayClock('#white-clock .clock-time', initialTime, true); |
| Clock.displayClock('#black-clock .clock-time', initialTime, false); |
| return; |
| } |
| |
| |
| let whiteTime = null; |
| let blackTime = null; |
| let nextToMove = 'w'; |
| |
| |
| const nodeIndex = moveTree.getNodeIndex(currentNode); |
| if (nodeIndex !== -1) { |
| |
| if (currentNode.move) { |
| nextToMove = currentNode.move.color === 'w' ? 'b' : 'w'; |
| } |
| |
| |
| for (let i = nodeIndex; i >= 1; i--) { |
| const node = moveTree.mainline[i]; |
| if (node.clock && node.move) { |
| if (node.move.color === 'w' && !whiteTime) { |
| whiteTime = node.clock; |
| } else if (node.move.color === 'b' && !blackTime) { |
| blackTime = node.clock; |
| } |
| } |
| |
| |
| if (whiteTime && blackTime) break; |
| } |
| } |
| |
| |
| const initialTime = getInitialTime(); |
| if (!whiteTime) whiteTime = initialTime; |
| if (!blackTime) blackTime = initialTime; |
| |
| |
| const isWhiteActive = nextToMove === 'w'; |
| Clock.displayClock('#white-clock .clock-time', whiteTime, isWhiteActive); |
| Clock.displayClock('#black-clock .clock-time', blackTime, !isWhiteActive); |
| } |
| |
| |
| |
| |
| |
| |
| static setInitialClocks(moveTree, pgn) { |
| if (!moveTree) { |
| Clock.resetClocks(); |
| return; |
| } |
| |
| |
| const initialTime = Clock.getTimeControlFromPGN(pgn); |
| |
| |
| Clock.displayClock('#white-clock .clock-time', initialTime, true); |
| Clock.displayClock('#black-clock .clock-time', initialTime, false); |
| } |
| |
| |
| |
| |
| |
| |
| static getTimeControlFromPGN(pgn) { |
| if (!pgn) return null; |
| |
| |
| const timeControlMatch = pgn.match(/\[TimeControl\s+"([^"]+)"\]/i); |
| if (!timeControlMatch) return null; |
| |
| const timeControlValue = timeControlMatch[1]; |
| |
| |
| if (timeControlValue === '-') { |
| |
| return null; |
| } |
| |
| |
| const parts = timeControlValue.split('+'); |
| const baseTimeSeconds = parseInt(parts[0]); |
| |
| if (isNaN(baseTimeSeconds)) return null; |
| |
| |
| return Clock.secondsToTimeString(baseTimeSeconds); |
| } |
| |
| |
| |
| |
| |
| |
| static secondsToTimeString(seconds) { |
| const hours = Math.floor(seconds / 3600); |
| const minutes = Math.floor((seconds % 3600) / 60); |
| const remainingSeconds = seconds % 60; |
| |
| if (hours > 0) { |
| return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; |
| } else { |
| return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; |
| } |
| } |
| } |