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
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'
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 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
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 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
def sq_to_str(sq): return 'abcdefgh'[file_(sq)] + '12345678'[rank(sq)]
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 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