♜ RacBot v4.0 ♞

Gelişmiş Saf Minimax Satranç Motoru | ~1700-1850 ELO
📦

Motor Kodu

📄 RacBot.js ~ 500 satır
/**
 * RacBot.js v4.0 - Gelişmiş Saf Satranç Motoru
 * İteratif Derinleştirme, Zaman Kontrolü, Hamle Sıralama, Oyun Sonu Matrisleri ve FEN Yönetimi
 */

let board = [];
let turn = 'W';
let castleRights = { W_K: true, W_Q: true, B_K: true, B_Q: true };
let enPassantTarget = null;

// Zaman Kontrolü İçin Global Bayraklar
let searchStartTime = 0;
let searchTimeLimit = 0;
let isSearchCancelled = false;

const PIECE_VALUES = {
    'p': 100, 'n': 320, 'b': 330, 'r': 500, 'q': 900, 'k': 20000,
    'P': 100, 'N': 320, 'B': 330, 'R': 500, 'Q': 900, 'K': 20000
};

// --- KONUMSAL MATRİSLER (OYUN ORTASI) ---
const PAWN_METRIC = [
    [0,  0,  0,  0,  0,  0,  0,  0],
    [50, 50, 50, 50, 50, 50, 50, 50],
    [10, 10, 20, 30, 30, 20, 10, 10],
    [5,   5, 10, 25, 25, 10,  5,  5],
    [0,   0,  0, 20, 20,  0,  0,  0],
    [5,  -5,-10,  0,  0,-10, -5,  5],
    [5,  10, 10,-20,-20, 10, 10,  5],
    [0,   0,  0,  0,  0,  0,  0,  0]
];

const KNIGHT_METRIC = [
    [-50,-40,-30,-30,-30,-30,-40,-50],
    [-40,-20,  0,  5,  5,  0,-20,-40],
    [-30,  0, 15, 20, 20, 15,  0,-30],
    [-30,  5, 20, 25, 25, 20,  5,-30],
    [-30,  0, 20, 25, 25, 20,  0,-30],
    [-30,  5, 15, 20, 20, 15,  5,-30],
    [-40,-20,  0,  0,  0,  0,-20,-40],
    [-50,-40,-30,-30,-30,-30,-40,-50]
];

const BISHOP_METRIC = [
    [-20,-10,-10,-10,-10,-10,-10,-20],
    [-10,  0,  0,  0,  0,  0,  0,-10],
    [-10,  0,  5, 10, 10,  5,  0,-10],
    [-10,  5,  5, 10, 10,  5,  5,-10],
    [-10,  0, 10, 10, 10, 10,  0,-10],
    [-10, 10, 10, 10, 10, 10, 10,-10],
    [-10,  5,  0,  0,  0,  0,  5,-10],
    [-20,-10,-10,-10,-10,-10,-10,-20]
];

const QUEEN_METRIC = [
    [-20,-10,-10, -5, -5,-10,-10,-20],
    [-10,  0,  0,  0,  0,  0,  0,-10],
    [-10,  0,  5,  5,  5,  5,  0,-10],
    [ -5,  0,  5,  5,  5,  5,  0, -5],
    [  0,  0,  5,  5,  5,  5,  0,  0],
    [-10,  5,  5,  5,  5,  5,  0,-10],
    [-10,  0,  5,  0,  0,  0,  0,-10],
    [-20,-10,-10, -5, -5,-10,-10,-20]
];

const KING_MIDDLE_METRIC = [
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-30,-40,-40,-50,-50,-40,-40,-30],
    [-20,-30,-30,-40,-40,-30,-30,-20],
    [-10,-20,-20,-20,-20,-20,-20,-10],
    [20, 20,  0,  0,  0,  0, 20, 20],
    [20, 30, 10,  0,  0, 10, 30, 20]
];

const KING_ENDGAME_METRIC = [
    [-50,-40,-30,-20,-20,-30,-40,-50],
    [-30,-20,-10,  0,  0,-10,-20,-30],
    [-30,-10, 20, 30, 30, 20,-10,-30],
    [-30,-10, 30, 40, 40, 30,-10,-30],
    [-30,-10, 30, 40, 40, 30,-10,-30],
    [-30,-10, 20, 30, 30, 20,-10,-30],
    [-30,-30,  0,  0,  0,  0,-30,-30],
    [-50,-30,-30,-30,-30,-30,-30,-50]
];

function isEndgame(boardState) {
    let minorPieces = 0, queens = 0;
    for (let r = 0; r < 8; r++) {
        for (let c = 0; c < 8; c++) {
            let p = boardState[r][c];
            if (p) {
                let char = p.toLowerCase();
                if (char === 'q') queens++;
                if (char === 'r' || char === 'b' || char === 'n') minorPieces++;
            }
        }
    }
    return queens === 0 || (queens <= 2 && minorPieces <= 2);
}

function evaluateBoard(boardState) {
    let totalEvaluation = 0;
    let endgameActive = isEndgame(boardState);
    for (let r = 0; r < 8; r++) {
        for (let c = 0; c < 8; c++) {
            let piece = boardState[r][c];
            if (piece) {
                let value = PIECE_VALUES[piece];
                let positionalBonus = 0;
                switch(piece) {
                    case 'P': positionalBonus = PAWN_METRIC[7-r][c]; break;
                    case 'p': positionalBonus = PAWN_METRIC[r][c]; break;
                    case 'N': positionalBonus = KNIGHT_METRIC[7-r][c]; break;
                    case 'n': positionalBonus = KNIGHT_METRIC[r][c]; break;
                    case 'B': positionalBonus = BISHOP_METRIC[7-r][c]; break;
                    case 'b': positionalBonus = BISHOP_METRIC[r][c]; break;
                    case 'Q': positionalBonus = QUEEN_METRIC[7-r][c]; break;
                    case 'q': positionalBonus = QUEEN_METRIC[r][c]; break;
                    case 'K': positionalBonus = endgameActive ? KING_ENDGAME_METRIC[7-r][c] : KING_MIDDLE_METRIC[7-r][c]; break;
                    case 'k': positionalBonus = endgameActive ? KING_ENDGAME_METRIC[r][c] : KING_MIDDLE_METRIC[r][c]; break;
                }
                if (piece === piece.toUpperCase()) totalEvaluation += (value + positionalBonus);
                else totalEvaluation -= (value + positionalBonus);
            }
        }
    }
    if (endgameActive) {
        let whiteKing = null, blackKing = null;
        for (let r = 0; r < 8; r++) {
            for (let c = 0; c < 8; c++) {
                if (boardState[r][c] === 'K') whiteKing = {r, c};
                if (boardState[r][c] === 'k') blackKing = {r, c};
            }
        }
        if (whiteKing && blackKing) {
            if (totalEvaluation > 500) {
                let dist = Math.max(3 - blackKing.r, blackKing.r - 4) + Math.max(3 - blackKing.c, blackKing.c - 4);
                totalEvaluation += dist * 10;
            } else if (totalEvaluation < -500) {
                let dist = Math.max(3 - whiteKing.r, whiteKing.r - 4) + Math.max(3 - whiteKing.c, whiteKing.c - 4);
                totalEvaluation -= dist * 10;
            }
        }
    }
    return totalEvaluation;
}

function loadFEN(fen) {
    const parts = fen.split(" ");
    const rows = parts[0].split("/");
    board = Array.from({length: 8}, () => Array(8).fill(null));
    for (let r = 0; r < 8; r++) {
        let c = 0;
        for (let char of rows[r]) {
            if (isNaN(char)) { board[r][c] = char; c++; }
            else { c += parseInt(char); }
        }
    }
    turn = parts[1].toUpperCase();
    castleRights = { W_K: parts[2].includes('K'), W_Q: parts[2].includes('Q'), B_K: parts[2].includes('k'), B_Q: parts[2].includes('q') };
    if (parts[3] === "-") enPassantTarget = null;
    else { let col = parts[3].charCodeAt(0) - 97; let row = 8 - parseInt(parts[3][1]); enPassantTarget = { r: row, c: col }; }
}

function generateFEN() {
    let fenRows = [];
    for (let r = 0; r < 8; r++) {
        let empty = 0, rowStr = "";
        for (let c = 0; c < 8; c++) {
            if (board[r][c] === null) empty++;
            else { if (empty > 0) { rowStr += empty; empty = 0; } rowStr += board[r][c]; }
        }
        if (empty > 0) rowStr += empty;
        fenRows.push(rowStr);
    }
    let castling = "";
    if (castleRights.W_K) castling += "K";
    if (castleRights.W_Q) castling += "Q";
    if (castleRights.B_K) castling += "k";
    if (castleRights.B_Q) castling += "q";
    if (castling === "") castling = "-";
    let ep = "-";
    if (enPassantTarget) ep = String.fromCharCode(97 + enPassantTarget.c) + (8 - enPassantTarget.r);
    return `${fenRows.join("/")} ${turn.toLowerCase()} ${castling} ${ep} 0 1`;
}

function calculateBestMove(timeLimitMs = 2000) {
    let moves = getAllStrictLegalMoves(board, turn, castleRights, enPassantTarget);
    if (moves.length === 0) return null;
    moves = orderMoves(board, moves);
    searchStartTime = Date.now();
    searchTimeLimit = timeLimitMs;
    isSearchCancelled = false;
    let absoluteBestMove = moves[0];
    let currentDepth = 1;
    while (!isSearchCancelled && currentDepth <= 10) {
        let alpha = -Infinity, beta = Infinity;
        let bestMoveForDepth = null;
        if (turn === 'B') {
            let bestValue = Infinity;
            for (let move of moves) {
                let nextBoard = board.map(row => [...row]);
                simMove(nextBoard, move);
                let val = alphabeta(currentDepth - 1, nextBoard, alpha, beta, 'W', {...castleRights}, enPassantTarget);
                if (isSearchCancelled) break;
                if (val < bestValue) { bestValue = val; bestMoveForDepth = move; }
                beta = Math.min(beta, val);
            }
        } else {
            let bestValue = -Infinity;
            for (let move of moves) {
                let nextBoard = board.map(row => [...row]);
                simMove(nextBoard, move);
                let val = alphabeta(currentDepth - 1, nextBoard, alpha, beta, 'B', {...castleRights}, enPassantTarget);
                if (isSearchCancelled) break;
                if (val > bestValue) { bestValue = val; bestMoveForDepth = move; }
                alpha = Math.max(alpha, val);
            }
        }
        if (!isSearchCancelled && bestMoveForDepth) absoluteBestMove = bestMoveForDepth;
        currentDepth++;
    }
    return absoluteBestMove;
}

function alphabeta(depth, boardState, alpha, beta, color, castle, ep) {
    if (Date.now() - searchStartTime >= searchTimeLimit) { isSearchCancelled = true; return evaluateBoard(boardState); }
    if (depth === 0) return evaluateBoard(boardState);
    let moves = getAllStrictLegalMoves(boardState, color, castle, ep);
    if (moves.length === 0) return evaluateBoard(boardState);
    moves = orderMoves(boardState, moves);
    if (color === 'W') {
        let maxEval = -Infinity;
        for (let move of moves) {
            let saved = simMove(boardState, move);
            let evalScore = alphabeta(depth - 1, boardState, alpha, beta, 'B', castle, ep);
            unsimMove(boardState, move, saved);
            if (isSearchCancelled) return maxEval;
            maxEval = Math.max(maxEval, evalScore);
            alpha = Math.max(alpha, evalScore);
            if (beta <= alpha) break;
        }
        return maxEval;
    } else {
        let minEval = Infinity;
        for (let move of moves) {
            let saved = simMove(boardState, move);
            let evalScore = alphabeta(depth - 1, boardState, alpha, beta, 'W', castle, ep);
            unsimMove(boardState, move, saved);
            if (isSearchCancelled) return minEval;
            minEval = Math.min(minEval, evalScore);
            beta = Math.min(beta, evalScore);
            if (beta <= alpha) break;
        }
        return minEval;
    }
}

function orderMoves(boardState, moves) {
    return moves.map(move => {
        let score = 0;
        let attackingPiece = boardState[move.fromR][move.fromC];
        let targetPiece = boardState[move.toR][move.toC];
        if (targetPiece) score = 10 * PIECE_VALUES[targetPiece] - PIECE_VALUES[attackingPiece];
        if (attackingPiece && attackingPiece.toLowerCase() === 'p' && (move.toR === 0 || move.toR === 7)) score += 900;
        if (move.type === 'castle-king' || move.type === 'castle-queen') score += 50;
        return { move, score };
    }).sort((a, b) => b.score - a.score).map(item => item.move);
}

function simMove(b, m) {
    let savedTarget = b[m.toR][m.toC];
    let savedSource = b[m.fromR][m.fromC];
    b[m.toR][m.toC] = savedSource;
    b[m.fromR][m.fromC] = null;
    return { target: savedTarget, source: savedSource };
}

function unsimMove(b, m, s) {
    b[m.fromR][m.fromC] = s.source;
    b[m.toR][m.toC] = s.target;
}

function makeEngineMove(move) {
    let piece = board[move.fromR][move.fromC];
    if (move.type === 'en-passant') { let pdir = piece === 'P' ? 1 : -1; board[move.toR + pdir][move.toC] = null; }
    if (move.type === 'castle-king') { if (move.toR === 7) { board[7][5] = 'R'; board[7][7] = null; } if (move.toR === 0) { board[0][5] = 'r'; board[0][7] = null; } }
    if (move.type === 'castle-queen') { if (move.toR === 7) { board[7][3] = 'R'; board[7][0] = null; } if (move.toR === 0) { board[0][3] = 'r'; board[0][0] = null; } }
    if (piece === 'K') { castleRights.W_K = false; castleRights.W_Q = false; }
    if (piece === 'k') { castleRights.B_K = false; castleRights.B_Q = false; }
    if (move.type === 'pawn-double') { let pdir = piece === 'P' ? 1 : -1; enPassantTarget = { r: move.toR + pdir, c: move.toC }; }
    else { enPassantTarget = null; }
    if (piece === 'P' && move.toR === 0) piece = 'Q';
    if (piece === 'p' && move.toR === 7) piece = 'q';
    board[move.toR][move.toC] = piece;
    board[move.fromR][move.fromC] = null;
    turn = turn === 'W' ? 'B' : 'W';
}

function isKingInCheck(boardState, color) {
    let kingChar = color === 'W' ? 'K' : 'k';
    let kingPos = null;
    for (let r = 0; r < 8; r++) { for (let c = 0; c < 8; c++) { if (boardState[r][c] === kingChar) { kingPos = {r, c}; break; } } if (kingPos) break; }
    if (!kingPos) return true;
    let opponentColor = color === 'W' ? 'B' : 'W';
    for (let r = 0; r < 8; r++) { for (let c = 0; c < 8; c++) { let piece = boardState[r][c]; if (piece) { let isOpponent = opponentColor === 'W' ? piece === piece.toUpperCase() : piece === piece.toLowerCase(); if (isOpponent) { let pMoves = getPseudoMoves(r, c, boardState, {W_K:false,W_Q:false,B_K:e,B_Q:false}, null);
                    if (pMoves.some(m => m.r === kingPos.r && m.c === kingPos.c)) return true;
                }
            }
        }
    }
    return false;
}

function getAllStrictLegalMoves(boardState, color, castleRightsState, enPassantTargetState) {
    let allMoves = [];
    for (let r = 0; r < 8; r++) {
        for (let c = 0; c < 8; c++) {
            let piece = boardState[r][c];
            if (piece) {
                let isWhite = piece === piece.toUpperCase();
                if ((color === 'W' && isWhite) || (color === 'B' && !isWhite)) {
                    let pMoves = getPseudoMoves(r, c, boardState, castleRightsState, enPassantTargetState);
                    pMoves.forEach(move => {
                        let saved = simMove(boardState, move);
                        if (!isKingInCheck(boardState, color)) {
                            allMoves.push({ fromR: r, fromC: c, toR: move.r, toC: move.c, type: move.type });
                        }
                        unsimMove(boardState, move, saved);
                    });
                }
            }
        }
    }
    return allMoves;
}

function getPseudoMoves(row, col, currentBoard, castleRightsState, enPassantTargetState) {
    let piece = currentBoard[row][col];
    if (!piece) return [];
    let moves = [];
    let isWhite = piece === piece.toUpperCase();
    
    switch (piece.toLowerCase()) {
        case 'p':
            let dir = isWhite ? -1 : 1;
            let start = isWhite ? 6 : 1;
            if (currentBoard[row + dir] && currentBoard[row + dir][col] === null) {
                moves.push({r: row + dir, c: col, type: 'normal'});
                if (row === start && currentBoard[row + 2 * dir] && currentBoard[row + 2 * dir][col] === null) {
                    moves.push({r: row + 2 * dir, c: col, type: 'pawn-double'});
                }
            }
            [-1, 1].forEach(dc => {
                let c = col + dc;
                if (c >= 0 && c <= 7) {
                    let target = currentBoard[row + dir] ? currentBoard[row + dir][c] : null;
                    if (target && (target === target.toUpperCase()) !== isWhite) {
                        moves.push({r: row + dir, c: c, type: 'normal'});
                    }
                    if (enPassantTargetState && enPassantTargetState.r === row + dir && enPassantTargetState.c === c) {
                        moves.push({r: row + dir, c: c, type: 'en-passant'});
                    }
                }
            });
            break;
            
        case 'n':
            let nMoves = [[-2,-1], [-2,1], [-1,-2], [-1,2], [1,-2], [1,2], [2,-1], [2,1]];
            for (let [dr, dc] of nMoves) {
                let r = row + dr, c = col + dc;
                if (r >= 0 && r < 8 && c >= 0 && c < 8) {
                    if (currentBoard[r][c] === null || (currentBoard[r][c] === currentBoard[r][c].toUpperCase()) !== isWhite) {
                        moves.push({r, c, type: 'normal'});
                    }
                }
            }
            break;
            
        case 'b':
            let bDirs = [[-1,-1], [-1,1], [1,-1], [1,1]];
            for (let [dr, dc] of bDirs) {
                let r = row + dr, c = col + dc;
                while (r >= 0 && r < 8 && c >= 0 && c < 8) {
                    if (currentBoard[r][c] === null) {
                        moves.push({r, c, type: 'normal'});
                    } else {
                        if ((currentBoard[r][c] === currentBoard[r][c].toUpperCase()) !== isWhite) {
                            moves.push({r, c, type: 'normal'});
                        }
                        break;
                    }
                    r += dr;
                    c += dc;
                }
            }
            break;
            
        case 'r':
            let rDirs = [[-1,0], [1,0], [0,-1], [0,1]];
            for (let [dr, dc] of rDirs) {
                let r = row + dr, c = col + dc;
                while (r >= 0 && r < 8 && c >= 0 && c < 8) {
                    if (currentBoard[r][c] === null) {
                        moves.push({r, c, type: 'normal'});
                    } else {
                        if ((currentBoard[r][c] === currentBoard[r][c].toUpperCase()) !== isWhite) {
                            moves.push({r, c, type: 'normal'});
                        }
                        break;
                    }
                    r += dr;
                    c += dc;
                }
            }
            break;
            
        case 'q':
            let qDirs = [[-1,0], [1,0], [0,-1], [0,1], [-1,-1], [-1,1], [1,-1], [1,1]];
            for (let [dr, dc] of qDirs) {
                let r = row + dr, c = col + dc;
                while (r >= 0 && r < 8 && c >= 0 && c < 8) {
                    if (currentBoard[r][c] === null) {
                        moves.push({r, c, type: 'normal'});
                    } else {
                        if ((currentBoard[r][c] === currentBoard[r][c].toUpperCase()) !== isWhite) {
                            moves.push({r, c, type: 'normal'});
                        }
                        break;
                    }
                    r += dr;
                    c += dc;
                }
            }
            break;
            
        case 'k':
            let kMoves = [[-1,-1], [-1,0], [-1,1], [0,-1], [0,1], [1,-1], [1,0], [1,1]];
            for (let [dr, dc] of kMoves) {
                let r = row + dr, c = col + dc;
                if (r >= 0 && r < 8 && c >= 0 && c < 8) {
                    if (currentBoard[r][c] === null || (currentBoard[r][c] === currentBoard[r][c].toUpperCase()) !== isWhite) {
                        moves.push({r, c, type: 'normal'});
                    }
                }
            }
            if (isWhite && row === 7 && col === 4) {
                if (castleRightsState.W_K && currentBoard[7][5] === null && currentBoard[7][6] === null) {
                    moves.push({r: 7, c: 6, type: 'castle-king'});
                }
                if (castleRightsState.W_Q && currentBoard[7][3] === null && currentBoard[7][2] === null && currentBoard[7][1] === null) {
                    moves.push({r: 7, c: 2, type: 'castle-queen'});
                }
            } else if (!isWhite && row === 0 && col === 4) {
                if (castleRightsState.B_K && currentBoard[0][5] === null && currentBoard[0][6] === null) {
                    moves.push({r: 0, c: 6, type: 'castle-king'});
                }
                if (castleRightsState.B_Q && currentBoard[0][3] === null && currentBoard[0][2] === null && currentBoard[0][1] === null) {
                    moves.push({r: 0, c: 2, type: 'castle-queen'});
                }
            }
            break;
    }
    return moves;
}

// KOD SONU
📖

Kullanım Kılavuzu

1

Kodu projenize ekleyin

RacBot.js dosyasını oluşturun ve HTML'inize ekleyin: <script src="RacBot.js"></script>

2

Başlangıç pozisyonunu yükleyin

Standart başlangıç için: loadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");

📋 Örnek FEN dizeleri:
• Başlangıç: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
• Beyaz şah mat: 7k/8/8/8/8/8/8/K7 w - - 0 1
• Siyah şah mat: k7/8/8/8/8/8/8/7K b - - 0 1
3

Motorun en iyi hamlesini hesaplatın

let bestMove = calculateBestMove(2000); // 2000ms = 2 saniye düşünme süresi

Dönen nesne şu formattadır: { fromR, fromC, toR, toC, type }

4

Hamleyi tahtada uygulayın

makeEngineMove(bestMove);

Bu fonksiyon tahtayı günceller, sırayı değiştirir, rok haklarını ve geçerken alma verilerini otomatik yönetir.

5

Oyun durumunu kontrol edin

• Mevcut FEN'i al: generateFEN()
• Sıra kimde: turn değişkeni ('W' veya 'B')
• Mevcut tahta: board 8x8'lik dizi

⚙️

Motor Özellikleri

✓ İteratif derinleştirme (1-10 hamle)
✓ Zaman kontrolü (2 saniye varsayılan)
✓ Alfa-beta budaması
✓ MVV-LVA hamle sıralama
✓ Konum tabloları (tüm taşlar)
✓ Oyun sonu şah matrisi
✓ FEN okuma/yazma
✓ Rok, geçerken alma, piyon terfisi
✓ ~1700-1850 ELO tahmini güç

💡

Kod Örneği

📄 Örnek: RacBot ile oynama
// RacBot'u başlat
loadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");

// Hamle yapma döngüsü
while (true) {
    let bestMove = calculateBestMove(2000); // 2 saniye düşün
    
    if (!bestMove) {
        console.log("Oyun bitti - mat veya pat");
        break;
    }
    
    console.log(`Hamle: ${String.fromCharCode(97+bestMove.fromC)}${8-bestMove.fromR} → ${String.fromCharCode(97+bestMove.toC)}${8-bestMove.toR}`);
    
    makeEngineMove(bestMove);
    
    if (turn === 'W') console.log("Sıra Beyaz'da");
    else console.log("Sıra Siyah'ta");
}