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] in ['r', '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] in ['r', '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)

    # Traps
    if dest_cell_type in ['x', 'X']:
        # Determine the owner of the trap
        trap_owner = 1 if dest_cell_type == 'x' else 2
        if dest_owner != trap_owner:
            # Opponent's piece is in your trap
            dest_rank = 0  # Opponent's piece in your trap has 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']
                piece_rank = pieces[cell]['rank']
                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)
        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)
        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)  # Updated here to capture the returned 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()


This program has a bug:
in classic/standard Dou Shou Qi (DSQ), when a piece enters its own (home) trap, this doesn't give it any bonues, so any stronger or equal opponent piece could capture it
only if a piece has entered an opponent trap, from that point on it has been weakened to zero strength (permanently, so it's wise to enter the opponent den later to claim the win asap)