def under_attack(self, sq, wtm): """determine whether a square is attacked by the given side""" # pawn attacks if wtm: if (self._is_pc_at('P', sq - 0x11) or self._is_pc_at('P', sq - 0xf)): return True else: if (self._is_pc_at('p', sq + 0x11) or self._is_pc_at('p', sq + 0xf)): return True # knight attacks npc = 'N' if wtm else 'n' for d in piece_moves['n']: if self._is_pc_at(npc, sq + d): return True # king attacks kpc = 'K' if wtm else 'k' for d in piece_moves['k']: if self._is_pc_at(kpc, sq + d): return True # bishop/queen attacks for d in piece_moves['b']: cur_sq = sq + d while valid_sq(cur_sq): if self.board[cur_sq] != '-': if wtm: if self.board[cur_sq] in ['B', 'Q']: return True else: if self.board[cur_sq] in ['b', 'q']: return True # square blocked break cur_sq += d # rook/queen attacks for d in piece_moves['r']: cur_sq = sq + d while valid_sq(cur_sq): if self.board[cur_sq] != '-': if wtm: if self.board[cur_sq] in ['R', 'Q']: return True else: if self.board[cur_sq] in ['r', 'q']: return True # square blocked break cur_sq += d return False
def _any_pc_moves(self, sq, pc): if pc == 'P': if self.board[sq + 0x10] == '-': return True if self._pawn_cap_at(sq + 0xf): return True if self._pawn_cap_at(sq + 0x11): return True elif pc == 'p': if self.board[sq - 0x10] == '-': return True if self._pawn_cap_at(sq - 0xf): return True if self._pawn_cap_at(sq - 0x11): return True else: for d in piece_moves[pc.lower()]: cur_sq = sq + d while valid_sq(cur_sq): topc = self.board[cur_sq] if topc == '-' or piece_is_white(topc) != self.wtm: return True if not pc in sliding_pieces or self.board[cur_sq] != '-': break cur_sq += d
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')
def init_direction_table(): for r in range(8): for f in range(8): sq = 0x10 * r + f for d in piece_moves['q']: cur_sq = sq + d while valid_sq(cur_sq): assert (0 <= cur_sq - sq + 0x7f <= 0xff) if direction_table[cur_sq - sq + 0x7f] != 0: assert (d == direction_table[cur_sq - sq + 0x7f]) else: direction_table[cur_sq - sq + 0x7f] = d cur_sq += d
def _is_legal_ep(self, ep): # According to Geurt Gijssen's "An Arbiter's Notebook" #110, # if an en passant capture that is otherwise legal is not # permitted because it would leave the king in check, # then for the puposes of claiming a draw by repetition, the # position is identical to one where there is no such en # passant capture. So we have to test the legality of # en passant captures. if self.wtm: if (valid_sq(ep - 0x11) and self.board[ep - 0x11] == 'P' and Move(self, ep - 0x11, ep, is_ep=True).is_legal()): return True elif (valid_sq(ep - 0xf) and self.board[ep - 0xf] == 'P' and Move(self, ep - 0xf, ep, is_ep=True).is_legal()): return True else: if (valid_sq(ep + 0xf) and self.board[ep + 0xf] == 'p' and Move(self, ep + 0xf, ep, is_ep=True).is_legal()): return True elif (valid_sq(ep + 0x11) and self.board[ep + 0x11] == 'p' and Move(self, ep + 0x11, ep, is_ep=True).is_legal()): return True return False
def get_from_sqs(self, pc, sq): '''given a piece (not including a pawn) and a destination square, return a list of all legal source squares''' ret = [] is_sliding = pc in sliding_pieces for d in piece_moves[pc.lower()]: cur_sq = sq while 1: cur_sq += d if not valid_sq(cur_sq): break if self.board[cur_sq] == pc: if Move(self, cur_sq, sq).is_legal(): ret.append(cur_sq) if not (self.board[cur_sq] == '-' and is_sliding): break return ret
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
def _is_pc_at(self, pc, sq): return valid_sq(sq) and self.board[sq] == pc
def _pawn_cap_at(self, sq): if not valid_sq(sq): return False pc = self.board[sq] return pc != '-' and piece_is_white(pc) != self.wtm
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
def piece_hash(self, sq, pc): assert ((0xf << 7) & sq == 0) assert (valid_sq(sq)) return self._piece[(self._piece_index[pc] << 7) | sq]
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