I have proceeded a bit with my goal to obtain a chess-game endgame engine where both players would play fully optimally while there are D<5 pieces on the board so that I can wait for the result.The player who can give a checkmate (be it B or W) should give it as soon as possible while the player who gets inevitably checkmated delays it as long as possible. Draw should be treated reasonably as well. I have obtained this code: https://pastebin.com/FKugyrXW which does 100% this: it computes the minimal number of plies between 2 fen strings which are both descendants of a given initial_fen string. However I’m unable to extract from that functionality the optimal move, it says c1c8 which is not true, c1c6 is OK here,please see the bottom of this page: https://pastebin.com/x8CZ5FwS
Should anyone be interested in a small amount of money for fixing this, contact me at LinkedIn, the name is Jan Pax.The code is included for convenience:
import chess
from collections import deque, defaultdict
def simplify_fen(fen):
parts = fen.split(' ')
return ' '.join(parts[:4]) # Normalize FEN string
def generate_moves_and_distances(board):
initial_fen = simplify_fen(board.fen())
queue = deque([(initial_fen, 0)])
distances = defaultdict(lambda: defaultdict(lambda: float('inf')))
visited = set()
while queue:
current_fen, current_depth = queue.popleft()
if current_fen in visited:
continue
visited.add(current_fen)
distances[initial_fen][current_fen] = current_depth
current_board = chess.Board(current_fen)
for move in current_board.legal_moves:
current_board.push(move)
next_fen = simplify_fen(current_board.fen())
if next_fen not in visited or distances[initial_fen][next_fen] > current_depth + 1:
distances[initial_fen][next_fen] = current_depth + 1
queue.append((next_fen, current_depth + 1))
current_board.pop()
return distances
def minimax(board, depth, maximizing_player):
if depth == 0 or board.is_game_over():
evaluation = evaluate_board(board)
if evaluation is None:
return 0 # Neutrální hodnota pro nedeterminované pozice
return evaluation
if maximizing_player:
max_eval = float('-inf')
for move in board.legal_moves:
board.push(move)
eval = minimax(board, depth - 1, not maximizing_player)
board.pop()
if eval is not None:
max_eval = max(max_eval, eval)
else:
max_eval = max(max_eval, 0) # Neutrální hodnota pokud eval je None
return max_eval
else:
min_eval = float('inf')
for move in board.legal_moves:
board.push(move)
eval = minimax(board, depth - 1, not maximizing_player)
board.pop()
if eval is not None:
min_eval = min(min_eval, eval)
else:
min_eval = min(min_eval, 0) # Neutrální hodnota pokud eval je None
return min_eval
def find_best_move(board, depth):
best_move = None
best_value = float('-inf')
for move in board.legal_moves:
board.push(move)
value = minimax(board, depth - 1, False)
board.pop()
if value > best_value:
best_value = value
best_move = move
return best_move
def evaluate_board(board):
if board.is_checkmate():
if board.turn: # Black's turn, white gave checkmate
return float('-inf')
else:
return float('inf')
if board.is_stalemate() or board.is_insufficient_material():
return 0
return None # No evaluation, return a neutral score
# Main usage
initial_fen = "5k2/8/3K4/6Q1/8/8/8/8 w - - 0 1"
initial_fen = "8/8/4k3/8/2K5/5Q2/8/8 w - - 0 1"
initial_fen = "8/5k2/8/8/8/8/8/1KQ5 w - - 0 1"
initial_fen = simplify_fen(initial_fen)
board = chess.Board(initial_fen)
AB = generate_moves_and_distances(board)
def print_distances_less_than_two(AB, initial_fen):
for fen2 in AB[initial_fen]:
if AB[initial_fen][fen2] < 2:
print(f"From {initial_fen} to {fen2}: {AB[initial_fen][fen2]} moves")
print_distances_less_than_two(AB, initial_fen)
# Finding best move using minimax
best_move = find_best_move(board, 8)
print("Best move for white:", best_move)
fen3 = "8/5k2/8/8/8/8/8/1KQ5 w - -"
fen4 = "8/8/5k2/8/8/4Q3/8/1K6 w - -"
print(f"Od {fen3} do {fen4}: {AB[fen3][fen4]} moves")