Example #1
0
 def test___eq__(self):
     self.assertEqual(True, Fen().__eq__(Fen()))
Example #2
0
 def test___getitem__(self):
     fen = Fen()
     self.assertEqual('R', fen.__getitem__(Square('a1')))
Example #3
0
 def test_text(self):
     self.assertEqual(Fen.start, Fen().text)
Example #4
0
 def test_turn(self):
     self.assertEqual('w', Fen().turn)
Example #5
0
class Position(object):
    """Represents a chess position.

    :param fen:
        Optional. The FEN of the position. Defaults to the standard
        chess start position.

    Squares can be accessed via their name, a square object and their
    x88 index:

    >>> import chess
    >>> pos = chess.Position()
    >>> pos["e4"] = Piece("Q")
    >>> pos[chess.Square("e4")]
    Piece('Q')
    >>> del pos["a8"]
    >>> pos[0] # 0 is the x88 index of a8.
    None

    """
    def __init__(self, fen=Fen.start):

        self.fen = Fen(fen)
        self._pieces = self.fen._pieces

    def copy(self):
        """Gets a copy of the position. The copy will not change when the
        original instance is changed.

        :return:
            An exact copy of the positon.
        """
        return copy.deepcopy(self)

    def get_pseudo_legal_moves(self, source=None):
        """:yield: Pseudo legal moves in the current position.
         
        :param source: The source square to limit moves or None for
            all possible moves.
        """

        tomove = self.fen._to_move

        for x88 in [
                x88 for x88 in Square._x88_squares.keys()
                if self._pieces[x88] and Piece.color(self._pieces[x88]) ==
                tomove and (source is None or x88 == source._x88)
        ]:

            piece = self._pieces[x88]
            klass = Piece.klass(piece)

            # pawn moves
            if klass == PAWN:
                single, double, capleft, capright = X88.PAWN_OFFSETS[tomove]

                # Single square ahead. Do not capture.
                offset = x88 + single
                if not self._pieces[offset]:
                    # Promotion.
                    if X88.is_backrank(offset, tomove):
                        for promote_to in Piece.promote_to:
                            yield Move.from_x88(x88, offset, promote_to)
                    else:
                        yield Move.from_x88(x88, offset)

                    # Two squares ahead. Do not capture.
                    if X88.is_secondrank(x88, tomove):
                        offset = x88 + double
                        if not self._pieces[offset]:
                            yield Move.from_x88(x88, offset)

                # Pawn captures.
                for cap in [capleft, capright]:
                    offset = x88 + cap
                    if offset & X88.X88:
                        continue
                    target = self._pieces[offset]
                    if target and Piece.color(target) != tomove:
                        # Promotion.
                        if X88.is_backrank(offset, tomove):
                            for promote_to in Piece.promote_to:
                                yield Move.from_x88(x88, offset, promote_to)
                        else:
                            yield Move.from_x88(x88, offset)
                # En-passant.
                    elif not target and offset == self.fen._ep:
                        yield Move.from_x88(target, self.fen._ep)

            #piece moves
            else:
                # for each a direction a piece moves in
                for offset in X88.PIECE_OFFSETS[Piece.klass(piece)]:

                    t_x88 = x88 + offset

                    # while we do not fall off the board
                    while not t_x88 & 0x88:

                        # if there was not piece to attack then yield a quiet move
                        if not self._pieces[t_x88]:
                            yield Move.from_x88(x88, t_x88)
                            # do not break out

                        # else there is a piece there
                        else:
                            # if we can attack generate a move
                            if Piece.color(self._pieces[t_x88]) != tomove:
                                yield Move.from_x88(x88, t_x88)
                            # we hit something so break out
                            break

                        # Knight and king do not go multiple times in their direction.
                        if klass in [KNIGHT, KING]:
                            break

                        # travel down the board in the direction
                        t_x88 += offset

        # castling moves
        opponent = Piece.opposite_color(tomove)
        ok = True

        # get possible castling for the side to move
        for castle in [
                c for c in self.fen._castle_rights if Piece.color(c) == tomove
        ]:

            (square, enum), _ = Piece.castle_squares[castle]
            king = Square(square)

            if Piece.klass(castle) == KING:
                direc = 1
            else:
                direc = -1

            # for offset in the squares the king will travel
            for offset in range(0, 3):
                s = Square.from_x88(king._x88 + (offset * direc))

                # if we are not the king square and we are occuppied
                if offset and self._pieces[s._x88]:
                    ok = False
                    break

                # if we are trying to travel through check
                if self.is_attacked(opponent, s):
                    ok = False
                    break

            # kludge: we have to check occupancy for one more square on the queen side
            if direc == -1 and self._pieces[s._x88 - 1]:
                ok = False
            if ok:
                yield Move(king, s)

    def is_attacked(self, color, square):
        """Checks whether a square is attacked.

        :param color:
            Check if this player is attacking.
        :param square:
            The square the player might be attacking.

        :return:
            A boolean indicating whether the given square is attacked
            by the player of the given color.
        """
        try:
            self.get_attackers(color, square).next()
            return True
        except StopIteration:
            return False

    def get_attackers(self, color, square):
        """Gets the attackers of a specific square.

        :param color:
            Filter attackers by this piece color.
        :param square:
            The square to check for.

        :yield:
            Source squares of the attack.
        """
        if not color in [BLACK, WHITE]:
            raise KeyError("Invalid color: %s." % repr(color))

        for x88, source in Square._x88_squares.iteritems():

            piece = self._pieces[x88]
            if not piece or Piece.color(piece) != color:
                continue

            difference = x88 - square._x88
            index = difference + X88.ATTACKER_DIFF
            klass = Piece.klass(piece)

            if X88.ATTACKS[index] & (1 << X88.SHIFTS[klass]):
                # Handle pawns.
                if klass == PAWN:
                    if difference > 0:
                        if Piece.color(piece) == WHITE:
                            yield source
                    else:
                        if Piece.color(piece) == BLACK:
                            yield source
                    continue

                # Handle knights and king.
                if klass in [KNIGHT, KING]:
                    yield source

                # Handle the others.
                offset = X88.RAYS[index]
                j = source._x88 + offset
                blocked = False
                while j != square._x88:
                    if self._pieces[j]:
                        blocked = True
                        break
                    j += offset
                if not blocked:
                    yield source

    def make_move(self, move, validate=True):
        """Makes a move.

        :param move:
            The move to make.
        :param validate:
            Defaults to `True`. Whether the move should be validated.

        :return:
            Making a move changes the position object. The same
            (changed) object is returned for chainability.

        :raise MoveError:
            If the validate parameter is `True` and the move is not
            legal in the position.
        """

        #if validate:
        if validate:
            if move not in self.get_legal_moves(source=move.source):
                raise MoveError("%s is not a legal move in the position %s." %
                                (move, self.fen))

        piece = self._pieces[move._source_x88]
        capture = self._pieces[move._target_x88]
        target = move.target
        source = move.source

        # Move the piece.
        self._pieces[move._target_x88] = piece
        self._pieces[move._source_x88] = None

        # It is the next players turn.
        ocolor = Piece.opposite_color(self.fen._to_move)
        self.fen._to_move = ocolor

        # Pawn moves.
        self._ep = None
        if Piece.klass(piece) == PAWN:

            # En-passant.
            if target.x != source.x and not capture:
                offset = 16 if self.fen._to_move == WHITE else -16
                self._pieces[target.x88() + offset] = None
                capture = True

            # If big pawn move, set the en-passant file.
            if abs(target.y - source.y) == 2:
                if self.get_theoretical_ep_right(target.x):
                    self._ep = move.target

        # Promotion.
        if move.promotion:
            self._pieces[move.target._x88] = move.promotion

        # Potential castling.
        if Piece.klass(piece) == KING:
            steps = move.target.x - move.source.x
            if abs(steps) == 2:
                # Queen-side castling.
                if steps == -2:
                    rook_target = move.target.x88 + 1
                    rook_source = move.target.x88 - 2
                # King-side castling.
                else:
                    rook_target = move.target.x88 - 1
                    rook_source = move.target.x88 + 1
                self._pieces[rook_target] = self._pieces[rook_source]
                self._pieces[rook_source] = None

        # Update castling rights.
        for klass in self.fen._castle_rights:
            if not self.get_theoretical_castling_right(klass):
                self.fen._castle_rights.remove(klass)
                # XXX Castling rights can only be removed
                #self.set_castling_right(klass, False)

        # Increment the 50 move half move counter.
        if Piece.klass(piece) == PAWN or capture:
            self.fen._fifty_move = 0
        else:
            self.fen._fifty_move += 1

        # Increment the move number.
        if self.fen._to_move == WHITE:
            self.fen._full_move += 1

        return self

    def get_legal_moves(self, source=None):
        """:yield: All legal moves in the current position."""

        for move in self.get_pseudo_legal_moves(source=source):
            potential_position = self.copy()
            potential_position.make_move(move, False)
            if not potential_position.is_king_attacked(self.fen._to_move):
                yield move

    def is_king_attacked(self, color):
        """:return: Whether the king of the given color is attacked.

        :param color: `"w"` or `"b"`.
        """
        square = self.get_king(color)
        if square:
            return self.is_attacked(Piece.opposite_color(color), square)
        # XXX No king?
        else:
            return False

    def get_king(self, color):
        """Gets the square of the king.

        :param color:
            `"w"` for the white players king. `"b"` for the black
            players king.

        :return:
            The first square with a matching king or `None` if that
            player has no king.
        """
        #if not color in ["w", "b"]:
        #   raise KeyError("Invalid color: %s." % repr(color))

        for square, piece in [(s, self._pieces[s._x88])
                              for s in Square.get_all()
                              if self._pieces[s._x88]]:
            if Piece.is_klass_and_color(piece, KING, color):
                return square

    def get_theoretical_ep_right(self, x):
        """Checks if a player could have an ep-move in theory from
        looking just at the piece positions.

        :param file:
            The file to check as a letter between `"a"` and `"h"`.

        :return:
            A boolean indicating whether the player could theoretically
            have that en-passant move.

        """
        if x < 0 or x > 7:
            raise ValueError(x)
        '''
        3 states of en-passant
        p.  pP  ..
        ..  ..  .p
        .P  ..  ..
        '''

        # Check there is a pawn on the right rank for e.p.
        y = 3 if self.fen._to_move == WHITE else 4
        x88 = X88.from_x_and_y(x, y)

        piece = self._pieces[x88]
        if not piece:
            return False

        # If the square is not an opposite colored pawn then its not possible.
        ocolor = Piece.opposite_color(self.fen._to_move)
        if not Piece.is_klass_and_color(piece, PAWN, ocolor):
            return False

        # If the square below the pawn is not empty then it not possible.
        y = 2 if self.fen.turn == WHITE else 5
        x88 = X88.from_x_and_y(x, y)
        if self[x88]:
            return False

        # If there is not pawn of opposite color on a neighboring file then its not possible.
        xs = [_x for _x in range(8) if _x >= 0 and _x < 8 and abs(x - _x) == 1]
        for _x in xs:
            x88 = X88.from_x_and_y(_x, y)
            piece = self._pieces[x88]
            if Piece.is_klass_and_color(
                    piece, PAWN, Piece.opposite_color(self.fen._to_move)):
                return True
        # Else its just not possible.
        return False

    def get_theoretical_castling_right(self, klass):
        """Checks if a player could have a castling right in theory from
        looking just at the piece positions.

        :param klass:
            The type of castling move to check. See
            `Position.get_castling_right(type)` for values.

        :return:
            A boolean indicating whether the player could theoretically
            have that castling right.
        """
        if klass not in Piece.castleclass:
            raise KeyError(klass)

        # TODO: Support Chess960.

        for square, enum in Piece.castle_squares[klass]:
            piece = self._pieces[Square(square)._x88]
            if not piece or Piece.enum(piece) != enum:
                return False
        return True

    def is_check(self):
        """:return: Whether the current player is in check."""
        return self.is_king_attacked(self.fen._to_move)

    def is_checkmate(self):
        """:return: Whether the current player has been checkmated."""
        if not self.is_check():
            return False
        else:
            try:
                self.get_legal_moves().next()
                return False
            except StopIteration:
                return True

    def is_stalemate(self):
        """:return: Whether the current player is in stalemate."""
        if self.is_check():
            return False
        else:
            try:
                self.get_legal_moves().next()
                return False
            except StopIteration:
                return True

    def get_piece_counts(self, colors=[WHITE, BLACK]):
        """Counts the pieces on the board.

        :param color:
            list of colors to check. Defualts to black and white

        :return:
            A dictionary of piece counts, keyed by lowercase piece type
            letters.
        """
        #if not color in ["w", "b", "wb", "bw"]:
        #   raise KeyError(
        #       "Expected color filter to be one of 'w', 'b', 'wb', 'bw', "
        #       "got: %s." % repr(color))

        counts = {
            PAWN: 0,
            BISHOP: 0,
            KNIGHT: 0,
            ROOK: 0,
            KING: 0,
            QUEEN: 0,
        }
        for piece in self._pieces:
            if piece and Piece.color(piece) in colors:
                counts[Piece.klass(piece)] += 1
        return counts

    def is_insufficient_material(self):
        """Checks if there is sufficient material to mate.

        Mating is impossible in:

        * A king versus king endgame.
        * A king with bishop versus king endgame.
        * A king with knight versus king endgame.
        * A king with bishop versus king with bishop endgame, where both
          bishops are on the same color. Same goes for additional
          bishops on the same color.

        Assumes that the position is valid and each player has exactly
        one king.

        :return:
            Whether there is insufficient material to mate.
        """
        piece_counts = self.get_piece_counts()

        # King versus king.
        if sum(piece_counts.values()) == 2:
            return True

        # King and knight or bishop versus king.
        elif sum(piece_counts.values()) == 3:
            if piece_counts["b"] == 1 or piece_counts["n"] == 1:
                return True

        # Each player with only king and any number of bishops,
        # where all bishops are on the same color.
        elif sum(piece_counts.values()) == 2 + piece_counts[BISHOP]:
            white_has_bishop = self.get_piece_counts([WHITE])[BISHOP] != 0
            black_has_bishop = self.get_piece_counts([BLACK])[BISHOP] != 0
            if white_has_bishop and black_has_bishop:
                color = None
                for square in Square.get_all():
                    p = self._pieces[square._x88]
                    if p and Piece.klass(p) == BISHOP:
                        if color and color != square.is_light():
                            return False
                        color = square.is_light()
                return True
        return False

    def is_game_over(self):
        """Checks if the game is over.

        :return:
            Whether the game is over by the rules of chess,
            disregarding that players can agree on a draw, claim a draw
            or resign.
        """
        return (self.is_checkmate() or self.is_stalemate()
                or self.is_insufficient_material())

    def __str__(self):
        return str(self.fen)

    def __repr__(self):
        return "Position(%s)" % repr(str(self.fen))

    def __eq__(self, other):
        return self.fen == other.fen

    def __ne__(self, other):
        return self.fen != other.fen

    def __hash__(self):
        hasher = ZobristHasher(ZobristHasher.POLYGLOT_RANDOM_ARRAY)
        return hasher.hash_position(self)

    def __getitem__(self, square):
        return self.fen.__getitem__(square)

    def __setitem__(self, square, piece):
        self.fen.__setitem__(square, piece)

    def __delitem__(self, square):
        self.fen.__delitem__(square)

    #TODO
    def clear_board(self):
        pass

    def reset(self):
        pass

    # do we want this public?
    def toggle_turn(self):
        """Toggles whos turn it is."""
Example #6
0
 def test___hash__(self):
     hash(Fen())
Example #7
0
 def test_castle_rights(self):
     self.assertEqual('KQkq', Fen().castle_rights)
Example #8
0
 def test___repr__(self):
     self.assertEqual("Position('%s')" % Fen(), repr(self.pos))
Example #9
0
 def test___setitem__(self):
     f, s = Fen(), Square('a1')
     f[s] = 'K'
     self.assertEqual('K', f[s])
Example #10
0
 def test___str__(self):
     self.assertEqual(Fen.start, Fen().__str__())
Example #11
0
 def test___repr__(self):
     # fen = Fen(fen)
     self.assertEqual('Fen(\'' + Fen.start + '\')', Fen().__repr__())
Example #12
0
 def test___ne__(self):
     self.assertEqual(False, Fen().__ne__(Fen()))
Example #13
0
    def test___init__(self):

        Fen()
        Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1')
        Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - e3 0 1')

        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP w KQkq - 0')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/71/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')
        with self.assertRaises(FenError):
            Fen('Znbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/ppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR Z KQkq - 0 1')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w ZQkq - 0 1')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - e4 0 1')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - -1 1')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - Z 1')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 0')
        with self.assertRaises(FenError):
            Fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 Z')
Example #14
0
class Position(object):

    """Represents a chess position.

    :param fen:
        Optional. The FEN of the position. Defaults to the standard
        chess start position.

    Squares can be accessed via their name, a square object and their
    x88 index:

    >>> import chess
    >>> pos = chess.Position()
    >>> pos["e4"] = Piece("Q")
    >>> pos[chess.Square("e4")]
    Piece('Q')
    >>> del pos["a8"]
    >>> pos[0] # 0 is the x88 index of a8.
    None

    """

    def __init__(self, fen=Fen.start):
        
        self.fen = Fen(fen)
        self._pieces = self.fen._pieces

    def copy(self):
        """Gets a copy of the position. The copy will not change when the
        original instance is changed.

        :return:
            An exact copy of the positon.
        """
        return copy.deepcopy(self)
    
    def get_pseudo_legal_moves(self, source=None):
        """:yield: Pseudo legal moves in the current position.
         
        :param source: The source square to limit moves or None for
            all possible moves.
        """

        tomove = self.fen._to_move

        for x88 in [ x88 for x88 in Square._x88_squares.keys() 
            if self._pieces[x88] 
            and Piece.color(self._pieces[x88]) == tomove
            and (source is None or x88 == source._x88)]:

            piece = self._pieces[x88]
            klass = Piece.klass(piece)

            # pawn moves
            if klass == PAWN:
                single, double, capleft, capright = X88.PAWN_OFFSETS[tomove]

                # Single square ahead. Do not capture.
                offset = x88 + single
                if not self._pieces[offset]:
                    # Promotion.
                    if X88.is_backrank(offset, tomove):
                        for promote_to in Piece.promote_to:
                            yield Move.from_x88(x88, offset, promote_to)
                    else:
                        yield Move.from_x88(x88, offset)

                    # Two squares ahead. Do not capture.
                    if X88.is_secondrank(x88, tomove):
                        offset = x88 + double
                        if not self._pieces[offset]:
                            yield Move.from_x88(x88, offset)

                # Pawn captures.
                for cap in [capleft, capright]:
                    offset = x88 + cap
                    if offset & X88.X88:
                        continue
                    target = self._pieces[offset]
                    if target and Piece.color(target) != tomove:
                       # Promotion.
                        if X88.is_backrank(offset, tomove):
                            for promote_to in Piece.promote_to:
                                yield Move.from_x88(x88, offset, promote_to)
                        else:
                            yield Move.from_x88(x88, offset)
                   # En-passant.
                    elif not target and offset == self.fen._ep:
                        yield Move.from_x88(target, self.fen._ep)
            
            #piece moves
            else:
                # for each a direction a piece moves in
                for offset in X88.PIECE_OFFSETS[Piece.klass(piece)]:

                    t_x88 = x88 + offset

                    # while we do not fall off the board
                    while not t_x88 & 0x88:
                        
                        # if there was not piece to attack then yield a quiet move
                        if not self._pieces[t_x88]:
                            yield Move.from_x88(x88, t_x88)
                            # do not break out

                        # else there is a piece there
                        else:
                            # if we can attack generate a move
                            if Piece.color(self._pieces[t_x88]) != tomove:
                                yield Move.from_x88(x88, t_x88)
                            # we hit something so break out
                            break

                        # Knight and king do not go multiple times in their direction.
                        if klass in [KNIGHT, KING]:
                            break

                        # travel down the board in the direction
                        t_x88 += offset


        # castling moves
        opponent = Piece.opposite_color(tomove)
        ok = True

        # get possible castling for the side to move
        for castle in [c for c in self.fen._castle_rights if Piece.color(c) == tomove]:

            (square, enum), _ = Piece.castle_squares[castle]
            king = Square(square)

            if Piece.klass(castle) == KING:
                direc = 1
            else:
                direc = -1

            # for offset in the squares the king will travel
            for offset in range(0, 3):
                s = Square.from_x88(king._x88 + (offset * direc))

                # if we are not the king square and we are occuppied
                if offset and self._pieces[s._x88]:
                    ok = False
                    break
                    
                # if we are trying to travel through check
                if self.is_attacked(opponent, s):
                    ok = False
                    break

            # kludge: we have to check occupancy for one more square on the queen side
            if direc == -1 and self._pieces[s._x88 - 1]:
                    ok = False
            if ok:
                yield Move(king, s)

    def is_attacked(self, color, square):
        """Checks whether a square is attacked.

        :param color:
            Check if this player is attacking.
        :param square:
            The square the player might be attacking.

        :return:
            A boolean indicating whether the given square is attacked
            by the player of the given color.
        """
        try:
            self.get_attackers(color, square).next()
            return True
        except StopIteration:
            return False

    def get_attackers(self, color, square):
        """Gets the attackers of a specific square.

        :param color:
            Filter attackers by this piece color.
        :param square:
            The square to check for.

        :yield:
            Source squares of the attack.
        """
        if not color in [BLACK, WHITE]:
            raise KeyError("Invalid color: %s." % repr(color))


        for x88, source in Square._x88_squares.iteritems():

            piece = self._pieces[x88]
            if not piece or Piece.color(piece) != color:
                continue

            difference = x88 - square._x88
            index = difference + X88.ATTACKER_DIFF
            klass = Piece.klass(piece)

            if X88.ATTACKS[index] & (1 << X88.SHIFTS[klass]):
                # Handle pawns.
                if klass == PAWN:
                    if difference > 0:
                        if Piece.color(piece) == WHITE:
                            yield source
                    else:
                        if Piece.color(piece) == BLACK:
                            yield source
                    continue

                # Handle knights and king.
                if klass in [KNIGHT, KING]:
                    yield source

                # Handle the others.
                offset = X88.RAYS[index]
                j = source._x88 + offset
                blocked = False
                while j != square._x88:
                    if self._pieces[j]:
                        blocked = True
                        break
                    j += offset
                if not blocked:
                    yield source
                

    def make_move(self, move, validate=True):
        """Makes a move.

        :param move:
            The move to make.
        :param validate:
            Defaults to `True`. Whether the move should be validated.

        :return:
            Making a move changes the position object. The same
            (changed) object is returned for chainability.

        :raise MoveError:
            If the validate parameter is `True` and the move is not
            legal in the position.
        """

        #if validate:
        if validate:
            if move not in self.get_legal_moves(source=move.source):
                raise MoveError(
                    "%s is not a legal move in the position %s." % (move, self.fen))

        piece = self._pieces[move._source_x88]
        capture = self._pieces[move._target_x88]
        target = move.target
        source = move.source

        # Move the piece.
        self._pieces[move._target_x88] = piece
        self._pieces[move._source_x88] = None

        # It is the next players turn.
        ocolor = Piece.opposite_color(self.fen._to_move)
        self.fen._to_move = ocolor

        # Pawn moves.
        self._ep = None
        if Piece.klass(piece) == PAWN:

            # En-passant.
            if target.x != source.x and not capture:
                offset = 16 if self.fen._to_move == WHITE else -16
                self._pieces[target.x88() + offset] = None
                capture = True

            # If big pawn move, set the en-passant file.
            if abs(target.y - source.y) == 2:
                if self.get_theoretical_ep_right(target.x):
                    self._ep = move.target

        # Promotion.
        if move.promotion:
            self._pieces[move.target._x88] = move.promotion

        # Potential castling.
        if Piece.klass(piece) == KING:
            steps = move.target.x - move.source.x
            if abs(steps) == 2:
                # Queen-side castling.
                if steps == -2:
                    rook_target = move.target.x88 + 1
                    rook_source = move.target.x88 - 2
                # King-side castling.
                else:
                    rook_target = move.target.x88 - 1
                    rook_source = move.target.x88 + 1
                self._pieces[rook_target] = self._pieces[rook_source]
                self._pieces[rook_source] = None

        # Update castling rights.
        for klass in self.fen._castle_rights:
            if not self.get_theoretical_castling_right(klass):
                self.fen._castle_rights.remove(klass)
                # XXX Castling rights can only be removed 
                #self.set_castling_right(klass, False)


        # Increment the 50 move half move counter.
        if Piece.klass(piece) == PAWN or capture:
            self.fen._fifty_move = 0
        else:
            self.fen._fifty_move += 1

        # Increment the move number.
        if self.fen._to_move == WHITE:
            self.fen._full_move += 1
        
        return self

    def get_legal_moves(self, source=None):
        """:yield: All legal moves in the current position."""

        for move in self.get_pseudo_legal_moves(source=source):
            potential_position = self.copy()
            potential_position.make_move(move, False)
            if not potential_position.is_king_attacked(self.fen._to_move):
                yield move

    def is_king_attacked(self, color):
        """:return: Whether the king of the given color is attacked.

        :param color: `"w"` or `"b"`.
        """
        square = self.get_king(color)
        if square:
            return self.is_attacked(Piece.opposite_color(color), square)
        # XXX No king?
        else:
            return False

    def get_king(self, color):
        """Gets the square of the king.

        :param color:
            `"w"` for the white players king. `"b"` for the black
            players king.

        :return:
            The first square with a matching king or `None` if that
            player has no king.
        """
        #if not color in ["w", "b"]:
        #   raise KeyError("Invalid color: %s." % repr(color))

        for square, piece in [(s, self._pieces[s._x88]) for s in Square.get_all() if self._pieces[s._x88]]:
            if Piece.is_klass_and_color(piece, KING, color):
                return square

    def get_theoretical_ep_right(self, x):
        """Checks if a player could have an ep-move in theory from
        looking just at the piece positions.

        :param file:
            The file to check as a letter between `"a"` and `"h"`.

        :return:
            A boolean indicating whether the player could theoretically
            have that en-passant move.

        """
        if x < 0 or x > 7:
            raise ValueError(x)

        '''
        3 states of en-passant
        p.  pP  ..
        ..  ..  .p
        .P  ..  ..
        '''

        # Check there is a pawn on the right rank for e.p.
        y = 3 if self.fen._to_move == WHITE else 4
        x88 = X88.from_x_and_y(x, y) 

        piece = self._pieces[x88]
        if not piece:
            return False

        # If the square is not an opposite colored pawn then its not possible.
        ocolor = Piece.opposite_color(self.fen._to_move)
        if not Piece.is_klass_and_color(piece, PAWN, ocolor):
            return False

        # If the square below the pawn is not empty then it not possible.
        y = 2 if self.fen.turn == WHITE else 5
        x88 = X88.from_x_and_y(x, y) 
        if self[x88]:
            return False

        # If there is not pawn of opposite color on a neighboring file then its not possible.
        xs = [_x for _x in range(8) if _x>=0 and  _x<8 and abs(x-_x) == 1]
        for _x in xs:
            x88 = X88.from_x_and_y(_x, y) 
            piece = self._pieces[x88] 
            if Piece.is_klass_and_color(piece, PAWN, Piece.opposite_color(self.fen._to_move)):
                return True
        # Else its just not possible.
        return False

    def get_theoretical_castling_right(self, klass):
        """Checks if a player could have a castling right in theory from
        looking just at the piece positions.

        :param klass:
            The type of castling move to check. See
            `Position.get_castling_right(type)` for values.

        :return:
            A boolean indicating whether the player could theoretically
            have that castling right.
        """
        if klass not in Piece.castleclass:
            raise KeyError(klass)

        # TODO: Support Chess960.

        for square, enum in Piece.castle_squares[klass]:
            piece = self._pieces[Square(square)._x88]
            if not piece or Piece.enum(piece) != enum:
                return False
        return True

    def is_check(self):
        """:return: Whether the current player is in check."""
        return self.is_king_attacked(self.fen._to_move)

    def is_checkmate(self):
        """:return: Whether the current player has been checkmated."""
        if not self.is_check():
            return False
        else:
            try:
                self.get_legal_moves().next()
                return False
            except StopIteration:
                return True

    def is_stalemate(self):
        """:return: Whether the current player is in stalemate."""
        if self.is_check():
            return False
        else:
            try:
                self.get_legal_moves().next()
                return False
            except StopIteration:
                return True

    def get_piece_counts(self, colors=[WHITE, BLACK]):
        """Counts the pieces on the board.

        :param color:
            list of colors to check. Defualts to black and white

        :return:
            A dictionary of piece counts, keyed by lowercase piece type
            letters.
        """
        #if not color in ["w", "b", "wb", "bw"]:
        #   raise KeyError(
        #       "Expected color filter to be one of 'w', 'b', 'wb', 'bw', "
        #       "got: %s." % repr(color))

        counts = {
            PAWN:   0,
            BISHOP: 0,
            KNIGHT: 0,
            ROOK:   0,
            KING:   0,
            QUEEN:  0,
        }
        for piece in self._pieces:
            if piece and Piece.color(piece) in colors:
                counts[Piece.klass(piece)] += 1
        return counts

    def is_insufficient_material(self):
        """Checks if there is sufficient material to mate.

        Mating is impossible in:

        * A king versus king endgame.
        * A king with bishop versus king endgame.
        * A king with knight versus king endgame.
        * A king with bishop versus king with bishop endgame, where both
          bishops are on the same color. Same goes for additional
          bishops on the same color.

        Assumes that the position is valid and each player has exactly
        one king.

        :return:
            Whether there is insufficient material to mate.
        """
        piece_counts = self.get_piece_counts()

        # King versus king.
        if sum(piece_counts.values()) == 2:
            return True

        # King and knight or bishop versus king.
        elif sum(piece_counts.values()) == 3:
            if piece_counts["b"] == 1 or piece_counts["n"] == 1:
                return True

        # Each player with only king and any number of bishops, 
        # where all bishops are on the same color.
        elif sum(piece_counts.values()) == 2 + piece_counts[BISHOP]:
            white_has_bishop = self.get_piece_counts([WHITE])[BISHOP] != 0
            black_has_bishop = self.get_piece_counts([BLACK])[BISHOP] != 0
            if white_has_bishop and black_has_bishop:
                color = None
                for square in Square.get_all():
                    p = self._pieces[square._x88]
                    if p and Piece.klass(p) == BISHOP:
                        if color and color != square.is_light():
                            return False
                        color = square.is_light()
                return True
        return False

    def is_game_over(self):
        """Checks if the game is over.

        :return:
            Whether the game is over by the rules of chess,
            disregarding that players can agree on a draw, claim a draw
            or resign.
        """
        return (self.is_checkmate() or self.is_stalemate() or
                self.is_insufficient_material())

    def __str__(self):
        return str(self.fen)

    def __repr__(self):
        return "Position(%s)" % repr(str(self.fen))

    def __eq__(self, other):
        return self.fen == other.fen

    def __ne__(self, other):
        return self.fen != other.fen

    def __hash__(self):
        hasher = ZobristHasher(ZobristHasher.POLYGLOT_RANDOM_ARRAY)
        return hasher.hash_position(self)

    def __getitem__(self, square):
        return self.fen.__getitem__(square)

    def __setitem__(self, square, piece):
        self.fen.__setitem__(square, piece)

    def __delitem__(self, square):
        self.fen.__delitem__(square)

    #TODO
    def clear_board(self): pass
    def reset(self): pass

    # do we want this public?
    def toggle_turn(self): """Toggles whos turn it is."""
Example #15
0
 def test_ep_square(self):
     self.assertEqual('-', Fen().ep_square)
Example #16
0
 def __init__(self, fen=Fen.start):
     
     self.fen = Fen(fen)
     self._pieces = self.fen._pieces
Example #17
0
 def test_fifty_move(self):
     self.assertEqual(0, Fen().fifty_move)
Example #18
0
 def test___str__(self):
     self.assertEqual(str(Fen()), str(self.pos))
     self.assertEqual(False, str(self.pos) == str(self.pos_other))
Example #19
0
 def test_fullmove(self):
     self.assertEqual(1, Fen().full_move)
Example #20
0
    def __init__(self, fen=Fen.start):

        self.fen = Fen(fen)
        self._pieces = self.fen._pieces
Example #21
0
 def test___getitem__(self):
     fen = Fen()
     self.assertEqual('R', fen.__getitem__(Square('a1')))