| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Arcane Touch Analyzer</title> |
| |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/chessboard-js/1.0.0/chessboard-1.0.0.min.css"> |
| |
| <style> |
| :root { |
| --bg-color: #212121; |
| --text-color: #e0e0e0; |
| --accent-color: #4caf50; |
| --highlight: #00e5ff; |
| --selected: #ffeb3b; |
| --panel-bg: #333; |
| } |
| |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background-color: var(--bg-color); |
| color: var(--text-color); |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| margin: 0; |
| padding: 10px; |
| user-select: none; |
| -webkit-tap-highlight-color: transparent; |
| } |
| |
| h1 { margin-bottom: 10px; text-align: center; font-size: 1.5rem; } |
| |
| .container { |
| display: flex; |
| flex-direction: column; |
| gap: 15px; |
| justify-content: center; |
| align-items: center; |
| max-width: 900px; |
| width: 100%; |
| } |
| |
| |
| #board { |
| width: 100%; |
| max-width: 380px; |
| aspect-ratio: 1 / 1; |
| margin: 0 auto; |
| background-color: #303030; |
| border: 3px solid #555; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: #777; |
| touch-action: none; |
| } |
| |
| |
| .highlight-best { |
| box-shadow: inset 0 0 3px 3px var(--highlight) !important; |
| background-color: rgba(0, 229, 255, 0.3) !important; |
| } |
| |
| .highlight-selected { |
| box-shadow: inset 0 0 3px 3px var(--selected) !important; |
| background-color: rgba(255, 235, 59, 0.4) !important; |
| } |
| |
| |
| .turn-controls { |
| display: flex; |
| gap: 15px; |
| justify-content: center; |
| width: 100%; |
| max-width: 380px; |
| margin-bottom: 5px; |
| } |
| |
| .turn-card { |
| flex: 1; |
| background: #444; |
| padding: 8px; |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 10px; |
| cursor: pointer; |
| border: 2px solid transparent; |
| opacity: 0.5; |
| transition: all 0.2s ease; |
| } |
| |
| .turn-card img { height: 30px; } |
| .turn-card span { font-weight: bold; font-size: 0.9em; } |
| |
| .turn-card.active { |
| opacity: 1; |
| background: #505050; |
| border-color: var(--accent-color); |
| box-shadow: 0 0 10px rgba(76, 175, 80, 0.3); |
| } |
| |
| |
| .panel { |
| width: 100%; |
| max-width: 380px; |
| background: var(--panel-bg); |
| padding: 15px; |
| border-radius: 8px; |
| box-sizing: border-box; |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| .best-move-box { |
| background: #2a2a2a; |
| padding: 15px; |
| border-radius: 8px; |
| text-align: center; |
| border: 1px solid #555; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .loading-bar { |
| position: absolute; |
| bottom: 0; |
| left: 0; |
| height: 4px; |
| background: var(--highlight); |
| width: 0%; |
| transition: width 0.1s linear; |
| } |
| |
| .best-move-label { display: block; color: #888; font-size: 0.8em; margin-bottom: 5px; } |
| .best-move-text { |
| font-size: 1.8em; |
| font-weight: bold; |
| color: var(--highlight); |
| font-family: monospace; |
| letter-spacing: 2px; |
| } |
| |
| .controls { display: flex; gap: 10px; margin-top: 5px; } |
| button { |
| flex: 1; |
| padding: 12px; |
| border: none; |
| border-radius: 4px; |
| cursor: pointer; |
| font-weight: bold; |
| color: white; |
| background-color: #555; |
| font-size: 16px; |
| } |
| button:active { transform: scale(0.98); } |
| #flipBtn { background-color: #008CBA; } |
| |
| #debug-log { display:none; color:red; background:#300; padding:10px; width:100%; font-size:12px;} |
| </style> |
| </head> |
| <body> |
|
|
| <h1>⚡ Arcane Touch Analyzer</h1> |
|
|
| <div id="debug-log"></div> |
|
|
| <div class="container"> |
| |
| <div class="turn-controls"> |
| <div id="turn-w" class="turn-card active" onclick="forceTurn('w')"> |
| <img src="https://chessboardjs.com/img/chesspieces/wikipedia/wK.png" alt="White"> |
| <span>White</span> |
| </div> |
| <div id="turn-b" class="turn-card" onclick="forceTurn('b')"> |
| <img src="https://chessboardjs.com/img/chesspieces/wikipedia/bK.png" alt="Black"> |
| <span>Black</span> |
| </div> |
| </div> |
|
|
| <div id="board"> |
| <div style="text-align:center;">Loading...</div> |
| </div> |
|
|
| <div class="panel"> |
| |
| <div class="best-move-box"> |
| <div id="loading-bar" class="loading-bar"></div> |
| <span class="best-move-label">BEST MOVE (2s Limit)</span> |
| <div id="best-move" class="best-move-text">...</div> |
| |
| <div style="display:flex; justify-content:space-between; margin-top:10px; font-size:0.9em; color:#aaa;"> |
| <span>Eval: <span id="eval-score" style="color:white">0.00</span></span> |
| <span id="engineStatus">Init...</span> |
| </div> |
| </div> |
|
|
| <div class="controls"> |
| <button id="resetBtn">Reset Board</button> |
| <button id="flipBtn">Flip View</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.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/chessboard-js/1.0.0/chessboard-1.0.0.min.js"></script> |
|
|
| <script> |
| window.onerror = function(msg) { $('#debug-log').show().text("Err: " + msg); }; |
| |
| const STOCKFISH_PATH = 'https://cdnjs.cloudflare.com/ajax/libs/stockfish.js/10.0.0/stockfish.js'; |
| let board, game, engine; |
| let selectedSquare = null; |
| |
| $(document).ready(function() { initGame(); }); |
| |
| function initGame() { |
| game = new Chess(); |
| |
| const config = { |
| draggable: true, |
| position: 'start', |
| onDragStart: onDragStart, |
| onDrop: onDrop, |
| onSnapEnd: onSnapEnd, |
| pieceTheme: 'https://chessboardjs.com/img/chesspieces/wikipedia/{piece}.png' |
| }; |
| |
| $('#board').empty(); |
| board = Chessboard('board', config); |
| $(window).resize(board.resize); |
| |
| |
| |
| $('#board').on('click', '[class^="square-"]', onSquareClick); |
| |
| initStockfish(); |
| updateUI(); |
| } |
| |
| |
| function onSquareClick() { |
| |
| const square = $(this).attr('data-square'); |
| const piece = game.get(square); |
| const turn = game.turn(); |
| |
| |
| if (selectedSquare === null) { |
| |
| if (piece && piece.color === turn) { |
| selectedSquare = square; |
| highlightSelected(square); |
| } |
| return; |
| } |
| |
| |
| const move = game.move({ |
| from: selectedSquare, |
| to: square, |
| promotion: 'q' |
| }); |
| |
| |
| if (move) { |
| board.position(game.fen()); |
| selectedSquare = null; |
| removeSelectionHighlights(); |
| updateUI(); |
| analyzePosition(); |
| } |
| |
| else { |
| |
| if (piece && piece.color === turn) { |
| selectedSquare = square; |
| highlightSelected(square); |
| } else { |
| |
| selectedSquare = null; |
| removeSelectionHighlights(); |
| } |
| } |
| } |
| |
| |
| function highlightBestMove(move) { |
| $('#board .square-55d63').removeClass('highlight-best'); |
| let source = move.substring(0, 2); |
| let target = move.substring(2, 4); |
| $('#board .square-' + source).addClass('highlight-best'); |
| $('#board .square-' + target).addClass('highlight-best'); |
| } |
| |
| function highlightSelected(square) { |
| removeSelectionHighlights(); |
| $('#board .square-' + square).addClass('highlight-selected'); |
| } |
| |
| function removeSelectionHighlights() { |
| $('#board .square-55d63').removeClass('highlight-selected'); |
| } |
| |
| function removeBestHighlights() { |
| $('#board .square-55d63').removeClass('highlight-best'); |
| } |
| |
| |
| function onDragStart(source, piece) { |
| if (game.game_over()) return false; |
| if ((game.turn() === 'w' && piece.search(/^b/) !== -1) || |
| (game.turn() === 'b' && piece.search(/^w/) !== -1)) { |
| return false; |
| } |
| |
| selectedSquare = null; |
| removeSelectionHighlights(); |
| } |
| |
| function onDrop(source, target) { |
| const move = game.move({ |
| from: source, |
| to: target, |
| promotion: 'q' |
| }); |
| |
| if (move === null) return 'snapback'; |
| |
| updateUI(); |
| analyzePosition(); |
| } |
| |
| function onSnapEnd() { |
| board.position(game.fen()); |
| } |
| |
| |
| function forceTurn(color) { |
| if (game.turn() === color) return; |
| let fen = game.fen(); |
| let parts = fen.split(' '); |
| parts[1] = color; |
| parts[3] = '-'; |
| let newFen = parts.join(' '); |
| if(game.load(newFen)) { |
| board.position(newFen); |
| selectedSquare = null; |
| removeSelectionHighlights(); |
| updateUI(); |
| analyzePosition(); |
| } |
| } |
| |
| function updateUI() { |
| $('.turn-card').removeClass('active'); |
| if (game.turn() === 'w') $('#turn-w').addClass('active'); |
| else $('#turn-b').addClass('active'); |
| } |
| |
| |
| function initStockfish() { |
| try { |
| const blob = new Blob([`importScripts('${STOCKFISH_PATH}')`], {type: 'application/javascript'}); |
| const url = URL.createObjectURL(blob); |
| engine = new Worker(url); |
| |
| engine.onmessage = function(event) { |
| const line = event.data; |
| |
| if (line === 'uciok') { |
| $('#engineStatus').text('Ready').css('color', '#4caf50'); |
| } |
| |
| if (line.startsWith('info') && line.includes('score cp')) { |
| const matchScore = line.match(/score cp (-?\d+)/); |
| const matchMate = line.match(/score mate (-?\d+)/); |
| const matchPv = line.match(/ pv (.+)/); |
| |
| if (matchMate) { |
| $('#eval-score').text(`M${Math.abs(matchMate[1])}`).css('color', '#ff5252'); |
| } else if (matchScore) { |
| const score = parseInt(matchScore[1]) / 100; |
| const finalScore = game.turn() === 'b' ? -score : score; |
| const sign = finalScore > 0 ? '+' : ''; |
| $('#eval-score').text(`${sign}${finalScore}`).css('color', finalScore >= 0 ? '#4caf50' : '#ff5252'); |
| } |
| |
| if (matchPv) { |
| let moves = matchPv[1].split(' '); |
| let bestMove = moves[0]; |
| $('#best-move').html(`➜ ${bestMove}`); |
| highlightBestMove(bestMove); |
| } |
| } |
| |
| if (line.startsWith('bestmove')) { |
| $('#loading-bar').css('width', '0%'); |
| $('#engineStatus').text('Done'); |
| } |
| }; |
| engine.postMessage('uci'); |
| engine.postMessage('isready'); |
| } catch(e) { |
| $('#engineStatus').text('Offline'); |
| } |
| } |
| |
| function analyzePosition() { |
| $('#best-move').text('...'); |
| removeBestHighlights(); |
| $('#engineStatus').text('Thinking...'); |
| $('#loading-bar').css('width', '0%').animate({width: '100%'}, 2000); |
| |
| if(engine) { |
| engine.postMessage(`position fen ${game.fen()}`); |
| engine.postMessage('go movetime 2000'); |
| } |
| } |
| |
| $('#resetBtn').click(function() { |
| game.reset(); |
| board.start(); |
| selectedSquare = null; |
| $('#best-move').text('...'); |
| $('#eval-score').text('0.00'); |
| removeSelectionHighlights(); |
| removeBestHighlights(); |
| updateUI(); |
| }); |
| |
| $('#flipBtn').click(function() { |
| board.flip(); |
| |
| let currentText = $('#best-move').text(); |
| if(currentText.includes('➜')) { |
| let move = currentText.replace('➜ ', ''); |
| highlightBestMove(move); |
| } |
| if(selectedSquare) { |
| highlightSelected(selectedSquare); |
| } |
| }); |
| |
| </script> |
| </body> |
| </html> |
|
|