Exemple #1
0
 def _to_san(self):
     if self.is_oo:
         san = 'O-O'
     elif self.is_ooo:
         san = 'O-O-O'
     elif self.pc in ['P', 'p']:
         san = ''
         if self.is_capture or self.is_ep:
             san += 'abcdefgh'[file_(self.fr)] + 'x'
         san += sq_to_str(self.to)
         if self.prom:
             san += '=' + self.prom.upper()
     else:
         assert (not self.is_ep)
         san = self.pc.upper()
         ambigs = self.pos.get_from_sqs(self.pc, self.to)
         assert (len(ambigs) >= 1)
         if len(ambigs) > 1:
             r = rank(self.fr)
             f = file_(self.fr)
             # try disambiguating with file
             if len(filter(lambda sq: file_(sq) == f, ambigs)) == 1:
                 san += 'abcdefgh'[f]
             elif len(filter(lambda sq: rank(sq) == r, ambigs)) == 1:
                 san += '12345678'[r]
             else:
                 san += sq_to_str(self.fr)
         if self.is_capture:
             san += 'x'
         san += sq_to_str(self.to)
     return san
Exemple #2
0
    def __init__(self,
                 pos,
                 fr,
                 to,
                 prom=None,
                 is_oo=False,
                 is_ooo=False,
                 is_ep=False,
                 new_ep=None):
        self.pos = pos
        self.fr = fr
        self.to = to
        self.pc = self.pos.board[self.fr]
        self.prom = prom
        self.is_oo = is_oo
        self.is_ooo = is_ooo
        self.capture = pos.board[to]
        self.is_capture = self.capture != '-'
        self.is_ep = is_ep
        self.new_ep = new_ep
        self.time = None
        self.clock_time = None
        self._san = None
        self._verbose_alg = None
        self.lag = 0

        # if a promotion piece is not given, assume queen
        if not self.prom:
            if self.pc == 'p' and rank(to) == 0:
                self.prom = 'q'
            elif self.pc == 'P' and rank(to) == 7:
                self.prom = 'Q'
Exemple #3
0
    def check_pseudo_legal(self):
        """Tests if a move is pseudo-legal, that is, legal ignoring the
        fact that a capture must be made if possible. Also sets en passant
        flags for this move. This is used for long algebraic moves,
        but not san, which does these checks implicitly."""

        if self.pc == '-' or piece_is_white(self.pc) != self.pos.wtm:
            raise IllegalMoveError('can only move own pieces')

        if self.is_capture and piece_is_white(self.capture) == self.pos.wtm:
            raise IllegalMoveError('cannot capture own piece')

        diff = self.to - self.fr
        if self.pc == 'p':
            if self.pos.board[self.to] == '-':
                if diff == -0x10:
                    pass
                elif diff == -0x20 and rank(self.fr) == 6:
                    self.new_ep = self.fr - 0x10
                    if self.pos.board[self.new_ep] != '-':
                        raise IllegalMoveError('bad en passant')
                elif diff in [-0x11, -0xf] and self.to == self.pos.ep:
                    self.is_ep = True
                else:
                    raise IllegalMoveError('bad pawn push')
            else:
                if not diff in [-0x11, -0xf]:
                    raise IllegalMoveError('bad pawn capture')
        elif self.pc == 'P':
            if self.pos.board[self.to] == '-':
                if diff == 0x10:
                    pass
                elif diff == 0x20 and rank(self.fr) == 1:
                    self.new_ep = self.fr + 0x10
                    if self.pos.board[self.new_ep] != '-':
                        raise IllegalMoveError('bad en passant')
                elif diff in [0x11, 0xf] and self.to == self.pos.ep:
                    self.is_ep = True
                else:
                    raise IllegalMoveError('bad pawn push')
            else:
                if not diff in [0x11, 0xf]:
                    raise IllegalMoveError('bad pawn capture')
        else:
            if self.pc in sliding_pieces:
                d = dir(self.fr, self.to)
                if d == 0 or not d in piece_moves[self.pc.lower()]:
                    raise IllegalMoveError('piece cannot make that move')
                # now check if there are any pieces in the way
                cur_sq = self.fr + d
                while cur_sq != self.to:
                    assert (valid_sq(cur_sq))
                    if self.pos.board[cur_sq] != '-':
                        raise IllegalMoveError('sliding piece blocked')
                    cur_sq += d
            else:
                if not diff in piece_moves[self.pc.lower()]:
                    raise IllegalMoveError('piece cannot make that move')
Exemple #4
0
    def detect_check(self):
        """detect whether the player to move is out of material
        (has committed suicide) or stalemated, or if the game
        is drawn by opposite color bishops"""
        self.is_suicide = not any(pc != '-' and (
            piece_is_white(pc) if self.wtm else not piece_is_white(pc))
                                  for (sq, pc) in self)

        self.is_stalemate = not self._any_legal_moves()
        if self.is_stalemate:
            white_material = sum(piece_material[pc.lower()]
                                 for (sq, pc) in self
                                 if pc != '-' and piece_is_white(pc))
            black_material = sum(piece_material[pc.lower()]
                                 for (sq, pc) in self
                                 if pc != '-' and not piece_is_white(pc))
            self.is_stalemate_white = white_material < black_material
            self.is_stalemate_black = white_material > black_material

        self.is_draw_bishops = False
        for (sq, pc) in self:
            if pc == 'B':
                if (rank(sq) ^ file_(sq)) & 1 != 0:
                    break
            elif pc == 'b':
                if (rank(sq) ^ file_(sq)) & 1 == 0:
                    break
            elif pc != '-':
                break
        else:
            self.is_draw_bishops = True
            return

        for (sq, pc) in self:
            if pc == 'B':
                if (rank(sq) ^ file_(sq)) & 1 == 0:
                    break
            elif pc == 'b':
                if (rank(sq) ^ file_(sq)) & 1 != 0:
                    break
            elif pc != '-':
                break
        else:
            self.is_draw_bishops = True
Exemple #5
0
 def _any_pc_moves(self, sq, pc):
     if pc == 'P':
         if self.board[sq + 0x10] == '-':
             if Move(self, sq, sq + 0x10).is_legal():
                 return True
             if rank(sq) == 1 and self.board[sq + 0x20] == '-' and Move(
                     self, sq, sq + 0x20).is_legal():
                 return True
         if self._pawn_cap_at(sq + 0xf) and Move(
                 self, sq, sq + 0xf, is_ep=sq + 0xf == self.ep).is_legal():
             return True
         if self._pawn_cap_at(sq + 0x11) and Move(
                 self, sq, sq + 0x11, is_ep=sq + 0x11
                 == self.ep).is_legal():
             return True
     elif pc == 'p':
         if self.board[sq - 0x10] == '-':
             if Move(self, sq, sq - 0x10).is_legal():
                 return True
             if rank(sq) == 6 and self.board[sq - 0x20] == '-' and Move(
                     self, sq, sq - 0x20).is_legal():
                 return True
         if self._pawn_cap_at(sq - 0xf) and Move(
                 self, sq, sq - 0xf, is_ep=sq - 0xf == self.ep).is_legal():
             return True
         if self._pawn_cap_at(sq - 0x11) and Move(
                 self, sq, sq - 0x11, is_ep=sq - 0x11
                 == self.ep).is_legal():
             return True
     else:
         for d in piece_moves[pc.lower()]:
             cur_sq = sq + d
             # we don't need to check castling because if castling
             # is legal, some other king move must be also
             while valid_sq(cur_sq):
                 topc = self.board[cur_sq]
                 if topc == '-' or piece_is_white(topc) != self.wtm:
                     mv = Move(self, sq, cur_sq)
                     if mv.is_legal():
                         return True
                 if not pc in sliding_pieces or self.board[cur_sq] != '-':
                     break
                 cur_sq += d
Exemple #6
0
    def move_from_san(self, s):
        s = self.decorator_re.sub('', s)
        mv = None

        # examples: e4 e8=Q
        m = self.san_pawn_push_re.match(s)
        if m:
            to = str_to_sq(m.group(1))
            if self.board[to] != '-':
                raise IllegalMoveError('pawn push blocked')
            prom = m.group(2)
            if prom:
                if self.wtm:
                    prom = m.group(2)
                    assert (prom == prom.upper())
                else:
                    prom = m.group(2).lower()
            new_ep = None
            if self.wtm:
                fr = to - 0x10
                if rank(to) == 3 and self.board[fr] == '-':
                    new_ep = fr
                    fr = to - 0x20
                if self.board[fr] != 'P':
                    raise IllegalMoveError('illegal white pawn move')
                if prom:
                    if rank(to) == 7:
                        mv = Move(self, fr, to, prom=prom)
                    else:
                        raise IllegalMoveError('illegal promotion')
                else:
                    mv = Move(self, fr, to, new_ep=new_ep)
            else:
                fr = to + 0x10
                if rank(to) == 4 and self.board[fr] == '-':
                    new_ep = fr
                    fr = to + 0x20
                if self.board[fr] != 'p':
                    raise IllegalMoveError('illegal black pawn move')
                if prom:
                    if rank(to) == 0:
                        mv = Move(self, fr, to, prom=prom)
                    else:
                        raise IllegalMoveError('illegal promotion')
                else:
                    mv = Move(self, fr, to, new_ep=new_ep)

        # examples: dxe4 dxe8=Q
        m = None
        if not mv:
            m = self.san_pawn_capture_re.match(s)
        if m:
            to = str_to_sq(m.group(2))
            prom = m.group(3)
            if prom:
                if self.wtm:
                    assert (prom == prom.upper())
                else:
                    prom = prom.lower()

            is_ep = to == self.ep
            if is_ep:
                assert (self.board[to] == '-')
            else:
                topc = self.board[to]
                if topc == '-' or piece_is_white(topc) == self.wtm:
                    raise IllegalMoveError('bad pawn capture')

            f = 'abcdefgh'.index(m.group(1))
            if f == file_(to) - 1:
                if self.wtm:
                    fr = to - 0x11
                    if self.board[fr] != 'P':
                        raise IllegalMoveError('bad pawn capture')
                else:
                    fr = to + 0xf
                    if self.board[fr] != 'p':
                        raise IllegalMoveError('bad pawn capture')
            elif f == file_(to) + 1:
                if self.wtm:
                    fr = to - 0xf
                    if self.board[fr] != 'P':
                        raise IllegalMoveError('bad pawn capture')
                else:
                    fr = to + 0x11
                    if self.board[fr] != 'p':
                        raise IllegalMoveError('bad pawn capture')
            else:
                raise IllegalMoveError('bad pawn capture file')

            mv = Move(self, fr, to, prom=prom, is_ep=is_ep)

        # examples: Nf3 Nxf3 Ng1xf3
        m = None
        if not mv:
            m = self.san_piece_re.match(s)
        if m:
            to = str_to_sq(m.group(5))
            if m.group(4):
                # capture
                if self.board[to] == '-':
                    raise IllegalMoveError('capture on blank square')
                if piece_is_white(self.board[to]) == self.wtm:
                    raise IllegalMoveError('capture own piece')
            else:
                if self.board[to] != '-':
                    raise IllegalMoveError('missing "x" to indicate capture')

            pc = m.group(1) if self.wtm else m.group(1).lower()
            # TODO: it would be faster to disambiguate first, so we
            # do not check whether moves are legal unnecessarily
            froms = self.get_from_sqs(pc, to)

            if m.group(2):
                if len(froms) <= 1:
                    raise IllegalMoveError('unnecessary disambiguation')
                f = 'abcdefgh'.index(m.group(2))
                froms = filter(lambda sq: file_(sq) == f, froms)

            if m.group(3):
                r = '12345678'.index(m.group(3))
                if len(froms) <= 1:
                    raise IllegalMoveError('unnecessary disambiguation')
                froms = filter(lambda sq: rank(sq) == r, froms)

            if len(froms) != 1:
                raise IllegalMoveError(
                    'illegal or ambiguous move %s: %d interpretations' %
                    (s, len(froms)))

            mv = Move(self, froms[0], to)

        # Legality checking is implicitly done above, so we don't need
        # to check it here.
        '''if mv:
            try:
                mv.check_pseudo_legal()
            except IllegalMoveError:
                raise RuntimeError('san inconsistency')
            mv.check_legal()'''

        return mv
Exemple #7
0
def sq_to_str(sq):
    return 'abcdefgh'[file_(sq)] + '12345678'[rank(sq)]
Exemple #8
0
    def set_pos(self, fen, detect_check=True):
        """Set the position from Forsyth-Fdwards notation.  The format
        is intentionally interpreted strictly; better to give the user an
        error than take in bad data."""
        try:
            # rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
            m = self.set_pos_re.match(fen)
            if not m:
                raise BadFenError('does not look like FEN')
            (pos, side, castle_flags, ep, fifty_count,
             full_moves) = [m.group(i) for i in range(1, 7)]

            ranks = pos.split('/')
            ranks.reverse()
            self.hash = 0
            self.material = [0, 0]
            for (r, rank_str) in enumerate(ranks):
                sq = 0x10 * r
                for c in rank_str:
                    d = '12345678'.find(c)
                    if d >= 0:
                        sq += d + 1
                    else:
                        assert (valid_sq(sq))
                        self.board[sq] = c
                        self.hash ^= zobrist.piece_hash(sq, c)
                        self.material[piece_is_white(c)] += \
                            piece_material[c.lower()]
                        if c == 'k':
                            if self.king_pos[0] is not None:
                                # multiple kings
                                raise BadFenError()
                            self.king_pos[0] = sq
                        elif c == 'K':
                            if self.king_pos[1] is not None:
                                # multiple kings
                                raise BadFenError()
                            self.king_pos[1] = sq
                        elif c.lower() == 'p':
                            if rank(sq) in [0, 7]:
                                # pawn on 1st or 8th rank
                                raise BadFenError()
                        sq += 1
                if sq & 0xf != 8:
                    # wrong row length
                    raise BadFenError()

            if None in self.king_pos:
                # missing king
                raise BadFenError()

            self.wtm = side == 'w'
            if self.wtm:
                self.hash ^= zobrist.side_hash

            if castle_flags == '-':
                self.castle_flags = 0
            else:
                (w_oo, w_ooo, b_oo, b_ooo) = (False, False, False, False)
                for c in castle_flags:
                    if c == 'K':
                        if self.board[E1] != 'K' or self.board[H1] != 'R':
                            raise BadFenError()
                        if w_oo:
                            raise BadFenError()
                        w_oo = True
                    elif c == 'Q':
                        if self.board[E1] != 'K' or self.board[A1] != 'R':
                            raise BadFenError()
                        if w_ooo:
                            raise BadFenError()
                        w_ooo = True
                    elif c == 'k':
                        if self.board[E8] != 'k' or self.board[H8] != 'r':
                            raise BadFenError()
                        if b_oo:
                            raise BadFenError()
                        b_oo = True
                    elif c == 'q':
                        if self.board[E8] != 'k' or self.board[A8] != 'r':
                            raise BadFenError()
                        if b_ooo:
                            raise BadFenError()
                        b_ooo = True
                self.castle_flags = to_castle_flags(w_oo, w_ooo, b_oo, b_ooo)
            self.hash ^= zobrist.castle_hash(self.castle_flags)

            self.fifty_count = int(fifty_count, 10)
            self.ply = 2 * (int(full_moves, 10) - 1) + int(not self.wtm)
            self.start_ply = self.ply  # 0 for new games

            if ep == '-':
                self.ep = None
            else:
                # only set ep if there is a legal capture
                # XXX: this is even stricter than X-FEN; maybe
                # we should not check for true legality, but only
                # whether there is an enemy pawn on an adjacent
                # square?
                self.ep = 'abcdefgh'.index(ep[0]) + \
                    0x10 * '1234567'.index(ep[1])
                if rank(self.ep) not in (2, 5):
                    raise BadFenError('bad en passant square')
                self.hash ^= zobrist.ep_hash(self.ep)
                # legality checking needs a value for in_check
                self.in_check = None
                if not self._is_legal_ep(self.ep):
                    # undo the en passant square
                    self.ep = None
                    self.hash ^= zobrist.ep_hash(self.ep)

            #assert(self.hash == self._compute_hash())
            self.history.set_hash(self.ply, self.hash)

            if detect_check:
                self.detect_check()
                if self.is_checkmate or self.is_stalemate \
                        or self.is_draw_nomaterial:
                    raise BadFenError('got a terminal position')

        except AssertionError:
            raise
Exemple #9
0
    def set_pos(self, fen):
        """Set the position from Forsyth-Fdwards notation.  The format
        is intentionally interpreted strictly; better to give the user an
        error than take in bad data."""
        try:
            # rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1
            m = self.set_pos_re.match(fen)
            if not m:
                raise BadFenError('does not look like FEN')
            (pos, side, castle_flags, ep, fifty_count,
             full_moves) = [m.group(i) for i in range(1, 7)]

            ranks = pos.split('/')
            ranks.reverse()
            self.hash = 0
            self.material = [0, 0]
            for (r, rank_str) in enumerate(ranks):
                sq = 0x10 * r
                for c in rank_str:
                    d = '12345678'.find(c)
                    if d >= 0:
                        sq += d + 1
                    else:
                        assert (valid_sq(sq))
                        self.board[sq] = c
                        self.hash ^= zobrist.piece_hash(sq, c)
                        self.material[piece_is_white(c)] += \
                            piece_material[c.lower()]
                        if c.lower() == 'p':
                            if rank(sq) in [0, 7]:
                                # pawn on 1st or 8th rank
                                raise BadFenError()
                        sq += 1
                if sq & 0xf != 8:
                    # wrong row length
                    raise BadFenError()

            self.wtm = side == 'w'
            if self.wtm:
                self.hash ^= zobrist.side_hash

            self.fifty_count = int(fifty_count, 10)
            self.ply = 2 * (int(full_moves, 10) - 1) + int(not self.wtm)
            self.start_ply = self.ply  # 0 for new games

            if ep == '-':
                self.ep = None
            else:
                # only set ep if there is a legal capture
                # XXX: this is even stricter than X-FEN; maybe
                # we should not check for true legality, but only
                # whether there is an enemy pawn on an adjacent
                # square?
                self.ep = 'abcdefgh'.index(ep[0]) + \
                    0x10 * '1234567'.index(ep[1])
                if rank(self.ep) not in (2, 5):
                    raise BadFenError('bad en passant square')
                self.hash ^= zobrist.ep_hash(self.ep)

            #assert(self.hash == self._compute_hash())
            self.history.set_hash(self.ply, self.hash)

        except AssertionError:
            raise