| import { Chess } from '../../libs/chess.js'; |
|
|
| import { Chessboard } from './board/Chessboard.js'; |
| import { MoveTree } from './moves/MoveTree.js'; |
| import { MoveNavigator } from './moves/MoveNavigator.js'; |
| import { EvaluationQueue } from '../evaluation/EvaluationQueue.js'; |
|
|
| import { GamesList } from './games/GamesList.js'; |
| import { GameStats } from './report/GameStats.js'; |
| import { GameGraph } from './report/GameGraph.js'; |
| import { EvaluationBar } from './board/EvaluationBar.js'; |
| import { EngineLines } from './moves/EngineLines.js'; |
| import { MoveInformation } from './moves/MoveInformation.js'; |
| import { Clock } from './board/Clock.js'; |
|
|
| import { MoveEvaluator } from '../evaluation/MoveEvaluator.js'; |
| import { Classification } from '../classification/MoveClassifier.js'; |
| import { SidebarOverlay } from './report/SidebarOverlay.js'; |
| import { GameClassifier } from '../classification/GameClassifier.js'; |
| import { SettingsMenu } from './settings/SettingsMenu.js'; |
|
|
| |
| |
| |
| export class ChessUI { |
| |
| |
| |
| |
| |
| constructor() { |
| this.chess = new Chess(); |
| this.settingsMenu = new SettingsMenu('.settings-menu-container'); |
| |
| |
| this.board = new Chessboard("#chessboard", { |
| theme: { |
| boardDarkSquareColor: this.settingsMenu.getSettingValue('theme.boardDarkSquareColor') || 'rgba(110, 161, 118, 1)', |
| boardLightSquareColor: this.settingsMenu.getSettingValue('theme.boardLightSquareColor') || 'rgba(224, 224, 224, 1)', |
| pieceFolderName: this.settingsMenu.getSettingValue('pieceTheme') || 'cburnett' |
| }, |
| showBoardLabels: this.settingsMenu.getSettingValue('theme.boardLabels') === 'letter' || true |
| }, this.chess); |
|
|
| this.settingsMenu.init(this.board); |
| this.board.setOption({ isInteractive: false }); |
|
|
| this.moveTree = new MoveTree(); |
| this.moveNavigator = new MoveNavigator(this); |
| this.evaluationQueue = new EvaluationQueue(this.settingsMenu); |
|
|
| this.gamesList = new GamesList(); |
|
|
| GameGraph.render(); |
| GameGraph.setClickCallback((clickedMove) => { |
| |
| const moveIndex = this.analysis?.moves?.indexOf(clickedMove); |
| if (moveIndex !== undefined && moveIndex >= 0) { |
| |
| const mainlineIndex = moveIndex + 1; |
| if (mainlineIndex < this.moveTree.mainline.length) { |
| const targetNode = this.moveTree.mainline[mainlineIndex]; |
| this.moveNavigator.handleTreeNodeClick(targetNode); |
| } |
| } |
| }); |
|
|
| GameStats.render(); |
| EvaluationBar.updateEvaluationBar(); |
| MoveInformation.updateMoveInfo(this.moveTree.mainline[0], this.moveTree.mainline[0]); |
| EngineLines.updateEngineLines(this.moveTree.mainline[0], |
| this.moveTree, |
| (node) => this.moveNavigator.handleTreeNodeClick(node), |
| (node, resultFen, prevFen) => this.moveNavigator.queueMoveForEvaluation(node, resultFen, prevFen) |
| ); |
| |
| |
| Clock.resetClocks(); |
| } |
|
|
| async load(game) { |
| this.moveNavigator.handleRestart(); |
|
|
| this.game = game; |
|
|
| |
| this.chess.loadPgn(this.game.pgn); |
| this.chess.reset(); |
| |
| this.moveTree.buildFromPGN(this.game.pgn, this.chess); |
|
|
| |
| Clock.setInitialClocks(this.moveTree, this.game.pgn); |
|
|
| SidebarOverlay.show(); |
| SidebarOverlay.startFactCycling(); |
| SidebarOverlay.updateEvaluationProgress(0); |
|
|
| this.board.fen(this.moveTree.mainline[0].fen); |
|
|
| |
| const userIsBlack = this.game.username.toLowerCase() === this.game.black.name.toLowerCase(); |
| if (userIsBlack) { |
| this.moveNavigator.handleFlipBoard(); |
| } |
|
|
| $('.analysis-overlay').addClass('active'); |
| $('.tab-content, .bottom-content').addClass('blur-content'); |
| this.board.setOption({ isInteractive: false }); |
|
|
| const engineType = this.settingsMenu.getSettingValue('engineType'); |
| const engineDepth = this.settingsMenu.getSettingValue('engineDepth') || 14; |
|
|
| const analysis = await MoveEvaluator.analyzeGame( |
| this.game, |
| (progress) => { |
| SidebarOverlay.updateEvaluationProgress(progress); |
| }, |
| { engineType, engineDepth } |
| ); |
|
|
| this.board.setOption({ isInteractive: true }); |
|
|
| |
| this.analysis = analysis; |
|
|
| const graphedMoves = analysis.moves.map(move => move.graph / 100); |
|
|
| const classify = new GameClassifier(); |
| const gameClass = classify.classifyGame(graphedMoves, userIsBlack ? 'w' : 'b', this.game.result); |
| $(".game-info").empty().append(`<p>${gameClass.message}</p>`); |
|
|
| SidebarOverlay.hide(); |
| SidebarOverlay.stopFactCycling(); |
| |
| MoveEvaluator.applyClassificationsToMoveTree(this.moveTree, analysis.moves, this.game.pgn); |
| GameGraph.setAnalysis(analysis); |
| GameStats.render('.game-stats', analysis, game.white.name, game.black.name); |
|
|
| this.moveTree.render('move-tree', (node) => { |
| this.moveNavigator.handleTreeNodeClick(node); |
| }); |
|
|
| $('.analysis-overlay').removeClass('active'); |
| $('.tab-content, .bottom-content').removeClass('blur-content'); |
|
|
| |
| Clock.updateFromMoveTree(this.moveTree, this.board.flipped, this.game?.pgn); |
|
|
| if (!this.eventHandlersSetup) { |
| this.moveNavigator.setupEventHandlers(); |
| this.eventHandlersSetup = true; |
| } |
| } |
|
|
| async cacheClassifications() { |
| const classifications = Object.entries(Classification); |
| const loadPromises = []; |
|
|
| |
| $.each(classifications, (_, classif) => { |
| const path = classif[1].src; |
| const type = classif[1].type; |
|
|
| const promise = $.ajax({ |
| url: path, |
| dataType: 'text' |
| }) |
| .then(svgText => { |
| |
| const base64 = btoa(unescape(encodeURIComponent(svgText))); |
| const dataUri = `data:image/svg+xml;base64,${base64}`; |
|
|
| |
| const $img = $('<img>', { |
| src: dataUri, |
| class: 'move-icon', |
| alt: type |
| })[0]; |
|
|
| Classification[classif[0]].cachedImg = $img; |
| return type; |
| }) |
| .catch(error => { |
| console.error(`Failed to load ${path}:`, error); |
| return null; |
| }); |
|
|
| loadPromises.push(promise); |
| }); |
|
|
| |
| await Promise.allSettled(loadPromises); |
| } |
| } |