#!/usr/bin/env python3
"""
Single-file chess agent (Python 3).
Reads FEN from stdin, outputs best UCI move to stdout.
Stays alive so the worker can reuse the process.
"""

import sys, time, random

# --- Constants ---
EMPTY = 0
W, B = 1, -1
P, N, BISHOP, R, Q, K = 1, 2, 3, 4, 5, 6

PIECE_VAL = {P: 100, N: 320, BISHOP: 330, R: 500, Q: 900, K: 20000}
MATE = 1000000

# 0x88 offsets
ORTH = (-16, -1, 1, 16)
DIAG = (-17, -15, 15, 17)
KNIGHT_OFFS = (-33, -31, -18, -14, 14, 18, 31, 33)
KING_OFFS = ORTH + DIAG

# Castling rights bits
CR_WK = 1
CR_WQ = 2
CR_BK = 4
CR_BQ = 8

# Piece-square tables (64 squares: a1=0 .. h8=63)
PST = {
    P: [
         0,  0,  0,  0,  0,  0,  0,  0,
        50, 50, 50, 50, 50, 50, 50, 50,
        10, 10, 20, 30, 30, 20, 10, 10,
         5,  5, 10, 25, 25, 10,  5,  5,
         0,  0,  0, 20, 20,  0,  0,  0,
         5, -5,-10,  0,  0,-10, -5,  5,
         5, 10, 10,-20,-20, 10, 10,  5,
         0,  0,  0,  0,  0,  0,  0,  0,
    ],
    N: [
       -50,-40,-30,-30,-30,-30,-40,-50,
       -40,-20,  0,  0,  0,  0,-20,-40,
       -30,  0, 10, 15, 15, 10,  0,-30,
       -30,  5, 15, 20, 20, 15,  5,-30,
       -30,  0, 15, 20, 20, 15,  0,-30,
       -30,  5, 10, 15, 15, 10,  5,-30,
       -40,-20,  0,  5,  5,  0,-20,-40,
       -50,-40,-30,-30,-30,-30,-40,-50,
    ],
    BISHOP: [
       -20,-10,-10,-10,-10,-10,-10,-20,
       -10,  0,  0,  0,  0,  0,  0,-10,
       -10,  0, 10, 10, 10, 10,  0,-10,
       -10,  5,  5, 10, 10,  5,  5,-10,
       -10,  0,  5, 10, 10,  5,  0,-10,
       -10, 10, 10, 10, 10, 10, 10,-10,
       -10,  5,  0,  0,  0,  0,  5,-10,
       -20,-10,-10,-10,-10,-10,-10,-20,
    ],
    R: [
         0,  0,  0,  0,  0,  0,  0,  0,
         5, 10, 10, 10, 10, 10, 10,  5,
        -5,  0,  0,  0,  0,  0,  0, -5,
        -5,  0,  0,  0,  0,  0,  0, -5,
        -5,  0,  0,  0,  0,  0,  0, -5,
        -5,  0,  0,  0,  0,  0,  0, -5,
         5, 10, 10, 10, 10, 10, 10,  5,
         0,  0,  0,  5,  5,  0,  0,  0,
    ],
    Q: [
       -20,-10,-10, -5, -5,-10,-10,-20,
       -10,  0,  0,  0,  0,  0,  0,-10,
       -10,  0,  5,  5,  5,  5,  0,-10,
        -5,  0,  5,  5,  5,  5,  0, -5,
         0,  0,  5,  5,  5,  5,  0, -5,
       -10,  5,  5,  5,  5,  5,  0,-10,
       -10,  0,  5,  0,  0,  0,  0,-10,
       -20,-10,-10, -5, -5,-10,-10,-20,
    ],
    K: [
       -30,-40,-40,-50,-50,-40,-40,-30,
       -30,-40,-40,-50,-50,-40,-40,-30,
       -30,-40,-40,-50,-50,-40,-40,-30,
       -30,-40,-40,-50,-50,-40,-40,-30,
       -20,-30,-30,-40,-40,-30,-30,-20,
       -10,-20,-20,-20,-20,-20,-20,-10,
        20, 20,  0,  0,  0,  0, 20, 20,
        20, 30, 10,  0,  0, 10, 30, 20,
    ],
}

# --- Helpers ---
def sq_to_idx(s):
    return (int(s[1]) - 1) * 16 + (ord(s[0]) - ord('a'))

def idx_to_sq(i):
    return chr(ord('a') + (i & 7)) + str((i >> 4) + 1)

def move_to_uci(m):
    f, t, p = m
    s = idx_to_sq(f) + idx_to_sq(t)
    if p:
        s += {N: 'n', BISHOP: 'b', R: 'r', Q: 'q'}[p]
    return s

# --- Board ---
class Board:
    def __init__(self, fen=None):
        self.board = [EMPTY] * 128
        self.kings = {W: 0, B: 0}
        self.side = W
        self.ep = None
        self.castle = 0
        self.halfmove = 0
        self.full = 1
        if fen:
            self.parse_fen(fen)

    def parse_fen(self, fen):
        parts = fen.strip().split()
        # Board
        rows = parts[0].split('/')
        sq = 0x70
        for row in rows:
            for ch in row:
                if ch.isdigit():
                    for _ in range(int(ch)):
                        self.board[sq] = EMPTY
                        sq += 1
                else:
                    color = W if ch.isupper() else B
                    pt = {'P': P, 'N': N, 'B': BISHOP, 'R': R, 'Q': Q, 'K': K}[ch.upper()]
                    self.board[sq] = color * pt
                    if pt == K:
                        self.kings[color] = sq
                    sq += 1
            sq -= 0x18
        # Side
        self.side = W if parts[1] == 'w' else B
        # Castling
        c = 0
        if len(parts) > 2 and parts[2] != '-':
            for ch in parts[2]:
                if ch == 'K': c |= CR_WK
                if ch == 'Q': c |= CR_WQ
                if ch == 'k': c |= CR_BK
                if ch == 'q': c |= CR_BQ
        self.castle = c
        # EP
        if len(parts) > 3 and parts[3] != '-':
            self.ep = sq_to_idx(parts[3])
        else:
            self.ep = None
        # Clocks
        self.halfmove = int(parts[4]) if len(parts) > 4 else 0
        self.full = int(parts[5]) if len(parts) > 5 else 1

    def is_attacked(self, sq, by_color):
        # Pawns
        if by_color == W:
            for d in (-15, -17):
                t = sq + d
                if not (t & 0x88) and self.board[t] == W * P:
                    return True
        else:
            for d in (15, 17):
                t = sq + d
                if not (t & 0x88) and self.board[t] == B * P:
                    return True
        # Knights
        for d in KNIGHT_OFFS:
            t = sq + d
            if not (t & 0x88) and abs(self.board[t]) == N and (self.board[t] > 0) == (by_color == W):
                return True
        # King
        for d in KING_OFFS:
            t = sq + d
            if not (t & 0x88) and abs(self.board[t]) == K and (self.board[t] > 0) == (by_color == W):
                return True
        # Rook/Queen
        for d in ORTH:
            t = sq + d
            while not (t & 0x88):
                p = self.board[t]
                if p:
                    if (p > 0) == (by_color == W) and (abs(p) == R or abs(p) == Q):
                        return True
                    break
                t += d
        # Bishop/Queen
        for d in DIAG:
            t = sq + d
            while not (t & 0x88):
                p = self.board[t]
                if p:
                    if (p > 0) == (by_color == W) and (abs(p) == BISHOP or abs(p) == Q):
                        return True
                    break
                t += d
        return False

    def in_check(self):
        return self.is_attacked(self.kings[self.side], -self.side)

    def generate_pseudo_moves(self, captures_only=False):
        moves = []
        for sq in range(128):
            if sq & 0x88:
                continue
            p = self.board[sq]
            if p == 0 or (p > 0) != (self.side == W):
                continue
            pt = abs(p)
            if pt == P:
                dir1 = 16 if self.side == W else -16
                dir2 = 32 if self.side == W else -32
                cap1 = 15 if self.side == W else -15
                cap2 = 17 if self.side == W else -17
                start_rank = 1 if self.side == W else 6
                promo_rank = 7 if self.side == W else 0
                # Captures & EP (always)
                for to in (sq + cap1, sq + cap2):
                    if to & 0x88:
                        continue
                    if self.board[to] != EMPTY and (self.board[to] > 0) != (self.side == W):
                        if (to >> 4) == promo_rank:
                            for prom in (Q, R, BISHOP, N):
                                moves.append((sq, to, prom))
                        else:
                            moves.append((sq, to, 0))
                    if to == self.ep and self.ep is not None:
                        moves.append((sq, to, 0))
                if not captures_only:
                    # Single push
                    to = sq + dir1
                    if not (to & 0x88) and self.board[to] == EMPTY:
                        if (to >> 4) == promo_rank:
                            for prom in (Q, R, BISHOP, N):
                                moves.append((sq, to, prom))
                        else:
                            moves.append((sq, to, 0))
                            if (sq >> 4) == start_rank:
                                to2 = sq + dir2
                                if self.board[to2] == EMPTY:
                                    moves.append((sq, to2, 0))
            elif pt == N:
                for d in KNIGHT_OFFS:
                    to = sq + d
                    if to & 0x88:
                        continue
                    if self.board[to] == EMPTY:
                        if not captures_only:
                            moves.append((sq, to, 0))
                    elif (self.board[to] > 0) != (self.side == W):
                        moves.append((sq, to, 0))
            elif pt == BISHOP:
                for d in DIAG:
                    to = sq + d
                    while not (to & 0x88):
                        if self.board[to] == EMPTY:
                            if not captures_only:
                                moves.append((sq, to, 0))
                        elif (self.board[to] > 0) != (self.side == W):
                            moves.append((sq, to, 0))
                            break
                        else:
                            break
                        to += d
            elif pt == R:
                for d in ORTH:
                    to = sq + d
                    while not (to & 0x88):
                        if self.board[to] == EMPTY:
                            if not captures_only:
                                moves.append((sq, to, 0))
                        elif (self.board[to] > 0) != (self.side == W):
                            moves.append((sq, to, 0))
                            break
                        else:
                            break
                        to += d
            elif pt == Q:
                for d in ORTH + DIAG:
                    to = sq + d
                    while not (to & 0x88):
                        if self.board[to] == EMPTY:
                            if not captures_only:
                                moves.append((sq, to, 0))
                        elif (self.board[to] > 0) != (self.side == W):
                            moves.append((sq, to, 0))
                            break
                        else:
                            break
                        to += d
            elif pt == K:
                for d in KING_OFFS:
                    to = sq + d
                    if to & 0x88:
                        continue
                    if self.board[to] == EMPTY:
                        if not captures_only:
                            moves.append((sq, to, 0))
                    elif (self.board[to] > 0) != (self.side == W):
                        moves.append((sq, to, 0))
                # Castling
                if not captures_only:
                    if self.side == W and sq == 0x04:
                        if self.castle & CR_WK:
                            if self.board[0x05] == EMPTY and self.board[0x06] == EMPTY:
                                if not self.is_attacked(0x04, B) and not self.is_attacked(0x05, B):
                                    moves.append((0x04, 0x06, 0))
                        if self.castle & CR_WQ:
                            if self.board[0x03] == EMPTY and self.board[0x02] == EMPTY and self.board[0x01] == EMPTY:
                                if not self.is_attacked(0x04, B) and not self.is_attacked(0x03, B):
                                    moves.append((0x04, 0x02, 0))
                    elif self.side == B and sq == 0x74:
                        if self.castle & CR_BK:
                            if self.board[0x75] == EMPTY and self.board[0x76] == EMPTY:
                                if not self.is_attacked(0x74, W) and not self.is_attacked(0x75, W):
                                    moves.append((0x74, 0x76, 0))
                        if self.castle & CR_BQ:
                            if self.board[0x73] == EMPTY and self.board[0x72] == EMPTY and self.board[0x71] == EMPTY:
                                if not self.is_attacked(0x74, W) and not self.is_attacked(0x73, W):
                                    moves.append((0x74, 0x72, 0))
        return moves

    def generate_legal_moves(self, pv_move=None):
        pseudo = self.generate_pseudo_moves()
        # Move ordering
        def score(m):
            if pv_move and m == pv_move:
                return 100000
            victim = abs(self.board[m[1]])
            prom = m[2]
            if victim or prom:
                attacker = abs(self.board[m[0]])
                vval = PIECE_VAL.get(victim, 0)
                if prom:
                    vval += 800
                return 5000 + vval * 10 - PIECE_VAL.get(attacker, 0)
            return 0
        pseudo.sort(key=score, reverse=True)
        legal = []
        for m in pseudo:
            state = self.make_move(m)
            if not self.is_attacked(self.kings[-self.side], self.side):
                legal.append(m)
            self.undo_move(m, state)
        return legal

    def make_move(self, move):
        f, t, prom = move
        piece = self.board[f]
        captured = self.board[t]
        old_ep = self.ep
        old_castle = self.castle
        old_half = self.halfmove
        old_king = self.kings[self.side]

        # En passant capture detection (update captured piece)
        if abs(piece) == P and t == self.ep and self.ep is not None:
            cap_sq = t - 16 if self.side == W else t + 16
            captured = self.board[cap_sq]
            self.board[cap_sq] = EMPTY

        # Halfmove clock
        self.halfmove += 1
        if abs(piece) == P or captured != EMPTY:
            self.halfmove = 0

        # Move piece
        self.board[t] = piece
        self.board[f] = EMPTY
        if abs(piece) == K:
            self.kings[self.side] = t

        # Promotion
        if prom:
            self.board[t] = self.side * prom

        # Update EP square
        if abs(piece) == P and abs(t - f) == 32:
            self.ep = (f + t) // 2
        else:
            self.ep = None

        # Castling rook move
        if abs(piece) == K and abs(t - f) == 2:
            if t > f:
                rf, rt = f + 3, f + 1
            else:
                rf, rt = f - 4, f - 1
            self.board[rt] = self.board[rf]
            self.board[rf] = EMPTY

        # Update castling rights
        nc = self.castle
        if f == 0x00 and abs(piece) == R and self.side == W: nc &= ~CR_WQ
        if f == 0x07 and abs(piece) == R and self.side == W: nc &= ~CR_WK
        if f == 0x70 and abs(piece) == R and self.side == B: nc &= ~CR_BQ
        if f == 0x77 and abs(piece) == R and self.side == B: nc &= ~CR_BK
        if f == 0x04 and abs(piece) == K and self.side == W: nc &= ~(CR_WK | CR_WQ)
        if f == 0x74 and abs(piece) == K and self.side == B: nc &= ~(CR_BK | CR_BQ)
        if t == 0x00 and abs(captured) == R and self.side == B: nc &= ~CR_WQ
        if t == 0x07 and abs(captured) == R and self.side == B: nc &= ~CR_WK
        if t == 0x70 and abs(captured) == R and self.side == W: nc &= ~CR_BQ
        if t == 0x77 and abs(captured) == R and self.side == W: nc &= ~CR_BK
        self.castle = nc

        # Switch side
        self.side = -self.side
        if self.side == W:
            self.full += 1

        return (captured, old_ep, old_castle, old_half, old_king)

    def undo_move(self, move, state):
        if self.side == W:
            self.full -= 1
        self.side = -self.side

        f, t, prom = move
        captured, old_ep, old_castle, old_half, old_king = state
        piece = self.board[t]
        if prom:
            piece = self.side * P
        self.board[f] = piece
        self.kings[self.side] = old_king
        self.ep = old_ep
        self.castle = old_castle
        self.halfmove = old_half

        # Undo en passant
        if abs(piece) == P and t == old_ep and old_ep is not None:
            cap_sq = t - 16 if self.side == W else t + 16
            self.board[cap_sq] = captured
            self.board[t] = EMPTY
        else:
            self.board[t] = captured

        # Undo castling rook
        if abs(piece) == K and abs(t - f) == 2:
            if t > f:
                rf, rt = f + 3, f + 1
            else:
                rf, rt = f - 4, f - 1
            self.board[rf] = self.board[rt]
            self.board[rt] = EMPTY

    def evaluate(self):
        score = 0
        for sq in range(128):
            if sq & 0x88:
                continue
            p = self.board[sq]
            if p:
                idx = (sq >> 4) * 8 + (sq & 7)
                v = PIECE_VAL[abs(p)]
                pst = PST[abs(p)][idx if p > 0 else (idx ^ 0x38)]
                if p > 0:
                    score += v + pst
                else:
                    score -= v + pst
        return score if self.side == W else -score


# --- Search ---
def quiesce(board, alpha, beta, ply, start_time, time_limit, nodes, qdepth=0):
    nodes[0] += 1
    if nodes[0] & 2047 == 0:
        if time.time() - start_time > time_limit:
            raise TimeoutError()

    in_check = board.in_check()
    if in_check:
        # In check: search all evasions
        moves = board.generate_legal_moves()
        if not moves:
            return -MATE + ply
        best = -9999999
        for move in moves:
            state = board.make_move(move)
            val = -quiesce(board, -beta, -alpha, ply + 1, start_time, time_limit, nodes, qdepth + 1)
            board.undo_move(move, state)
            if val > best:
                best = val
            if val > alpha:
                alpha = val
            if alpha >= beta:
                break
        return best

    stand_pat = board.evaluate()
    if stand_pat >= beta:
        return beta
    if stand_pat + 900 < alpha:
        return alpha
    if alpha < stand_pat:
        alpha = stand_pat

    moves = board.generate_legal_moves()
    caps = [m for m in moves if board.board[m[1]] != EMPTY or m[2]]
    for move in caps:
        state = board.make_move(move)
        val = -quiesce(board, -beta, -alpha, ply + 1, start_time, time_limit, nodes, qdepth + 1)
        board.undo_move(move, state)
        if val >= beta:
            return beta
        if val > alpha:
            alpha = val
    return alpha


def search(board, depth, alpha, beta, ply, start_time, time_limit, nodes):
    nodes[0] += 1
    if nodes[0] & 2047 == 0:
        if time.time() - start_time > time_limit:
            raise TimeoutError()

    if depth == 0:
        return quiesce(board, alpha, beta, ply, start_time, time_limit, nodes)

    moves = board.generate_legal_moves()
    if not moves:
        if board.in_check():
            return -MATE + ply
        return 0

    best = -9999999
    for move in moves:
        state = board.make_move(move)
        val = -search(board, depth - 1, -beta, -alpha, ply + 1, start_time, time_limit, nodes)
        board.undo_move(move, state)
        if val > best:
            best = val
        if val > alpha:
            alpha = val
        if alpha >= beta:
            break
    return best


def search_root(board, depth, start_time, time_limit, pv_move):
    nodes = [0]
    best_move = None
    best_score = -9999999
    alpha = -9999999
    beta = 9999999

    try:
        moves = board.generate_legal_moves(pv_move)
    except TimeoutError:
        return best_move, best_score

    for move in moves:
        state = board.make_move(move)
        try:
            val = -search(board, depth - 1, -beta, -alpha, 1, start_time, time_limit, nodes)
        except TimeoutError:
            board.undo_move(move, state)
            return best_move if best_move is not None else move, best_score
        board.undo_move(move, state)
        if val > best_score or best_move is None:
            best_score = val
            best_move = move
        if val > alpha:
            alpha = val
    return best_move, best_score


def find_best_move(board, max_time=4.5):
    start = time.time()
    best_move = None
    pv_move = None

    for depth in range(1, 20):
        if time.time() - start > max_time * 0.6:
            break
        move, score = search_root(board, depth, start, max_time, pv_move)
        if move is not None:
            best_move = move
            pv_move = move
        if time.time() - start > max_time * 0.85:
            break
        if abs(score) > MATE - 100:
            break
    if best_move is None:
        legal = board.generate_legal_moves()
        if legal:
            best_move = legal[0]
    return best_move


# --- Main loop ---
def main():
    for line in sys.stdin:
        fen = line.strip()
        if not fen:
            continue
        board = Board(fen)
        move = find_best_move(board)
        sys.stdout.write(move_to_uci(move) + "\n")
        sys.stdout.flush()


if __name__ == "__main__":
    main()
