| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Neural Chess Engine</title> |
| <link rel="stylesheet" href="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css" /> |
| <script src="https://code.jquery.com/jquery-3.5.1.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> |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;700&display=swap" rel="stylesheet"> |
| <style> |
| body { |
| font-family: 'Outfit', sans-serif; |
| background: linear-gradient(135deg, #0f2027, #203a43, #2c5364); |
| color: white; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| height: 100vh; |
| margin: 0; |
| } |
| .game-container { |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| margin-bottom: 20px; |
| } |
| #board { |
| width: 500px; |
| box-shadow: 0 15px 35px rgba(0,0,0,0.5); |
| border-radius: 4px; |
| overflow: hidden; |
| border: 4px solid #fff; |
| } |
| .eval-bar-container { |
| width: 30px; |
| height: 500px; |
| background-color: #333; |
| border-radius: 4px; |
| border: 4px solid #fff; |
| position: relative; |
| overflow: hidden; |
| display: flex; |
| flex-direction: column-reverse; |
| box-shadow: 0 15px 35px rgba(0,0,0,0.5); |
| } |
| .eval-bar-fill { |
| width: 100%; |
| height: 50%; |
| background-color: #fff; |
| transition: height 0.8s cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| .eval-marker { |
| position: absolute; |
| top: 50%; |
| left: 0; |
| width: 100%; |
| height: 2px; |
| background-color: #ff5e7e; |
| z-index: 10; |
| } |
| h1 { |
| font-weight: 700; |
| font-size: 2.5rem; |
| margin-bottom: 10px; |
| text-shadow: 2px 2px 10px rgba(0,0,0,0.5); |
| } |
| .status-panel { |
| padding: 15px 30px; |
| background: rgba(255, 255, 255, 0.1); |
| backdrop-filter: blur(10px); |
| border-radius: 12px; |
| font-size: 1.3rem; |
| min-width: 350px; |
| text-align: center; |
| box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); |
| border: 1px solid rgba(255, 255, 255, 0.18); |
| } |
| .controls-panel { |
| display: flex; |
| gap: 15px; |
| margin-top: 20px; |
| } |
| .styled-select { |
| padding: 10px 15px; |
| font-size: 1.1rem; |
| font-family: 'Outfit', sans-serif; |
| color: #fff; |
| background: rgba(255, 255, 255, 0.1); |
| border: 1px solid rgba(255, 255, 255, 0.18); |
| border-radius: 8px; |
| cursor: pointer; |
| backdrop-filter: blur(10px); |
| } |
| .styled-select option { |
| background: #203a43; |
| color: #fff; |
| } |
| .btn-reset { |
| padding: 10px 25px; |
| font-size: 1.1rem; |
| font-family: 'Outfit', sans-serif; |
| color: white; |
| background: #e94560; |
| border: none; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| .btn-reset:hover { |
| background: #ff5e7e; |
| transform: translateY(-2px); |
| box-shadow: 0 5px 15px rgba(233, 69, 96, 0.4); |
| } |
| .loading-bar-container { |
| width: 100%; |
| height: 6px; |
| background-color: rgba(255, 255, 255, 0.1); |
| border-radius: 3px; |
| overflow: hidden; |
| margin-top: 15px; |
| display: none; |
| } |
| .loading-bar { |
| width: 0%; |
| height: 100%; |
| background: #00ff88; |
| box-shadow: 0 0 10px #00ff88; |
| transition: width 0.1s linear; |
| } |
| </style> |
| </head> |
| <body> |
| <h1>โ๏ธ Neural Chess Engine</h1> |
| <div class="game-container"> |
| <div class="eval-bar-container" title="Neural Network Evaluation"> |
| <div class="eval-bar-fill" id="evalBar"></div> |
| <div class="eval-marker"></div> |
| </div> |
| <div id="board"></div> |
| </div> |
| <div class="status-panel"> |
| <div id="status">Your turn (White)</div> |
| <div class="loading-bar-container" id="loadingBar"> |
| <div class="loading-bar"></div> |
| </div> |
| </div> |
| <div class="controls-panel" style="flex-wrap: wrap; justify-content: center;"> |
| <select id="whitePlayerSelect" class="styled-select" onchange="resetGame()"> |
| <option value="human">White: Human</option> |
| <option value="neural">White: Neural Network</option> |
| <option value="veloct">White: VeloCT Ultimate</option> |
| </select> |
| <select id="blackPlayerSelect" class="styled-select" onchange="resetGame()"> |
| <option value="neural" selected>Black: Neural Network</option> |
| <option value="human">Black: Human</option> |
| <option value="veloct">Black: VeloCT Ultimate</option> |
| </select> |
| <button class="btn-reset" onclick="resetGame()">Restart Game</button> |
| </div> |
|
|
| <script> |
| var board = null; |
| var game = new Chess(); |
| var $status = $('#status'); |
| var history_moves = ["<bos>"]; |
| var progressInterval; |
| |
| var whitePlayer = "human"; |
| var blackPlayer = "neural"; |
| |
| function isHumanTurn() { |
| var turn = game.turn(); |
| if (turn === 'w' && whitePlayer === 'human') return true; |
| if (turn === 'b' && blackPlayer === 'human') return true; |
| return false; |
| } |
| |
| function getEngineChoice() { |
| return game.turn() === 'w' ? whitePlayer : blackPlayer; |
| } |
| |
| function getPlayerName(engineCode) { |
| if (engineCode === 'neural') return "Neural Network"; |
| if (engineCode === 'veloct') return "VeloCT Ultimate"; |
| return "Human"; |
| } |
| |
| function onDragStart (source, piece, position, orientation) { |
| if (game.game_over()) return false; |
| |
| if (!isHumanTurn()) return false; |
| |
| |
| if (game.turn() === 'w' && piece.search(/^b/) !== -1) return false; |
| if (game.turn() === 'b' && piece.search(/^w/) !== -1) return false; |
| } |
| |
| function getAiMove() { |
| if (game.game_over()) return; |
| |
| var engineChoice = getEngineChoice(); |
| var engineName = getPlayerName(engineChoice); |
| |
| $status.text(engineName + " is calculating best move..."); |
| |
| $('#loadingBar').show(); |
| $('.loading-bar').css('width', '0%'); |
| |
| var progress = 0; |
| if(progressInterval) clearInterval(progressInterval); |
| progressInterval = setInterval(function() { |
| progress += (95 - progress) * 0.05; |
| $('.loading-bar').css('width', progress + '%'); |
| }, 100); |
| |
| |
| $.ajax({ |
| url: '/move', |
| type: 'POST', |
| contentType: 'application/json', |
| data: JSON.stringify({ history: history_moves, fen: game.fen(), engine_choice: engineChoice }), |
| success: function(response) { |
| clearInterval(progressInterval); |
| $('.loading-bar').css('width', '100%'); |
| |
| setTimeout(function() { |
| $('#loadingBar').hide(); |
| if (response.ai_move) { |
| game.move(response.ai_move, {sloppy: true}); |
| history_moves.push(response.ai_move); |
| board.position(game.fen()); |
| |
| if(game.game_over()) { |
| if (game.in_checkmate()) { |
| var winner = game.turn() === 'w' ? 'Black' : 'White'; |
| $status.html("<span style='color: #ff5e7e'><b>Game Over! " + winner + " won by Checkmate! ๐</b></span>"); |
| } else { |
| $status.html("<b>Game Over! Draw.</b>"); |
| } |
| |
| |
| $.ajax({ |
| url: '/save_pgn', |
| type: 'POST', |
| contentType: 'application/json', |
| data: JSON.stringify({ pgn: game.pgn() }) |
| }); |
| } else { |
| $status.html(engineName + " played <b>" + response.ai_move + "</b>. " + (isHumanTurn() ? "Your turn!" : "Next AI's turn...")); |
| |
| |
| if (!isHumanTurn()) { |
| setTimeout(getAiMove, 500); |
| } |
| } |
| } else if (response.error) { |
| $status.text(response.error); |
| } |
| |
| if (response.eval !== undefined) { |
| var heightPercent = ((response.eval + 1.0) / 2.0) * 100; |
| heightPercent = Math.max(0, Math.min(100, heightPercent)); |
| $('#evalBar').css('height', heightPercent + '%'); |
| } |
| }, 300); |
| }, |
| error: function() { |
| clearInterval(progressInterval); |
| $('#loadingBar').hide(); |
| $status.text("Error communicating with AI server."); |
| } |
| }); |
| } |
| |
| function onDrop (source, target) { |
| |
| var move = game.move({ |
| from: source, |
| to: target, |
| promotion: 'q' |
| }); |
| |
| |
| if (move === null) return 'snapback'; |
| |
| var uci_move = source + target; |
| if (move.flags.includes('p') || move.flags.includes('cp')) uci_move += 'q'; |
| |
| history_moves.push(uci_move); |
| |
| if (game.game_over()) { |
| if (game.in_checkmate()) { |
| var winner = game.turn() === 'w' ? 'Black' : 'White'; |
| $status.html("<span style='color: #00ff88'><b>Game Over! " + winner + " won by Checkmate! ๐</b></span>"); |
| } else { |
| $status.html("<b>Game Over! Draw.</b>"); |
| } |
| |
| |
| $.ajax({ |
| url: '/save_pgn', |
| type: 'POST', |
| contentType: 'application/json', |
| data: JSON.stringify({ pgn: game.pgn() }) |
| }); |
| return; |
| } |
| |
| if (!isHumanTurn()) { |
| getAiMove(); |
| } |
| } |
| |
| function onSnapEnd () { |
| board.position(game.fen()); |
| } |
| |
| function resetGame() { |
| whitePlayer = $('#whitePlayerSelect').val(); |
| blackPlayer = $('#blackPlayerSelect').val(); |
| |
| game.reset(); |
| |
| |
| if (whitePlayer === 'human') board.orientation('white'); |
| else if (blackPlayer === 'human') board.orientation('black'); |
| else board.orientation('white'); |
| |
| board.start(); |
| history_moves = ["<bos>"]; |
| $('#loadingBar').hide(); |
| $('#evalBar').css('height', '50%'); |
| |
| if (isHumanTurn()) { |
| $status.text("Your turn!"); |
| } else { |
| $status.text("Starting Match..."); |
| getAiMove(); |
| } |
| } |
| |
| var config = { |
| draggable: true, |
| position: 'start', |
| orientation: 'white', |
| onDragStart: onDragStart, |
| onDrop: onDrop, |
| onSnapEnd: onSnapEnd, |
| pieceTheme: 'https://chessboardjs.com/img/chesspieces/wikipedia/{piece}.png' |
| }; |
| board = Chessboard('board', config); |
| |
| |
| $(document).ready(function() { |
| resetGame(); |
| }); |
| </script> |
| </body> |
| </html> |
|
|