示例#1
0
    def can_promote_pawn(self, start_position, end_position):
        """
        Test if pawn promotion is possible for the provided position.

        :param start_position: string
            Algebraic notation position.
        :param end_position: string
            Algebraic notation position.
        :return:
        """
        try:
            ChessHelper.validate_position(start_position)
            ChessHelper.validate_position(end_position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            if self._board[start_position] is None:
                return False

            piece = self._board[start_position]
            if piece.type != Type.PAWN:
                return False

            _, start_row = self._board.position_to_row_and_column(
                start_position, piece.color)
            _, end_row = self._board.position_to_row_and_column(
                end_position, piece.color)
            if start_row == self._board.get_dimension(
            ) - 2 and end_row == self._board.get_dimension() - 1:
                return True

            return False
示例#2
0
    def _get_nearest_piece_in_direction(self,
                                        start_position,
                                        move_direction,
                                        piece_color,
                                        ghost_pieces=None):
        """
        Get the nearest piece from the starting position heading in the direction specified. Not expecting
        MoveDirection.l_shape as a direction.

        :param start_position: string
            Position to start searching from
        :param move_direction: int
            Direction to search in. Ex MoveDirection.forward
        :param piece_color: Color
            Color of player who's perspective should be used.
        :param ghost_pieces: dict
            Positions to either ignore or contain ghost pieces.
        :return: dict
            [position]
            [color]
            [offset]
            [type]
        """
        ChessHelper.validate_position(start_position)

        offset = 0
        positions = self._get_possible_positions(start_position,
                                                 move_direction, piece_color)

        for position in positions:
            offset += 1
            piece_on_destination = self.is_position_occupied(position)

            ghost_pieces = {} if not ghost_pieces else ghost_pieces
            if position in ghost_pieces and not ghost_pieces[position]:
                continue
            elif position in ghost_pieces:
                return {
                    'position': position,
                    'color': ghost_pieces[position]['color'],
                    'offset': offset,
                    'type': ghost_pieces[position]['type']
                }
            elif piece_on_destination:
                piece = self[position]
                return {
                    'position': position,
                    'color': piece.color,
                    'offset': offset,
                    'type': piece.type
                }

        return None
示例#3
0
    def is_position_occupied(self, position):
        """
        Check if a position is occupied with a piece.

        :param position: string
            Algebraic notation position.
        :return: bool
            True if piece on position, False otherwise
        """
        ChessHelper.validate_position(position)

        return self._pieces[position] is not None
示例#4
0
    def _position_to_index(self, position, piece_color):
        """
        Convert an algebraic notation position to an index.

        :param position: string
            Algebraic notation for a position.
        :param piece_color: Color
            Color of player who's perspective should be used.
        :return: int
            Index for that position.
        """
        ChessHelper.validate_position(position)

        return self._indexes[piece_color][position]
示例#5
0
    def _remove_piece(self, position):
        """
        Remove a piece from the board.

        :param position: string
            Algebraic notation for a position.
        :return:
        :raises: InvalidPositionError
            If position is not valid, exception will be raised.
        """
        ChessHelper.validate_position(position)

        # Color does not matter since both indexes point to the same board
        index = self._indexes[Color.WHITE][position]
        self._pieces[self._board_positions[index]] = None
示例#6
0
    def __setitem__(self, position, piece):
        """
        Put a piece on the board at the specified position.

        :param position: string
            Algebraic notation for a position.
        :param piece: Piece
            Piece object.
        :return:
        """
        ChessHelper.validate_position(position)

        self._pieces[position] = copy.deepcopy(piece)
        if piece.type == Type.KING:
            self._king_positions[piece.color] = position
示例#7
0
    def get_legal_moves(self, position):
        """
        Retrieve possible legal moves for a piece on a position.

        :param position: string
            Algebraic notation position.
        :return:
        """
        try:
            ChessHelper.validate_position(position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            return self._board.get_legal_moves(position)
示例#8
0
    def _get_possible_positions(self, start_position, move_direction,
                                piece_color):
        """
        Get a list of all possible positions in the direction provided as if the board were empty. Positions returned
        are in algebraic notation.

        :param start_position: string
            Algebraic notation position.
        :param move_direction: int
            Direction to move. Ex MoveDirection.forward
        :param piece_color: Color
            Color of player who's perspective should be used.
        :return: list
            List of positions in algebraic notation. Positions in the list are sorted by the offset from the start
            position. Index 0 is the nearest position in the specified direction.
        """
        ChessHelper.validate_position(start_position)

        possible_positions = []
        current_index = next_index = self._position_to_index(
            start_position, piece_color)
        current_column, current_row = self.position_to_row_and_column(
            self._index_to_position(current_index, piece_color), piece_color)
        if move_direction == MoveDirection.L_SHAPE:
            for shift in self.PIECE_SHIFTING[MoveDirection.L_SHAPE]:
                try:
                    next_index = current_index + shift
                    self._index_to_position(next_index, piece_color)
                except InvalidIndexError:
                    continue
                else:
                    next_column, next_row = self.position_to_row_and_column(
                        self._index_to_position(next_index, piece_color),
                        piece_color)
                    column_diff = abs(next_column - current_column)
                    if current_row != next_row and column_diff <= 2:
                        possible_positions.append(
                            self._index_to_position(next_index, piece_color))
        else:
            num_spaces = self._get_direction_square_count(
                start_position, move_direction, piece_color)
            for count in range(0, num_spaces):
                next_index += self.PIECE_SHIFTING[move_direction]
                possible_positions.append(
                    self._index_to_position(next_index, piece_color))

        return possible_positions
示例#9
0
    def _get_direction_square_count(self, start_position, move_direction,
                                    piece_color):
        """
        Retrieve the number of squares from the starting position to the edge of the board in the direction specified.

        :param start_position: string
            Algebraic notation position.
        :param move_direction: int
            Direction to work in. Ex MoveDirection.forward
        :param piece_color: Color
            Color of player who's perspective should be used.
        :return: int
            Number of squares till the edge of the board.
        """
        ChessHelper.validate_position(start_position)

        board_length = self.get_dimension()
        max_movement = board_length - 1
        num_spaces = max_movement
        current_x_coord, current_y_coord = self.position_to_row_and_column(
            start_position, piece_color)

        if move_direction == MoveDirection.FORWARD:
            max_movement = board_length - current_y_coord - 1
        elif move_direction == MoveDirection.F_RIGHT_DIAG:
            from_right = board_length - current_x_coord - 1
            from_top = board_length - current_y_coord - 1
            max_movement = min(from_top, from_right)
        elif move_direction == MoveDirection.RIGHT:
            max_movement = board_length - current_x_coord - 1
        elif move_direction == MoveDirection.B_RIGHT_DIAG:
            from_right = board_length - current_x_coord - 1
            max_movement = min(current_y_coord, from_right)
        elif move_direction == MoveDirection.BACKWARD:
            max_movement = current_y_coord
        elif move_direction == MoveDirection.B_LEFT_DIAG:
            max_movement = min(current_y_coord, current_x_coord)
        elif move_direction == MoveDirection.LEFT:
            max_movement = current_x_coord
        elif move_direction == MoveDirection.F_LEFT_DIAG:
            from_top = board_length - current_y_coord - 1
            max_movement = min(from_top, current_x_coord)

        return min(num_spaces, max_movement)
示例#10
0
    def _get_position_shifted_by_offset(self, position, direction, offset,
                                        piece_color):
        """
        Retrieve position after shifting over by offset in the direction specified. Ex position=a1,
        direction=MoveDirection.right, offset=1, piece_color=Color.white. Return value is a2

        :param position: string
            Algebraic notation position.
        :param offset: int
            Number of times to shift over in direction
        :param piece_color: int
            Color.white or Colore.black
        :return: string
            Algebraic notation position.
        """
        ChessHelper.validate_position(position)

        index = self._position_to_index(position, piece_color)
        shifted_index = index + self.PIECE_SHIFTING[direction] * offset
        return self._index_to_position(shifted_index, piece_color)
示例#11
0
    def promote_pawn(self, start_position, end_position, piece_type):
        """
        Promote a pawn to another piece type.

        :param start_position: string
            Algebraic notation for pawn position.
        :param end_position: string
            Algebraic notation for destination position.
        :param piece_type: Type
            Value from Type enum
        :return:
        """
        try:
            ChessHelper.validate_position(start_position)
            ChessHelper.validate_position(end_position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            if piece_type not in self.get_pawn_promote_types():
                raise PieceTypeError(
                    piece_type, 'Cannot promote pawn to supplied piece type')
            if self._board[start_position] is None:
                raise EmptyPositionError(start_position)
            # TODO confirm pawn on second to last row

            piece = self._board[start_position]
            piece_class = {
                Type.ROOK: Rook,
                Type.KNIGHT: Knight,
                Type.BISHOP: Bishop,
                Type.QUEEN: Queen
            }
            self._board[start_position] = piece_class[piece_type](piece.color)

            return self.move_piece(start_position, end_position)
示例#12
0
文件: routes.py 项目: miyanda2/Chess
def move_piece(json):
    if 'current_game' in session and 'game_room' in session:
        game = ChessGame.load_by_id(session['current_game'])
        try:
            ChessHelper.validate_position(json['start_position'])
            ChessHelper.validate_position(json['end_position'])
        except InvalidPositionError as e:
            data = {'error': str(e)}
            emit('error', data, room=session['game_room'])
        else:
            try:
                result = game.move_piece(json['start_position'],
                                         json['end_position'])
            except EmptyPositionError as e:
                data = {'error': str(e)}
                emit('error', data, room=session['game_room'])
            else:
                game.save_to_db()

                game_dict = game.to_dict()
                game_dict['result'] = result.to_dict()
                game_dict['board_string'] = str(game)

                emit('update_game', game_dict, room=session['game_room'])
示例#13
0
    def position_to_row_and_column(self, position, piece_color):
        """
        Treat position at bottom left corner as the origin in x,y coordinate system. Return offset the passed in
        position is from the origin.

        :param position: string
            Algebraic notation for chess position
        :param piece_color: Color
            Color of player who's perspective should be used.
        :return: tuple
            The coordinates for the position.
            Ex (1,1) from white perspective is B2 but G7 from black perspective.
        """
        ChessHelper.validate_position(position)

        if piece_color == Color.WHITE:
            column = 'abcdefgh'.index(position[0])
            row = int(position[1]) - 1
        else:
            column = 'hgfedcba'.index(position[0])
            dimension = self.get_dimension()
            row = dimension - int(position[1])

        return column, row
示例#14
0
    def _direction_and_position_offset(self, start_position, end_position,
                                       color):
        """
        Determine the direction and number of positions away end_position is relative to start_position.

        :param start_position: string
            Position in algebraic notation.
        :param end_position: string
            Position in algebraic notation.
        :param color: int
            Dictionary where MoveDirection is the key and value is a tuple containing the number of rows and columns
            end_position is away. Treats start_position as 0,0 on x,y coordinate system. Ex a2 would be (1,1) from a1
        :return: MoveDirection value
        """
        ChessHelper.validate_position(start_position)
        ChessHelper.validate_position(end_position)

        start_column, start_row = self.position_to_row_and_column(
            start_position, color)
        end_column, end_row = self.position_to_row_and_column(
            end_position, color)
        row_diff = end_row - start_row
        column_diff = end_column - start_column

        # Compute offsets
        is_forward = row_diff > 0
        is_back = row_diff < 0
        is_right = column_diff > 0
        is_left = column_diff < 0

        # Determine direction
        if is_forward and is_right:
            positions_away = {
                'direction': MoveDirection.F_RIGHT_DIAG,
                'offset': (column_diff, row_diff)
            }
        elif is_forward and is_left:
            positions_away = {
                'direction': MoveDirection.F_LEFT_DIAG,
                'offset': (column_diff, row_diff)
            }
        elif is_back and is_right:
            positions_away = {
                'direction': MoveDirection.B_RIGHT_DIAG,
                'offset': (column_diff, row_diff)
            }
        elif is_back and is_left:
            positions_away = {
                'direction': MoveDirection.B_LEFT_DIAG,
                'offset': (column_diff, row_diff)
            }
        elif is_forward:
            positions_away = {
                'direction': MoveDirection.FORWARD,
                'offset': (column_diff, row_diff)
            }
        elif is_back:
            positions_away = {
                'direction': MoveDirection.BACKWARD,
                'offset': (column_diff, row_diff)
            }
        elif is_right:
            positions_away = {
                'direction': MoveDirection.RIGHT,
                'offset': (column_diff, row_diff)
            }
        elif is_left:
            positions_away = {
                'direction': MoveDirection.LEFT,
                'offset': (column_diff, row_diff)
            }
        else:
            positions_away = {'direction': None, 'offset': (0, 0)}

        return positions_away
示例#15
0
    def is_check(self, king_color, position=None, ghost_pieces=None):
        """
        Test for check against king of the specified color.

        :param king_color: Color
            Color of king to test for check against.
        :param position: string
            If set, look for check from this position instead of the position of the king specified by king_color.
        :param ghost_pieces: dict
            Positions to ignore or contain ghost pieces.
        :return: bool
            True if king is in check, False otherwise.
        """
        if position:
            ChessHelper.validate_position(position)

        king_position = position if position else self._king_positions[
            king_color]
        if not self._king_positions[king_color]:
            return False

        # Check file and rank
        diagonal_pieces = (Type.QUEEN, Type.BISHOP)
        file_and_rank_pieces = (Type.ROOK, Type.QUEEN)
        pawn_capture_directions = (MoveDirection.F_RIGHT_DIAG,
                                   MoveDirection.F_LEFT_DIAG)
        opponent_piece_list = {
            MoveDirection.FORWARD: file_and_rank_pieces,
            MoveDirection.F_RIGHT_DIAG: diagonal_pieces,
            MoveDirection.RIGHT: file_and_rank_pieces,
            MoveDirection.B_RIGHT_DIAG: diagonal_pieces,
            MoveDirection.BACKWARD: file_and_rank_pieces,
            MoveDirection.B_LEFT_DIAG: diagonal_pieces,
            MoveDirection.LEFT: file_and_rank_pieces,
            MoveDirection.F_LEFT_DIAG: diagonal_pieces
        }

        for direction, pieces in opponent_piece_list.items():
            ghost_pieces = {} if not ghost_pieces else ghost_pieces
            nearest_piece = self._get_nearest_piece_in_direction(
                king_position, direction, king_color, ghost_pieces)
            if nearest_piece and nearest_piece['color'] != king_color:
                offset = nearest_piece['offset']
                piece_type = nearest_piece['type']
                if direction in pawn_capture_directions and offset == 1 and piece_type == Type.PAWN:
                    return True
                elif offset == 1 and piece_type == Type.KING:
                    return True
                elif piece_type in pieces:
                    return True

        # Color doesnt matter here
        l_shape_positions = self._get_possible_positions(
            king_position, MoveDirection.L_SHAPE, king_color)
        for position in l_shape_positions:
            if self.is_position_occupied(position):
                piece = self[position]
                if piece.type == Type.KNIGHT and piece.color != king_color:
                    return True

        return False
示例#16
0
    def move_piece(self, start_position, end_position):
        """
        Move piece from starting position to end position. Does not check if end position is valid. Ex. king in check
        or if overtaking square with piece of same color. Also does not check if moving piece has passed through other
        pieces.

        :param start_position: string
            Algebraic notation position.
        :param end_position: string
            Algebraic notation position.
        :return: dict
            [position]: Piece. Use None if position is now empty
        """
        ChessHelper.validate_position(start_position)
        ChessHelper.validate_position(end_position)

        start_position_piece = self[start_position]
        if not start_position_piece:
            raise EmptyPositionError(start_position)

        updated_positions = {}
        move_info = self._direction_and_position_offset(
            start_position, end_position, start_position_piece.color)
        direction = move_info['direction']
        column_offset, row_offset = move_info['offset']

        # Copy of en_passant info
        en_passant_info = copy.copy(self._en_passant_info)

        # Have to clone the piece before moving it. Otherwise, it makes it really hard to compare
        # expected move result to actual move result since the piece here could have a modified
        # move_directions . Plus, we don't really care about any values other than piece type and color
        updated_positions[start_position] = None
        updated_positions[end_position] = copy.deepcopy(start_position_piece)

        if start_position_piece.type == Type.PAWN and row_offset == 2:
            tartget_position = self._get_position_shifted_by_offset(
                start_position, MoveDirection.FORWARD, 1,
                start_position_piece.color)
            en_passant_info['pawn_position'] = end_position
            en_passant_info['target_position'] = tartget_position
        else:
            en_passant_info['pawn_position'] = None
            en_passant_info['target_position'] = None

        # Check pawn special movements
        if start_position_piece.type == Type.PAWN:
            # Update move_directions after pawn moves forward twice
            if start_position_piece.move_directions[MoveDirection.FORWARD] > 1:
                start_position_piece.move_directions[MoveDirection.FORWARD] = 1
            # Check for en passant
            if self.can_en_passant(start_position, direction):
                self._remove_piece(self._en_passant_info['pawn_position'])
                updated_positions[
                    self._en_passant_info['pawn_position']] = None
        # Check for castling
        elif start_position_piece.type == Type.KING:
            if abs(column_offset) == 2 and self.can_castle(
                    start_position_piece.color, direction):
                rook_positions = {
                    Color.WHITE: {
                        MoveDirection.LEFT: 'a1',
                        MoveDirection.RIGHT: 'h1'
                    },
                    Color.BLACK: {
                        MoveDirection.LEFT: 'h8',
                        MoveDirection.RIGHT: 'a8'
                    }
                }
                new_rook_position = self._get_position_shifted_by_offset(
                    start_position, direction, 1, start_position_piece.color)
                self[new_rook_position] = self[rook_positions[
                    start_position_piece.color][direction]]
                self._remove_piece(
                    rook_positions[start_position_piece.color][direction])
                updated_positions[new_rook_position] = copy.deepcopy(
                    self[new_rook_position])
                updated_positions[rook_positions[start_position_piece.color]
                                  [direction]] = None

                # Update King left and right move_directions
                start_position_piece.move_directions[MoveDirection.LEFT] = 1
                start_position_piece.move_directions[MoveDirection.RIGHT] = 1

            start_position_piece.move_directions[MoveDirection.LEFT] = 1
            start_position_piece.move_directions[MoveDirection.RIGHT] = 1
        elif start_position_piece.type == Type.ROOK:
            king_position = self._king_positions[start_position_piece.color]
            column, row = self.position_to_row_and_column(
                start_position, start_position_piece.color)
            rook_direction = None
            if column == 0:
                rook_direction = MoveDirection.LEFT
            elif column == self.get_dimension() - 1:
                rook_direction = MoveDirection.RIGHT
            # Check needed because might be dealing with a board without a king
            if king_position and rook_direction:
                king = self[king_position]
                if king.move_directions[rook_direction] > 1:
                    king.move_directions[rook_direction] = 1

        self._en_passant_info = en_passant_info
        self._remove_piece(start_position)
        self._remove_piece(end_position)
        self[end_position] = start_position_piece

        return updated_positions
示例#17
0
    def move_piece(self, start_position, end_position):
        """
        Move a piece from start_position to end_position.

        :param start_position:
        :param end_position:
        :return:
        """
        try:
            ChessHelper.validate_position(start_position)
            ChessHelper.validate_position(end_position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            current_fen = Fen(self.fen)
            current_player = current_fen.current_player
            next_player = Color.WHITE if current_player == Color.BLACK else Color.BLACK
            move_result = MoveResult()

            # If moving a pawn to the end of the board, dont update anything on the board.
            # Instead, just return the pawn promote info.
            if self.can_promote_pawn(start_position, end_position):
                player = self._get_player_by_color(current_player)
                promote_info = {
                    'player': player,
                    'promote_types': self.get_pawn_promote_types()
                }
                move_result.pawn_promote_info = promote_info
                return move_result

            move_result.update_positions = self._board.move_piece(
                start_position, end_position)

            # Determine which directions castling is possible for.
            castle_info = {Color.BLACK: [], Color.WHITE: []}
            for color in [Color.WHITE, Color.BLACK]:
                for direction in [MoveDirection.LEFT, MoveDirection.RIGHT]:
                    if self._board.can_castle(color, direction):
                        castle_info[color].append(direction)

            # Generate and save fen string after move
            next_fen = Fen()
            next_fen_str = next_fen.generate_fen(
                self._board.get_board_pieces(), next_player,
                castle_info[Color.WHITE], castle_info[Color.BLACK],
                self._board.get_enpassant_position())
            self.fen = next_fen_str

            # If checkmate or draw, set game over flag. Also create game_score object and fill
            # the move results object.
            is_checkmate = self._board.is_checkmate(next_player)
            is_stalemate = self._board.is_stalemate(next_player)
            if is_checkmate or is_stalemate:
                self.is_over = True
                if is_checkmate:
                    if current_player == Color.WHITE:
                        self.score = GameScore(game=self, white_score=1)
                        player_in_checkmate = self.black_player
                        player_in_checkmate.color = Color.BLACK
                    else:
                        self.score = GameScore(game=self, black_score=1)
                        player_in_checkmate = self.white_player
                        player_in_checkmate.color = Color.WHITE
                    move_result.king_in_checkmate = player_in_checkmate
                else:
                    self.score = GameScore(game=self,
                                           white_score=0.5,
                                           black_score=0.5)
                    move_result.draw = True

            # If it is check, add info to move_result.
            is_check = self._board.is_check(next_player)
            if is_check:
                if current_player == Color.WHITE:
                    player_in_check = self._get_player_by_color(Color.BLACK)
                else:
                    player_in_check = self._get_player_by_color(Color.WHITE)
                move_result.king_in_check = player_in_check

            return move_result