Enensja/atomic-chess
0
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)