2

I have the following backtracking solution to chess endgames with D=3 figures on the board. I should have obtained white king to move on Kc2, but I'm getting Qh3h8. I have printed the print(board.legal_moves) and I'm obtaining <LegalMoveGenerator at 0x1d4739daed0 (Kb3, Ka3)> which doesn't print Qh3h8 at all. I gives at first move Kb3 and Ka3 while there is black k which can move to kb3 and ka3, not white Kb3 nor Ka3. It is white (K) turn to move, but only black (k) can move to ka3 and kb3. The correct move is Kc2 anyway There must be some bug in the code, can someone kindly help to me ? I cannot find it by myself.

The code:

import chess
import time
import threading

# Globální proměnná pro sledování počtu prohledaných pozic
positions_count = 0

def update_positions_count(last_time_printed):
    global positions_count
    while not board.is_game_over():
        if time.time() - last_time_printed > 1:
            print(f"\rProhledaných pozic: {positions_count}", end='')
            last_time_printed = time.time()

def evaluate_board(board, depth):
    global positions_count
    positions_count += 1

    if board.is_checkmate():
        return 10000 - depth
    if board.is_stalemate() or board.can_claim_draw():
        return 0
    return None

# ... negamax, find_best_move ...
# Negamax algoritmus
def negamax(board, depth, alpha, beta, color):
    evaluated = evaluate_board(board, depth)
    if evaluated is not None:
        return color * evaluated

    if depth == 0 or board.is_game_over():
        return 0

    max_eval = float('-inf')
    print(board.legal_moves)
    for move in board.legal_moves:
        board.push(move)
        eval = -negamax(board, depth - 1, -beta, -alpha, -color)
        board.pop()
        max_eval = max(max_eval, eval)
        alpha = max(alpha, eval)
        if alpha >= beta:
            break

    return max_eval

# def find_best_move(board, depth):
#     best_move = None
#     best_value = float('-inf')
#     alpha = float('-inf')
#     beta = float('inf')
#     color = 1 if board.turn else -1

#     for move in board.legal_moves:
#         print(move)
#         if str(move) == "Kc2":
#             print("HERE")
#         board.push(move)
#         board_value = -negamax(board, depth - 1, -beta, -alpha, -color)
#         board.pop()

#         if board_value > best_value:
#             best_value = board_value
#             best_move = move

#     return best_move

def find_best_move(board, depth):
    best_move = None
    best_value = float('-inf')
    alpha = float('-inf')
    beta = float('inf')
    color = 1 if board.turn else -1

    print(f"Na tahu je: {'bílý' if board.turn else 'černý'}")

    for move in board.legal_moves:
        print(move.uci())  # Vypíše tahy ve formátu UCI
        if move.uci() == "c1c2":  # Příklad pro tah bílého krále
            print("HERE")
        board.push(move)
        board_value = -negamax(board, depth - 1, -beta, -alpha, -color)
        board.pop()

        if board_value > best_value:
            best_value = board_value
            best_move = move

    return best_move



# Hlavní část kódu
start_position = "8/8/8/8/8/7Q/k7/2K5 w - - 0 1"
board = chess.Board(start_position)
depth = 7  # Můžete zvážit snížení hloubky pro rychlejší výsledky
last_time_printed = time.time()

positions_count_thread = threading.Thread(target=update_positions_count, args=(last_time_printed,), daemon=True)
positions_count_thread.start()

print(board)
print()

while not board.is_game_over():
    best_move = find_best_move(board, depth)
    if best_move is not None:
        board.push(best_move)
        print("\n", board)  # Vytiskne šachovnici po provedení nejlepšího tahu
    else:
        print("Žádný další legální tah není možný.")
        break

print("\nKonec hry")

THE OUTPUT

. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . Q
k . . . . . . .
. . K . . . . .

Na tahu je: bílý
h3h8
<LegalMoveGenerator at 0x1d4739daed0 (Kb3, Ka3)>
<LegalMoveGenerator at 0x1d4739da050 (Qg8+, Qf8, Qe8, Qd8, Qc8, Qb8+, Qa8, Qh7, Qg7, Qh6, Qf6, Qh5, Qe5, Qh4, Qd4, Qh3+, Qc3+, Qh2, Qb2+, Qh1, Qa1, Kd2, Kd1, Kb1)>
<LegalMoveGenerator at 0x1d4739da690 (Kb4, Ka4, Kc3, Ka3)>
<LegalMoveGenerator at 0x1d4739dac50 (Qh8, Qf8+, Qe8, Qd8, Qc8, Qb8+, Qa8, Qh7, Qg7, Qf7, Qg6, Qe6, Qg5, Qd5, Qg4+, Qc4+, Qg3, Qb3+, Qg2, Qa2, Qg1, Kd2, Kc2, Kb2, Kd1, Kb1)>
<LegalMoveGenerator at 0x1d4739dbcd0 (Kc5, Kb5, Ka5, Kc4, Ka4, Kb3, Ka3)>
<LegalMoveGenerator at 0x1d4739dae50 (Qg8, Qf8+, Qe8, Qd8, Qc8+, Qb8, Qa8, Qh7, Qg7, Qh6, Qf6, Qh5+, Qe5+, Qh4, Qd4+, Qh3, Qc3+, Qh2, Qb2, Qh1, Qa1, Kd2, Kc2, Kb2, Kd1, Kb1)>
<LegalMoveGenerator at 0x1d4739da890 (Qg8, Qf8, Qe8+, Qd8, Qc8, Qb8+, Qa8, Qh7, Qg7, Qh6, Qf6, Qh5+, Qe5+, Qh4, Qd4, Qh3, Qc3, Qh2, Qb2+, Qh1, Qa1, Kd2, Kc2, Kb2, Kd1, Kb1)>
<LegalMoveGenerator at 0x1d4739daf90 (Qg8, Qf8, Qe8, Qd8+, Qc8, Qb8, Qa8+, Qh7, Qg7, Qh6, Qf6, Qh5+, Qe5+, Qh4, Qd4, Qh3, Qc3+, Qh2, Q

EDIT: modification of the code and new oputputs of it, but I'm still note getting an optimal result

Why ka6 moves to ka7 instead of ph4 ?

Isn't ph4 more optimal for black?

The oputput is here and the code what has achieved below:

Output:

Počáteční pozice:
. . . . . . . K
. . . . . . . .
k . P . . . . .
. . . . . . . p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .

Prohledaných pozic: 43661880 Čas: 0d 02h 19m 22sPo tahu:
. . . . . . . .
. . . . . . K .
k . P . . . . .
. . . . . . . p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .

Prohledaných pozic: 165219395 Čas: 0d 08h 41m 57sPo tahu:
. . . . . . . .
k . . . . . K .
. . P . . . . .
. . . . . . . p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .

Prohledaných pozic: 275368567 Čas: 0d 14h 35m 14s

Code:

import chess
import time
import threading

positions_count = 0
stop_thread = False

def count_pieces(board):
"""Spočítá a vrátí počet figurek na šachovnici."""
count = 0
for square in chess.SQUARES:
if board.piece_at(square):
count += 1
return count

def update_positions_count_and_time():
global positions_count, start_time
while not stop_thread:
elapsed_time = time.time() - start_time
formatted_time = format_time(elapsed_time)
print(f"\rProhledaných pozic: {positions_count} Čas: {formatted_time}", end='')
time.sleep(1)

def format_time(seconds):
"""Převede sekundy na formát %dd %hh %mm %ss."""
days, seconds = divmod(seconds, 86400)
hours, seconds = divmod(seconds, 3600)
minutes, seconds = divmod(seconds, 60)
return f"{int(days)}d {int(hours):02d}h {int(minutes):02d}m {int(seconds):02d}s"

def evaluate_board(board, depth):
global positions_count
positions_count += 1
if board.is_checkmate():
return 10000 - depth
if board.is_stalemate() or board.can_claim_draw():
return 0
return None

def evaluate_material(board):
"""Vrátí celkovou hodnotu materiálu na šachovnici."""
piece_values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9, chess.KING: 0}
total_value = 0
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece:
value = piece_values[piece.piece_type]
if piece.color == chess.WHITE:
total_value += value
else:
total_value -= value
return total_value

def negamax(board, depth, alpha, beta, color, move_history):
evaluated = evaluate_board(board, depth)
if evaluated is not None:
return color * evaluated

if depth == 0 or board.is_game_over():
return color * evaluate_material(board)

max_eval = float('-inf')
for move in board.legal_moves:
board.push(move)
move_history.append(board.fen())
eval = -negamax(board, depth - 1, -beta, -alpha, -color, move_history)
board.pop()
move_history.pop()
if eval > max_eval:
max_eval = eval
alpha = max(alpha, eval)
if alpha >= beta:
break

return max_eval

def find_best_move(board, depth):
best_move = None
best_value = float('-inf')
alpha = float('-inf')
beta = float('inf')
color = 1 if board.turn else -1
move_history = [board.fen()]

for move in board.legal_moves:
board.push(move)
move_history.append(board.fen())
board_value = -negamax(board, depth - 1, -beta, -alpha, -color, move_history)
board.pop()
move_history.pop()
if board_value > best_value:
best_value = board_value
best_move = move

return best_move, move_history

# ... (Předchozí definice funkcí zůstávají stejné) ...

def find_and_execute_best_move(board, depth):
"""Najde a provede nejlepší tah na šachovnici."""
best_move, move_history = find_best_move(board, depth)
if best_move:
board.push(best_move)
print("Po tahu:")
print(board)
print()
return best_move

start_position = "7K/8/k1P5/7p/8/8/8/8 w - - 0 1"
board = chess.Board(start_position)
depth = 13

start_time = time.time()
print("Počáteční pozice:")
print(board)
print()

positions_count_thread = threading.Thread(target=update_positions_count_and_time, daemon=True)
positions_count_thread.start()

while not board.is_game_over():
find_and_execute_best_move(board, depth)
if board.is_checkmate() or board.is_stalemate() or board.can_claim_draw():
break

stop_thread = True

print("Konec hry")
if board.is_checkmate():
print("Mat!")
elif board.is_stalemate() or board.can_claim_draw():
print("Remíza!")

stop_thread = True
4
  • 1
    There's several suspect things in your code. First, the condition if depth == 0 or board.is_game_over(): will pollute your alpha/beta scores with nonsense. You need to be checking for game termination inside your move loop, before assigning bogus scores to alpha/max_eval. Commented Jan 20, 2024 at 8:17
  • @JaneDoe Please see my EDIT which I have modified along the way you have suggested. But why ka6 still moves to ka7 instead of ph4 ? Isn't ph4 more optimal for black? How can I make black p struggle harder with moving it to ph4, where is the bug in the code now ? Commented Jan 21, 2024 at 9:21
  • Seems to be a draw either way, so it's not "more optimal" in that sense. If you want to make your engine move in specific ways you will have to work on your evaluation function, as well as increasing the depth of the search. As it seems now, you only evaluate the piece values, but you might want to try to evaluate the piece position as well. That's what piece-square tables are used for. Commented Jan 21, 2024 at 18:13
  • You might want to check the Simplified Evaluation Function. While it is fairly primitive, it is easy to implement and works relatively well. Commented Jan 21, 2024 at 18:15

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.