Hi, this program has a bug:


import random
import copy

# Define the board size
BOARD_WIDTH = 7
BOARD_HEIGHT = 9

# Define the terrain layout without pieces
terrain_layout = [
    ['-', '-', 'X', 'O', 'X', '-', '-'],
    ['-', '-', '-', 'X', '-', '-', '-'],
    ['-', '-', '-', '-', '-', '-', '-'],
    ['-', 'u', 'u', '-', 'u', 'u', '-'],
    ['-', 'u', 'u', '-', 'u', 'u', '-'],
    ['-', 'u', 'u', '-', 'u', 'u', '-'],
    ['-', '-', '-', '-', '-', '-', '-'],
    ['-', '-', '-', 'x', '-', '-', '-'],
    ['-', '-', 'x', 'o', 'x', '-', '-'],
]

# Define the initial positions of the pieces
initial_positions = {
    # Player 1 pieces (lowercase)
    'r': (6, 6),
    'c': (7, 1),
    'd': (7, 5),
    'w': (6, 2),
    'p': (6, 4),
    't': (8, 0),
    'l': (8, 6),
    'e': (6, 0),

    # Player 2 pieces (uppercase)
    'R': (2, 0),
    'C': (1, 5),
    'D': (1, 1),
    'W': (2, 4),
    'P': (2, 2),
    'T': (0, 6),
    'L': (0, 0),
    'E': (2, 6),
}

# Define the pieces
pieces = {
    'r': {'rank': 1, 'owner': 1},  # Rat
    'c': {'rank': 2, 'owner': 1},  # Cat
    'd': {'rank': 3, 'owner': 1},  # Dog
    'w': {'rank': 4, 'owner': 1},  # Wolf
    'p': {'rank': 5, 'owner': 1},  # Panther
    't': {'rank': 6, 'owner': 1},  # Tiger
    'l': {'rank': 7, 'owner': 1},  # Lion
    'e': {'rank': 8, 'owner': 1},  # Elephant

    'R': {'rank': 1, 'owner': 2},
    'C': {'rank': 2, 'owner': 2},
    'D': {'rank': 3, 'owner': 2},
    'W': {'rank': 4, 'owner': 2},
    'P': {'rank': 5, 'owner': 2},
    'T': {'rank': 6, 'owner': 2},
    'L': {'rank': 7, 'owner': 2},
    'E': {'rank': 8, 'owner': 2},
}

# Value of each piece for evaluation
piece_values = {
    'r': 10,
    'c': 20,
    'd': 30,
    'w': 40,
    'p': 50,
    't': 60,
    'l': 70,
    'e': 80,
}

# Directions mapping
directions = {
    'n': (-1, 0),  # North
    's': (1, 0),   # South
    'w': (0, -1),  # West
    'e': (0, 1),   # East
}

# Initialize the board
def initialize_board():
    board = [row.copy() for row in terrain_layout]
    # Place the pieces on the board
    for piece, (y, x) in initial_positions.items():
        board[y][x] = piece
    return board

# Get the cell type at a position
def get_cell_type(y, x):
    if 0 <= y < BOARD_HEIGHT and 0 <= x < BOARD_WIDTH:
        cell = terrain_layout[y][x]
        return cell
    else:
        return None  # Outside the board

# Display the board
def display_board(board):
    print("Current Board:")
    for y, row in enumerate(board):
        line = ''
        for x, cell in enumerate(row):
            piece = board[y][x]
            line += piece + ' '
        print(line)
    print()

# Find the position of a specific piece
def find_piece(board, piece_char, owner):
    for y in range(BOARD_HEIGHT):
        for x in range(BOARD_WIDTH):
            cell = board[y][x]
            if cell == piece_char and pieces[cell]['owner'] == owner:
                return y, x
    return None

# Check if the move is legal
def is_legal_move(board, y, x, dy, dx, owner):
    ny, nx = y + dy, x + dx
    if not (0 <= ny < BOARD_HEIGHT and 0 <= nx < BOARD_WIDTH):
        return False  # Move is off the board

    src_piece = board[y][x]
    dest_piece = board[ny][nx]
    src_info = pieces.get(src_piece)
    dest_info = pieces.get(dest_piece) if dest_piece in pieces else None

    src_cell_type = get_cell_type(y, x)
    dest_cell_type = get_cell_type(ny, nx)

    # Cannot move onto own pieces
    if dest_info and dest_info['owner'] == owner:
        return False

    # Den Rules
    if dest_cell_type == 'o' and owner == 1:
        return False  # Player cannot enter own den
    if dest_cell_type == 'O' and owner == 2:
        return False  # Player cannot enter own den

    # River Rules
    if dest_cell_type == 'u':
        if src_piece.lower() != 'r':
            return False  # Only rat can enter river
    if src_cell_type == 'u' and dest_cell_type != 'u':
        if src_piece.lower() != 'r':
            return False  # Only rat can move from river to land
    if src_cell_type != 'u' and dest_cell_type == 'u':
        if src_piece.lower() != 'r':
            return False  # Only rat can move from land to river

    # Movement: One square orthogonally
    if abs(dy) + abs(dx) != 1:
        # Jumping for Lion and Tiger
        if src_piece.lower() in ['l', 't']:
            if is_valid_jump(board, y, x, dy, dx, owner):
                return True
            else:
                return False
        else:
            return False  # Can only move one square orthogonally

    # Capture Rules
    if dest_info:
        return can_capture(board, y, x, ny, nx)
    else:
        return True  # Empty square is always legal to move onto

# Check if Lion/Tiger can perform a valid jump
def is_valid_jump(board, y, x, dy, dx, owner):
    src_piece = board[y][x]
    if src_piece.lower() not in ['l', 't']:
        return False

    dir_y, dir_x = dy, dx

    if dir_y != 0 and dir_x == 0:
        step_y = dir_y // abs(dir_y)
        ny = y + step_y
        # Check for river
        if get_cell_type(ny, x) != 'u':
            return False  # No river to jump over
        # Check path over the river
        while 0 <= ny < BOARD_HEIGHT:
            cell_type = get_cell_type(ny, x)
            if cell_type != 'u':
                break  # Reached land
            if board[ny][x].lower() == 'r':
                return False  # Rat blocks the jump
            ny += step_y
        if not (0 <= ny < BOARD_HEIGHT):
            return False  # Can't jump off the board
        dest_piece = board[ny][x]
        dest_info = pieces.get(dest_piece)
        dest_cell_type = get_cell_type(ny, x)

        # Can't land on own piece or own den
        if dest_info and dest_info['owner'] == owner:
            return False
        if (dest_cell_type == 'o' and owner == 1) or (dest_cell_type == 'O' and owner == 2):
            return False  # Cannot enter own den

        return True  # Valid jump

    elif dir_x != 0 and dir_y == 0:
        step_x = dir_x // abs(dir_x)
        nx = x + step_x
        # Check for river
        if get_cell_type(y, nx) != 'u':
            return False  # No river to jump over
        # Check path over the river
        while 0 <= nx < BOARD_WIDTH:
            cell_type = get_cell_type(y, nx)
            if cell_type != 'u':
                break  # Reached land
            if board[y][nx].lower() == 'r':
                return False  # Rat blocks the jump
            nx += step_x
        if not (0 <= nx < BOARD_WIDTH):
            return False  # Can't jump off the board
        dest_piece = board[y][nx]
        dest_info = pieces.get(dest_piece)
        dest_cell_type = get_cell_type(y, nx)

        # Can't land on own piece or own den
        if dest_info and dest_info['owner'] == owner:
            return False
        if (dest_cell_type == 'o' and owner == 1) or (dest_cell_type == 'O' and owner == 2):
            return False  # Cannot enter own den

        return True  # Valid jump

    else:
        return False  # Can't jump diagonally

# Determine if a piece can capture another
def can_capture(board, y, x, ny, nx):
    src_piece = board[y][x]
    dest_piece = board[ny][nx]
    src_info = pieces[src_piece]
    dest_info = pieces[dest_piece]

    src_rank = src_info['rank']
    dest_rank = dest_info['rank']
    src_owner = src_info['owner']
    dest_owner = dest_info['owner']

    # Cannot capture own pieces
    if src_owner == dest_owner:
        return False

    src_cell_type = get_cell_type(y, x)
    dest_cell_type = get_cell_type(ny, nx)

    # Adjust src_rank if src_piece is in opponent's trap
    if src_cell_type in ['x', 'X']:
        # Corrected trap ownership mapping
        trap_owner = 1 if src_cell_type == 'X' else 2
        if src_owner != trap_owner:
            src_rank = 0

    # Adjust dest_rank if dest_piece is in your trap
    if dest_cell_type in ['x', 'X']:
        # Corrected trap ownership mapping
        trap_owner = 1 if dest_cell_type == 'X' else 2
        if dest_owner != trap_owner:
            dest_rank = 0

    # Rat and Elephant interaction
    if src_piece.lower() == 'r' and dest_piece.lower() == 'e':
        # Rat cannot capture Elephant if one is in water and one is on land
        if src_cell_type == dest_cell_type:
            return True  # Both on same terrain
        else:
            return False
    if src_piece.lower() == 'e' and dest_piece.lower() == 'r':
        return False  # Elephant cannot capture Rat

    # Rat in water cannot capture other animals on land or vice versa
    if src_cell_type != dest_cell_type:
        return False
    if src_cell_type == 'u' and dest_cell_type == 'u':
        if src_piece.lower() != 'r' or dest_piece.lower() != 'r':
            return False  # Only rats can be captured in water

    # General case
    if src_rank >= dest_rank:
        return True
    else:
        return False

# Make a move on the board
def make_move(board, y, x, ny, nx):
    new_board = [row.copy() for row in board]
    new_board[ny][nx] = new_board[y][x]
    new_board[y][x] = terrain_layout[y][x]
    return new_board

# Generate all legal moves for a player
def generate_legal_moves(board, owner):
    legal_moves = []
    for y in range(BOARD_HEIGHT):
        for x in range(BOARD_WIDTH):
            cell = board[y][x]
            if cell in pieces and pieces[cell]['owner'] == owner:
                for dir_key, (dy, dx) in directions.items():
                    ny = y + dy
                    nx = x + dx
                    if not (0 <= ny < BOARD_HEIGHT and 0 <= nx < BOARD_WIDTH):
                        continue  # Skip moves off the board
                    # Handle jumping
                    if cell.lower() in ['l', 't']:
                        if is_valid_jump(board, y, x, dy, dx, owner):
                            # Calculate landing position after jump
                            if dy != 0:
                                step_y = dy // abs(dy)
                                ny = y
                                while True:
                                    ny += step_y
                                    if get_cell_type(ny, x) != 'u':
                                        break
                            elif dx != 0:
                                step_x = dx // abs(dx)
                                nx = x
                                while True:
                                    nx += step_x
                                    if get_cell_type(y, nx) != 'u':
                                        break
                        else:
                            if not is_legal_move(board, y, x, dy, dx, owner):
                                continue
                    if is_legal_move(board, y, x, ny - y, nx - x, owner):
                        legal_moves.append(((y, x), (ny, nx)))
    return legal_moves

# Evaluation function for the board state
def evaluate_board(board, owner):
    # Check for victory
    winner = check_victory_silent(board)
    if winner == owner:
        return float('inf')  # Winning state
    elif winner is not None and winner != owner:
        return float('-inf')  # Losing state

    score = 0

    for y in range(BOARD_HEIGHT):
        for x in range(BOARD_WIDTH):
            cell = board[y][x]
            if cell in pieces:
                piece_owner = pieces[cell]['owner']
                value = piece_values[cell.lower()]
                if piece_owner == owner:
                    score += value
                else:
                    score -= value

    return score

# Alpha-beta pruning algorithm
def alpha_beta(board, depth, alpha, beta, maximizing_player, owner):
    winner = check_victory_silent(board)
    if depth == 0 or winner is not None:
        return evaluate_board(board, owner)

    if maximizing_player:
        max_eval = float('-inf')
        moves = generate_legal_moves(board, owner)
        if not moves:
            # No legal moves, opponent wins
            return float('-inf')
        for move in moves:
            new_board = make_move(board, move[0][0], move[0][1], move[1][0], move[1][1])
            eval = alpha_beta(new_board, depth - 1, alpha, beta, False, owner)
            max_eval = max(max_eval, eval)
            alpha = max(alpha, eval)
            if beta <= alpha:
                break  # Beta cutoff
        return max_eval
    else:
        min_eval = float('inf')
        opponent = 1 if owner == 2 else 2
        moves = generate_legal_moves(board, opponent)
        if not moves:
            # No legal moves, current player wins
            return float('inf')
        for move in moves:
            new_board = make_move(board, move[0][0], move[0][1], move[1][0], move[1][1])
            eval = alpha_beta(new_board, depth - 1, alpha, beta, True, owner)
            min_eval = min(min_eval, eval)
            beta = min(beta, eval)
            if beta <= alpha:
                break  # Alpha cutoff
        return min_eval

# Bot makes a move using Alpha-Beta pruning
def bot_move(board):
    owner = 2  # Bot is player 2
    best_value = float('-inf')
    beta = float('inf')
    best_move = None

    moves = generate_legal_moves(board, owner)
    if not moves:
        print("Bot has no legal moves!")
        return board  # Return the unchanged board

    for move in moves:
        new_board = make_move(board, move[0][0], move[0][1], move[1][0], move[1][1])
        move_value = alpha_beta(new_board, 3, best_value, beta, False, owner)  # Depth is 3 for total of 4 plies
        if move_value > best_value:
            best_value = move_value
            best_move = move

    if best_move:
        y, x = best_move[0]
        ny, nx = best_move[1]
        piece = board[y][x]
        board = make_move(board, y, x, ny, nx)
        print(f"Bot moved {piece} from ({y}, {x}) to ({ny}, {nx})")
    else:
        print("Bot failed to find a move!")
    return board  # Return the updated board

# Simplified victory check without printing
def check_victory_silent(board):
    # Check if any player has entered opponent's den
    for y in range(BOARD_HEIGHT):
        for x in range(BOARD_WIDTH):
            cell = board[y][x]
            cell_type = get_cell_type(y, x)
            if cell in pieces:
                owner = pieces[cell]['owner']
                if cell_type == 'o' and owner == 2:
                    return owner  # Player 2 wins
                if cell_type == 'O' and owner == 1:
                    return owner  # Player 1 wins
    # Check if a player has no pieces left
    player1_pieces = any(pieces[cell]['owner'] == 1 for row in board for cell in row if cell in pieces)
    player2_pieces = any(pieces[cell]['owner'] == 2 for row in board for cell in row if cell in pieces)
    if not player1_pieces:
        return 2  # Player 2 wins
    if not player2_pieces:
        return 1  # Player 1 wins
    return None  # No winner yet

# Original victory check with print statements
def check_victory(board):
    winner = check_victory_silent(board)
    if winner == 1:
        print("Player 1 wins!")
    elif winner == 2:
        print("Player 2 wins!")
    return winner

# Main game loop
def main():
    board = initialize_board()
    display_board(board)

    while True:
        # Bot's turn
        board = bot_move(board)
        display_board(board)

        # Check for victory condition
        winner = check_victory(board)
        if winner:
            print(f"Game over! Player {winner} wins!")
            break

        # Player's turn
        while True:
            input_str = input("Enter your move ([piece][direction], e.g., rn): ").strip().lower()
            if len(input_str) != 2:
                print("Invalid input format. Please enter in format [piece][direction].")
                continue
            piece_char, dir_char = input_str[0], input_str[1]
            if piece_char not in 'rcdwptle':
                print("Invalid piece. Choose from r, c, d, w, p, t, l, e.")
                continue
            if dir_char not in 'nesw':
                print("Invalid direction. Choose from n, e, s, w.")
                continue
            owner = 1  # Human player is owner 1
            position = find_piece(board, piece_char, owner)
            if not position:
                print(f"You do not have a '{piece_char}' on the board.")
                continue
            y, x = position
            dy, dx = directions[dir_char]
            ny = y + dy
            nx = x + dx
            if not (0 <= ny < BOARD_HEIGHT and 0 <= nx < BOARD_WIDTH):
                print("Move is off the board. Try again.")
                continue
            # Handle jumping
            if piece_char in ['l', 't']:
                if is_valid_jump(board, y, x, dy, dx, owner):
                    # Calculate landing position after jump
                    if dy != 0:
                        step_y = dy // abs(dy)
                        ny = y
                        while True:
                            ny += step_y
                            if get_cell_type(ny, x) != 'u':
                                break
                    elif dx != 0:
                        step_x = dx // abs(dx)
                        nx = x
                        while True:
                            nx += step_x
                            if get_cell_type(y, nx) != 'u':
                                break
                else:
                    # Not a valid jump, check for normal move
                    ny = y + dy
                    nx = x + dx
            if is_legal_move(board, y, x, ny - y, nx - x, owner):
                board = make_move(board, y, x, ny, nx)
                print(f"You moved {board[ny][nx]} from ({y}, {x}) to ({ny}, {nx})")
                break
            else:
                print("Illegal move. Try again.")
        display_board(board)

        # Check for victory condition
        winner = check_victory(board)
        if winner:
            print(f"Game over! Player {winner} wins!")
            break



if __name__ == "__main__":
    main()



.. it disallows me to capture a trapped piece.
The engine seems to (wrongly) think, that a piece is safe, if it's in their own home trap.
This is wrong, please correct it.