/**
* 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
RacBot.js dosyasını oluşturun ve HTML'inize ekleyin: <script src="RacBot.js"></script>
Standart başlangıç için: loadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 17k/8/8/8/8/8/8/K7 w - - 0 1k7/8/8/8/8/8/8/7K b - - 0 1
let bestMove = calculateBestMove(2000); // 2000ms = 2 saniye düşünme süresi
Dönen nesne şu formattadır: { fromR, fromC, toR, toC, type }
makeEngineMove(bestMove);
Bu fonksiyon tahtayı günceller, sırayı değiştirir, rok haklarını ve geçerken alma verilerini otomatik yönetir.
• Mevcut FEN'i al: generateFEN()
• Sıra kimde: turn değişkeni ('W' veya 'B')
• Mevcut tahta: board 8x8'lik dizi
// 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");
}