Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- MATE_SCORE = 10000
- INF = 10000000
- # Parse FEN string into board matrix and side to move
- def parse_fen(fen_str):
- parts = fen_str.split()
- rows = parts[0].split('/')
- side_to_move = parts[1]
- board = []
- for rank in rows:
- row = []
- for ch in rank:
- if ch.isdigit():
- row.extend(['.'] * int(ch))
- else:
- row.append(ch)
- board.append(row)
- # Ensure 8 columns per rank
- for i in range(len(board)):
- if len(board[i]) < 8:
- board[i].extend(['.'] * (8 - len(board[i])))
- # Ensure 8 ranks
- while len(board) < 8:
- board.append(['.'] * 8)
- return board, side_to_move
- # Directions for moves of each piece type
- king_dirs = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
- rook_dirs = [(-1,0), (1,0), (0,-1), (0,1)]
- bishop_dirs = [(-1,-1), (-1,1), (1,-1), (1,1)]
- knight_moves = [(-2,-1), (-2,1), (-1,-2), (-1,2), (1,-2), (1,2), (2,-1), (2,1)]
- # Generate all pseudo-legal moves (not filtering self-check yet) for the given side
- def generate_pseudo_legal_moves(board, side):
- moves = []
- # Find opponent king position (to avoid moving king adjacent to it)
- opp_king_char = 'K' if side == 'b' else 'k'
- opp_king_pos = None
- for r in range(8):
- for c in range(8):
- if board[r][c] == opp_king_char:
- opp_king_pos = (r, c)
- break
- if opp_king_pos: break
- for r in range(8):
- for c in range(8):
- piece = board[r][c]
- if piece == '.':
- continue
- # Skip pieces of the opposite side
- if side == 'w' and not piece.isupper():
- continue
- if side == 'b' and not piece.islower():
- continue
- p = piece.lower()
- if p == 'k': # King moves
- for dr, dc in king_dirs:
- nr, nc = r + dr, c + dc
- if 0 <= nr < 8 and 0 <= nc < 8:
- dest = board[nr][nc]
- # Cannot move onto own piece
- if side == 'w' and dest.isupper():
- continue
- if side == 'b' and dest.islower():
- continue
- # Cannot move adjacent to opponent king
- if opp_king_pos and abs(opp_king_pos[0] - nr) <= 1 and abs(opp_king_pos[1] - nc) <= 1:
- continue
- moves.append((r, c, nr, nc))
- elif p in ('r', 'b', 'q'): # Rook, Bishop, Queen moves (sliding pieces)
- dirs = []
- if p == 'r':
- dirs = rook_dirs
- elif p == 'b':
- dirs = bishop_dirs
- elif p == 'q':
- dirs = rook_dirs + bishop_dirs
- for dr, dc in dirs:
- nr, nc = r + dr, c + dc
- while 0 <= nr < 8 and 0 <= nc < 8:
- dest = board[nr][nc]
- if side == 'w' and dest.isupper():
- break
- if side == 'b' and dest.islower():
- break
- # Stop if hitting any king (cannot capture king, treat as block)
- if dest.lower() == 'k':
- break
- moves.append((r, c, nr, nc))
- # Stop if we captured an opponent piece (can't go further)
- if dest != '.':
- break
- nr += dr
- nc += dc
- elif p == 'n': # Knight moves
- for dr, dc in knight_moves:
- nr, nc = r + dr, c + dc
- if 0 <= nr < 8 and 0 <= nc < 8:
- dest = board[nr][nc]
- if side == 'w' and dest.isupper():
- continue
- if side == 'b' and dest.islower():
- continue
- if dest.lower() == 'k': # cannot capture king
- continue
- moves.append((r, c, nr, nc))
- elif p == 'p': # Pawn moves (just in case, though default FEN has none)
- if side == 'w':
- # one step forward
- if r-1 >= 0 and board[r-1][c] == '.':
- moves.append((r, c, r-1, c))
- # two steps from starting rank 2
- if r == 6 and board[5][c] == '.':
- moves.append((r, c, 4, c))
- # captures
- for dc in (-1, 1):
- nr, nc = r-1, c+dc
- if 0 <= nr < 8 and 0 <= nc < 8:
- if board[nr][nc] != '.' and board[nr][nc].islower():
- if board[nr][nc].lower() != 'k': # not capturing king
- moves.append((r, c, nr, nc))
- else: # side == 'b'
- if r+1 < 8 and board[r+1][c] == '.':
- moves.append((r, c, r+1, c))
- if r == 1 and board[2][c] == '.':
- moves.append((r, c, 3, c))
- for dc in (-1, 1):
- nr, nc = r+1, c+dc
- if 0 <= nr < 8 and 0 <= nc < 8:
- if board[nr][nc] != '.' and board[nr][nc].isupper():
- if board[nr][nc].lower() != 'k':
- moves.append((r, c, nr, nc))
- return moves
- # Check if the king of the given side is in check
- def is_in_check(board, side):
- king_char = 'K' if side == 'w' else 'k'
- kr = kc = None
- # Find this side's king
- for r in range(8):
- for c in range(8):
- if board[r][c] == king_char:
- kr, kc = r, c
- break
- if kr is not None:
- break
- if kr is None:
- # King not found (should not happen in normal play)
- return True
- opp_side = 'b' if side == 'w' else 'w'
- # Opponent king (to check adjacency threat)
- opp_king_char = 'K' if opp_side == 'w' else 'k'
- opp_kr = opp_kc = None
- for r in range(8):
- for c in range(8):
- if board[r][c] == opp_king_char:
- opp_kr, opp_kc = r, c
- break
- if opp_kr is not None:
- break
- if opp_kr is not None:
- if abs(opp_kr - kr) <= 1 and abs(opp_kc - kc) <= 1:
- return True # adjacent enemy king (illegal position)
- # Knights
- for dr, dc in knight_moves:
- rr, cc = kr+dr, kc+dc
- if 0 <= rr < 8 and 0 <= cc < 8:
- piece = board[rr][cc]
- if piece.lower() == 'n':
- # If an opponent knight is at (rr,cc)
- if opp_side == 'w' and piece.isupper():
- return True
- if opp_side == 'b' and piece.islower():
- return True
- # Pawns
- if opp_side == 'w':
- # White pawn attacks downwards (from its perspective)
- attack_deltas = [(kr+1, kc-1), (kr+1, kc+1)]
- for (pr, pc) in attack_deltas:
- if 0 <= pr < 8 and 0 <= pc < 8 and board[pr][pc] == 'P':
- return True
- else:
- # Black pawn attacks upwards
- attack_deltas = [(kr-1, kc-1), (kr-1, kc+1)]
- for (pr, pc) in attack_deltas:
- if 0 <= pr < 8 and 0 <= pc < 8 and board[pr][pc] == 'p':
- return True
- # Rooks, bishops, queens
- directions = rook_dirs + bishop_dirs
- for dr, dc in directions:
- rr, cc = kr + dr, kc + dc
- while 0 <= rr < 8 and 0 <= cc < 8:
- piece = board[rr][cc]
- if piece != '.':
- if opp_side == 'w' and piece.isupper():
- # opponent (white) piece
- if dr == 0 or dc == 0: # along rank/file
- if piece.lower() in ('r', 'q'):
- return True
- else: # diagonal
- if piece.lower() in ('b', 'q'):
- return True
- if opp_side == 'b' and piece.islower():
- if dr == 0 or dc == 0:
- if piece.lower() in ('r', 'q'):
- return True
- else:
- if piece.lower() in ('b', 'q'):
- return True
- break # stop at first piece encountered
- rr += dr
- cc += dc
- return False
- # Move ordering priority function (helps the search prune faster)
- def move_priority(board, move, side):
- fr, fc, tr, tc = move
- piece = board[fr][fc]
- target = board[tr][tc]
- if side == 'b':
- # Black (the mating side in default position) prioritizes moves:
- # 1. Avoid moves that place the rook next to the enemy king unprotected (risk immediate capture)
- if piece.lower() == 'r':
- # Check if rook's destination is adjacent to white king
- wk_r = wk_c = None
- for r in range(8):
- for c in range(8):
- if board[r][c] == 'K':
- wk_r, wk_c = r, c
- break
- if wk_r is not None:
- break
- if wk_r is not None and abs(wk_r - tr) <= 1 and abs(wk_c - tc) <= 1:
- # Rook moves adjacent to white king
- # Check if black's own king is *not* also adjacent to that square (meaning rook would be unprotected)
- bk_r = bk_c = None
- for r in range(8):
- for c in range(8):
- if board[r][c] == 'k':
- bk_r, bk_c = r, c
- break
- if bk_r is not None:
- break
- if not (bk_r is not None and abs(bk_r - tr) <= 1 and abs(bk_c - tc) <= 1):
- return 5 # low priority (likely bad move)
- # 2. Capturing any opponent piece (if any existed) is good
- if target != '.' and target.isupper():
- return 0
- # 3. Moves by high-value pieces (rook/queen) first
- if piece.lower() in ('r', 'q'):
- return 1
- # 4. King moves last
- if piece.lower() == 'k':
- return 3
- return 2
- else:
- # White (defending side) prioritizes moves:
- # 1. Capture the opponent's rook if possible (to force draw)
- if target != '.' and target.islower():
- if target.lower() in ('r', 'q'):
- return 0
- else:
- return 1
- # 2. Other moves considered equal
- return 2
- # Cache for transposition table: maps (state, depth) -> (score, best_line)
- cache = {}
- # Negamax search with alpha-beta pruning and iterative deepening
- def negamax(board, side, depth, alpha, beta, visited, ply):
- state_key = (side, tuple(tuple(row) for row in board))
- # Transposition table lookup
- if (state_key, depth) in cache:
- return cache[(state_key, depth)]
- # Prevent cycles (threefold repetition or perpetual chasing)
- if state_key in visited:
- return 0, []
- if depth == 0:
- # Horizon reached, no static eval -> assume draw (0)
- return 0, []
- # Generate moves and filter legal moves (those not leaving own king in check)
- moves = generate_pseudo_legal_moves(board, side)
- legal_moves = []
- for move in moves:
- fr, fc, tr, tc = move
- piece = board[fr][fc]
- captured = board[tr][tc]
- # Make the move
- board[fr][fc] = '.'
- board[tr][tc] = piece
- if not is_in_check(board, side):
- legal_moves.append(move)
- # Undo move
- board[fr][fc] = piece
- board[tr][tc] = captured
- if not legal_moves:
- # No moves: check terminal condition
- if is_in_check(board, side):
- # Checkmate: current side has no moves and is in check -> losing
- return -(MATE_SCORE - ply), []
- else:
- # Stalemate
- return 0, []
- # Order moves to improve pruning
- legal_moves.sort(key=lambda m: move_priority(board, m, side))
- visited.add(state_key)
- best_score = -INF
- best_line = []
- next_side = 'w' if side == 'b' else 'b'
- for move in legal_moves:
- fr, fc, tr, tc = move
- piece = board[fr][fc]
- captured = board[tr][tc]
- # Apply move
- board[fr][fc] = '.'
- board[tr][tc] = piece
- score, line = negamax(board, next_side, depth-1, -beta, -alpha, visited, ply+1)
- score = -score
- # Undo move
- board[fr][fc] = piece
- board[tr][tc] = captured
- if score > best_score:
- best_score = score
- best_line = [move] + line
- alpha = max(alpha, best_score)
- if alpha >= beta:
- # Alpha-beta cutoff
- break
- visited.remove(state_key)
- # Distance-to-mate scoring: ensure shorter mate is better
- cache[(state_key, depth)] = (best_score, best_line.copy())
- return best_score, best_line
- # Print board in ASCII
- def print_board(board):
- for r in range(8):
- print(" ".join(board[r]))
- # Main execution
- if __name__ == "__main__":
- # Starting FEN: K+R vs K endgame position
- start_fen = "8/1K2k3/r7/8/8/8/8/8 b - - 0 1"
- board, side = parse_fen(start_fen)
- final_score = None
- final_line = []
- # Iterative deepening search
- for depth in range(1, 51):
- score, line = negamax(board, side, depth, -INF, INF, set(), 0)
- final_score = score
- final_line = line
- # Prepare score display
- if abs(score) >= MATE_SCORE - 100:
- # If score indicates a checkmate sequence
- mate_dist = MATE_SCORE - abs(score)
- if score > 0:
- score_str = f"Mate in {mate_dist} half-moves"
- elif score < 0:
- score_str = f"-Mate in {mate_dist} half-moves"
- else:
- score_str = "Mate"
- else:
- score_str = str(score)
- # Prepare principal variation moves display
- pv_moves = []
- for (fr, fc, tr, tc) in line:
- pv_moves.append(chr(ord('a') + fc) + str(8 - fr) + chr(ord('a') + tc) + str(8 - tr))
- print(f"Depth {depth}: Score = {score_str}, PV = {' '.join(pv_moves)}")
- if abs(score) > 9000 or score == MATE_SCORE or score == -MATE_SCORE:
- # Stop when a checkmate is found
- break
- # Execute the best sequence of moves found
- print("\nExecuting best sequence:")
- print_board(board)
- print(f"Side to move: {'White' if side == 'w' else 'Black'}")
- current_side = side
- for (fr, fc, tr, tc) in final_line:
- piece = board[fr][fc]
- board[fr][fc] = '.'
- board[tr][tc] = piece
- current_side = 'w' if current_side == 'b' else 'b'
- print() # blank line between moves
- print_board(board)
- print(f"Side to move: {'White' if current_side == 'w' else 'Black'}")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement