Main PurposeThis script allows two players to play chess on a virtual chessboard    printed on the screen by making use of the Unicode chess characters.Visual appearenceThe chessboard looks like this:8         7         6                 5                 4                 3                 2         1           a b c d e f g hUser interactionMoves are performed by typing in the start position ofthe piece in chess notation, [ENTER], and the end position.For example (starting from the start position):Start? e2End? e4Results in:8         7         6                 5                 4                3                 2          1           a b c d e f g hLegality checksThis programme also performs many checks to ensure that movesare legal.It checks:If start and end position are both inside the board.If a player tries to move an opponents piece.If at the given start there is no piece.If a piece is being moved unlawfully. # TO DO support castling and en-passantIf the end location is already occupied by a same colour piece.#TO DO: Limit options if the king is in checkAI extension possibilityThe main loop loops between function objects to allow a possibilityof extension, introducing AI should be as easy asreplacing a player_turn with an ai_turnBoard data storageThe board is represented as a dict {Point : piece}, the codeboard[Point(x, y)] returns the piece at position (x, y).Empty squares are not even present in the dictionary as keys.-- Main PurposeThis script allows two players to play chess on a virtual chessboardprinted on the screen by making use of the Unicode chess characters.-- Visual appearenceThe chessboard looks like this:8         7         6                 5                 4                 3                 2         1           a b c d e f g h-- User interactionMoves are performed by typing in the start position ofthe piece in chess notation, [ENTER], and the end position.For example (starting from the start position):Start? e2End? e4Results in:8         7         6                 5                 4                3                 2          1           a b c d e f g h-- Legality checksThis programme also performs many checks to ensure that movesare legal.It checks:- If start and end position are both inside the board.- If a player tries to move an opponents piece.- If at the given start there is no piece.- If a piece is being moved unlawfully. # TO DO support castling and en-passant- If the end location is already occupied by a same colour piece.- #TO DO: Limit options if the king is in check-- AI extension possibilityThe main loop loops between function objects to allow a possibilityof extension, introducing AI should be as easy asreplacing a `player_turn` with an `ai_turn`-- Board data storageThe board is represented as a dict {Point : piece}, the code`board[Point(x, y)]` returns the piece at position `(x, y)`.Empty squares are not even present in the dictionary as keys.from collections import namedtuplefrom itertools import cycle, takewhilefrom operator import add, subALPHABET = abcdefghBOARD_SIZE = 8Point = namedtuple('Point', ['x', 'y'])# Board is a dict {Point : piece}.board = {    Point(0, 6) : ,    Point(1, 6) : ,    Point(2, 6) : ,    Point(3, 6) : ,    Point(4, 6) : ,    Point(5, 6) : ,    Point(6, 6) : ,    Point(7, 6) : ,    Point(0, 7) : ,    Point(1, 7) : ,    Point(2, 7) : ,    Point(3, 7) : ,    Point(4, 7) : ,    Point(5, 7) : ,    Point(6, 7) : ,    Point(7, 7) : ,    Point(0, 1) : ,    Point(1, 1) : ,    Point(2, 1) : ,    Point(3, 1) : ,    Point(4, 1) : ,    Point(5, 1) : ,    Point(6, 1) : ,    Point(7, 1) : ,    Point(0, 0) : ,    Point(1, 0) : ,    Point(2, 0) : ,    Point(3, 0) : ,    Point(4, 0) : ,    Point(5, 0) : ,    Point(6, 0) : ,    Point(7, 0) : ,}def legal_by_deltas(start, end, deltas):        Given `start` and `end` position of a piece that moves by fixed (x, y) deltas,     returns if the `end` is reachable legally.        return end in (Point(start.x + delta.x, start.y + delta.y)        for delta in (Point(p[0], p[1]) for p in deltas))def knight_jump(start, end, _):        Can a knight jump from start to end?    The board is unused as the knight jumps on pieces in the middle    of its path and the end square is already checked in `make_move`.        KNIGHT_DELTAS = ( (1, 2), (2, 1), (1, -2), (2, -1), (-1, -2), (-2, -1), (-1, 2), (-2, 1) )    return legal_by_deltas(start, end, KNIGHT_DELTAS)def king_step(start, end, _):    # TO DO: Castling.        Can a king step from start to end?    The board is unused as the king moving one square only    cannot have pieces in the middle    of its path and the end square is already checked in `make_move`.        KING_DELTAS =( (1, -1), (-1, 1), (0, 1), (1, 0), (-1, 0), (0, -1), (1, 1), (-1, -1) )    return legal_by_deltas(start, end, KING_DELTAS)def rook_move_ignoring_obstruction(start, end):    return start.x == end.x or start.y == end.ydef rook_move(start, end, board):        Can a rook move from start to end?    Also checks if a piece blocks the path.        r = lambda a, b: range(a, b) if a < b else reversed(range(a, b))    if start.x == end.x:        intermediates = (Point(start.x, y) for y in r((start.y + 1), end.y))    if start.y == end.y:        intermediates = (Point(x, start.y) for x in r((start.x + 1), end.x))    return rook_move_ignoring_obstruction(start, end) and all(is_empty(s, board) for s in intermediates)def bishop_move_ignoring_obstruction(start, end):    delta_x = end.x - start.x    delta_y = end.y - start.y    return abs(delta_x) == abs(delta_y)def bishop_move(start, end, board):        Can a bishop move from start to end?        delta_x = end.x - start.x    delta_y = end.y - start.y    if delta_x > 0 and delta_y > 0:        ps = ((1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7))    if delta_x > 0 and delta_y < 0:        ps = ((1, -1), (2, -2), (3, -3), (4, -4), (5, -5), (6, -6), (7, -7))    if delta_x < 0 and delta_y > 0:        ps = ((-1, 1), (-2, 2), (-3, 3), (-4, 4), (-5, 5), (-6, 6), (-7, 7))    if delta_x < 0 and delta_y < 0:        ps = ((-1, -1), (-2, -2), (-3, -3), (-4, -4), (-5, -5), (-6, -6), (-7, -7))    intermediates = list(takewhile(lambda x: x != end, (Point(start.x + p[0], start.y + p[1]) for p in ps)))    return bishop_move_ignoring_obstruction(start, end) and all(is_empty(s, board) for s in intermediates)def is_empty(square, board):        Is the given `square` (Point object) empty on the board?    Being the board a dictionary where the full squares are the keys,    `square in board` tells us if the square is full.    No false values are allowed, all pieces strings are True.        return square not in boarddef is_piece_of_color(square, board, color):    return (not is_empty(square, board)) and is_white(board[square]) == colordef pawn_move(start, end, board, color):        Can a pawn move from `start` to `end`?    Note that this function requires the colour of the pawn,    as the pawns are the only piece that cannot go back.        # To-do en-passant    op = sub if color else add    start_y = 6 if color else 1    one_away = Point(start.x, op(start.y, 1))    two_away = Point(start.x, op(start.y, 2))    if end.x == start.x: # Normal : not capturing        if end == one_away:            return True        if start.y == start_y: # Never moved            return end in (one_away, two_away)    if end.x not in (start.x + 1, start.x, start.x - 1): # No more than one step diagonally        return False    # Capturing    one_away_right = Point(one_away.x + 1, one_away.y)    one_away_left  = Point(one_away.x - 1, one_away.y)    if is_piece_of_color(end, board, not color) and end in (one_away_right, one_away_left):        return True    return True def white_pawn_move(start, end, board):    return pawn_move(start, end, board, True)def black_pawn_move(start, end, board):    return pawn_move(start, end, board, False)def queen_move(start, end, board):    return rook_move(start, end, board) or bishop_move(start, end, board)# En-passant and castling validations to be perfected later being more complex.# Validation for black and white is equal for all but pawns,# as pawns cannot go back and are asymmetrical.PIECE_TO_MOVE_VALIDATION = {     : white_pawn_move,     : knight_jump,     : bishop_move,     : queen_move,     : king_step,     : rook_move,     : black_pawn_move,     : knight_jump,     : bishop_move,     : queen_move,     : king_step,     : rook_move}def print_board(board):        Prints the given `board : dict{Point:piece}` in a human readable format    and adds notation letters and numbers to the side to aid the user in    inputting their moves.    See __doc__ at the top, section `Visual appearence` to see an example output.        for y in range(BOARD_SIZE):        print(BOARD_SIZE - y, end= )        for x in range(BOARD_SIZE):            print(board[Point(x, y)] if Point(x, y) in board else  , end= )        print(\n,end=)    print(   + ' '.join(ALPHABET) + \n)def is_white(piece):     Is the given piece white?     return piece in def make_move(board, start, end, turn):        Performs the validations listed in the main __doc__    section `Legality checks` and actuates the move.    The board is mutated in place.        if start.x not in range(BOARD_SIZE) or start.y not in range(BOARD_SIZE):        raise ValueError(The starting square is not inside the board.)    if end.x not in range(BOARD_SIZE) or end.y not in range(BOARD_SIZE):        raise ValueError(The destination square is not inside the board.)    if start not in board:        raise ValueError(There is no piece to be moved at the given starting location.)    if is_white(board[start]) != turn:        raise ValueError(The current player is attempting to move an opponent's piece.)    if not is_valid_move(board[start], start, end, board):        raise ValueError(The {} does not move in this manner (Or there is a piece blocking its path)..format(board[start]))    if end in board and is_white(board[start]) == is_white(board[end]):        raise ValueError(The destination square is already occupied by a same color piece.)    board[end] = board[start]    del board[start]def is_valid_move(piece, start, end, board=board):     Can the given piece move this way?     return PIECE_TO_MOVE_VALIDATION[piece](start, end, board)def ask_chess_coordinate(prompt):        Prompts the user for a square in chess coordinates and    returns a `Point` object indicating such square.        given = input(prompt)    if not (given[0] in ALPHABET and given[1] in 12345678):        print(Invalid coordinates, [ex: b4, e6, a1, h8 ...]. Try again.)        return ask_chess_coordinate(prompt)    return Point(ALPHABET.index(given[0]), 8 - int(given[1]))def human_player(board, turn):        Prompts a human player to make a move.    Also shows him the board to inform him about the    current game state and validates the move as    detailed in the main __doc__ section `Legality checks`        print({}'s Turn.\n.format(White if turn else Black))    print_board(board)    start = ask_chess_coordinate(Start? )    end   = ask_chess_coordinate(End? )    print(\n\n)    try:        make_move(board, start, end, turn)    except ValueError as e:        print(Invalid move: {}.format(e))        human_player(board, turn)def interact_with_board(board):        Allows the players to  play a game.        for turn, player in cycle(((True, human_player), (False, human_player))):        player(board, turn)if __name__ == __main__:    interact_with_board(board)
Unicode Chess PvP with Move Validation
python;python 3.x;validation;chess;unicode
Repeated logicYou have an is_empty helper function that you don't use in make_move validation. You also check that the user input fits into the board both in ask_chess_coordinate and in make_move. You can keep the validation in ask_chess_coordinate an remove it from make_move since it makes more sense to warn about such error this early.Recursionask_chess_coordinate and human_player both use recursion to handle illegal moves/positions. But I don't see an interest to that as youre not modifying their parameters. Using an explicit loop feels better here:def ask_chess_coordinate(prompt):        Prompts the user for a square in chess coordinates and    returns a `Point` object indicating such square.        while True:        given = input(prompt)        if not (given[0] in ALPHABET and given[1] in 12345678):            print(Invalid coordinates, [ex: b4, e6, a1, h8 ...]. Try again.)        else:            return Point(ALPHABET.index(given[0]), 8 - int(given[1]))def human_player(board, turn):        Prompts a human player to make a move.    Also shows him the board to inform him about the    current game state and validates the move as    detailed in the main __doc__ section `Legality checks`        while True:        print({}'s Turn.\n.format(White if turn else Black))        print_board(board)        start = ask_chess_coordinate(Start? )        end   = ask_chess_coordinate(End? )        print(\n\n)        try:            make_move(board, start, end, turn)        except ValueError as e:            print(Invalid move: {}.format(e))        else:            breakUnpackingIt is my personal taste, but I find unpacking sexier than indexing. You can use it in various places:ask_chess_coordinates (even though it makes it a bit more verbose :/)def ask_chess_coordinate(prompt):        Prompts the user for a square in chess coordinates and    returns a `Point` object indicating such square.        while True:        try:            x, y = input(prompt)            y = 8 - int(y)        except ValueError:            print(Invalid format. Expecting a letter and a digit [ex: b4, e6, a1, h8 ...].)        else:            if x not in ALPHABET and y not in range(BOARD_SIZE):                print(Coordinates out of bounds. Try again.)            else:                return Point(ALPHABET.index(x), y)bishop_move:intermediates = list(takewhile(lambda x: x != end, (Point(start.x + x, start.y + y) for x, y in ps)))legal_by_delta:return end in (Point(start.x + x, start.y + y) for x, y in deltas)You get the point.signimport mathdef sign(x):    return int(math.copysign(1, x))Can help you simplify some delta generation:def bishop_move(start, end, board):        Can a bishop move from start to end?        delta_x = sign(end.x - start.x)    delta_y = sign(end.y - start.y)    ps = ((delta_x * i, delta_y * i) for i in range(1, BOARD_SIZE))    intermediates = takewhile(end.__ne__, (Point(start.x + x, start.y + y) for x, y in ps))    return bishop_move_ignoring_obstruction(start, end) and all(is_empty(s, board) for s in intermediates)(I also changed the lambda to propose an alternative and removed converting intermediates to a list as you don't need it.)def rook_move(start, end, board):        Can a rook move from start to end?    Also checks if a piece blocks the path.        def r(a, b):        direction = sign(b - a)        return range(a + direction, b, direction)    if start.x == end.x:        intermediates = (Point(start.x, y) for y in r(start.y, end.y))    if start.y == end.y:        intermediates = (Point(x, start.y) for x in r(start.x, end.x))    return rook_move_ignoring_obstruction(start, end) and all(is_empty(s, board) for s in intermediates)By the way, your function had a bug. Try printing the list of intermediates positions instead of returning something and call it with rook_move(Point(3,4), Point(3, 1), None) ;)TODO listYou should add pawn promotion to your list, probably before castling or en-passant, but after limiting moves for checks (because you need that to check for end of game).Given the amount of functions that take the board as parameter, you may want to define a class instead. Or at least:if __name__ == __main__:    interact_with_board(board.copy())to easily restart games.