CoolFace
Apppublic

Enensja/atomic-chess

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
app.py739 linesDownload Raw Back to root
1import os2import uvicorn3import tarfile4import stat5import random6import asyncio7from fastapi import FastAPI, Request8from fastapi.responses import HTMLResponse, JSONResponse9import chess10import chess.engine11import chess.variant12 13# --- 1. HTML ARAYÜZÜ ---14HTML_CONTENT = """15<!DOCTYPE html>16<html lang="tr">17<head>18    <meta charset="UTF-8">19    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">20    <title>Atomic Pro v40 - UI Polish</title>21    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>22    <link rel="stylesheet" href="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css">23    <script src="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"></script>24    25    <style>26        :root { --bg: #161512; --panel: #262421; --accent: #629924; --text: #bababa; --error: #d64f00; --blitz: #e69500; --opening: #884dff; }27        body { background-color: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; display: flex; flex-direction: column; align-items: center; height: 100vh; overflow: hidden; touch-action: none; }28        29        #top-bar { width: 100%; padding: 0 15px; background: var(--panel); display: flex; justify-content: space-between; align-items: center; height: 50px; border-bottom: 2px solid #3c3b39; box-sizing:border-box;}30        .app-title { font-weight: 800; color: #fff; letter-spacing: 1px; font-size: 1.1rem; }31        #eval-display { font-weight: bold; font-size: 1rem; color: var(--accent); white-space: nowrap; }32        33        #mode-selector { 34            margin: 8px 0; display: flex; gap: 6px; width: 98%; overflow-x: auto; 35            white-space: nowrap; padding-bottom: 4px; -webkit-overflow-scrolling: touch; scrollbar-width: none; 36        }37        #mode-selector::-webkit-scrollbar { display: none; }38 39        .mode-btn { 40            background: #3c3b39; color: #989898; border: none; padding: 8px 12px; border-radius: 6px; 41            font-size: 0.8rem; font-weight: 600; cursor: pointer; flex-shrink: 0; transition: all 0.2s;42        }43        .mode-btn.active { background: var(--accent); color: #fff; transform: translateY(-1px); }44        .mode-btn.blitz-active { background: var(--blitz); color: #fff; transform: translateY(-1px); }45        .mode-btn.opening-active { background: var(--opening); color: #fff; transform: translateY(-1px); }46 47        #board-area { position: relative; width: 96vw; max-width: 480px; aspect-ratio: 1/1; margin-bottom: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); }48        #board { width: 100%; height: 100%; }49        50        .highlight-square { box-shadow: inset 0 0 3px 3px rgba(255, 255, 0, 0.5); }51        52        #controls { display: flex; gap: 15px; align-items: center; justify-content: center; width: 100%; padding: 5px; box-sizing: border-box; }53        54        .ctrl-btn { background: var(--panel); color: #989898; border: 2px solid #3c3b39; width: 55px; height: 55px; border-radius: 50%; font-size: 1.5rem; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background 0.2s; }55        .ctrl-btn:active { transform: scale(0.95); background: #333; }56        .ctrl-btn:disabled { opacity: 0.3; cursor: not-allowed; transform: none; }57        58        /* Geri alma tuşu başlangıçta gizli */59        #btn-undo { display: none; }60 61        #solve-btn { background: var(--error); color: #fff; border:none; border-radius: 25px; padding: 0 25px; font-size: 1rem; display: none; height: 45px; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.3); }62        #solve-btn:active { transform: scale(0.95); }63        #solve-btn:disabled { background: #444; color: #888; cursor: default; transform: none; box-shadow: none; }64 65        #status-text { font-size: 0.95rem; color: #888; text-align: center; margin-bottom: 5px; min-height: 25px; display:flex; align-items:center; justify-content:center; gap: 5px; font-weight: 500;}66        #overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(22, 21, 18, 0.85); z-index: 10; display: flex; align-items: center; justify-content: center; color: white; font-size: 1.5rem; pointer-events: none; backdrop-filter: blur(2px); }67        68        .flash-success { animation: flash 0.6s; background-color: rgba(98, 153, 36, 0.2) !important; }69        @keyframes flash { 0% { background-color: rgba(98, 153, 36, 0.6); } 100% { background-color: transparent; } }70    </style>71</head>72<body>73 74    <div id="top-bar">75        <div class="app-title">⚛️ ATOMIC</div>76        <div id="eval-display"></div>77    </div>78 79    <div id="mode-selector">80        <button class="mode-btn" onclick="initGame('vs')">AI 🆚</button>81        <button class="mode-btn" onclick="initGame('opening')">📖 Açılış</button>82        <button class="mode-btn" onclick="initGame('puzzle')">🧩 Puzzle</button>83        <button class="mode-btn" onclick="initGame('blitz_mate')">🚀 Hızlı Mat</button>84        <button class="mode-btn" onclick="initGame('analysis')">🧠 Analiz</button>85        <button class="mode-btn" onclick="initGame('ai')">🤖 Bot Maçı</button>86    </div>87 88    <div id="status-text">Hoşgeldin...</div>89 90    <div id="board-area">91        <div id="overlay">Mod Seçiniz 👆</div>92        <div id="board"></div>93    </div>94 95    <div id="controls">96        <button id="btn-undo" class="ctrl-btn" onclick="undoMove()">↩️</button>97        <button id="solve-btn" onclick="getHint()">İpucu</button>98    </div>99 100    <script>101        var board = null;102        var gameActive = false;103        var currentMode = null;104        var userColor = 'w';105        var currentTurn = 'w';106        var aiLoop = null;107        var aiTimer = null;108 109        function initGame(mode) {110            clearInterval(aiLoop);111            clearTimeout(aiTimer);112            113            $('#overlay').hide();114            $('#solve-btn').hide();115            116            // Oyun başladığı için Geri Al butonunu göster117            $('#btn-undo').css('display', 'flex'); 118 119            $('.mode-btn').removeClass('active').removeClass('blitz-active').removeClass('opening-active');120            121            var btns = document.querySelectorAll('.mode-btn');122            var idx = {'vs':0, 'opening':1, 'puzzle':2, 'blitz_mate':3, 'analysis':4, 'ai':5}[mode];123            var btn = btns[idx];124            125            if(mode === 'blitz_mate') btn.classList.add('blitz-active');126            else if(mode === 'opening') btn.classList.add('opening-active');127            else btn.classList.add('active');128 129            currentMode = mode;130            $('#eval-display').text("");131            removeHighlights();132            enableControls(true);133 134            if (mode === 'puzzle') {135                $('#status-text').text("Soru aranıyor...");136                gameActive = false; requestScramble(mode);137            } else if (mode === 'blitz_mate') {138                 $('#status-text').text("Hızlı Mat aranıyor...");139                 gameActive = false; requestScramble(mode);140            } else if (mode === 'ai') {141                $('#status-text').text("Botlar Kapışıyor...");142                $.post('/reset', function(r) { setupBoard('start', 'w', "Bot Savaşı"); aiLoop = setInterval(triggerAiMove, 1500); });143            } else if (mode === 'opening') {144                $('#status-text').text("Açılış kurgulanıyor...");145                gameActive = false;146                $.post('/setup_opening', function(r) {147                    setupBoard(r.fen, r.user_color, "Açılış: " + r.opening_name);148                    $('#solve-btn').show().text("İpucu").prop('disabled', false);149                    var turnChar = r.fen.split(' ')[1];150                    if(turnChar !== r.user_color) {151                         aiTimer = setTimeout(triggerAiMove, 600);152                    }153                });154            } else {155                // VS ve Analiz156                var msg = (mode === 'vs') ? "Yapay Zekaya Karşı" : "Analiz Modu";157                $('#status-text').text(msg);158                159                $.post('/reset', function() {160                    var c = (mode === 'vs') ? (Math.random()<0.5?'w':'b') : 'w';161                    setupBoard('start', c, msg);162                    if(mode === 'vs') $('#solve-btn').show().text("İpucu").prop('disabled', false);163                });164            }165        }166        167        function requestScramble(mode) {168             $.ajax({169                url: '/scramble', type: 'POST', contentType: 'application/json',170                data: JSON.stringify({ mode: mode }),171                success: function(r) {172                    $('#solve-btn').show().text("İpucu").prop('disabled', false);173                    setupBoard(r.fen, r.turn, r.hint);174                }175            });176        }177 178        function setupBoard(fen, myColor, msg) {179            gameActive = true;180            userColor = myColor;181            removeHighlights();182            if(fen === 'start') { board.start(); currentTurn = 'w'; } 183            else { board.position(fen, false); }184            if(fen !== 'start') currentTurn = fen.split(' ')[1];185            else currentTurn = 'w';186            board.orientation(userColor === 'w' ? 'white' : 'black');187            $('#status-text').text(msg);188            189            if (currentMode === 'vs' && fen === 'start' && userColor === 'b') {190                aiTimer = setTimeout(triggerAiMove, 500);191            }192        }193 194        function triggerAiMove() {195            if(currentMode === 'puzzle' || currentMode === 'blitz_mate') $('#status-text').text("...");196            197            $.ajax({198                url: '/move', type: 'POST', contentType: 'application/json',199                data: JSON.stringify({ move: "ai_move", mode: currentMode, color: userColor }),200                success: function(r) {201                    board.position(r.fen, true);202                    currentTurn = r.turn;203                    $('#eval-display').text(""); 204                    if(r.last_move) highlightMove(r.last_move);205                    206                    if(r.game_over || r.puzzle_solved) {207                        checkGameOver(r);208                    } else if(currentMode === 'puzzle' || currentMode === 'blitz_mate') {209                        $('#status-text').text("Sıra Sende");210                        $('#solve-btn').text("İpucu").prop('disabled', false); 211                        enableControls(true); 212                    } else if (currentMode === 'opening' || currentMode === 'vs') {213                        $('#solve-btn').prop('disabled', false).text("İpucu");214                        $('#status-text').text("Sıra Sende");215                        $('#btn-undo').prop('disabled', false);216                    }217                }218            });219        }220 221        function onDrop (source, target, isAuto) {222            clearTimeout(aiTimer); 223            var isHint = (isAuto === true);224 225            if (!isAuto && (!gameActive || currentMode === 'ai')) return 'snapback';226            if (currentMode !== 'analysis') {227                var p = board.position()[source];228                if ((userColor=='w' && p[0]=='b') || (userColor=='b' && p[0]=='w')) return 'snapback';229                if ((userColor=='w' && currentTurn=='b') || (userColor=='b' && currentTurn=='w')) return 'snapback';230            }231            if (currentMode === 'analysis') {232                 var p = board.position()[source];233                 if (currentTurn === 'w' && p[0] === 'b') return 'snapback';234                 if (currentTurn === 'b' && p[0] === 'w') return 'snapback';235            }236            var move = source + target;237            238            if(currentMode === 'puzzle' || currentMode === 'blitz_mate' || currentMode === 'opening' || currentMode === 'vs') {239                 if(!isHint && (currentMode === 'opening' || currentMode === 'vs')) {240                     // Manuel241                 } else {242                    enableControls(false); 243                    $('#solve-btn').prop('disabled', true);244                }245            }246            247            $.ajax({248                url: '/move', type: 'POST', contentType: 'application/json',249                data: JSON.stringify({ move: move, mode: currentMode, color: userColor, is_hint: isHint }),250                success: function(r) {251                    if (r.valid) {252                        if ((currentMode === 'puzzle' || currentMode === 'blitz_mate') && r.eval_code === 1) {253                            $('#status-text').text("Yanlış!");254                            board.position(r.fen, true); 255                            enableControls(true);256                            $('#solve-btn').text("İpucu").prop('disabled', false);257                        } else {258                            board.position(r.fen, true);259                            currentTurn = r.turn;260                            highlightMove(move);261                            262                            if(!isHint && r.eval) {263                                $('#eval-display').text(r.eval).css('color', r.eval_color);264                            } else if (isHint) {265                                $('#eval-display').text("");266                            }267 268                            if(currentMode === 'puzzle' || currentMode === 'blitz_mate') {269                                $('#status-text').text("Doğru!");270                                if(r.game_over || r.puzzle_solved) {271                                    checkGameOver(r);272                                } else {273                                    $('#solve-btn').text("...").prop('disabled', true);274                                    enableControls(false); 275                                    var delay = (currentMode === 'blitz_mate') ? 200 : 1500;276                                    aiTimer = setTimeout(triggerAiMove, delay); 277                                }278                            } else if (r.game_over) {279                                checkGameOver(r);280                            } else if (currentMode === 'opening' || currentMode === 'vs') {281                                var aiDelay = isHint ? 2500 : 500; 282                                $('#solve-btn').prop('disabled', true);283                                284                                if(isHint) {285                                    $('#status-text').text("AI Bekliyor..."); 286                                    $('#btn-undo').prop('disabled', false);287                                }288                                289                                aiTimer = setTimeout(triggerAiMove, aiDelay);290                            }291                        }292                    } else {293                        board.position(r.fen, true);294                        if(currentMode === 'puzzle' || currentMode === 'blitz_mate' || currentMode === 'opening' || currentMode === 'vs') { 295                            enableControls(true); 296                            $('#solve-btn').prop('disabled', false); 297                        }298                    }299                }300            });301        }302        303        function highlightMove(move) {304            removeHighlights();305            if(!move || move.length < 4) return;306            var source = move.substring(0,2);307            var target = move.substring(2,4);308            $('#board .square-' + source).addClass('highlight-square');309            $('#board .square-' + target).addClass('highlight-square');310        }311        312        function removeHighlights() { $('#board [class*="square-"]').removeClass('highlight-square'); }313        314        function getHint() {315            if (currentMode !== 'puzzle' && currentMode !== 'blitz_mate' && currentMode !== 'opening' && currentMode !== 'vs') return;316            317            $('#solve-btn').prop('disabled', true).text("...");318            enableControls(false); 319            $.post('/solution_step', function(r) {320                if(r.best_move) {321                    var source = r.best_move.substring(0,2);322                    var target = r.best_move.substring(2,4);323                    onDrop(source, target, true); 324                } else {325                    $('#solve-btn').text("Yok").prop('disabled', false);326                    enableControls(true);327                }328            });329        }330 331        function checkGameOver(r) {332            clearTimeout(aiTimer);333            if(currentMode === 'puzzle' || currentMode === 'blitz_mate') {334                if(r.puzzle_solved || r.result === '1-0' || r.result === '0-1') {335                    $('#status-text').text("Tebrikler!");336                    $('#eval-display').text("HARİKA!").css('color', '#629924');337                    $('body').addClass('flash-success');338                    setTimeout(function(){ $('body').removeClass('flash-success'); }, 500);339                    gameActive = false;340                    var delay = (currentMode === 'blitz_mate') ? 1000 : 2500;341                    setTimeout(function() { initGame(currentMode); }, delay); 342                } else {343                    $('#status-text').text("Bitti: " + r.result);344                    enableControls(true);345                }346            } else {347                $('#status-text').text("Bitti: " + r.result);348                gameActive = false;349                $('#solve-btn').hide(); 350            }351            clearInterval(aiLoop);352        }353        354        function enableControls(enabled) { 355            $('#btn-undo').prop('disabled', !enabled); 356            gameActive = enabled; 357        }358 359        function undoMove() {360            clearTimeout(aiTimer);361            $.ajax({362                url: '/undo', type: 'POST', contentType: 'application/json',363                data: JSON.stringify({ mode: currentMode, color: userColor }),364                success: function(r) {365                    board.position(r.fen, true);366                    currentTurn = r.turn;367                    gameActive = true;368                    removeHighlights();369                    if(currentMode === 'puzzle' || currentMode === 'blitz_mate' || currentMode === 'opening' || currentMode === 'vs') { 370                        $('#solve-btn').text("İpucu").prop('disabled', false); 371                        $('#status-text').text("Sıra Sende"); 372                        $('#btn-undo').prop('disabled', false);373                    }374                }375            });376        }377 378        $(document).ready(function() {379            board = Chessboard('board', {380                draggable: true, position: 'start', onDrop: onDrop,381                pieceTheme: 'https://chessboardjs.com/img/chesspieces/wikipedia/{piece}.png'382            });383            $(window).resize(board.resize);384            var oldSetup = setupBoard;385            setupBoard = function(fen, myColor, msg) {386                if(fen !== 'start') currentTurn = fen.split(' ')[1]; 387                else currentTurn = 'w';388                oldSetup(fen, myColor, msg);389            };390        });391    </script>392</body>393</html>394"""395 396# --- 2. MOTOR KURULUMU ---397TARGET_NAME = "Fairy-stockfish-largeboard_x86-64"398 399def setup_engine():400    files = os.listdir('.')401    engine_path = None402    for f in files:403        if "fairy" in f.lower() and not f.endswith(".tar.gz"):404            engine_path = os.path.abspath(f)405            break406    if not engine_path:407        for f in files:408            if "fairy" in f.lower() and f.endswith(".tar.gz"):409                try:410                    with tarfile.open(f, "r:gz") as tar: tar.extractall()411                    return setup_engine() 412                except: pass413    if engine_path:414        try:415            st = os.stat(engine_path)416            os.chmod(engine_path, st.st_mode | stat.S_IEXEC)417            return engine_path418        except: pass419    return None420 421engine_path = setup_engine()422engine = None423if engine_path:424    try:425        engine = chess.engine.SimpleEngine.popen_uci(engine_path)426        engine.configure({"UCI_Variant": "atomic"})427    except: pass428 429app = FastAPI()430board = chess.variant.AtomicBoard()431 432OPENING_BOOK = [433    ("At Saldırısı (Klasik)", ["g1f3", "f7f6", "e2e3", "d7d5", "b1c3"]),434    ("Nf3 Main - e6", ["g1f3", "f7f6", "e2e3", "e7e6", "b1c3"]),435    ("Nf3 Main - c6", ["g1f3", "f7f6", "e2e3", "c7c6"]),436    ("Gamma (1. Nf3 f6 2. e3 e6 3. Nd4)", ["g1f3", "f7f6", "e2e3", "e7e6", "f3d4"]),437    ("Dominos (1. Nf3 f6 2. e3 d5)", ["g1f3", "f7f6", "e2e3", "d7d5"]),438    ("Sakamoto (1. Nf3 f6 2. Nd4)", ["g1f3", "f7f6", "f3d4", "e7e6", "b1c3"]),439    ("1. Nf3 f6 2. e4 (Merkez)", ["g1f3", "f7f6", "e2e4", "e7e5", "f1c4"]),440    ("1. Nf3 f6 2. h3 (Sessiz)", ["g1f3", "f7f6", "h2h3", "e7e5"]),441    ("1. Nf3 f6 2. Rg1", ["g1f3", "f7f6", "h1g1", "e7e6"]),442    ("Scorpion (1. Nh3 f6)", ["g1h3", "f7f6", "f2f4", "e7e6"]),443    ("Scorpion (Saldırgan g4)", ["g1h3", "h7h6", "g2g4", "e7e5"]),444    ("1. Nh3 e6", ["g1h3", "e7e6", "f2f3", "d7d5"]),445    ("1. Nh3 h6 2. e3", ["g1h3", "h7h6", "e2e3", "d7d5"]),446    ("1. e3 (Main Line)", ["e2e3", "e7e6", "d2d4", "d7d5", "g1f3"]),447    ("1. e3 f6 (Simetrik)", ["e2e3", "f7f6", "g1h3", "g7g6"]),448    ("1. e3 e6 2. Nf3", ["e2e3", "e7e6", "g1f3", "f7f6"]),449    ("1. f3 (Kıyamet)", ["f2f3", "e7e6", "g1h3", "d8h4", "g2g3"]),450    ("1. f3 d5", ["f2f3", "d7d5", "e2e4", "d5e4"]),451    ("At Gambiti (Riskli)", ["g1f3", "e7e5", "f3e5", "d8h4", "g2g3"]),452    ("Na3 (Kenar)", ["b1a3", "e7e6", "a3b5", "c7c6"]),453    ("Merkez Oyunu (1. e4)", ["e2e4", "e7e5", "g1f3", "f7f6"]),454    ("Çift Namlu (Double Muzzle)", ["g1f3", "f7f6", "e2e3", "e7e6", "d2d4"]),455    ("King's Fianchetto", ["g2g3", "e7e6", "f1g2", "d7d5"]),456    ("Queen's Gambit Atomik", ["d2d4", "d7d5", "c2c4", "e7e6"]),457    ("Tuzaklı 1. Nc3", ["b1c3", "e7e5", "g1f3", "f7f6", "e2e4"]),458    ("1. d3 (Pasif)", ["d2d3", "e7e6", "g1f3", "d7d5"]),459    ("1. h3 (Bekleme)", ["h2h3", "e7e5", "g1f3", "f7f6"]),460    ("1. c3 (Saragossa)", ["c2c3", "d7d5", "d2d4", "e7e6"]),461    ("1. b4 (Orangutan)", ["b2b4", "e7e6", "c1b2", "d7d5"]),462    ("1. b3 (Larsen)", ["b2b3", "e7e5", "c1b2", "d7d6"]),463    ("1. g4 (Grob - Çok Riskli)", ["g2g4", "d7d5", "f1g2", "c8g4"]),464    ("Rekabetçi 1. Nf3 (d5)", ["g1f3", "d7d5", "e2e3", "c7c6"]),465    ("Lulila (1. Nf3 f6 2. e3 e6 3. Nd4)", ["g1f3", "f7f6", "e2e3", "e7e6", "f3d4"]),466    ("1. Nf3 f6 2. d4 (Sağlam)", ["g1f3", "f7f6", "d2d4", "d7d5"]),467    ("Modern Defense (1. Nf3 g6)", ["g1f3", "g7g6", "e2e4", "f8g7"]),468    ("1. Nc3 e5 2. f3", ["b1c3", "e7e5", "f2f3", "f7f5"]),469    ("Reversed Opening", ["e2e3", "g8f6", "d2d4", "e7e6"])470]471 472OPENING_QUEUE = []473OPENING_INDEX = 0474 475def evaluate_move_quality(board_before, move_made, engine):476    if not engine: return "", 0, 0, 0477    try:478        limit = chess.engine.Limit(time=0.4)479        info_best = engine.analyse(board_before, limit)480        score_best = info_best["score"].white()481        best_pv = info_best.get("pv", [])482        engine_best_move = best_pv[0] if len(best_pv) > 0 else None483        484        board_after = board_before.copy()485        board_after.push(move_made)486        info_actual = engine.analyse(board_after, limit)487        score_actual = info_actual["score"].white()488        489        if engine_best_move and move_made == engine_best_move:490            return "✨ Mükemmel", 0, 0, score_actual491        if score_best.is_mate():492            if score_actual.is_mate(): return "🔥 MAT YOLU!", 0, 0, score_actual493            else: return "❌ MAT KAÇTI", 900, 1, score_actual494 495        val_best = score_best.score(mate_score=10000)496        val_actual = score_actual.score(mate_score=10000)497        if board_before.turn == chess.BLACK:498            val_best = -val_best499            val_actual = -val_actual500        loss = val_best - val_actual 501        502        if loss < 35: return "✨ Mükemmel", loss, 0, val_actual503        if loss < 120: return "✅ İyi", loss, 0, val_actual504        if val_actual > 400 and val_best > 400: return "✅ Kabul", loss, 0, val_actual505        return "❌ HATA", loss, 1, val_actual506    except: return "", 0, 0, 0507 508# --- ROUTES ---509@app.get("/", response_class=HTMLResponse)510async def read_root(): return HTML_CONTENT511 512@app.post("/move")513async def make_move(request: Request):514    global board, engine515    if not engine: return JSONResponse({'error': 'Motor Yok'})516    data = await request.json()517    uci, mode, user_color_str = data.get('move'), data.get('mode'), data.get('color')518    519    is_hint = data.get('is_hint', False)520    521    response = {'valid': False, 'fen': board.fen(), 'game_over': False, 'eval': '', 'eval_code': 0, 'eval_color': '#888', 'turn': '', 'puzzle_solved': False}522 523    if uci and uci != "ai_move":524        try:525            move = chess.Move.from_uci(uci)526            if move in board.legal_moves:527                if not is_hint:528                    quality, loss, code, raw_score = evaluate_move_quality(board, move, engine)529                else:530                    quality, loss, code = "", 0, 0 531                532                if mode == 'puzzle' or mode == 'blitz_mate':533                    if code == 1: 534                        response['valid'] = True535                        response['eval_code'] = 1 536                    else:537                        board.push(move)538                        response.update({'valid': True, 'fen': board.fen(), 'eval_code': 0, 'eval': ''})539                        540                        is_mate_now = board.is_game_over()541                        t_limit = 0.05 if mode == 'blitz_mate' else 0.1542                        info = engine.analyse(board, chess.engine.Limit(time=t_limit))543                        score = info["score"].white()544                        has_mate_path = score.is_mate()545                        546                        if is_mate_now:547                            response['puzzle_solved'] = True548                        elif mode == 'blitz_mate':549                            if not has_mate_path: response['eval_code'] = 1550                        elif mode == 'puzzle':551                            val = score.score(mate_score=10000)552                            if board.turn == chess.BLACK: val = -val553                            if not has_mate_path and val > 650: response['puzzle_solved'] = True554                            555                else:556                    board.push(move)557                    response.update({'valid': True, 'fen': board.fen(), 'eval_code': code})558                    559                    should_eval = (mode == 'analysis') or (mode == 'vs' and uci != 'ai_move') or (mode == 'opening' and uci != 'ai_move')560                    561                    if should_eval and not is_hint:562                        response['eval'] = quality563                        if "Mükemmel" in quality or "MAT" in quality: response['eval_color'] = "#00e676"564                        elif "İyi" in quality or "Kabul" in quality: response['eval_color'] = "#cddc39"565                        else: response['eval_color'] = "#ff5252"566            else: pass567        except: pass568    elif uci == "ai_move": response['valid'] = True569 570    if response['valid']:571        response['turn'] = 'w' if board.turn == chess.WHITE else 'b'572        response['game_over'] = board.is_game_over()573        if response['game_over']: response['result'] = board.result()574        if len(board.move_stack) > 0: response['last_move'] = board.peek().uci()575 576        should_ai_move = False577        if not board.is_game_over() and not response.get('puzzle_solved'):578            if mode == 'ai': should_ai_move = True579            elif (mode == 'vs' or mode == 'opening') and uci == "ai_move": should_ai_move = True580            elif (mode == 'puzzle' or mode == 'blitz_mate') and uci == "ai_move": should_ai_move = True581 582        if should_ai_move:583            try:584                think_time = 0.05 if mode == 'blitz_mate' else 0.3585                result = engine.play(board, chess.engine.Limit(time=think_time))586                board.push(result.move)587                response.update({'fen': board.fen(), 'turn': 'w' if board.turn == chess.WHITE else 'b', 'last_move': result.move.uci(), 'game_over': board.is_game_over()})588                if response['game_over']: response['result'] = board.result()589            except: pass590    return JSONResponse(response)591 592@app.post("/solution_step")593async def solution_step():594    global board, engine595    if not engine: return JSONResponse({'valid': False})596    try:597        result = engine.play(board, chess.engine.Limit(time=0.4))598        return JSONResponse({'best_move': result.move.uci()})599    except: return JSONResponse({'best_move': None})600 601@app.post("/reset")602async def reset():603    global board604    board = chess.variant.AtomicBoard()605    return JSONResponse({'status': 'ok'})606 607@app.post("/setup_opening")608async def setup_opening():609    global board, engine, OPENING_QUEUE, OPENING_INDEX610    611    if not OPENING_QUEUE:612        OPENING_QUEUE = list(OPENING_BOOK)613        random.shuffle(OPENING_QUEUE)614        OPENING_INDEX = 0615 616    for _ in range(10): 617        if OPENING_INDEX >= len(OPENING_QUEUE):618            random.shuffle(OPENING_QUEUE)619            OPENING_INDEX = 0620            621        board = chess.variant.AtomicBoard()622        opening_name, moves = OPENING_QUEUE[OPENING_INDEX]623        OPENING_INDEX += 1624        625        max_len = len(moves)626        target_ply = random.randint(3, min(6, max_len))627        628        valid_opening = True629        for i in range(target_ply):630            uci = moves[i]631            try:632                m = chess.Move.from_uci(uci)633                if m in board.legal_moves:634                    board.push(m)635                else: 636                    valid_opening = False; break637            except: 638                valid_opening = False; break639        640        if not valid_opening or board.is_game_over(): continue641        642        if engine:643            try:644                info = engine.analyse(board, chess.engine.Limit(time=0.05))645                score = info["score"].white()646                if score.is_mate(): continue647                cp = score.score()648                if cp is not None and abs(cp) > 1000: continue649                break650            except: break651        else: break652 653    user_color = 'w' if random.random() < 0.5 else 'b'654    return JSONResponse({655        'fen': board.fen(),656        'user_color': user_color,657        'opening_name': opening_name658    })659 660@app.post("/scramble")661async def scramble(request: Request):662    global board, engine663    data = await request.json()664    mode = data.get('mode') 665    666    attempts = 0667    max_attempts = 150 668    best_candidate = None 669    670    while attempts < max_attempts: 671        temp_board = chess.variant.AtomicBoard()672        target_ply = random.randint(10, 42)673        moves_played = 0674        675        while moves_played < target_ply and not temp_board.is_game_over():676            if engine:677                try:678                    res = engine.play(temp_board, chess.engine.Limit(time=0.002))679                    temp_board.push(res.move)680                    moves_played += 1681                except: break682            else: break683            684        if not temp_board.is_game_over() and engine:685            if temp_board.king(chess.WHITE) is None or temp_board.king(chess.BLACK) is None: 686                attempts += 1; continue687 688            try:689                info = engine.analyse(temp_board, chess.engine.Limit(time=0.08))690                score = info["score"].white()691                692                if mode == 'blitz_mate':693                     if score.is_mate():694                        m = score.mate()695                        if m is not None:696                            turn_ok = (temp_board.turn == chess.WHITE and m > 0) or (temp_board.turn == chess.BLACK and m < 0)697                            if turn_ok:698                                abs_m = abs(m)699                                if 3 <= abs_m <= 6:700                                    board = temp_board 701                                    turn = 'w' if board.turn == chess.WHITE else 'b'702                                    return JSONResponse({'fen': board.fen(), 'turn': turn, 'hint': f"⚡ HIZLI MAT ({abs_m})"})703                                elif best_candidate is None:704                                    best_candidate = (temp_board.copy(), f"⚡ MATI BUL ({abs_m})")705                706                elif mode == 'puzzle':707                    if not score.is_mate():708                        sc = score.score(mate_score=10000)709                        if temp_board.turn == chess.BLACK: sc = -sc710                        if sc > 400:711                            board = temp_board712                            turn = 'w' if board.turn == chess.WHITE else 'b'713                            return JSONResponse({'fen': board.fen(), 'turn': turn, 'hint': "ÜSTÜNLÜĞÜ YAKALA"})714            except: pass715        attempts += 1716 717    if best_candidate:718        board = best_candidate[0]719        turn = 'w' if board.turn == chess.WHITE else 'b'720        return JSONResponse({'fen': board.fen(), 'turn': turn, 'hint': best_candidate[1]})721 722    board = chess.variant.AtomicBoard()723    return JSONResponse({'fen': board.fen(), 'turn': 'w', 'hint': "Bulunamadı, tekrar dene"})724 725@app.post("/undo")726async def undo(request: Request):727    global board728    data = await request.json()729    mode, color = data.get('mode'), data.get('color')730    if mode in ['analysis', 'ai']:731        if len(board.move_stack) > 0: board.pop()732    else:733        user_turn = chess.WHITE if color == 'w' else chess.BLACK734        if board.turn == user_turn and len(board.move_stack) >= 2: board.pop(); board.pop()735        elif len(board.move_stack) >= 1: board.pop()736    return JSONResponse({'fen': board.fen(), 'turn': 'w' if board.turn == chess.WHITE else 'b'})737 738if __name__ == "__main__":739    uvicorn.run(app, host="0.0.0.0", port=7860)