#!/usr/bin/env python3
"""
Chess Engine inspired by Javokhir Sindarov's playing style.

Sindarov Style Profile (from 2026 Candidates analysis):
- Aggressive, dynamic, initiative-focused
- As White: 1.d4 player, favors Catalan (1.d4 Nf6 2.c4 e6 3.g3) and QGD Exchange
- Strong tactical eye with king pressure themes
- Confidence in sharp, unbalanced positions
- Patient positional play when needed, but always seeking activity
- "Fearless modern approach" - willing to sacrifice material for initiative
- Composure under pressure, practical decision-making
"""

import sys
import time
import math
import random

# ============================================================================
# CONSTANTS
# ============================================================================

PIECE_NONE = 0
PAWN = 1; KNIGHT = 2; BISHOP = 3; ROOK = 4; QUEEN = 5; KING = 6
WHITE = 0; BLACK = 1

PIECE_CHARS = {
    (WHITE, PAWN): 'P', (WHITE, KNIGHT): 'N', (WHITE, BISHOP): 'B',
    (WHITE, ROOK): 'R', (WHITE, QUEEN): 'Q', (WHITE, KING): 'K',
    (BLACK, PAWN): 'p', (BLACK, KNIGHT): 'n', (BLACK, BISHOP): 'b',
    (BLACK, ROOK): 'r', (BLACK, QUEEN): 'q', (BLACK, KING): 'k',
}
CHAR_TO_PIECE = {v: k for k, v in PIECE_CHARS.items()}

PIECE_VALUES = {PAWN: 100, KNIGHT: 320, BISHOP: 330, ROOK: 500, QUEEN: 900, KING: 20000}

# Piece-square tables (from White's perspective, flip for Black)
PST_PAWN = [
     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, 27, 27, 10,  5,  5,
     0,  0,  0, 25, 25,  0,  0,  0,
     5, -5,-10,  0,  0,-10, -5,  5,
     5, 10, 10,-25,-25, 10, 10,  5,
     0,  0,  0,  0,  0,  0,  0,  0,
]

PST_KNIGHT = [
    -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,
]

PST_BISHOP = [
    -20,-10,-10,-10,-10,-10,-10,-20,
    -10,  0,  0,  0,  0,  0,  0,-10,
    -10,  0,  5, 10, 10,  5,  0,-10,
    -10,  5,  5, 10, 10,  5,  5,-10,
    -10,  0, 10, 10, 10, 10,  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,
]

PST_ROOK = [
     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,  0,  0,  0,  0,  0,  0, -5,
     0,  0,  0,  5,  5,  0,  0,  0,
]

PST_QUEEN = [
    -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,
]

PST_KING_MG = [
    -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,
]

PST_KING_EG = [
    -50,-40,-30,-20,-20,-30,-40,-50,
    -30,-20,-10,  0,  0,-10,-20,-30,
    -30,-10, 20, 30, 30, 20,-10,-30,
    -30,-10, 30, 40, 40, 30,-10,-30,
    -30,-10, 30, 40, 40, 30,-10,-30,
    -30,-10, 20, 30, 30, 20,-10,-30,
    -30,-30,  0,  0,  0,  0,-30,-30,
    -50,-30,-30,-30,-30,-30,-30,-50,
]

PSTS = {
    PAWN: PST_PAWN, KNIGHT: PST_KNIGHT, BISHOP: PST_BISHOP,
    ROOK: PST_ROOK, QUEEN: PST_QUEEN,
}

KNIGHT_MOVES = {}
KING_MOVES = {}

def init_move_tables():
    for sq in range(64):
        r, c = sq // 8, sq % 8
        km = []
        for dr, dc in [(-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1)]:
            nr, nc = r+dr, c+dc
            if 0 <= nr < 8 and 0 <= nc < 8:
                km.append(nr*8+nc)
        KNIGHT_MOVES[sq] = km
        kingm = []
        for dr in [-1,0,1]:
            for dc in [-1,0,1]:
                if dr == 0 and dc == 0: continue
                nr, nc = r+dr, c+dc
                if 0 <= nr < 8 and 0 <= nc < 8:
                    kingm.append(nr*8+nc)
        KING_MOVES[sq] = kingm

init_move_tables()

# ============================================================================
# BOARD STATE
# ============================================================================

class Board:
    __slots__ = ['squares', 'side', 'castling', 'ep', 'halfmove', 'fullmove',
                 'king_sq', 'history']

    def __init__(self):
        self.squares = [None] * 64  # (color, piece_type) or None
        self.side = WHITE
        self.castling = [False, False, False, False]  # K Q k q
        self.ep = -1
        self.halfmove = 0
        self.fullmove = 1
        self.king_sq = [0, 0]  # [white_king_sq, black_king_sq]
        self.history = []

    def copy(self):
        b = Board()
        b.squares = self.squares[:]
        b.side = self.side
        b.castling = self.castling[:]
        b.ep = self.ep
        b.halfmove = self.halfmove
        b.fullmove = self.fullmove
        b.king_sq = self.king_sq[:]
        return b

    @staticmethod
    def from_fen(fen):
        b = Board()
        parts = fen.split()
        rows = parts[0].split('/')
        for ri, row in enumerate(rows):
            ci = 0
            for ch in row:
                if ch.isdigit():
                    ci += int(ch)
                else:
                    color, piece = CHAR_TO_PIECE[ch]
                    sq = ri * 8 + ci
                    b.squares[sq] = (color, piece)
                    if piece == KING:
                        b.king_sq[color] = sq
                    ci += 1

        b.side = WHITE if parts[1] == 'w' else BLACK
        c = parts[2]
        b.castling = ['K' in c, 'Q' in c, 'k' in c, 'q' in c]
        if parts[3] != '-':
            f = ord(parts[3][0]) - ord('a')
            r = 8 - int(parts[3][1])
            b.ep = r * 8 + f
        else:
            b.ep = -1
        b.halfmove = int(parts[4]) if len(parts) > 4 else 0
        b.fullmove = int(parts[5]) if len(parts) > 5 else 1
        return b

    def is_square_attacked(self, sq, by_color):
        """Check if square sq is attacked by by_color."""
        r, c = sq // 8, sq % 8

        # Pawn attacks
        if by_color == WHITE:
            for dc in [-1, 1]:
                ar, ac = r + 1, c + dc
                if 0 <= ar < 8 and 0 <= ac < 8:
                    p = self.squares[ar * 8 + ac]
                    if p and p[0] == WHITE and p[1] == PAWN:
                        return True
        else:
            for dc in [-1, 1]:
                ar, ac = r - 1, c + dc
                if 0 <= ar < 8 and 0 <= ac < 8:
                    p = self.squares[ar * 8 + ac]
                    if p and p[0] == BLACK and p[1] == PAWN:
                        return True

        # Knight attacks
        for nsq in KNIGHT_MOVES[sq]:
            p = self.squares[nsq]
            if p and p[0] == by_color and p[1] == KNIGHT:
                return True

        # King attacks
        for ksq in KING_MOVES[sq]:
            p = self.squares[ksq]
            if p and p[0] == by_color and p[1] == KING:
                return True

        # Sliding pieces (bishop/queen diagonals, rook/queen lines)
        for dr, dc in [(-1,-1),(-1,1),(1,-1),(1,1)]:
            nr, nc = r+dr, c+dc
            while 0 <= nr < 8 and 0 <= nc < 8:
                p = self.squares[nr*8+nc]
                if p:
                    if p[0] == by_color and p[1] in (BISHOP, QUEEN):
                        return True
                    break
                nr += dr; nc += dc

        for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
            nr, nc = r+dr, c+dc
            while 0 <= nr < 8 and 0 <= nc < 8:
                p = self.squares[nr*8+nc]
                if p:
                    if p[0] == by_color and p[1] in (ROOK, QUEEN):
                        return True
                    break
                nr += dr; nc += dc

        return False

    def in_check(self, color):
        return self.is_square_attacked(self.king_sq[color], 1 - color)

    def generate_pseudo_legal_moves(self):
        """Generate all pseudo-legal moves (may leave king in check)."""
        moves = []
        side = self.side
        opp = 1 - side
        sqs = self.squares

        for sq in range(64):
            p = sqs[sq]
            if not p or p[0] != side:
                continue
            pt = p[1]
            r, c = sq // 8, sq % 8

            if pt == PAWN:
                direction = -1 if side == WHITE else 1
                start_row = 6 if side == WHITE else 1
                promo_row = 0 if side == WHITE else 7

                # Forward
                nr = r + direction
                if 0 <= nr < 8:
                    tsq = nr * 8 + c
                    if not sqs[tsq]:
                        if nr == promo_row:
                            for pp in [QUEEN, ROOK, BISHOP, KNIGHT]:
                                moves.append((sq, tsq, pp))
                        else:
                            moves.append((sq, tsq, 0))
                            # Double push
                            if r == start_row:
                                tsq2 = (r + 2 * direction) * 8 + c
                                if not sqs[tsq2]:
                                    moves.append((sq, tsq2, 0))

                # Captures
                for dc in [-1, 1]:
                    nc2 = c + dc
                    if 0 <= nc2 < 8:
                        tsq = nr * 8 + nc2
                        tp = sqs[tsq]
                        if tp and tp[0] == opp:
                            if nr == promo_row:
                                for pp in [QUEEN, ROOK, BISHOP, KNIGHT]:
                                    moves.append((sq, tsq, pp))
                            else:
                                moves.append((sq, tsq, 0))
                        elif tsq == self.ep:
                            moves.append((sq, tsq, 0))

            elif pt == KNIGHT:
                for tsq in KNIGHT_MOVES[sq]:
                    tp = sqs[tsq]
                    if not tp or tp[0] == opp:
                        moves.append((sq, tsq, 0))

            elif pt == KING:
                for tsq in KING_MOVES[sq]:
                    tp = sqs[tsq]
                    if not tp or tp[0] == opp:
                        moves.append((sq, tsq, 0))

                # Castling
                if side == WHITE:
                    if self.castling[0] and not sqs[61] and not sqs[62]:
                        if sqs[63] and sqs[63] == (WHITE, ROOK):
                            if not self.is_square_attacked(60, BLACK) and \
                               not self.is_square_attacked(61, BLACK) and \
                               not self.is_square_attacked(62, BLACK):
                                moves.append((60, 62, 0))
                    if self.castling[1] and not sqs[59] and not sqs[58] and not sqs[57]:
                        if sqs[56] and sqs[56] == (WHITE, ROOK):
                            if not self.is_square_attacked(60, BLACK) and \
                               not self.is_square_attacked(59, BLACK) and \
                               not self.is_square_attacked(58, BLACK):
                                moves.append((60, 58, 0))
                else:
                    if self.castling[2] and not sqs[5] and not sqs[6]:
                        if sqs[7] and sqs[7] == (BLACK, ROOK):
                            if not self.is_square_attacked(4, WHITE) and \
                               not self.is_square_attacked(5, WHITE) and \
                               not self.is_square_attacked(6, WHITE):
                                moves.append((4, 6, 0))
                    if self.castling[3] and not sqs[3] and not sqs[2] and not sqs[1]:
                        if sqs[0] and sqs[0] == (BLACK, ROOK):
                            if not self.is_square_attacked(4, WHITE) and \
                               not self.is_square_attacked(3, WHITE) and \
                               not self.is_square_attacked(2, WHITE):
                                moves.append((4, 2, 0))

            else:  # Sliding pieces
                if pt == BISHOP or pt == QUEEN:
                    for dr, dc in [(-1,-1),(-1,1),(1,-1),(1,1)]:
                        nr, nc2 = r+dr, c+dc
                        while 0 <= nr < 8 and 0 <= nc2 < 8:
                            tsq = nr*8+nc2
                            tp = sqs[tsq]
                            if tp:
                                if tp[0] == opp:
                                    moves.append((sq, tsq, 0))
                                break
                            moves.append((sq, tsq, 0))
                            nr += dr; nc2 += dc

                if pt == ROOK or pt == QUEEN:
                    for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                        nr, nc2 = r+dr, c+dc
                        while 0 <= nr < 8 and 0 <= nc2 < 8:
                            tsq = nr*8+nc2
                            tp = sqs[tsq]
                            if tp:
                                if tp[0] == opp:
                                    moves.append((sq, tsq, 0))
                                break
                            moves.append((sq, tsq, 0))
                            nr += dr; nc2 += dc

        return moves

    def make_move(self, move):
        """Make a move and return undo info."""
        frm, to, promo = move
        sqs = self.squares
        piece = sqs[frm]
        captured = sqs[to]
        side = self.side

        # Save state for undo
        undo = (frm, to, promo, piece, captured, self.castling[:],
                self.ep, self.halfmove, self.king_sq[:])

        ep_capture = False

        # En passant capture
        if piece[1] == PAWN and to == self.ep and self.ep != -1:
            ep_capture = True
            cap_sq = to + (8 if side == WHITE else -8)
            sqs[cap_sq] = None

        sqs[to] = piece
        sqs[frm] = None

        # Promotion
        if promo:
            sqs[to] = (side, promo)

        # King move tracking
        if piece[1] == KING:
            self.king_sq[side] = to
            # Castling
            if abs(to - frm) == 2:
                if to == 62:  # White kingside
                    sqs[61] = sqs[63]; sqs[63] = None
                elif to == 58:  # White queenside
                    sqs[59] = sqs[56]; sqs[56] = None
                elif to == 6:  # Black kingside
                    sqs[5] = sqs[7]; sqs[7] = None
                elif to == 2:  # Black queenside
                    sqs[3] = sqs[0]; sqs[0] = None

        # Update castling rights
        if piece[1] == KING:
            if side == WHITE:
                self.castling[0] = self.castling[1] = False
            else:
                self.castling[2] = self.castling[3] = False
        if frm == 63 or to == 63: self.castling[0] = False
        if frm == 56 or to == 56: self.castling[1] = False
        if frm == 7 or to == 7: self.castling[2] = False
        if frm == 0 or to == 0: self.castling[3] = False

        # En passant square
        if piece[1] == PAWN and abs(to - frm) == 16:
            self.ep = (frm + to) // 2
        else:
            self.ep = -1

        # Halfmove clock
        if piece[1] == PAWN or captured or ep_capture:
            self.halfmove = 0
        else:
            self.halfmove += 1

        if side == BLACK:
            self.fullmove += 1

        self.side = 1 - side
        return undo

    def unmake_move(self, undo):
        frm, to, promo, piece, captured, castling, ep, halfmove, king_sq = undo
        sqs = self.squares
        side = piece[0]

        sqs[frm] = piece
        sqs[to] = captured

        # En passant capture undo
        if piece[1] == PAWN and to == ep and ep != -1:
            cap_sq = to + (8 if side == WHITE else -8)
            sqs[cap_sq] = (1 - side, PAWN)
            sqs[to] = None

        # Castling undo
        if piece[1] == KING and abs(to - frm) == 2:
            if to == 62:
                sqs[63] = sqs[61]; sqs[61] = None
            elif to == 58:
                sqs[56] = sqs[59]; sqs[59] = None
            elif to == 6:
                sqs[7] = sqs[5]; sqs[5] = None
            elif to == 2:
                sqs[0] = sqs[3]; sqs[3] = None

        self.castling = castling
        self.ep = ep
        self.halfmove = halfmove
        self.king_sq = king_sq
        if side == BLACK:
            self.fullmove -= 1
        self.side = side

    def generate_legal_moves(self):
        legal = []
        side = self.side
        for move in self.generate_pseudo_legal_moves():
            undo = self.make_move(move)
            if not self.in_check(side):
                legal.append(move)
            self.unmake_move(undo)
        return legal

# ============================================================================
# EVALUATION - Tuned for Sindarov's style
# ============================================================================

# Zobrist-like hash for transposition table
HASH_TABLE = {}

def count_material(board, color):
    total = 0
    for sq in range(64):
        p = board.squares[sq]
        if p and p[0] == color:
            total += PIECE_VALUES[p[1]]
    return total

def game_phase(board):
    """Return 0-256 where 0=endgame, 256=opening."""
    phase = 0
    for sq in range(64):
        p = board.squares[sq]
        if p:
            pt = p[1]
            if pt == KNIGHT or pt == BISHOP: phase += 32
            elif pt == ROOK: phase += 48
            elif pt == QUEEN: phase += 96
    return min(phase, 256)

def evaluate(board):
    """
    Evaluate position from the perspective of the side to move.
    Tuned to emulate Sindarov's style:
    - Higher weight on piece activity and king safety attacks
    - Bonus for initiative (having more attacking potential)
    - Preference for dynamic piece placement
    - Bishop pair bonus (Sindarov often uses bishops aggressively)
    - Bonus for central pawns and space advantage
    """
    sqs = board.squares
    side = board.side
    opp = 1 - side

    score = 0
    phase = game_phase(board)
    phase_ratio = phase / 256.0

    white_material = 0
    black_material = 0
    white_bishops = 0
    black_bishops = 0
    white_pawns = [0] * 8  # pawn count per file
    black_pawns = [0] * 8
    white_mobility = 0
    black_mobility = 0
    white_king_zone_attacks = 0
    black_king_zone_attacks = 0

    wk = board.king_sq[WHITE]
    bk = board.king_sq[BLACK]
    wkr, wkc = wk // 8, wk % 8
    bkr, bkc = bk // 8, bk % 8

    for sq in range(64):
        p = sqs[sq]
        if not p:
            continue
        color, pt = p
        r, c = sq // 8, sq % 8

        # Material
        val = PIECE_VALUES[pt]
        if color == WHITE:
            white_material += val
        else:
            black_material += val

        # PST
        if pt != KING:
            pst = PSTS[pt]
            if color == WHITE:
                pst_val = pst[sq]
            else:
                pst_val = pst[(7 - r) * 8 + c]
        else:
            if color == WHITE:
                mg = PST_KING_MG[sq]
                eg = PST_KING_EG[sq]
            else:
                mg = PST_KING_MG[(7 - r) * 8 + c]
                eg = PST_KING_EG[(7 - r) * 8 + c]
            pst_val = int(mg * phase_ratio + eg * (1 - phase_ratio))

        if color == WHITE:
            score += val + pst_val
        else:
            score -= val + pst_val

        # Count bishops and pawns
        if pt == BISHOP:
            if color == WHITE: white_bishops += 1
            else: black_bishops += 1
        if pt == PAWN:
            if color == WHITE: white_pawns[c] += 1
            else: black_pawns[c] += 1

        # Mobility (simplified - count moves for pieces)
        if pt in (KNIGHT, BISHOP, ROOK, QUEEN):
            mob = 0
            target_king_r = bkr if color == WHITE else wkr
            target_king_c = bkc if color == WHITE else wkc

            if pt == KNIGHT:
                for tsq in KNIGHT_MOVES[sq]:
                    tp = sqs[tsq]
                    if not tp or tp[0] != color:
                        mob += 1
                        tr, tc = tsq // 8, tsq % 8
                        if abs(tr - target_king_r) <= 2 and abs(tc - target_king_c) <= 2:
                            if color == WHITE: white_king_zone_attacks += 1
                            else: black_king_zone_attacks += 1
            elif pt == BISHOP or pt == QUEEN:
                for dr, dc in [(-1,-1),(-1,1),(1,-1),(1,1)]:
                    nr, nc = r+dr, c+dc
                    while 0 <= nr < 8 and 0 <= nc < 8:
                        tp = sqs[nr*8+nc]
                        if tp and tp[0] == color: break
                        mob += 1
                        if abs(nr - target_king_r) <= 2 and abs(nc - target_king_c) <= 2:
                            if color == WHITE: white_king_zone_attacks += 1
                            else: black_king_zone_attacks += 1
                        if tp: break
                        nr += dr; nc += dc
            if pt == ROOK or pt == QUEEN:
                for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                    nr, nc = r+dr, c+dc
                    while 0 <= nr < 8 and 0 <= nc < 8:
                        tp = sqs[nr*8+nc]
                        if tp and tp[0] == color: break
                        mob += 1
                        if abs(nr - target_king_r) <= 2 and abs(nc - target_king_c) <= 2:
                            if color == WHITE: white_king_zone_attacks += 1
                            else: black_king_zone_attacks += 1
                        if tp: break
                        nr += dr; nc += dc

            if color == WHITE: white_mobility += mob
            else: black_mobility += mob

    # Sindarov-style bonuses:

    # 1. Mobility bonus (Sindarov loves active pieces) - AMPLIFIED
    mob_bonus = (white_mobility - black_mobility) * 5

    # 2. Bishop pair (Sindarov uses bishops aggressively in Catalan structures)
    bp_bonus = 0
    if white_bishops >= 2: bp_bonus += 45
    if black_bishops >= 2: bp_bonus -= 45

    # 3. King zone attacks (Sindarov's signature: king pressure)
    # Increased weight - "his best games frequently show king pressure"
    attack_bonus = (white_king_zone_attacks - black_king_zone_attacks) * 12

    # 4. Doubled/isolated pawn penalties
    pawn_struct = 0
    for f in range(8):
        if white_pawns[f] > 1: pawn_struct -= 15 * (white_pawns[f] - 1)
        if black_pawns[f] > 1: pawn_struct += 15 * (black_pawns[f] - 1)
        # Isolated pawns
        wn = (white_pawns[f-1] if f > 0 else 0) + (white_pawns[f+1] if f < 7 else 0)
        bn = (black_pawns[f-1] if f > 0 else 0) + (black_pawns[f+1] if f < 7 else 0)
        if white_pawns[f] > 0 and wn == 0: pawn_struct -= 12
        if black_pawns[f] > 0 and bn == 0: pawn_struct += 12

    # 5. Central control bonus (Sindarov likes central tension)
    center_bonus = 0
    for csq in [27, 28, 35, 36]:  # d4, e4, d5, e5
        p = sqs[csq]
        if p:
            if p[0] == WHITE and p[1] == PAWN: center_bonus += 15
            elif p[0] == BLACK and p[1] == PAWN: center_bonus -= 15

    # 6. Initiative bonus (the side with more active pieces gets a bonus)
    # Sindarov's style: "activity matters more than static material balance"
    initiative = 0
    if white_king_zone_attacks > black_king_zone_attacks + 2:
        initiative += 20  # White has attacking initiative
    if black_king_zone_attacks > white_king_zone_attacks + 2:
        initiative -= 20

    # 7. King safety (penalize exposed kings more heavily)
    # Sindarov exploits king weaknesses ruthlessly
    king_safety = 0
    # Pawn shield for white king
    if wkc >= 5:  # Kingside castled
        for dc in [-1, 0, 1]:
            fc = wkc + dc
            if 0 <= fc < 8:
                shield_sq = (wkr - 1) * 8 + fc if wkr > 0 else -1
                if shield_sq >= 0 and sqs[shield_sq] == (WHITE, PAWN):
                    king_safety += 12
    elif wkc <= 2:  # Queenside castled
        for dc in [-1, 0, 1]:
            fc = wkc + dc
            if 0 <= fc < 8:
                shield_sq = (wkr - 1) * 8 + fc if wkr > 0 else -1
                if shield_sq >= 0 and sqs[shield_sq] == (WHITE, PAWN):
                    king_safety += 12

    if bkc >= 5:
        for dc in [-1, 0, 1]:
            fc = bkc + dc
            if 0 <= fc < 8:
                shield_sq = (bkr + 1) * 8 + fc if bkr < 7 else -1
                if shield_sq >= 0 and sqs[shield_sq] == (BLACK, PAWN):
                    king_safety -= 12
    elif bkc <= 2:
        for dc in [-1, 0, 1]:
            fc = bkc + dc
            if 0 <= fc < 8:
                shield_sq = (bkr + 1) * 8 + fc if bkr < 7 else -1
                if shield_sq >= 0 and sqs[shield_sq] == (BLACK, PAWN):
                    king_safety -= 12

    # 8. Passed pawns (important endgame factor)
    passed = 0
    for sq in range(64):
        p = sqs[sq]
        if not p or p[1] != PAWN: continue
        r, c = sq // 8, sq % 8
        is_passed = True
        if p[0] == WHITE:
            for pr in range(r-1, -1, -1):
                for dc in [-1, 0, 1]:
                    fc = c + dc
                    if 0 <= fc < 8:
                        pp = sqs[pr*8+fc]
                        if pp and pp[0] == BLACK and pp[1] == PAWN:
                            is_passed = False
                            break
                if not is_passed: break
            if is_passed:
                passed += (7 - r) * (7 - r) * 3  # Closer to promotion = more bonus
        else:
            for pr in range(r+1, 8):
                for dc in [-1, 0, 1]:
                    fc = c + dc
                    if 0 <= fc < 8:
                        pp = sqs[pr*8+fc]
                        if pp and pp[0] == WHITE and pp[1] == PAWN:
                            is_passed = False
                            break
                if not is_passed: break
            if is_passed:
                passed -= r * r * 3

    total = score + mob_bonus + bp_bonus + attack_bonus + pawn_struct + \
            center_bonus + initiative + int(king_safety * phase_ratio) + \
            int(passed * (1 - phase_ratio))

    # Return from side-to-move perspective
    return total if board.side == WHITE else -total


# ============================================================================
# OPENING BOOK - Sindarov's repertoire
# ============================================================================

def get_sindarov_opening_move(board):
    """
    Opening book based on Sindarov's known repertoire:
    As White: 1.d4, Catalan (g3 systems), QGD Exchange
    As Black vs 1.e4: Sicilian/dynamic systems
    As Black vs 1.d4: QGD/Catalan defenses
    """
    # Build a position signature from piece placement
    fen_pos = []
    for sq in range(64):
        p = board.squares[sq]
        if p:
            fen_pos.append((sq, p))

    # Count move number roughly
    move_num = board.fullmove

    sqs = board.squares

    if board.side == WHITE:
        if move_num == 1:
            # Sindarov plays 1.d4 predominantly
            return (51, 35, 0)  # d2d4

        if move_num == 2:
            # Check if Black played Nf6
            if sqs[21] == (BLACK, KNIGHT):  # f6
                # 2.c4 (standard Sindarov)
                if not sqs[34]:  # c4 is free
                    return (50, 34, 0)  # c2c4
            # If Black played d5
            if sqs[27] == (BLACK, PAWN):  # d5
                return (50, 34, 0)  # c2c4

        if move_num == 3:
            # After 1.d4 Nf6 2.c4 e6 -> 3.g3 (Catalan!)
            # After 1.d4 d5 2.c4 e6 -> 3.Nf3 or 3.Nc3
            if sqs[20] == (BLACK, PAWN):  # e6
                if sqs[34] == (WHITE, PAWN):  # c4 played
                    # 3.g3 - Catalan! Sindarov's favorite
                    if not sqs[46]:
                        return (54, 46, 0)  # g2g3
            if sqs[27] == (BLACK, PAWN) and sqs[34] == (WHITE, PAWN):
                # QGD - play Nc3 or Nf3
                if not sqs[45]:
                    return (62, 45, 0)  # Ng1f3

        if move_num == 4:
            # Catalan: after g3, play Bg2
            if sqs[46] == (WHITE, PAWN):  # g3 played
                if sqs[54] == (WHITE, BISHOP):  # Bf1 still home
                    return (61, 54, 0)  # wrong, let me fix
                # Actually Bg2 = f1 to g2
                if not sqs[54] and sqs[61] == (WHITE, BISHOP):
                    return (61, 54, 0)  # Bf1g2... wait g2 = sq 54
                    # g2 is row 6, col 6 = 6*8+6 = 54. f1 is row 7, col 5 = 61
                    # But g3 pawn is at 46 (row 5, col 6)
                    # Actually after g3, pawn is at g3. Bg2 means bishop to g2.
                    # g2 = index 54. But if g3 pawn at 46, g2 at 54 should be empty.
                    # Hmm wait - g2 = row 6, col 6 = 54. g3 = row 5, col 6 = 46.
                    # After g2g3, g2 (54) is empty, g3 (46) has pawn. Bishop goes f1(61)->g2(54)
                    pass

    return None  # No book move


OPENING_BOOK = {
    # White openings - Sindarov's 1.d4 repertoire
    # Starting position
    "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w": "d2d4",

    # After 1.d4 Nf6
    "rnbqkb1r/pppppppp/5n2/8/3P4/8/PPP1PPPP/RNBQKBNR w": "c2c4",
    # After 1.d4 d5
    "rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w": "c2c4",
    # After 1.d4 e6
    "rnbqkbnr/pppp1ppp/4p3/8/3P4/8/PPP1PPPP/RNBQKBNR w": "c2c4",

    # Catalan setup: 1.d4 Nf6 2.c4 e6 -> 3.g3
    "rnbqkb1r/pppp1ppp/4pn2/8/2PP4/8/PP2PPPP/RNBQKBNR w": "g2g3",
    # 1.d4 d5 2.c4 e6 -> 3.Nf3
    "rnbqkbnr/ppp2ppp/4p3/3p4/2PP4/8/PP2PPPP/RNBQKBNR w": "g1f3",
    # 1.d4 Nf6 2.c4 g6 -> 3.Nc3 (vs KID)
    "rnbqkb1r/pppppp1p/5np1/8/2PP4/8/PP2PPPP/RNBQKBNR w": "b1c3",

    # Catalan: 1.d4 Nf6 2.c4 e6 3.g3 d5 -> 4.Bg2
    "rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/6P1/PP2PP1P/RNBQKBNR w": "f1g2",
    # Catalan: 1.d4 Nf6 2.c4 e6 3.g3 Bb4+ -> various
    # Catalan: after Bg2 -> Nf3
    "rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/6P1/PP2PPBP/RNBQK1NR w": "g1f3",
    # Catalan: 1.d4 d5 2.c4 e6 3.Nf3 Nf6 4.g3
    "rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/5N2/PP2PPPP/RNBQKB1R w": "g2g3",

    # QGD Exchange: 1.d4 d5 2.c4 e6 3.Nc3 Nf6 4.cxd5
    "rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/2N5/PP2PPPP/R1BQKBNR w": "c4d5",

    # After 1.d4 Nf6 2.c4 c5 (Benoni) -> 3.d5
    "rnbqkb1r/pp1ppppp/5n2/2p5/2PP4/8/PP2PPPP/RNBQKBNR w": "d4d5",

    # Black responses - Sindarov as Black vs 1.e4
    "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b": "c7c5",  # Sicilian
    # Sicilian: after 2.Nf3
    "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b": "d7d6",
    # Sicilian Najdorf setup: after 3.d4 cxd4 4.Nxd4
    "rnbqkbnr/pp2pppp/3p4/8/3NP3/8/PPP2PPP/RNBQKB1R b": "g8f6",
    # After Nf6, play a6 (Najdorf)
    "rnbqkb1r/pp2pppp/3p1n2/8/3NP3/8/PPP2PPP/RNBQKB1R b": "a7a6",

    # Black vs 1.d4 -> Nf6 (flexible)
    "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b": "g8f6",
    # After 1.d4 Nf6 2.c4 -> e6 (QGD/Catalan defense)
    "rnbqkb1r/pppppppp/5n2/8/2PP4/8/PP2PPPP/RNBQKBNR b": "e7e6",
    # After 1.d4 Nf6 2.c4 e6 3.Nf3 -> d5
    "rnbqkb1r/pppp1ppp/4pn2/8/2PP4/5N2/PP2PPPP/RNBQKB1R b": "d7d5",
    # After 1.d4 Nf6 2.c4 e6 3.g3 -> d5 (accepting Catalan)
    "rnbqkb1r/pppp1ppp/4pn2/8/2PP4/6P1/PP2PP1P/RNBQKBNR b": "d7d5",

    # Black vs 1.c4 (English)
    "rnbqkbnr/pppppppp/8/8/2P5/8/PP1PPPPP/RNBQKBNR b": "g8f6",
    # Black vs 1.Nf3
    "rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R b": "d7d5",

    # Black vs 1.e4 c5 2.Nf3 d6 3.d4 -> cxd4
    "rnbqkbnr/pp2pppp/3p4/2p5/3PP3/5N2/PPP2PPP/RNBQKB1R b": "c5d4",
}


def lookup_book(board):
    """Look up position in opening book."""
    # Build a minimal FEN (just position + side)
    fen_parts = []
    for r in range(8):
        empty = 0
        row_str = ""
        for c in range(8):
            p = board.squares[r * 8 + c]
            if p:
                if empty:
                    row_str += str(empty)
                    empty = 0
                row_str += PIECE_CHARS[p]
            else:
                empty += 1
        if empty:
            row_str += str(empty)
        fen_parts.append(row_str)
    pos_fen = "/".join(fen_parts) + " " + ("w" if board.side == WHITE else "b")

    if pos_fen in OPENING_BOOK:
        uci = OPENING_BOOK[pos_fen]
        frm = (8 - int(uci[1])) * 8 + (ord(uci[0]) - ord('a'))
        to = (8 - int(uci[3])) * 8 + (ord(uci[2]) - ord('a'))
        promo = 0
        if len(uci) == 5:
            promo = {'q': QUEEN, 'r': ROOK, 'b': BISHOP, 'n': KNIGHT}[uci[4]]
        return (frm, to, promo)
    return None


# ============================================================================
# SEARCH - Alpha-Beta with Quiescence
# ============================================================================

class Searcher:
    def __init__(self):
        self.nodes = 0
        self.start_time = 0
        self.time_limit = 4.5  # seconds (leave margin from 5s limit)
        self.best_move = None
        self.tt = {}  # Transposition table
        self.history = [[0]*64 for _ in range(64)]  # History heuristic
        self.killer_moves = [[(None, None)] * 2 for _ in range(64)]
        self.stopped = False

    def check_time(self):
        if self.nodes & 4095 == 0:
            if time.time() - self.start_time > self.time_limit:
                self.stopped = True

    def move_score(self, board, move, ply):
        """Score move for ordering. Higher = search first."""
        frm, to, promo = move
        sqs = board.squares
        captured = sqs[to]
        piece = sqs[frm]

        # Promotion
        if promo:
            return 900000 + PIECE_VALUES.get(promo, 0)

        # Captures: MVV-LVA
        if captured:
            return 100000 + PIECE_VALUES[captured[1]] * 10 - PIECE_VALUES[piece[1]]

        # Killer moves
        killers = self.killer_moves[ply] if ply < 64 else [(None, None)]
        if move == killers[0]:
            return 90000
        if move == killers[1]:
            return 89000

        # History heuristic
        return self.history[frm][to]

    def order_moves(self, board, moves, ply):
        """Order moves for better alpha-beta pruning."""
        scored = [(self.move_score(board, m, ply), m) for m in moves]
        scored.sort(key=lambda x: -x[0])
        return [m for _, m in scored]

    def quiescence(self, board, alpha, beta, depth=0):
        """Quiescence search - only look at captures to avoid horizon effect."""
        self.nodes += 1
        if self.stopped:
            return 0

        stand_pat = evaluate(board)
        if stand_pat >= beta:
            return beta
        if stand_pat > alpha:
            alpha = stand_pat

        if depth > 8:
            return stand_pat

        # Generate capture moves only
        side = board.side
        moves = []
        for move in board.generate_pseudo_legal_moves():
            frm, to, promo = move
            captured = board.squares[to]
            is_ep = (board.squares[frm] and board.squares[frm][1] == PAWN and
                     to == board.ep and board.ep != -1)
            if captured or promo or is_ep:
                moves.append(move)

        # Order captures by MVV-LVA
        def cap_score(m):
            f, t, p = m
            cap = board.squares[t]
            pc = board.squares[f]
            s = 0
            if cap: s += PIECE_VALUES[cap[1]] * 10 - PIECE_VALUES[pc[1]]
            if p: s += PIECE_VALUES[p]
            return s
        moves.sort(key=cap_score, reverse=True)

        for move in moves:
            if self.stopped:
                return 0

            # Delta pruning
            frm, to, promo = move
            captured = board.squares[to]
            delta = PIECE_VALUES[captured[1]] if captured else 0
            if promo: delta += PIECE_VALUES[promo] - 100
            if stand_pat + delta + 200 < alpha and not promo:
                continue

            undo = board.make_move(move)
            if board.in_check(side):
                board.unmake_move(undo)
                continue

            score = -self.quiescence(board, -beta, -alpha, depth + 1)
            board.unmake_move(undo)

            if score >= beta:
                return beta
            if score > alpha:
                alpha = score

        return alpha

    def alpha_beta(self, board, depth, alpha, beta, ply, do_null=True):
        """Alpha-beta search with enhancements."""
        self.nodes += 1
        self.check_time()
        if self.stopped:
            return 0

        # Draw detection
        if board.halfmove >= 100:
            return 0

        orig_alpha = alpha
        side = board.side
        in_check = board.in_check(side)

        # Check extension
        if in_check:
            depth += 1

        if depth <= 0:
            return self.quiescence(board, alpha, beta)

        # Null move pruning (Sindarov-style: aggressive pruning for tempo)
        if do_null and not in_check and depth >= 3:
            # Don't do null move in very simple endgames
            mat = count_material(board, side) - PIECE_VALUES[KING]
            if mat > 500:  # Has enough material
                # Make null move
                old_ep = board.ep
                board.side = 1 - side
                board.ep = -1
                R = 3 if depth >= 6 else 2
                score = -self.alpha_beta(board, depth - 1 - R, -beta, -beta + 1, ply + 1, False)
                board.side = side
                board.ep = old_ep

                if self.stopped:
                    return 0
                if score >= beta:
                    return beta

        moves = board.generate_legal_moves()
        if not moves:
            if in_check:
                return -20000 + ply  # Checkmate (prefer shorter mates)
            return 0  # Stalemate

        moves = self.order_moves(board, moves, ply)

        best_move = moves[0]
        best_score = -99999
        moves_searched = 0

        for move in moves:
            if self.stopped:
                return 0

            undo = board.make_move(move)

            # Late Move Reduction (LMR)
            if moves_searched >= 4 and depth >= 3 and not in_check and \
               not board.squares[move[1]] and not move[2] and \
               not board.in_check(board.side):
                # Reduced search
                score = -self.alpha_beta(board, depth - 2, -alpha - 1, -alpha, ply + 1)
                if score <= alpha:
                    board.unmake_move(undo)
                    moves_searched += 1
                    continue
            
            # PVS
            if moves_searched == 0:
                score = -self.alpha_beta(board, depth - 1, -beta, -alpha, ply + 1)
            else:
                score = -self.alpha_beta(board, depth - 1, -alpha - 1, -alpha, ply + 1)
                if score > alpha and score < beta:
                    score = -self.alpha_beta(board, depth - 1, -beta, -alpha, ply + 1)

            board.unmake_move(undo)

            if score > best_score:
                best_score = score
                best_move = move

            if score > alpha:
                alpha = score
                self.history[move[0]][move[1]] += depth * depth

            if alpha >= beta:
                # Update killers
                if not board.squares[move[1]]:  # Quiet move
                    if ply < 64:
                        self.killer_moves[ply][1] = self.killer_moves[ply][0]
                        self.killer_moves[ply][0] = move
                break

            moves_searched += 1

        if ply == 0:
            self.best_move = best_move

        return best_score

    def search(self, board, max_time=4.5):
        """Iterative deepening search."""
        self.start_time = time.time()
        self.time_limit = max_time
        self.stopped = False
        self.nodes = 0
        self.best_move = None

        # Check opening book first
        book_move = lookup_book(board)
        if book_move:
            # Verify it's legal
            legal = board.generate_legal_moves()
            if book_move in legal:
                return book_move

        legal = board.generate_legal_moves()
        if not legal:
            return None
        if len(legal) == 1:
            return legal[0]

        self.best_move = legal[0]
        best_overall = legal[0]

        # Iterative deepening
        for depth in range(1, 50):
            score = self.alpha_beta(board, depth, -99999, 99999, 0)

            if self.stopped:
                break

            best_overall = self.best_move

            # Mate found - no need to search deeper
            if abs(score) > 19000:
                break

            elapsed = time.time() - self.start_time
            if elapsed > max_time * 0.6:
                break

        return best_overall


# ============================================================================
# UCI INTERFACE
# ============================================================================

def move_to_uci(move):
    frm, to, promo = move
    uci = ""
    uci += chr(ord('a') + frm % 8) + str(8 - frm // 8)
    uci += chr(ord('a') + to % 8) + str(8 - to // 8)
    if promo:
        uci += {QUEEN: 'q', ROOK: 'r', BISHOP: 'b', KNIGHT: 'n'}[promo]
    return uci


def main():
    import readline as rl_module
    searcher = Searcher()

    for line in sys.stdin:
        fen = line.strip()
        if not fen:
            continue

        try:
            board = Board.from_fen(fen)
            move = searcher.search(board, max_time=4.5)

            if move:
                uci = move_to_uci(move)
                sys.stdout.write(uci + "\n")
                sys.stdout.flush()
            else:
                # No legal moves - shouldn't happen if FEN is valid
                legal = board.generate_legal_moves()
                if legal:
                    sys.stdout.write(move_to_uci(legal[0]) + "\n")
                    sys.stdout.flush()

            # Reset search state for next position but keep some learning
            searcher.stopped = False
            searcher.tt.clear()
            searcher.nodes = 0

        except Exception as e:
            # Fallback: play any legal move
            try:
                board = Board.from_fen(fen)
                legal = board.generate_legal_moves()
                if legal:
                    sys.stdout.write(move_to_uci(legal[0]) + "\n")
                    sys.stdout.flush()
            except:
                sys.stdout.write("e2e4\n")
                sys.stdout.flush()


if __name__ == "__main__":
    main()
