#!/usr/bin/env python3
import sys, time

INF = 10**9
MATE = 10**7
MAX_QPLY = 8
MAX_DEPTH = 64
TT_LIMIT = 190000
CHAOS_CANDIDATES = 8

FILES = "abcdefgh"
PROMOS = "qrbn"

VAL = {
    'P': 100, 'N': 320, 'B': 330, 'R': 500, 'Q': 900, 'K': 0,
    'p': 100, 'n': 320, 'b': 330, 'r': 500, 'q': 900, 'k': 0,
    '.': 0
}
PHASE_VAL = {'N': 1, 'B': 1, 'R': 2, 'Q': 4, 'P': 0, 'K': 0}

KNIGHT_DIRS = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)]
KING_DIRS = [(1, 1), (1, 0), (1, -1), (0, 1), (0, -1), (-1, 1), (-1, 0), (-1, -1)]
BISHOP_DIRS = [(1, 1), (1, -1), (-1, 1), (-1, -1)]
ROOK_DIRS = [(1, 0), (-1, 0), (0, 1), (0, -1)]
QUEEN_DIRS = BISHOP_DIRS + ROOK_DIRS

# Move tuple: (from_square, to_square, promotion_char_or_empty, flag)
# flag: 0 normal, 1 en-passant, 2 castle
TT = {}
KILLERS = [[None, None] for _ in range(128)]
HISTORY = {}
DEADLINE = 0.0
NODES = 0
ROOT_BEST = None

BOOK = {
    ("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", "w", "KQkq", "-"): ("e2e4", "d2d4", "g1f3", "c2c4"),
    ("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR", "b", "KQkq", "e3"): ("e7e5", "c7c5", "e7e6"),
    ("rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR", "b", "KQkq", "d3"): ("d7d5", "g8f6", "e7e6"),
    ("rnbqkbnr/pppppppp/8/8/2P5/8/PP1PPPPP/RNBQKBNR", "b", "KQkq", "c3"): ("g8f6", "e7e5", "c7c5"),
    ("rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R", "b", "KQkq", "-"): ("d7d5", "g8f6", "e7e6"),
}

def opening_book_move(fen, legal):
    parts = fen.strip().split()
    if len(parts) < 4:
        return None
    key = (parts[0], parts[1], parts[2], parts[3])
    choices = BOOK.get(key)
    if not choices:
        return None
    legal_by_uci = {move_uci(m): m for m in legal}
    for u in choices:
        if u in legal_by_uci:
            return legal_by_uci[u]
    return None


def sq_name(sq):
    return FILES[sq & 7] + str((sq >> 3) + 1)


def move_uci(m):
    if m is None:
        return "0000"
    return sq_name(m[0]) + sq_name(m[1]) + (m[2] if m[2] else "")


def inside(f, r):
    return 0 <= f < 8 and 0 <= r < 8


def mirror(sq):
    return (sq & 7) + ((7 - (sq >> 3)) << 3)


def build_tables():
    pst = {p: [0] * 64 for p in "PNBRQK"}
    k_end = [0] * 64
    for sq in range(64):
        f, r = sq & 7, sq >> 3
        cd = abs(f - 3.5) + abs(r - 3.5)
        file_center = 3.5 - abs(f - 3.5)
        # Relative-rank tables for White; Black uses mirrored square.
        pawn_rank = [0, 4, 10, 22, 38, 62, 95, 0][r]
        pst['P'][sq] = int(pawn_rank + file_center * 2 - (8 if f in (0, 7) and r < 4 else 0))
        pst['N'][sq] = int(32 - cd * 12)
        pst['B'][sq] = int(24 - cd * 7 + (6 if (f + r) in (6, 7, 8) else 0))
        pst['R'][sq] = int((12 if r == 6 else 0) + (5 if r >= 3 else 0) - abs(f - 3.5) * 2)
        pst['Q'][sq] = int(12 - cd * 3)
        # Middlegame king: prefer the back rank and corners/near-castled files.
        pst['K'][sq] = int(-28 * r - 10 * (3.5 - abs(f - 3.5)))
        # Endgame king: centralize.
        k_end[sq] = int(42 - cd * 12)
    return pst, k_end

PST, KING_END = build_tables()

FILES_BB = []
for f in range(8):
    bb = 0
    for r in range(8):
        bb |= 1 << (r * 8 + f)
    FILES_BB.append(bb)

ADJ_FILE_BB = []
for f in range(8):
    bb = 0
    if f > 0:
        bb |= FILES_BB[f - 1]
    if f < 7:
        bb |= FILES_BB[f + 1]
    ADJ_FILE_BB.append(bb)

PASSED_W = [0] * 64
PASSED_B = [0] * 64
for sq in range(64):
    f, r = sq & 7, sq >> 3
    mask_w = 0
    mask_b = 0
    for ff in (f - 1, f, f + 1):
        if 0 <= ff < 8:
            for rr in range(r + 1, 8):
                mask_w |= 1 << (rr * 8 + ff)
            for rr in range(0, r):
                mask_b |= 1 << (rr * 8 + ff)
    PASSED_W[sq] = mask_w
    PASSED_B[sq] = mask_b

PASSED_BONUS = [0, 6, 14, 28, 52, 90, 150, 0]


class Position:
    __slots__ = ("b", "side", "castling", "ep", "halfmove")

    def __init__(self, b, side, castling, ep, halfmove=0):
        self.b = b
        self.side = side
        self.castling = castling
        self.ep = ep
        self.halfmove = halfmove

    def key(self):
        return ''.join(self.b) + self.side + self.castling + str(self.ep) + str(self.halfmove >= 100)

    def make(self, m):
        fr, to, promo, flag = m
        b = self.b[:]
        piece = b[fr]
        captured = b[to]
        b[fr] = '.'

        if flag == 1:  # en-passant
            cap_sq = to - 8 if piece == 'P' else to + 8
            captured = b[cap_sq]
            b[cap_sq] = '.'
        elif flag == 2:  # castle
            if to == 6:      # white O-O
                b[5], b[7] = b[7], '.'
            elif to == 2:    # white O-O-O
                b[3], b[0] = b[0], '.'
            elif to == 62:   # black O-O
                b[61], b[63] = b[63], '.'
            elif to == 58:   # black O-O-O
                b[59], b[56] = b[56], '.'

        if promo:
            b[to] = promo.upper() if piece.isupper() else promo
        else:
            b[to] = piece

        cast = self.castling
        if piece == 'K':
            cast = cast.replace('K', '').replace('Q', '')
        elif piece == 'k':
            cast = cast.replace('k', '').replace('q', '')
        if fr == 0 or to == 0:
            cast = cast.replace('Q', '')
        if fr == 7 or to == 7:
            cast = cast.replace('K', '')
        if fr == 56 or to == 56:
            cast = cast.replace('q', '')
        if fr == 63 or to == 63:
            cast = cast.replace('k', '')
        cast = ''.join(ch for ch in "KQkq" if ch in cast)

        ep = -1
        if piece == 'P' and to - fr == 16:
            ep = fr + 8
        elif piece == 'p' and fr - to == 16:
            ep = fr - 8

        half = self.halfmove + 1
        if piece.upper() == 'P' or captured != '.':
            half = 0

        return Position(b, 'b' if self.side == 'w' else 'w', cast, ep, half)


def parse_fen(fen):
    parts = fen.strip().split()
    board_part = parts[0]
    side = parts[1] if len(parts) > 1 else 'w'
    castling = '' if len(parts) < 3 or parts[2] == '-' else ''.join(ch for ch in "KQkq" if ch in parts[2])
    ep = -1
    if len(parts) > 3 and parts[3] != '-':
        f = FILES.find(parts[3][0])
        r = int(parts[3][1]) - 1
        ep = r * 8 + f if 0 <= f < 8 and 0 <= r < 8 else -1
    halfmove = int(parts[4]) if len(parts) > 4 and parts[4].isdigit() else 0

    b = ['.'] * 64
    ranks = board_part.split('/')
    for fen_r, row in enumerate(ranks):
        r = 7 - fen_r
        f = 0
        for ch in row:
            if ch.isdigit():
                f += int(ch)
            else:
                if 0 <= f < 8 and 0 <= r < 8:
                    b[r * 8 + f] = ch
                f += 1
    return Position(b, side, castling, ep, halfmove)


def king_square(pos, side):
    k = 'K' if side == 'w' else 'k'
    try:
        return pos.b.index(k)
    except ValueError:
        return -1


def attacked(pos, sq, by_side):
    b = pos.b
    f, r = sq & 7, sq >> 3

    if by_side == 'w':
        for df in (-1, 1):
            ff, rr = f + df, r - 1
            if inside(ff, rr) and b[rr * 8 + ff] == 'P':
                return True
    else:
        for df in (-1, 1):
            ff, rr = f + df, r + 1
            if inside(ff, rr) and b[rr * 8 + ff] == 'p':
                return True

    n = 'N' if by_side == 'w' else 'n'
    for df, dr in KNIGHT_DIRS:
        ff, rr = f + df, r + dr
        if inside(ff, rr) and b[rr * 8 + ff] == n:
            return True

    kb = 'K' if by_side == 'w' else 'k'
    for df, dr in KING_DIRS:
        ff, rr = f + df, r + dr
        if inside(ff, rr) and b[rr * 8 + ff] == kb:
            return True

    bishops = ('B', 'Q') if by_side == 'w' else ('b', 'q')
    rooks = ('R', 'Q') if by_side == 'w' else ('r', 'q')
    for df, dr in BISHOP_DIRS:
        ff, rr = f + df, r + dr
        while inside(ff, rr):
            p = b[rr * 8 + ff]
            if p != '.':
                if p in bishops:
                    return True
                break
            ff += df; rr += dr
    for df, dr in ROOK_DIRS:
        ff, rr = f + df, r + dr
        while inside(ff, rr):
            p = b[rr * 8 + ff]
            if p != '.':
                if p in rooks:
                    return True
                break
            ff += df; rr += dr
    return False


def in_check(pos, side):
    ks = king_square(pos, side)
    return ks >= 0 and attacked(pos, ks, 'b' if side == 'w' else 'w')


def add_promos(moves, fr, to, capture_flag=0):
    for pr in PROMOS:
        moves.append((fr, to, pr, capture_flag))


def pseudo_moves(pos, tactical=False):
    b, side, ep = pos.b, pos.side, pos.ep
    white = side == 'w'
    own_upper = white
    moves = []

    for sq, p in enumerate(b):
        if p == '.' or p.isupper() != own_upper:
            continue
        f, r = sq & 7, sq >> 3
        up = p.upper()

        if up == 'P':
            if white:
                one = sq + 8
                if not tactical and r < 7 and b[one] == '.':
                    if r == 6:
                        add_promos(moves, sq, one)
                    else:
                        moves.append((sq, one, '', 0))
                        two = sq + 16
                        if r == 1 and b[two] == '.':
                            moves.append((sq, two, '', 0))
                elif tactical and r == 6 and b[one] == '.':
                    add_promos(moves, sq, one)
                for df in (-1, 1):
                    ff = f + df
                    if 0 <= ff < 8 and r < 7:
                        to = sq + 8 + df
                        target = b[to]
                        if target != '.' and target.islower():
                            if r == 6:
                                add_promos(moves, sq, to)
                            else:
                                moves.append((sq, to, '', 0))
                        elif to == ep:
                            moves.append((sq, to, '', 1))
            else:
                one = sq - 8
                if not tactical and r > 0 and b[one] == '.':
                    if r == 1:
                        add_promos(moves, sq, one)
                    else:
                        moves.append((sq, one, '', 0))
                        two = sq - 16
                        if r == 6 and b[two] == '.':
                            moves.append((sq, two, '', 0))
                elif tactical and r == 1 and b[one] == '.':
                    add_promos(moves, sq, one)
                for df in (-1, 1):
                    ff = f + df
                    if 0 <= ff < 8 and r > 0:
                        to = sq - 8 + df
                        target = b[to]
                        if target != '.' and target.isupper():
                            if r == 1:
                                add_promos(moves, sq, to)
                            else:
                                moves.append((sq, to, '', 0))
                        elif to == ep:
                            moves.append((sq, to, '', 1))

        elif up == 'N':
            for df, dr in KNIGHT_DIRS:
                ff, rr = f + df, r + dr
                if not inside(ff, rr):
                    continue
                to = rr * 8 + ff
                t = b[to]
                if t == '.':
                    if not tactical:
                        moves.append((sq, to, '', 0))
                elif t.isupper() != own_upper:
                    moves.append((sq, to, '', 0))

        elif up in ('B', 'R', 'Q'):
            dirs = BISHOP_DIRS if up == 'B' else ROOK_DIRS if up == 'R' else QUEEN_DIRS
            for df, dr in dirs:
                ff, rr = f + df, r + dr
                while inside(ff, rr):
                    to = rr * 8 + ff
                    t = b[to]
                    if t == '.':
                        if not tactical:
                            moves.append((sq, to, '', 0))
                    else:
                        if t.isupper() != own_upper:
                            moves.append((sq, to, '', 0))
                        break
                    ff += df; rr += dr

        elif up == 'K':
            for df, dr in KING_DIRS:
                ff, rr = f + df, r + dr
                if not inside(ff, rr):
                    continue
                to = rr * 8 + ff
                t = b[to]
                if t == '.':
                    if not tactical:
                        moves.append((sq, to, '', 0))
                elif t.isupper() != own_upper:
                    moves.append((sq, to, '', 0))
            if not tactical:
                if white and sq == 4 and p == 'K' and not in_check(pos, 'w'):
                    if 'K' in pos.castling and b[5] == b[6] == '.' and b[7] == 'R':
                        if not attacked(pos, 5, 'b') and not attacked(pos, 6, 'b'):
                            moves.append((4, 6, '', 2))
                    if 'Q' in pos.castling and b[3] == b[2] == b[1] == '.' and b[0] == 'R':
                        if not attacked(pos, 3, 'b') and not attacked(pos, 2, 'b'):
                            moves.append((4, 2, '', 2))
                elif (not white) and sq == 60 and p == 'k' and not in_check(pos, 'b'):
                    if 'k' in pos.castling and b[61] == b[62] == '.' and b[63] == 'r':
                        if not attacked(pos, 61, 'w') and not attacked(pos, 62, 'w'):
                            moves.append((60, 62, '', 2))
                    if 'q' in pos.castling and b[59] == b[58] == b[57] == '.' and b[56] == 'r':
                        if not attacked(pos, 59, 'w') and not attacked(pos, 58, 'w'):
                            moves.append((60, 58, '', 2))
    return moves


def legal_moves(pos, tactical=False):
    side = pos.side
    out = []
    for m in pseudo_moves(pos, tactical):
        if not in_check(pos.make(m), side):
            out.append(m)
    return out


def capture_value(pos, m):
    fr, to, promo, flag = m
    if flag == 1:
        return 100
    return VAL[pos.b[to]]


def is_capture(pos, m):
    return m[3] == 1 or pos.b[m[1]] != '.'


def is_quiet(pos, m):
    return m[2] == '' and m[3] == 0 and pos.b[m[1]] == '.'


def is_checking_move(pos, m):
    return in_check(pos.make(m), 'b' if pos.side == 'w' else 'w')


def move_score(pos, m, tt_move=None, ply=0):
    if tt_move is not None and m == tt_move:
        return 10_000_000
    fr, to, promo, flag = m
    piece = pos.b[fr]
    cap = capture_value(pos, m)
    s = 0
    if cap:
        s += 1_000_000 + cap * 16 - VAL[piece]
    if promo:
        s += 800_000 + VAL[promo]
    if ply < len(KILLERS):
        if KILLERS[ply][0] == m:
            s += 700_000
        elif KILLERS[ply][1] == m:
            s += 650_000
    s += HISTORY.get((piece, to), 0)
    return s


def order_moves(pos, moves, tt_move=None, ply=0):
    moves.sort(key=lambda m: move_score(pos, m, tt_move, ply), reverse=True)
    return moves


def evaluate(pos):
    b = pos.b
    if pos.halfmove >= 100:
        return 0

    score = 0
    phase = 0
    wp = 0
    bp = 0
    bishops_w = bishops_b = 0
    rooks = []

    for sq, p in enumerate(b):
        if p == '.':
            continue
        up = p.upper()
        phase += PHASE_VAL[up]
        if p.isupper():
            rel = sq
            if up == 'K':
                # Blend middlegame and endgame king tables later.
                score += PST['K'][rel]
            else:
                score += VAL[p] + PST[up][rel]
            if up == 'P':
                wp |= 1 << sq
            elif up == 'B':
                bishops_w += 1
            elif up == 'R':
                rooks.append((1, sq))
        else:
            rel = mirror(sq)
            if up == 'K':
                score -= PST['K'][rel]
            else:
                score -= VAL[p] + PST[up][rel]
            if up == 'P':
                bp |= 1 << sq
            elif up == 'B':
                bishops_b += 1
            elif up == 'R':
                rooks.append((-1, sq))

    # King endgame correction: as material disappears, central king matters more.
    end_weight = max(0, 24 - min(24, phase))
    if end_weight:
        wk = king_square(pos, 'w')
        bk = king_square(pos, 'b')
        if wk >= 0:
            score += (KING_END[wk] - PST['K'][wk]) * end_weight // 24
        if bk >= 0:
            rbk = mirror(bk)
            score -= (KING_END[rbk] - PST['K'][rbk]) * end_weight // 24

    # Pawn structure, passed pawns, bishop pair, rook files.
    for f in range(8):
        wc = (wp & FILES_BB[f]).bit_count()
        bc = (bp & FILES_BB[f]).bit_count()
        if wc > 1:
            score -= 12 * (wc - 1)
        if bc > 1:
            score += 12 * (bc - 1)

    wtmp = wp
    while wtmp:
        lsb = wtmp & -wtmp
        sq = lsb.bit_length() - 1
        f, r = sq & 7, sq >> 3
        if not (wp & ADJ_FILE_BB[f]):
            score -= 10
        if not (bp & PASSED_W[sq]):
            score += PASSED_BONUS[r]
        wtmp ^= lsb

    btmp = bp
    while btmp:
        lsb = btmp & -btmp
        sq = lsb.bit_length() - 1
        f, r = sq & 7, sq >> 3
        if not (bp & ADJ_FILE_BB[f]):
            score += 10
        if not (wp & PASSED_B[sq]):
            score -= PASSED_BONUS[7 - r]
        btmp ^= lsb

    if bishops_w >= 2:
        score += 28
    if bishops_b >= 2:
        score -= 28

    for sign, sq in rooks:
        f = sq & 7
        friendly = wp if sign > 0 else bp
        enemy = bp if sign > 0 else wp
        if not (friendly & FILES_BB[f]):
            score += sign * (16 if not (enemy & FILES_BB[f]) else 8)

    return score if pos.side == 'w' else -score


class TimeoutSearch(Exception):
    pass


def check_time():
    global NODES
    NODES += 1
    if (NODES & 127) == 0 and time.perf_counter() >= DEADLINE:
        raise TimeoutSearch


def qsearch(pos, alpha, beta, ply):
    check_time()
    in_chk = in_check(pos, pos.side)
    if not in_chk:
        stand = evaluate(pos)
        if stand >= beta:
            return beta
        if stand > alpha:
            alpha = stand
        if ply >= MAX_QPLY:
            return alpha
        moves = legal_moves(pos, tactical=True)
    else:
        moves = legal_moves(pos, tactical=False)
        if not moves:
            return -MATE + ply

    order_moves(pos, moves, None, ply)
    for m in moves:
        child = pos.make(m)
        score = -qsearch(child, -beta, -alpha, ply + 1)
        if score >= beta:
            return beta
        if score > alpha:
            alpha = score
    return alpha


def negamax(pos, depth, alpha, beta, ply):
    check_time()
    orig_alpha = alpha
    key = pos.key()
    ent = TT.get(key)
    tt_move = None
    if ent is not None:
        ent_depth, ent_score, ent_flag, ent_move = ent
        tt_move = ent_move
        if ent_depth >= depth:
            if ent_flag == 0:
                return ent_score
            if ent_flag == 1 and ent_score >= beta:
                return ent_score
            if ent_flag == -1 and ent_score <= alpha:
                return ent_score

    if depth <= 0:
        return qsearch(pos, alpha, beta, ply)

    checked = in_check(pos, pos.side)
    moves = legal_moves(pos, tactical=False)
    if not moves:
        return -MATE + ply if checked else 0

    order_moves(pos, moves, tt_move, ply)
    best = None
    best_score = -INF

    for i, m in enumerate(moves):
        child = pos.make(m)
        # Principal variation search. Late quiet moves are searched one ply shallower first,
        # then re-searched if they look surprisingly good. This buys depth without using
        # any unsafe/non-legal shortcuts.
        if i == 0:
            score = -negamax(child, depth - 1, -beta, -alpha, ply + 1)
        else:
            reduction = 1 if depth >= 3 and i >= 4 and is_quiet(pos, m) and not checked else 0
            score = -negamax(child, depth - 1 - reduction, -alpha - 1, -alpha, ply + 1)
            if alpha < score < beta:
                score = -negamax(child, depth - 1, -beta, -alpha, ply + 1)
        if score > best_score:
            best_score = score
            best = m
        if score > alpha:
            alpha = score
        if alpha >= beta:
            if is_quiet(pos, m):
                if ply < len(KILLERS) and KILLERS[ply][0] != m:
                    KILLERS[ply][1] = KILLERS[ply][0]
                    KILLERS[ply][0] = m
                piece = pos.b[m[0]]
                HISTORY[(piece, m[1])] = HISTORY.get((piece, m[1]), 0) + depth * depth
            break

    flag = 0
    if best_score <= orig_alpha:
        flag = -1
    elif best_score >= beta:
        flag = 1
    if len(TT) > TT_LIMIT:
        TT.clear()
    TT[key] = (depth, best_score, flag, best)
    return best_score



def pressure_after_move(pos_after):
    """Opponent Error Pressure: positive if the side to move has only a few good replies.

    pos_after is the board after our candidate move, so pos_after.side is the opponent.
    Scores inside this function are measured after the opponent replies, when it is our
    turn again, so larger values mean better outcomes for us.
    """
    opp_moves = legal_moves(pos_after, tactical=False)
    if not opp_moves:
        return 10000 if in_check(pos_after, pos_after.side) else -80

    pressure = 0
    if in_check(pos_after, pos_after.side):
        pressure += 75

    n = len(opp_moves)
    if n <= 2:
        pressure += 95
    elif n <= 5:
        pressure += 62
    elif n <= 10:
        pressure += 30
    elif n >= 34:
        pressure += 12

    order_moves(pos_after, opp_moves, None, 1)
    vals = []
    tactical_replies = 0
    limit = min(n, 14)
    for r in opp_moves[:limit]:
        if is_capture(pos_after, r) or r[2] or is_checking_move(pos_after, r):
            tactical_replies += 1
        reply_pos = pos_after.make(r)
        vals.append(qsearch(reply_pos, -INF, INF, 2))

    if not vals:
        return pressure
    vals.sort()
    best_reply = vals[0]                    # opponent's best reply minimizes our score
    second_reply = vals[1] if len(vals) > 1 else best_reply + 260
    median_reply = vals[len(vals) // 2]
    worst_reply = vals[-1]

    only_move_gap = min(260, max(0, second_reply - best_reply))
    error_surface = min(260, max(0, median_reply - best_reply))
    tail_risk = min(140, max(0, worst_reply - median_reply))
    already_unpleasant = min(100, max(0, best_reply + 35))

    pressure += only_move_gap // 2
    pressure += error_surface // 5
    pressure += tail_risk // 8
    pressure += tactical_replies * 7
    pressure += already_unpleasant // 2

    # If the opponent has a clean reply that leaves us badly worse, do not romanticize chaos.
    if best_reply < -350:
        pressure -= min(140, (-350 - best_reply) // 3)
    return pressure


def choose_pressure_move(pos, moves, root_scores, objective_best):
    if len(moves) < 2 or not root_scores:
        return objective_best

    best_score = max(root_scores.values())
    if best_score > MATE // 2:
        return objective_best

    # The worse we are, the more we should seek practical trouble. When fine or winning,
    # we keep the leash tight and only use pressure as a tie-breaker.
    if in_check(pos, pos.side):
        margin, weight = 45, 0.16
    elif best_score < -250:
        margin, weight = 180, 0.62
    elif best_score < -80:
        margin, weight = 125, 0.48
    elif best_score < 180:
        margin, weight = 78, 0.30
    else:
        margin, weight = 48, 0.14

    candidates = [m for m in moves if root_scores.get(m, -INF) >= best_score - margin]
    if len(candidates) <= 1:
        return objective_best
    candidates.sort(key=lambda m: root_scores.get(m, -INF), reverse=True)
    candidates = candidates[:CHAOS_CANDIDATES]

    pick = objective_best
    pick_value = root_scores.get(objective_best, -INF)
    for m in candidates:
        p = pressure_after_move(pos.make(m))
        if m[2]:
            p += 45
        if is_checking_move(pos, m):
            p += 28
        if is_capture(pos, m):
            p += 8
        adjusted = root_scores.get(m, -INF) + int(p * weight)
        if adjusted > pick_value:
            pick_value = adjusted
            pick = m
    return pick

def choose_move(fen, seconds=4.75):
    global DEADLINE, NODES, ROOT_BEST
    pos = parse_fen(fen)
    moves = legal_moves(pos, tactical=False)
    if not moves:
        return "0000"
    if len(moves) == 1:
        return move_uci(moves[0])
    bm = opening_book_move(fen, moves)
    if bm is not None:
        return move_uci(bm)

    DEADLINE = time.perf_counter() + seconds
    NODES = 0
    best = moves[0]
    best_score = -INF
    root_scores = {}
    ROOT_BEST = best

    # Search forcing captures first even at depth 1.
    root_tt_move = TT.get(pos.key(), (0, 0, 0, None))[3]
    order_moves(pos, moves, root_tt_move, 0)

    depth = 1
    while depth <= MAX_DEPTH:
        if time.perf_counter() >= DEADLINE:
            break
        try:
            local_best = best
            local_score = -INF
            local_scores = {}
            alpha, beta = -INF, INF
            order_moves(pos, moves, best, 0)
            for m in moves:
                child = pos.make(m)
                score = -negamax(child, depth - 1, -beta, -alpha, 1)
                local_scores[m] = score
                if score > local_score:
                    local_score = score
                    local_best = m
                if score > alpha:
                    alpha = score
            best, best_score = local_best, local_score
            root_scores = local_scores
            ROOT_BEST = best
            # Put PV move first for next iteration.
            moves.sort(key=lambda m: 1 if m == best else 0, reverse=True)
            # Avoid starting a hopelessly deeper search with crumbs of time left.
            if time.perf_counter() + 0.03 >= DEADLINE:
                break
            depth += 1
        except TimeoutSearch:
            break
        except RecursionError:
            break

    # New idea: objective search picks the safe shortlist; Opponent Error Pressure
    # then chooses the move most likely to make the opponent solve a narrow, tactical problem.
    try:
        if time.perf_counter() + 0.015 < DEADLINE:
            best = choose_pressure_move(pos, moves, root_scores, best)
    except TimeoutSearch:
        pass
    except Exception:
        pass

    return move_uci(best)


def main():
    for line in sys.stdin:
        fen = line.strip()
        if not fen:
            continue
        try:
            sys.stdout.write(choose_move(fen) + "\n")
            sys.stdout.flush()
        except Exception:
            # Last-ditch legality-preserving fallback for valid positions.
            try:
                pos = parse_fen(fen)
                ms = legal_moves(pos, tactical=False)
                sys.stdout.write((move_uci(ms[0]) if ms else "0000") + "\n")
                sys.stdout.flush()
            except Exception:
                sys.stdout.write("0000\n")
                sys.stdout.flush()


if __name__ == "__main__":
    main()
