#!/usr/bin/env python3
"""
Terminal Chess - Two-player, human vs human
Uses Unicode chess pieces
"""

import re
import sys

# Unicode chess pieces
PIECES = {
    'P': '♙', 'N': '♘', 'B': '♗', 'R': '♖', 'Q': '♕', 'K': '♔',
    'p': '♟', 'n': '♞', 'b': '♝', 'r': '♜', 'q': '♛', 'k': '♚'
}

# Board representation: 8x8 list, None for empty, piece character otherwise
# Uppercase = White, lowercase = Black

def create_initial_board():
    """Create the standard starting position."""
    board = [[None for _ in range(8)] for _ in range(8)]
    
    # Set up pawns
    for col in range(8):
        board[1][col] = 'p'
        board[6][col] = 'P'
    
    # Set up other pieces
    pieces = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']
    for col in range(8):
        board[0][col] = pieces[col].upper()
        board[7][col] = pieces[col]
    
    return board

def print_board(board):
    """Print the board with coordinates."""
    print("  a b c d e f g h")
    for row in range(7, -1, -1):
        print(f"{row+1} ", end="")
        for col in range(8):
            piece = board[row][col]
            if piece is None:
                print(". ", end="")
            else:
                print(f"{PIECES.get(piece, piece)} ", end="")
        print()
    print()

def is_white(piece):
    """Check if piece is white."""
    return piece is not None and piece.isupper()

def is_black(piece):
    """Check if piece is black."""
    return piece is not None and piece.islower()

def get_color(piece):
    """Return 'white', 'black', or None."""
    if piece is None:
        return None
    return 'white' if piece.isupper() else 'black'

def in_bounds(row, col):
    """Check if coordinates are within board bounds."""
    return 0 <= row < 8 and 0 <= col < 8

def find_king(board, color):
    """Find the king's position for the given color."""
    king_char = 'K' if color == 'white' else 'k'
    for row in range(8):
        for col in range(8):
            if board[row][col] == king_char:
                return (row, col)
    return None

def is_square_attacked(board, row, col, attacker_color):
    """Check if a square is attacked by any piece of the given color."""
    # Check all directions for attacks
    
    # Pawn attacks
    pawn_dir = -1 if attacker_color == 'white' else 1  # pawns move toward increasing row index for black, decreasing for white
    for dc in [-1, 1]:
        pr, pc = row + pawn_dir, col + dc
        if in_bounds(pr, pc):
            piece = board[pr][pc]
            if piece and get_color(piece) == attacker_color:
                if (attacker_color == 'white' and piece == 'P') or (attacker_color == 'black' and piece == 'p'):
                    return True
    
    # Knight attacks
    knight_moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2),
                    (1, -2), (1, 2), (2, -1), (2, 1)]
    for dr, dc in knight_moves:
        r, c = row + dr, col + dc
        if in_bounds(r, c):
            piece = board[r][c]
            if piece and get_color(piece) == attacker_color:
                if piece.lower() == 'n':
                    return True
    
    # King attacks (adjacent squares)
    for dr in [-1, 0, 1]:
        for dc in [-1, 0, 1]:
            if dr == 0 and dc == 0:
                continue
            r, c = row + dr, col + dc
            if in_bounds(r, c):
                piece = board[r][c]
                if piece and get_color(piece) == attacker_color:
                    if piece.lower() == 'k':
                        return True
    
    # Sliding pieces (rook, queen, bishop)
    directions = [
        (-1, 0), (1, 0), (0, -1), (0, 1),  # rook/queen
        (-1, -1), (-1, 1), (1, -1), (1, 1)  # bishop/queen
    ]
    
    for i, (dr, dc) in enumerate(directions):
        r, c = row + dr, col + dc
        while in_bounds(r, c):
            piece = board[r][c]
            if piece:
                if get_color(piece) == attacker_color:
                    if i < 4 and piece.lower() in ['r', 'q']:  # straight lines
                        return True
                    if i >= 4 and piece.lower() in ['b', 'q']:  # diagonals
                        return True
                break  # Blocked by any piece
            r += dr
            c += dc
    
    return False

def is_in_check(board, color):
    """Check if the king of the given color is in check."""
    king_pos = find_king(board, color)
    if not king_pos:
        return False
    opponent = 'black' if color == 'white' else 'white'
    return is_square_attacked(board, king_pos[0], king_pos[1], opponent)

def get_all_moves(board, color):
    """Get all legal moves for the given color."""
    moves = []
    for row in range(8):
        for col in range(8):
            piece = board[row][col]
            if piece and get_color(piece) == color:
                # Get all pseudo-legal moves for this piece
                piece_moves = get_piece_moves(board, row, col, color)
                for move in piece_moves:
                    # Check if move leaves king in check
                    if not move_leaves_king_in_check(board, (row, col), move, color):
                        moves.append(((row, col), move))
    return moves

def move_leaves_king_in_check(board, start, end, color):
    """Check if making a move would leave the moving player's king in check."""
    # Make a copy of the board
    new_board = [row[:] for row in board]
    
    # Execute move on copy
    row1, col1 = start
    row2, col2 = end
    piece = new_board[row1][col1]
    captured = new_board[row2][col2]
    
    # Handle special moves
    if abs(piece) == 'k' and abs(col2 - col1) == 2:  # Castling
        if col2 == 6:  # Kingside
            new_board[row1][5] = new_board[row1][7]
            new_board[row1][7] = None
        elif col2 == 2:  # Queenside
            new_board[row1][3] = new_board[row1][0]
            new_board[row1][0] = None
    
    new_board[row2][col2] = piece
    new_board[row1][col1] = None
    
    # Check if own king is in check
    return is_in_check(new_board, color)

def get_piece_moves(board, row, col, color):
    """Get all pseudo-legal moves for a piece (doesn't check if king is left in check)."""
    moves = []
    piece = board[row][col]
    if not piece or get_color(piece) != color:
        return moves
    
    piece_type = piece.lower()
    
    if piece_type == 'p':
        moves.extend(get_pawn_moves(board, row, col, color))
    elif piece_type == 'n':
        moves.extend(get_knight_moves(board, row, col))
    elif piece_type == 'b':
        moves.extend(get_bishop_moves(board, row, col))
    elif piece_type == 'r':
        moves.extend(get_rook_moves(board, row, col))
    elif piece_type == 'q':
        moves.extend(get_queen_moves(board, row, col))
    elif piece_type == 'k':
        moves.extend(get_king_moves(board, row, col, color))
    
    return moves

def get_pawn_moves(board, row, col, color):
    """Get pawn moves including double push and captures."""
    moves = []
    direction = 1 if color == 'white' else -1
    start_row = 1 if color == 'white' else 6
    promotion_row = 7 if color == 'white' else 0
    
    # Forward move
    new_row = row + direction
    if in_bounds(new_row, col) and board[new_row][col] is None:
        moves.append((new_row, col))
        # Double push from starting position
        if row == start_row:
            new_row2 = row + 2 * direction
            if in_bounds(new_row2, col) and board[new_row2][col] is None:
                moves.append((new_row2, col))
    
    # Captures
    for dc in [-1, 1]:
        new_row = row + direction
        new_col = col + dc
        if in_bounds(new_row, new_col):
            target = board[new_row][new_col]
            if target and get_color(target) != color:
                moves.append((new_row, new_col))
    
    return moves

def get_knight_moves(board, row, col):
    """Get knight moves."""
    moves = []
    directions = [(-2, -1), (-2, 1), (-1, -2), (-1, 2),
                  (1, -2), (1, 2), (2, -1), (2, 1)]
    
    for dr, dc in directions:
        new_row, new_col = row + dr, col + dc
        if in_bounds(new_row, new_col):
            target = board[new_row][new_col]
            if target is None or get_color(target) != get_color(board[row][col]):
                moves.append((new_row, new_col))
    
    return moves

def get_sliding_moves(board, row, col, directions):
    """Get sliding piece moves (bishop, rook, queen)."""
    moves = []
    piece = board[row][col]
    piece_color = get_color(piece)
    
    for dr, dc in directions:
        new_row, new_col = row + dr, col + dc
        while in_bounds(new_row, new_col):
            target = board[new_row][new_col]
            if target is None:
                moves.append((new_row, new_col))
            elif get_color(target) != piece_color:
                moves.append((new_row, new_col))
                break
            else:
                break
            new_row += dr
            new_col += dc
    
    return moves

def get_bishop_moves(board, row, col):
    """Get bishop moves."""
    directions = [(-1, -1), (-1, 1), (1, -1), (1, 1)]
    return get_sliding_moves(board, row, col, directions)

def get_rook_moves(board, row, col):
    """Get rook moves."""
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    return get_sliding_moves(board, row, col, directions)

def get_queen_moves(board, row, col):
    """Get queen moves."""
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1),
                  (-1, -1), (-1, 1), (1, -1), (1, 1)]
    return get_sliding_moves(board, row, col, directions)

def get_king_moves(board, row, col, color):
    """Get king moves including castling."""
    moves = []
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1),
                  (-1, -1), (-1, 1), (1, -1), (1, 1)]
    
    for dr, dc in directions:
        new_row, new_col = row + dr, col + dc
        if in_bounds(new_row, new_col):
            target = board[new_row][new_col]
            if target is None or get_color(target) != color:
                moves.append((new_row, new_col))
    
    # Castling (only if king is not in check and path is clear)
    if not is_in_check(board, color):
        if color == 'white' and row == 7:
            # White castling
            if col == 4:
                # Kingside
                if (board[7][7] == 'R' and 
                    board[7][5] is None and board[7][6] is None and
                    not is_square_attacked(board, 7, 4, 'black') and
                    not is_square_attacked(board, 7, 5, 'black') and
                    not is_square_attacked(board, 7, 6, 'black')):
                    moves.append((7, 6))
                
                # Queenside
                if (board[7][0] == 'R' and 
                    board[7][1] is None and board[7][2] is None and board[7][3] is None and
                    not is_square_attacked(board, 7, 4, 'black') and
                    not is_square_attacked(board, 7, 3, 'black') and
                    not is_square_attacked(board, 7, 2, 'black')):
                    moves.append((7, 2))
        
        elif color == 'black' and row == 0:
            # Black castling
            if col == 4:
                # Kingside
                if (board[0][7] == 'r' and 
                    board[0][5] is None and board[0][6] is None and
                    not is_square_attacked(board, 0, 4, 'white') and
                    not is_square_attacked(board, 0, 5, 'white') and
                    not is_square_attacked(board, 0, 6, 'white')):
                    moves.append((0, 6))
                
                # Queenside
                if (board[0][0] == 'r' and 
                    board[0][1] is None and board[0][2] is None and board[0][3] is None and
                    not is_square_attacked(board, 0, 4, 'white') and
                    not is_square_attacked(board, 0, 3, 'white') and
                    not is_square_attacked(board, 0, 2, 'white')):
                    moves.append((0, 2))
    
    return moves

def parse_move(move_str):
    """Parse algebraic notation like 'e2 e4' or 'e2e4'."""
    # Remove spaces and normalize
    move_str = move_str.strip().replace(' ', '')
    
    if len(move_str) < 4:
        return None
    
    # Try to parse as two squares
    try:
        from_col = ord(move_str[0].lower()) - ord('a')
        from_row = int(move_str[1]) - 1
        to_col = ord(move_str[2].lower()) - ord('a')
        to_row = int(move_str[3]) - 1
        
        if not (0 <= from_col <= 7 and 0 <= from_row <= 7 and
                0 <= to_col <= 7 and 0 <= to_row <= 7):
            return None
        
        return ((from_row, from_col), (to_row, to_col))
    except (ValueError, IndexError):
        return None

def make_move(board, start, end):
    """Execute a move on the board."""
    row1, col1 = start
    row2, col2 = end
    piece = board[row1][col1]
    
    # Handle castling
    if piece.lower() == 'k' and abs(col2 - col1) == 2:
        if col2 == 6:  # Kingside
            board[row1][5] = board[row1][7]
            board[row1][7] = None
        elif col2 == 2:  # Queenside
            board[row1][3] = board[row1][0]
            board[row1][0] = None
    
    # Move piece
    board[row2][col2] = piece
    board[row1][col1] = None
    
    # Handle promotion (auto-queen for simplicity)
    if piece.lower() == 'p':
        if (get_color(piece) == 'white' and row2 == 7) or \
           (get_color(piece) == 'black' and row2 == 0):
            board[row2][col2] = 'Q' if get_color(piece) == 'white' else 'q'

def is_checkmate(board, color):
    """Check if the given color is in checkmate."""
    if not is_in_check(board, color):
        return False
    
    # Check if any legal move exists
    moves = get_all_moves(board, color)
    return len(moves) == 0

def is_stalemate(board, color):
    """Check if the given color is in stalemate."""
    if is_in_check(board, color):
        return False
    
    # Check if any legal move exists
    moves = get_all_moves(board, color)
    return len(moves) == 0

def main():
    """Main game loop."""
    board = create_initial_board()
    current_player = 'white'
    
    print("Welcome to Terminal Chess!")
    print("White pieces are uppercase (♙, ♘, etc.), Black pieces are lowercase (♟, ♞, etc.)")
    print("Enter moves in format: 'e2 e4' or 'e2e4' (from-to squares)")
    print()
    
    while True:
        print_board(board)
        
        # Check for game end conditions
        if is_checkmate(board, current_player):
            print(f"Checkmate! {'Black' if current_player == 'white' else 'White'} wins!")
            break
        elif is_stalemate(board, current_player):
            print("Stalemate! The game is a draw.")
            break
        
        # Check if current player is in check
        if is_in_check(board, current_player):
            print(f"{current_player.capitalize()} is in check!")
        
        # Get move from player
        while True:
            try:
                move_str = input(f"{current_player.capitalize()}'s move: ").strip()
                if move_str.lower() in ['quit', 'exit', 'q']:
                    print("Game aborted.")
                    return
                
                move = parse_move(move_str)
                if not move:
                    print("Invalid move format. Use 'e2 e4' or 'e2e4'.")
                    continue
                
                start, end = move
                piece = board[start[0]][start[1]]
                
                if not piece:
                    print("No piece at starting position.")
                    continue
                
                if get_color(piece) != current_player:
                    print("That's not your piece!")
                    continue
                
                # Check if move is legal
                legal_moves = get_all_moves(board, current_player)
                if (start, end) not in legal_moves:
                    print("Illegal move.")
                    continue
                
                # Make the move
                make_move(board, start, end)
                break
                
            except (ValueError, IndexError):
                print("Invalid move format. Use 'e2 e4' or 'e2e4'.")
        
        # Switch players
        current_player = 'black' if current_player == 'white' else 'white'
    
    print("Game over.")

if __name__ == "__main__":
    main()
