I am trying to make a chess engine in python, and so far, everything was fine. I want to make it compatible with uci so I can sign it up on lichess and also display the moves in a chess gui like arena.
import time
import chess
import threading
import chess.pgn
from datetime import datetime
fens = ["rnbqkbnr/pppp1ppp/8/4p3/3PP3/8/PPP2PPP/RNBQKBNR b KQkq d3 0 2", "rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR b KQkq c3 0 2", "r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3", "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2", "rnbqkbnr/pppp1ppp/8/4p3/3P4/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 2", "rnbqkbnr/ppp2ppp/3p4/4p3/4PP2/8/PPPP2PP/RNBQKBNR w KQkq - 0 3", "rnbqkbnr/ppp1pppp/8/3p4/3PP3/8/PPP2PPP/RNBQKBNR b KQkq d3 0 2", "rnbqkbnr/ppp1pppp/8/3p4/3P1P2/8/PPP1P1PP/RNBQKBNR b KQkq d3 0 2", "rnbqkbnr/pp1ppppp/8/2p5/2PP4/8/PP2PPPP/RNBQKBNR b KQkq d3 0 2", "r1bqkbnr/pppp1ppp/2n5/4p3/4PP2/2N5/PPPP2PP/R1BQKBNR b KQkq f3 0 3", "r1bqkbnr/pppp1ppp/2n5/4p3/8/2N2N2/PPPPPPPP/R1BQKB1R w KQkq e6 0 3", "rnbqkbnr/pppp2pp/5p2/4p3/3P4/2N5/PPP1PPPP/R1BQKBNR w KQkq - 0 3", "1Nb2rk1/p4pp1/5p1p/2P5/5q2/P6P/2P2PP1/R2QK2R b - - 0 18", "r1bqk2r/pppp1ppp/2n2n2/8/1bBPP3/5N2/PP1B1PPP/RN1QK2R b KQkq - 2 7"
, "rnbqkbnr/ppp3pp/3p1p2/3Pp3/4PP2/8/PPP3PP/RNBQKBNR b KQkq - 0 4", "rn1qkbnr/ppp3pp/8/3Ppb2/8/8/PPP3PP/RNBQKBNR w KQkq - 0 7", "rn1qkbnr/ppp1p1pp/8/3p1b2/3P4/8/PPP2PPP/RNBQKBNR w KQkq - 0 4", "rnbqkbnr/ppp1p1pp/8/5p2/3Pp3/2N5/PPP2PPP/R1BQKBNR w KQkq f6 0 4", "rn1qkbnr/ppp3pp/8/3p1b2/3P1B2/8/PPP3PP/RN1QKBNR w KQkq - 0 7", "rnb1kbnr/pp3ppp/3p4/8/8/8/PPP1PPPP/RNB1KBNR w KQkq - 0 5"]
def get_pieces_on_board(board):
pieces_on_board = 0
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece is not None:
pieces_on_board += 1
return pieces_on_board
def evaluate(board):
piece_values = {
chess.PAWN: 100,
chess.KNIGHT: 300,
chess.BISHOP: 330,
chess.ROOK: 500,
chess.QUEEN: 900,
chess.KING: 15000
}
if get_pieces_on_board(board) < 7:
position_values = {
chess.PAWN: [
100,100,100,100,100,100,100,100,
50,50,50,50,50,50,50,50,
20,20,20,20,20,20,20,20,
10,10,10,10,10,10,10,10,
-10,-10,-10,-10,-10,-10,-10,-10,
-20,-20,-20,-20,-20,-20,-20,-20,
-50,-50,-50,-50,-50,-50,-50,-50,
-100,-100,-100,-100,-100,-100,-100,-100
],
chess.KNIGHT: [
-20,-15,-15,-15,-15,-15,-15,-20,
-15,-10,-10,-10,-10,-10,-10,-15,
-15,-10,0,0,0,0,-10,-15,
-15,-10,0,0,0,0,-10,-15,
-15,-10,0,0,0,0,-10,-15,
-15,-10,0,0,0,0,-10,-15,
-15,-10,-10,-10,-10,-10,-10,-15,
-20,-15,-15,-15,-15,-15,-15,-20,
],
chess.BISHOP: [
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
],
chess.ROOK: [
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
],
chess.QUEEN: [
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
],
chess.KING: [
-100,-50,0,0,0,0,-50,-100,
-50,0,0,0,0,0,0,-50,
0,0,10,10,10,10,0,0,
0,0,10,25,25,10,0,0,
0,0,10,25,25,10,0,0,
0,0,10,10,10,10,0,0,
-50,0,0,0,0,0,0,0,
-100,-50,0,0,0,0,-50,-100,
],
}
else:
position_values = {
chess.PAWN: [
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,50,50,0,0,0,
0,0,0,50,50,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
],
chess.KNIGHT: [
-40,-30,-30,-30,-30,-30,-30,-40,
-30,-20,-20,-20,-20,-20,-20,-30,
-30,-20,0,0,0,0,-20,-30,
-30,-20,0,0,0,0,-20,-30,
-30,-20,0,0,0,0,-20,-30,
-30,-20,0,0,0,0,-20,-30,
-30,-20,-20,-20,-20,-20,-20,-30,
-40,-30,-30,-130,-30,-30,-30,-40,
],
chess.BISHOP: [
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
],
chess.ROOK: [
0,-50,0,0,0,0,-50,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,-50,0,0,0,0,-50,0,
],
chess.QUEEN: [
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
],
chess.KING: [
0,30,20,0,0,0,30,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,50,30,0,0,0,50,0,
],
}
score = 0
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece == None:
continue
if piece.color == chess.WHITE:
score += piece_values[piece.piece_type] + position_values[piece.piece_type][square]
else:
score -= piece_values[piece.piece_type] + position_values[piece.piece_type][square]
if board.is_checkmate():
if board.turn == chess.WHITE:
score += float("-inf")
else:
score += float("inf")
if board.is_check():
if board.turn == chess.WHITE:
score += 50
else:
score -= 50
return score
def minimax(alpha, beta, board, depth, maximizing_player, checkext):
if depth == 0 or board.is_game_over():
return evaluate(board)
if maximizing_player:
max_eval = float("-inf")
capture_moves = [move for move in board.legal_moves if board.is_capture(move)]
check_moves = [move for move in board.legal_moves if board.gives_check(move)]
other_moves = [move for move in board.legal_moves if not board.is_capture(move) and not board.gives_check(move)]
for move in capture_moves:
board.push(move)
if board.is_check() and checkext > 0 and depth == 1:
evaluation = minimax(alpha, beta, board, depth, False, checkext-1)
else:
evaluation = minimax(alpha, beta, board, depth-1, False, checkext)
board.pop()
max_eval = max(max_eval, evaluation)
alpha = max(alpha, evaluation)
if beta <= alpha:
break
for move in check_moves:
board.push(move)
if board.is_check() and checkext > 0 and depth == 1:
evaluation = minimax(alpha, beta, board, depth, False, checkext-1)
else:
evaluation = minimax(alpha, beta, board, depth-1, False, checkext)
board.pop()
max_eval = max(max_eval, evaluation)
alpha = max(alpha, evaluation)
if beta <= alpha:
break
for move in other_moves:
board.push(move)
if board.is_check() and checkext > 0 and depth == 1:
evaluation = minimax(alpha, beta, board, depth, False, checkext-1)
else:
evaluation = minimax(alpha, beta, board, depth-1, False, checkext)
board.pop()
max_eval = max(max_eval, evaluation)
alpha = max(alpha, evaluation)
if beta <= alpha:
break
return max_eval
else:
min_eval = float("inf")
capture_moves = [move for move in board.legal_moves if board.is_capture(move)]
check_moves = [move for move in board.legal_moves if board.gives_check(move)]
other_moves = [move for move in board.legal_moves if not board.is_capture(move) and not board.gives_check(move)]
# First, evaluate capture moves
for move in capture_moves:
board.push(move)
if board.is_check() and checkext > 0 and depth == 1:
evaluation = minimax(alpha, beta, board, depth, True, checkext-1)
else:
evaluation = minimax(alpha, beta, board, depth-1, True, checkext)
board.pop()
min_eval = min(min_eval, evaluation)
beta = min(min_eval, beta)
if beta <= alpha:
break
for move in check_moves:
board.push(move)
if board.is_check() and checkext > 0 and depth == 1:
evaluation = minimax(alpha, beta, board, depth, True, checkext-1)
else:
evaluation = minimax(alpha, beta, board, depth-1, True, checkext)
board.pop()
min_eval = min(min_eval, evaluation)
beta = min(min_eval, beta)
if beta <= alpha:
break
for move in other_moves:
board.push(move)
if board.is_check() and checkext > 0 and depth == 1:
evaluation = minimax(alpha, beta, board, depth, True, checkext-1)
else:
evaluation = minimax(alpha, beta, board, depth-1, True, checkext)
board.pop()
min_eval = min(min_eval, evaluation)
beta = min(min_eval, beta)
if beta <= alpha:
break
return min_eval
Depthflag = True
def stop_depth():
global Depthflag
Depthflag = False
maxdepth = 0
def find_best_move(board,depth = 1, best_move=None, best_eval=None, recursive=False, starttime=None):
alpha = float("-inf")
beta = float("inf")
maximizing_player = board.turn
force_finish = False
if recursive == True:
best_move_of_previous_iteration = best_move
best_eval_of_previous_iteration = best_eval
best_move = None
best_eval = None
# Separate capture moves and other moves
capture_moves = [move for move in board.legal_moves if board.is_capture(move)]
check_moves = [move for move in board.legal_moves if board.gives_check(move)]
other_moves = [move for move in board.legal_moves if not board.is_capture(move) and not board.gives_check(move)]
if starttime is None:
starttime = time.time()
limittime = 3
if not recursive:
best_move = None # Reset best move at the start of a new iteration
best_eval = None
depth = 1
else:
if depth < 10:
print(depth)
for move in capture_moves:
if time.time() - starttime <= limittime:
board.push(move)
evaluation = minimax(alpha, beta, board, depth - 1, not maximizing_player, 10)
board.pop()
if maximizing_player and (best_eval is None or evaluation > best_eval):
best_eval = evaluation
best_move = move
alpha = evaluation
elif (not maximizing_player) and (best_eval is None or evaluation < best_eval):
best_eval = evaluation
best_move = move
beta = evaluation
if beta <= alpha:
break
else:
force_finish = True
for move in check_moves:
if time.time() - starttime <= limittime:
board.push(move)
evaluation = minimax(alpha, beta, board, depth - 1, not maximizing_player, 10)
board.pop()
if maximizing_player and (best_eval is None or evaluation > best_eval):
best_eval = evaluation
best_move = move
alpha = evaluation
elif (not maximizing_player) and (best_eval is None or evaluation < best_eval):
best_eval = evaluation
best_move = move
beta = evaluation
if beta <= alpha:
break
force_finish = True
for move in other_moves:
if time.time() - starttime <= limittime:
board.push(move)
evaluation = minimax(alpha, beta, board, depth - 1, not maximizing_player, 10)
board.pop()
if maximizing_player and (best_eval is None or evaluation > best_eval):
best_eval = evaluation
best_move = move
alpha = evaluation
elif (not maximizing_player) and (best_eval is None or evaluation < best_eval):
best_eval = evaluation
best_move = move
beta = evaluation
if beta <= alpha:
break
force_finish = True
if best_eval == None:
best_eval = 0
if recursive:
if force_finish:
if best_eval >= best_eval_of_previous_iteration and maximizing_player:
best_move = best_move
elif best_eval < best_eval_of_previous_iteration and maximizing_player:
best_move = best_move_of_previous_iteration
elif best_eval >= best_eval_of_previous_iteration and not maximizing_player:
best_move = best_move_of_previous_iteration
elif best_eval < best_eval_of_previous_iteration and not maximizing_player:
best_move = best_move
else:
best_move = best_move
if time.time() - starttime <= limittime and depth < 100:
best_move = find_best_move(board, depth + 1, best_move, best_eval, True, starttime)
else:
print(depth)
return best_move
fen_positions = {}
def main(turn, fen):
global fen_positions
board = chess.Board(fen)
depth = 1
moves = []
wTimeUse = 0
bTimeUse = 0
game = chess.pgn.Game() # Create a new Game object for PGN
if turn == 0:
while not board.is_game_over():
current_fen = board.fen()
if current_fen in fen_positions:
fen_positions[current_fen] += 1
if fen_positions[current_fen] > 1:
print("Draw due to repetition!")
break
if board.turn == chess.WHITE:
a = time.time()
move = find_best_move(board)
board.push(move)
print("AI played: ", move)
b = time.time()
e = b - a
print("elapsed time:", round(e, 2))
moves.append(move)
print(board)
print()
wTimeUse += e
else:
a = time.time()
move = best_move(board, 4)
board.push(move)
print("AI played: ", move)
b = time.time()
e = b - a
print("elapsed time:", round(e, 2))
moves.append(move)
print(board)
print()
bTimeUse += e
if turn == 1:
while not board.is_game_over():
current_fen = board.fen()
if current_fen in fen_positions:
fen_positions[current_fen] += 1
if fen_positions[current_fen] > 1:
print("Draw due to repetition!")
break
if board.turn == chess.BLACK:
a = time.time()
move = find_best_move(board)
board.push(move)
print("AI played: ", move)
b = time.time()
e = b - a
print("elapsed time:", round(e, 2))
moves.append(move)
print(board)
print()
wTimeUse += e
else:
a = time.time()
move = best_move(board, 4)
board.push(move)
print("AI played: ", move)
b = time.time()
e = b - a
print("elapsed time:", round(e, 2))
moves.append(move)
print(board)
print()
bTimeUse += e
print("Game over")
print("Moves in PGN format:")
print(len(moves))
print(wTimeUse, bTimeUse)
if board.is_checkmate():
print("Checkmate")
elif board.is_stalemate():
print("stalemate")
elif board.is_fifty_moves():
print("50 moves")
elif board.is_fivefold_repetition():
print("Fivefold")
if board.result() == "1-0" and turn == 0:
result = 1
elif board.result == "0-1" and turn == 0:
result = 0
elif board.result == "1-0" and turn == 1:
result = 0
elif board.result == "0-1" and turn == 1:
result = 1
else:
result = 0.5
return result
if __name__ == "__main__":
BotWins = 0
PilotWins = 0
draws = 0
for fen in fens:
result = main(0, fen)
if result == 1:
BotWins += 1
elif result == 0:
PilotWins += 1
else:
draws += 1
print("Current bot Wins:", BotWins, "/", len(fens) * 2)
print("1st bot Wins:", PilotWins, "/", len(fens) * 2)
print("draws:", draws, "/", len(fens) * 2)
for fen in fens:
result = main(1, fen)
if result == 1:
BotWins += 1
elif result == 0:
PilotWins += 1
else:
draws += 1
print("Current bot Wins:", BotWins, "/", len(fens) * 2)
print("1st bot Wins:", PilotWins, "/", len(fens) * 2)
print("draws:", draws, "/", len(fens) * 2)
print("Current bot Wins:", BotWins, "/", len(fens) * 2)
print("1st Bot Wins:", PilotWins, "/", len(fens) * 2)
print("draws:", draws, "/", len(fens) * 2)
I know this code is not very good, but I am 14 and not that good at programming. If anyone knows how to make it uci compatible, please help me out