def mobility(board: Board): mobility = 0 if board.turn == WHITE: mobility -= sum(1 for m in board.generate_pseudo_legal_moves() if not board.is_into_check(m)) board.push(Move.null()) mobility += sum(1 for m in board.generate_pseudo_legal_moves() if not board.is_into_check(m)) board.pop() else: mobility += sum(1 for m in board.generate_pseudo_legal_moves() if not board.is_into_check(m)) board.push(Move.null()) mobility -= sum(1 for m in board.generate_pseudo_legal_moves() if not board.is_into_check(m)) board.pop() return mobility
class TTEntry(): best: Move = Move.null() depth: float = 0 value: float = 0 type: int = 0 null: bool = True @classmethod def default(cls): return TTEntry() def is_null(self) -> bool: return self.null == True
def play_one_game(pwhite, pblack, rnd): """ :type pwhite: NNPLayer :type pblack: NNPLayer :type rnd: int """ board: BoardOptim = BoardOptim.from_chess960_pos(rnd % 960) pwhite.board = board pblack.board = board try: while True: # and board.fullmove_number < 150 if not pwhite.makes_move(rnd): break if not pblack.makes_move(rnd): break if is_debug(): board.write_pgn(pwhite, pblack, os.path.join(os.path.dirname(__file__), "last.pgn"), rnd) except: last = board.move_stack[-1] if board.move_stack else Move.null() logging.warning("Final move: %s %s %s", last, last.from_square, last.to_square) logging.warning("Final position:\n%s", board.unicode()) raise finally: if board.move_stack: board.write_pgn(pwhite, pblack, os.path.join(os.path.dirname(__file__), "last.pgn"), rnd) result = board.result(claim_draw=True) badp = 0 badc = 0 if isinstance(pwhite, NNPLayer): badp += pwhite.invalid_moves pwhite.invalid_moves = 0 badc += 1 if isinstance(pblack, NNPLayer): badp += pblack.invalid_moves pblack.invalid_moves = 0 badc += 1 badp = badp / badc logging.info("Game #%d/%d:\t%s by %s,\t%d moves, %d%% bad", rnd, rnd % 960, result, board.explain(), board.fullmove_number, badp) return result
def _push(self, move: chess.Move) -> None: self._updateJustMovedCells(False) turn = self.board.turn if self._isCellAccessible(self.cellWidgetAtSquare(move.from_square)) \ and move.promotion is None and self.isPseudoLegalPromotion(move): w = self.cellWidgetAtSquare(move.to_square) promotionDialog = _PromotionDialog(parent=self, color=turn, order=self._flipped) if not self._flipped and turn: promotionDialog.move(self.mapToGlobal(w.pos())) else: promotionDialog.move( self.mapToGlobal( QtCore.QPoint(w.x(), w.y() - 3 * w.height()))) promotionDialog.setFixedWidth(w.width()) promotionDialog.setFixedHeight(4 * w.height()) exitCode = promotionDialog.exec_() if exitCode == _PromotionDialog.Accepted: move.promotion = promotionDialog.chosenPiece elif exitCode == _PromotionDialog.Rejected: self.foreachCells(CellWidget.unhighlight, lambda w: w.setChecked(False)) return if not self.board.is_legal(move) or move.null(): # //HACK this is the only way for a move to be illegal (without playtesting) # self.illegalClassicalMove.emit(move) self.entangle_move = True # raise IllegalMove(f"illegal move {move} by ") # logging.debug(f"\n{self.board.lan(move)} ({move.from_square} -> {move.to_square})") san = self.board.san(move) self.board.push(move) # logging.debug(f"\n{self.board}\n") self._updateJustMovedCells(True) self.popStack.clear() self.foreachCells(CellWidget.unmark, CellWidget.unhighlight) self.synchronizeAndUpdateStyles() self.moveMade.emit(move) self.movePushed.emit(san)
def zugzwang(engine: SimpleEngine, puzzle: Puzzle) -> bool: for node in puzzle.mainline[1::2]: board = node.board() if board.is_check(): continue if len(list(board.legal_moves)) > 15: continue score = score_of(engine, board, not puzzle.pov) rev_board = node.board() rev_board.push(Move.null()) rev_score = score_of(engine, rev_board, not puzzle.pov) if win_chances(score) < win_chances(rev_score) - 0.3: return True return False
def push(self, move): backrank = BB_RANK_1 if self.turn == WHITE else BB_RANK_8 is_legal = True dest_sq = move.to_square clear_squares = 0 # Observations that are made as a result of this move are encoded by this var observation = np.zeros((8, 8, 13), dtype='float32') # TODO: Add observation from failed castling # TODO: Add observation from sliding pawn move # Castling is legal if the squares between the king and rook are empty if self.is_kingside_castling(move): cols = BB_FILE_F | BB_FILE_G squares = cols & backrank is_legal = (squares & self.occupied) == 0 elif self.is_queenside_castling(move): cols = BB_FILE_B | BB_FILE_C | BB_FILE_D squares = cols & backrank is_legal = (squares & self.occupied) == 0 elif BB_SQUARES[move.from_square] & self.pawns: # Pawn moves that are straight need to go to empty squares if move.from_square % 8 == move.to_square % 8: is_legal = (BB_SQUARES[move.to_square] & self.occupied) == 0 # Pawn moves that are diagonal need to be captures (accounts for ep) else: is_legal = self.is_capture(move) elif (BB_SQUARES[move.from_square] & (self.bishops | self.rooks | self.queens)): # Returns the new destination and a mask for all squares that were revealed to be empty dest_sq, clear_squares = self.adjust_sliding_move( move.from_square, move.to_square) true_move = Move(move.from_square, dest_sq, promotion=move.promotion) if not is_legal: true_move = Move.null() # The square the pawn is moving to is empty if BB_SQUARES[move.from_square] & self.pawns: observation[square_rank(move.to_square)][square_file( move.to_square)][12] = 1 capture = None if true_move != Move.null(): # Updates visible board. Moves pieces from from_square to to_square # Special case for promotion and castling needed visible = self.visible_state[self.turn] if true_move.promotion is None: visible.set_piece_at(true_move.to_square, visible.piece_at(true_move.from_square)) else: visible._set_piece_at(true_move.to_square, true_move.promotion, self.turn, True) visible.remove_piece_at(true_move.from_square) if self.is_castling(move): if self.is_kingside_castling(move): rook_from = H1 if self.turn else H8 rook_to = F1 if self.turn else F8 else: rook_from = A1 if self.turn else A8 rook_to = D1 if self.turn else D8 visible.set_piece_at(rook_to, visible.piece_at(rook_from)) visible.remove_piece_at(rook_from) capture = self.is_capture(true_move) # Update our visible board to be empty on any squares we moved through if clear_squares: observation = self.get_current_state(self.turn, clear_squares) from_rank = square_rank(true_move.from_square) from_file = square_file(true_move.from_square) to_rank = square_rank(true_move.to_square) to_file = square_file(true_move.to_square) if capture: # If you capture something, update your opponent's visibility self.visible_state[not self.turn].remove_piece_at( true_move.to_square) for i in range(6, 12): # We observe a -1 for all their pieces on that square observation[to_rank][to_file][i] = -1 # Our opponent observes a 1 for all our pieces on that square self.observation[not self.turn][to_rank][to_file][i] = 1 self.observation[self.turn] = observation super().push(true_move) # Return reward for move if true_move == Move.null(): return -5 elif capture: return 5 else: return 0
def negamax(self, depth: float, colour: int, alpha: float, beta: float) -> float: initial_alpha = alpha # (* Transposition Table Lookup; self.node is the lookup key for ttEntry *) tt_entry = self.tt_lookup(self.node) if not tt_entry.is_null() and tt_entry.depth >= depth: if tt_entry.type == EXACT: return tt_entry.value elif tt_entry.type == LOWERBOUND: alpha = max(alpha, tt_entry.value) elif tt_entry.type == UPPERBOUND: beta = min(beta, tt_entry.value) if alpha >= beta: return tt_entry.value if self.node.is_legal(tt_entry.best): moves = itertools.chain([tt_entry.best], filter(lambda x: x != tt_entry.best, self.ordered_moves())) else: moves = self.ordered_moves() else: moves = self.ordered_moves() gameover, checkmate, draw = self.gameover_check_info() if gameover: return colour * self.evaluate(depth, checkmate, draw) if depth < 1: return self.qsearch(alpha, beta, depth, colour, gameover, checkmate, draw) current_pos_is_check = self.node.is_check() if not current_pos_is_check and depth >= 3 and abs(alpha) < MATE_VALUE and abs(beta) < MATE_VALUE: # MAKE A NULL MOVE self.node.push(Move.null()) # PERFORM A LIMITED SEARCH value = - self.negamax(depth - 3, -colour, -beta, -beta + 1) # UNMAKE NULL MOVE self.node.pop() if value >= beta: return beta do_prune = self.pruning(depth, colour, alpha, beta, current_pos_is_check) best_move = Move.null() search_pv = True value = float("-inf") for move_idx, move in enumerate(moves): if move_idx == 0: best_move = move gives_check = self.node.gives_check(move) is_capture = self.node.is_capture(move) is_promo = bool(move.promotion) depth_reduction = search_reduction_factor( move_idx, current_pos_is_check, gives_check, is_capture, is_promo, depth) if do_prune: if not gives_check and not is_capture: continue self.node.push(move) if search_pv: r = -self.negamax(depth - depth_reduction, -colour, -beta, -alpha) value = max(value, r) else: r = -self.negamax(depth - depth_reduction, -colour, -alpha-1, -alpha) if (r > alpha): # // in fail-soft ... & & value < beta) is common r = -self.negamax(depth - depth_reduction, -colour, -beta, -alpha) #// re-search value = max(value, r) self.node.pop() if value > alpha: alpha = value best_move = move alpha = max(alpha, value) if alpha >= beta: break search_pv = False # (* Transposition Table Store; self.node is the lookup key for ttEntry *) # ttEntry = TTEntry() tt_entry.value = value if value <= initial_alpha: tt_entry.type = UPPERBOUND elif value >= beta: tt_entry.type = LOWERBOUND else: tt_entry.type = EXACT tt_entry.depth = depth tt_entry.best = best_move tt_entry.null = False self.tt_store(self.node, tt_entry) return value
from chess import Board, Move, SQUARE_NAMES, KING MOVE_NULL = Move.null() class State: def __init__(self, board: Board, moves_left: int, parent: 'State', move: Move, is_checkmate: bool, hf, zhf): # Core self.board = board self.moves_left = moves_left self.is_checkmate = is_checkmate # Pointer self.parent = parent self.move = move # Id self.zhf = zhf try: self.id = zhf.update(self.parent.id, self.parent.board, self.move, self.moves_left) except AttributeError: self.id = zhf.hash(self.board, self.moves_left) # Score self.hf = hf self.score = hf.score(self) def __hash__(self) -> int: return self.id