Загрузка данных


import copy
from typing import List, Tuple, Optional, Dict, Any

BOARD_SIZE = 8
WIN_SCORE = 100000

# ==============================================================================
# 1. ОЦЕНОЧНАЯ ФУНКЦИЯ ПОЗИЦИИ (STATIC EVALUATION)
# ==============================================================================
def evaluate_board(board: List[List[Optional[dict]]]) -> int:
    score = 0
    for r in range(BOARD_SIZE):
        for c in range(BOARD_SIZE):
            piece = board[r][c]
            if not piece:
                continue

            val = 350 if piece['type'] == 'king' else 100

            if piece['type'] == 'man':
                advancement = (7 - r) if piece['player'] == 'white' else r
                val += advancement * 7

            if 2 <= r <= 5 and 2 <= c <= 5:
                val += 15

            if piece['player'] == 'white':
                score += val
            else:
                score -= val

    return score


# ==============================================================================
# 2. ГЕНЕРАЦИЯ ХОДОВ И ПРАВИЛО МАКСИМАЛЬНОГО ВЗЯТИЯ
# ==============================================================================
def get_valid_moves(board: List[List[Optional[dict]]], player: str) -> List[dict]:
    captures = []
    quiet_moves = []

    for r in range(BOARD_SIZE):
        for c in range(BOARD_SIZE):
            piece = board[r][c]
            if not piece or piece['player'] != player:
                continue

            piece_captures = _find_captures_for_piece(board, r, c, piece)
            captures.extend(piece_captures)

            if not captures:
                piece_quiets = _find_quiet_moves_for_piece(board, r, c, piece)
                quiet_moves.extend(piece_quiets)

    if captures:
        max_captured_count = max(len(m['captured']) for m in captures)
        return [m for m in captures if len(m['captured']) == max_captured_count]

    return quiet_moves


def _find_quiet_moves_for_piece(board, r: int, c: int, piece: dict) -> List[dict]:
    moves = []
    dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] if piece['type'] == 'king' else (
        [(-1, 0), (0, -1), (0, 1)] if piece['player'] == 'white' else [(1, 0), (0, -1), (0, 1)]
    )

    for dr, dc in dirs:
        if piece['type'] == 'king':
            nr, nc = r + dr, c + dc
            while 0 <= nr < BOARD_SIZE and 0 <= nc < BOARD_SIZE and board[nr][nc] is None:
                moves.append({
                    'from': (r, c),
                    'to': (nr, nc),
                    'captured': [],
                    'is_capture': False
                })
                nr += dr
                nc += dc
        else:
            nr, nc = r + dr, c + dc
            if 0 <= nr < BOARD_SIZE and 0 <= nc < BOARD_SIZE and board[nr][nc] is None:
                moves.append({
                    'from': (r, c),
                    'to': (nr, nc),
                    'captured': [],
                    'is_capture': False
                })

    return moves


def _find_captures_for_piece(board, r: int, c: int, piece: dict, visited_captured=None) -> List[dict]:
    if visited_captured is None:
        visited_captured = []

    captures = []
    opponent = 'black' if piece['player'] == 'white' else 'white'
    dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]

    if piece['type'] == 'king':
        for dr, dc in dirs:
            nr, nc = r + dr, c + dc
            found_enemy = None
            while 0 <= nr < BOARD_SIZE and 0 <= nc < BOARD_SIZE:
                cell = board[nr][nc]
                if cell:
                    if cell['player'] == opponent and (nr, nc) not in visited_captured:
                        if found_enemy is None:
                            found_enemy = (nr, nc)
                        else:
                            break
                    else:
                        break
                else:
                    if found_enemy:
                        new_captured = visited_captured + [found_enemy]
                        temp_board = copy.deepcopy(board)
                        temp_board[nr][nc] = temp_board[r][c]
                        temp_board[r][c] = None

                        further = _find_captures_for_piece(temp_board, nr, nc, piece, new_captured)
                        if further:
                            captures.extend(further)
                        else:
                            captures.append({
                                'from': (r, c),
                                'to': (nr, nc),
                                'captured': new_captured,
                                'is_capture': True
                            })
                nr += dr
                nc += dc
    else:
        for dr, dc in dirs:
            mid_r, mid_c = r + dr, c + dc
            land_r, land_c = r + 2 * dr, c + 2 * dc

            if (0 <= land_r < BOARD_SIZE and 0 <= land_c < BOARD_SIZE and
                board[land_r][land_c] is None and
                board[mid_r][mid_c] and board[mid_r][mid_c]['player'] == opponent and
                (mid_r, mid_c) not in visited_captured):

                new_captured = visited_captured + [(mid_r, mid_c)]
                temp_board = copy.deepcopy(board)
                promoted = (land_r == 0 if piece['player'] == 'white' else land_r == 7)
                p_copy = copy.deepcopy(piece)
                if promoted:
                    p_copy['type'] = 'king'

                temp_board[land_r][land_c] = p_copy
                temp_board[r][c] = None

                further = _find_captures_for_piece(temp_board, land_r, land_c, p_copy, new_captured)
                if further:
                    captures.extend(further)
                else:
                    captures.append({
                        'from': (r, c),
                        'to': (land_r, land_c),
                        'captured': new_captured,
                        'is_capture': True
                    })

    return captures


def make_move(board: List[List[Optional[dict]]], move: dict) -> List[List[Optional[dict]]]:
    new_board = copy.deepcopy(board)
    fr, fc = move['from']
    tr, tc = move['to']

    piece = new_board[fr][fc]
    new_board[fr][fc] = None
    new_board[tr][tc] = piece

    if piece['type'] == 'man':
        if (piece['player'] == 'white' and tr == 0) or (piece['player'] == 'black' and tr == 7):
            piece['type'] = 'king'

    for cr, cc in move['captured']:
        new_board[cr][cc] = None

    return new_board


# ==============================================================================
# 3. АЛГОРИТМ НЕГАМАКС И QUIESCENCE SEARCH
# ==============================================================================
def quiescence(board, player: str, alpha: int, beta: int) -> int:
    static_eval = evaluate_board(board)
    current_eval = static_eval if player == 'white' else -static_eval

    if current_eval >= beta:
        return beta
    if current_eval > alpha:
        alpha = current_eval

    moves = get_valid_moves(board, player)
    captures = [m for m in moves if m['is_capture']]

    if not captures:
        return current_eval

    for move in captures:
        next_board = make_move(board, move)
        next_player = 'black' if player == 'white' else 'white'
        val = -quiescence(next_board, next_player, -beta, -alpha)

        if val >= beta:
            return beta
        if val > alpha:
            alpha = val

    return alpha


def negamax(board, depth: int, alpha: int, beta: int, player: str) -> Tuple[int, Optional[dict]]:
    moves = get_valid_moves(board, player)

    if not moves:
        return -WIN_SCORE + (10 - depth), None

    if depth <= 0:
        return quiescence(board, player, alpha, beta), None

    best_move = moves[0]
    best_val = -float('inf')

    moves.sort(key=lambda m: len(m['captured']), reverse=True)

    for move in moves:
        next_board = make_move(board, move)
        next_player = 'black' if player == 'white' else 'white'

        val, _ = negamax(next_board, depth - 1, -beta, -alpha, next_player)
        val = -val

        if val > best_val:
            best_val = val
            best_move = move

        if val > alpha:
            alpha = val

        if alpha >= beta:
            break

    return best_val, best_move


# ==============================================================================
# 4. АДАПТЕР С ОТЛАДКОЙ
# ==============================================================================
def matrix_to_internal_board(matrix: List[List[Any]]) -> List[List[Optional[dict]]]:
    internal_board = [[None for _ in range(8)] for _ in range(8)]
    
    if not matrix or len(matrix) != 8:
        return internal_board

    for r in range(8):
        for c in range(8):
            val = matrix[r][c]
            if val is None or val == 0:
                continue
            
            s_val = str(val).strip().rstrip('.0')
            
            if s_val in ('1', '2', 'black'):
                internal_board[r][c] = {'type': 'man', 'player': 'black'}
            elif s_val in ('3', '4', 'white'):
                internal_board[r][c] = {'type': 'man', 'player': 'white'}
            elif s_val in ('5', '6', 'b_king', 'black_king'):
                internal_board[r][c] = {'type': 'king', 'player': 'black'}
            elif s_val in ('7', '8', 'w_king', 'white_king'):
                internal_board[r][c] = {'type': 'king', 'player': 'white'}
                
    return internal_board


def get_best_move_from_matrix(matrix: List[List[Any]], side_to_move: int = 3, depth: int = 5):
    print("--- DEBUG ENGINE ---")
    print("Side to move:", side_to_move)
    
    board = matrix_to_internal_board(matrix)
    
    pieces_count = sum(1 for r in range(8) for c in range(8) if board[r][c] is not None)
    print("Recognized pieces on board:", pieces_count)

    player = 'white' if side_to_move == 3 else 'black'
    score, move = negamax(board, depth, -float('inf'), float('inf'), player)

    print("Calculated move:", move)
    print("--------------------")

    if not move:
        return None

    return {
        "score": score,
        "from": move['from'],
        "to": move['to'],
        "captured": move['captured'],
        "raw_move": move
    }