Example #1
0
def quiesce(fen, alpha, beta, flag, options):
    # flag check
    if not flag.is_set():
        return 0, 0

    # heuristic
    if options.heuristic == 'NeuralNetwork':
        stand_pat = heuristic.nn_heuristic(fen, options, options.model)
    elif options.heuristic == 'Random':
        stand_pat = heuristic.random_heuristic()
    else:
        stand_pat = heuristic.heuristic(fen, options)

    nodes = 1

    if (stand_pat >= beta):
        return beta, nodes

    board = Board(fen)
    if len(board.piece_map()) > 8:
        delta = True
    else:
        delta = False

    if delta:
        # full delta pruning
        if (stand_pat < alpha - 1000):
            return alpha, nodes

    if (stand_pat > alpha):
        alpha = stand_pat

    # expansion and search
    legal = board.legal_moves
    for move in legal:
        board.push(move)
        new = board.copy()
        board.pop()

        if board.is_capture(move) or new.is_check():
            # delta pruning
            if delta and board.is_capture(move):
                value = value_captured_piece(
                    board.piece_type_at(move.to_square)) + 200
                if (stand_pat + value < alpha):
                    continue

            score, count = quiesce(new.fen(), -beta, -alpha, flag, options)
            score = -score
            nodes += count

            if (score >= beta):
                return beta, nodes
            if (score > alpha):
                alpha = score
    return alpha, nodes
    def random_with_first_level_search(self, board: chess.Board):
        moves = list(board.legal_moves)

        best_move = random.sample(moves, 1)[0]
        best_move_value = 0

        for move in moves:
            board.push(move)
            if board.is_checkmate():
                move_value = 100
                if move_value > best_move_value:
                    best_move = move
            board.pop()

            if board.is_into_check(move):
                move_value = 90
                if move_value > best_move_value:
                    best_move = move

            if board.is_capture(move):
                move_value = 80
                if move_value > best_move_value:
                    best_move = move

            if board.is_castling(move):
                move_value = 70
                if move_value > best_move_value:
                    best_move = move

        return best_move
Example #3
0
def move_value(board: chess.Board, move: chess.Move, endgame: bool) -> float:
    """
    How good is a move?
    A promotion is great.
    A weaker piece taking a stronger piece is good.
    A stronger piece taking a weaker piece is bad.
    Also consider the position change via piece-square table.
    """
    if move.promotion is not None:
        return -float("inf") if board.turn == chess.BLACK else float("inf")

    _piece = board.piece_at(move.from_square)
    if _piece:
        _from_value = evaluate_piece(_piece, move.from_square, endgame)
        _to_value = evaluate_piece(_piece, move.to_square, endgame)
        position_change = _to_value - _from_value
    else:
        raise Exception(f"A piece was expected at {move.from_square}")

    capture_value = 0.0
    if board.is_capture(move):
        capture_value = evaluate_capture(board, move)

    current_move_value = capture_value + position_change
    if board.turn == chess.BLACK:
        current_move_value = -current_move_value

    return current_move_value
Example #4
0
def get_board_piece_take_total_value(board: chess.Board) -> float:
    total_value = 0

    for move in board.legal_moves:
        if board.is_capture(move):
            total_value += Macros.BOARD_PIECE_TAKE_VALUE

    return total_value
def capture_square_of_move(board: chess.Board, move: Optional[chess.Move]) -> Optional[Square]:
    capture_square = None
    if move is not None and board.is_capture(move):
        if board.is_en_passant(move):
            # taken from :func:`chess.Board.push()`
            down = -8 if board.turn == chess.WHITE else 8
            capture_square = board.ep_square + down
        else:
            capture_square = move.to_square
    return capture_square
Example #6
0
    def evaluate(self, board: chess.Board) -> None:
        """ Assigns highest value to capture moves and no value to others """
        self.reset_move_variables()

        legal_move_list = list(board.legal_moves)
        for m in legal_move_list:
            if get_piece_at(board, str(m)[:2].upper()) == self.piece.upper():
                piece_at_position = get_piece_at(board, str(m)[2:4]).upper()
                if (not board.is_capture(m)) or (not piece_at_position):
                    self.legal_moves[m] = 1
                else:
                    self.legal_moves[m] = self.value_mapping[
                        piece_at_position].value
Example #7
0
    def evaluate(self, board: chess.Board) -> None:
        """ Assigns highest value to capture moves and no value to others """
        self.reset_move_variables()

        legal_move_list = list(board.legal_moves)
        for m in legal_move_list:
            if board.is_capture(m):
                self.legal_moves[m] = 1
            else:
                self.legal_moves[m] = 0

        self.material_difference.append(
            self.evaluation_function.evaluate(board))
Example #8
0
    def evaluate(self, board: chess.Board) -> None:
        """ Assigns value to no capture moves and no value to captures """
        self.reset_move_variables()

        legal_move_list = list(board.legal_moves)
        # The only difference between this engine and RandomCapture is the
        # not condition.
        for m in legal_move_list:
            if not board.is_capture(m):
                self.legal_moves[m] = 1
            else:
                self.legal_moves[m] = 0

        self.material_difference.append(
            self.evaluation_function.evaluate(board))
Example #9
0
def get_board_attack_total_value(board: chess.Board) -> float:
    total_value = 0

    for move in board.legal_moves:
        captured_piece = board.is_capture(move)
        score_before = KEEP_PIECES_STRATEGY(board)[2] if captured_piece else 0

        board.push_uci(str(move))
        if captured_piece:
            total_value += score_before - KEEP_PIECES_STRATEGY(board)[1]

        total_value += evaluate_game_state(board, True)
        board.pop()

    return total_value
Example #10
0
    def evaluate(self, board: chess.Board) -> None:
        """ Assigns highest value to capture moves based off value system """
        self.reset_move_variables()

        legal_move_list = list(board.legal_moves)
        for m in legal_move_list:
            piece_at_position = get_piece_at(board, str(m)[2:4]).upper()

            if (not board.is_capture(m)) or (not piece_at_position):
                self.legal_moves[m] = 0.0
            else:
                self.legal_moves[m] = self.value_mapping[
                    piece_at_position].value

        self.material_difference.append(
            self.evaluation_function.evaluate(board))
Example #11
0
def quiescence_search(board: chess.Board, alpha: int, beta: int) -> int:
    standard_eval = evaluation(board)
    if standard_eval >= beta:
        return beta
    if alpha < standard_eval:
        alpha = standard_eval

    for move in board.legal_moves:
        if board.is_capture(move):
            board.push(move)
            score = -quiescence_search(board, -beta, -alpha)
            board.pop()
            if score >= beta:
                return beta
            if score > alpha:
                alpha = score
    return alpha
Example #12
0
 def MVVLVA(self, board: chess.Board):
     dic = {}
     for move in board.legal_moves:
         if board.is_capture(move):
             # get victim type
             v = board.piece_at(move.to_square)
             # en passant
             if v == None:
                 v = chess.Piece(chess.PAWN, True)
             v = v.piece_type
             # get attacker type
             a = board.piece_at(move.from_square).piece_type
             # 105 =< value <= 605
             dic[move] = (v * 100) + 6 - (a / 100)
         else:
             dic[move] = 0
     return sorted(dic, key=dic.get, reverse=True)
Example #13
0
def MVV_LVA(
    board: chess.Board, piece_values: Iterable = ConventionalPieceValues
) -> List[chess.Move]:
    """
    Most Valuable Victim - Least Valuable Aggressor implementation for
    move sorting

    Args:
        board (chess.Board): current board state to evaluate
    Returns:
        (List[chess.Move]): sorted list of moves according to MVV_LVA capture
            heuristic
    """
    available_captures: Dict[int, List[chess.Move]] = {}
    move_list = list(board.legal_moves)
    # For each move, evaluate if any captures. If so, rank captures based
    # off value gained
    for move in move_list:
        if board.is_capture(move):
            # Get difference in value between aggressor and victim pieces
            aggressor_piece = get_piece_at(board, str(move)[:2]).upper()
            victim_piece = get_piece_at(board, str(move)[2:]).upper()
            if aggressor_piece and victim_piece:
                value_diff = (
                    piece_values[victim_piece].value
                    - piece_values[aggressor_piece].value
                )

                if value_diff not in available_captures:
                    available_captures[value_diff] = [move]
                else:
                    available_captures[value_diff].append(move)

    # If any available captures, sort captures by value_diff of captures
    # and return list of sorted captures
    if available_captures:
        move_list_sorted = []
        for val_diff in sorted(available_captures, reverse=True):
            move_list_sorted.extend(available_captures[val_diff])
        return move_list_sorted

    # If no captures, return shuffled list of all legal moves
    random.shuffle(move_list)
    return move_list
Example #14
0
    def quiesce(self, board: chess.Board, alpha, beta, depthleft):
        stand_pat = evaluation.evaluation(board)
        if depthleft == 0:
            return stand_pat
        if (stand_pat >= beta):
            return beta
        if (alpha < stand_pat):
            alpha = stand_pat

        for move in self.order(board):
            if board.is_capture(move):
                board.push(move)
                score = -self.quiesce(board, -beta, -alpha, depthleft - 1)
                board.pop()

                if score >= beta:
                    return beta
                if score > alpha:
                    alpha = score
        return alpha
Example #15
0
def quiescence_search(board : chess.Board, alpha : int, beta : int, max_depth : int) -> int:
    standard_eval = evaluation(board)
    if max_depth == 0:
        return standard_eval
    if standard_eval >= beta:
        return beta
    if alpha < standard_eval:
        alpha = standard_eval
    
    for move in board.legal_moves:
        piece = str(board.piece_at(move.from_square))
        if board.is_capture(move):
            if piece == 'q' or piece == 'Q':
                continue
            board.push(move)
            score = -quiescence_search(board, -beta, -alpha, max_depth-1)
            board.pop()
            if score >= beta:
                return beta
            if score > alpha:
                alpha = score
    return alpha
Example #16
0
class Viridithas():
    def __init__(
        self,
        human: bool = False,
        fen: str = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
        pgn: str = '',
        time_limit: float = 15,
        fun: bool = False,
        contempt: int = 3000,
        book: bool = True,
        advancedTC: list = [],
    ):
        if pgn == '':
            self.node = Board(fen)
        else:
            self.node = Board()
            for move in pgn.split():
                try:
                    self.node.push_san(move)
                except Exception:
                    continue
        self.time_limit = time_limit
        if advancedTC:
            self.endpoint = time.time()+advancedTC[0]*60
            self.increment = advancedTC[1]
        else:
            self.endpoint = 0
            self.increment = 0
        self.fun = fun
        self.contempt = contempt
        self.human = human
        self.nodes = 0
        self.advancedTC = advancedTC
        self.hashtable: dict[Hashable, TTEntry] = dict()
        self.inbook = book

    def set_position(self, fen):
        self.node = Board(fen)

    def __repr__(self) -> str:
        return str(self.node) + '\n' + self.__class__.__name__+"-engine at position " + str(self.node.fen())

    def __str__(self) -> str:
        return self.__class__.__name__

    def user_setup(self):
        if input("Do you want to configure the bot? (Y/N) ").upper() != 'Y':
            return

        myname = self.__class__.__name__.upper()

        print(f"BEGINNING USER CONFIGURATION OF {myname}-BOT")

        datadict = get_engine_parameters()

        self.__init__(
            human=datadict["human"],
            fen=datadict["fen"],
            pgn=datadict["pgn"],
            time_limit=datadict["time_limit"],
            fun=datadict["fun"],
            contempt=datadict["contempt"],
            book=datadict["book"],
            advancedTC=datadict["advancedTC"]
        )

    def gameover_check_info(self):
        checkmate = self.node.is_checkmate()
        draw = self.node.is_stalemate() or \
            self.node.is_insufficient_material( ) or \
            self.node.is_repetition(2) or self.node.is_seventyfive_moves() or not any(self.node.generate_legal_moves())
        return checkmate or draw, checkmate, draw

    # @profile
    def evaluate(self, depth: float, checkmate: bool, draw: bool) -> float:
        self.nodes += 1

        if checkmate:
            return MATE_VALUE * int(max(depth+1, 1)) * (1 if self.node.turn else -1)
        if draw:
            return -self.contempt * (1 if self.node.turn else -1)

        rating: float = 0

        rating += pst_eval(self.node)
        # rating += see_eval(self.node)

        # rating += mobility(self.node) * MOBILITY_FACTOR
        
        # rating += piece_attack_counts(self.node) * ATTACK_FACTOR

        # rating += king_safety(self.node) * KING_SAFETY_FACTOR

        # rating += space(self.node) * SPACE_FACTOR

        return rating

    def single_hash_iterator(self, best):
        yield best

    def captures_piece(self, p):  # concentrate on MVV, then LVA
        return itertools.chain(
            self.node.generate_pseudo_legal_moves(self.node.pawns, p),
            self.node.generate_pseudo_legal_moves(self.node.knights, p),
            self.node.generate_pseudo_legal_moves(self.node.bishops, p),
            self.node.generate_pseudo_legal_moves(self.node.rooks, p),
            self.node.generate_pseudo_legal_moves(self.node.queens, p),
            self.node.generate_pseudo_legal_moves(self.node.kings, p),
        )

    #@profile
    def captures(self):  # (MVV/LVA)
        return (m for m in itertools.chain(
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.queens),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.rooks),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.bishops),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.knights),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.pawns),
        ) if self.node.is_legal(m))

    def winning_captures(self):  # (MVV/LVA)
        target_all = self.node.occupied_co[not self.node.turn]
        target_3 = target_all & ~self.node.pawns
        target_5 = target_3 & (~self.node.bishops | ~self.node.knights)
        target_9 = target_5 & ~self.node.rooks
        return itertools.chain(
            self.node.generate_pseudo_legal_moves(self.node.pawns, target_all),
            self.node.generate_pseudo_legal_moves(self.node.knights, target_3),
            self.node.generate_pseudo_legal_moves(self.node.bishops, target_3),
            self.node.generate_pseudo_legal_moves(self.node.rooks, target_5),
            self.node.generate_pseudo_legal_moves(self.node.queens, target_9),
            self.node.generate_pseudo_legal_moves(self.node.kings, target_9),
        )

    def losing_captures(self):  # (MVV/LVA)
        target_pawns = self.node.pawns
        target_pnb = target_pawns | self.node.bishops | self.node.knights
        target_pnbr = target_pnb | self.node.rooks
        return itertools.chain(
            self.node.generate_pseudo_legal_moves(self.node.knights, target_pawns),
            self.node.generate_pseudo_legal_moves(self.node.bishops, target_pawns),
            self.node.generate_pseudo_legal_moves(self.node.rooks, target_pnb),
            self.node.generate_pseudo_legal_moves(self.node.queens, target_pnbr),
            self.node.generate_pseudo_legal_moves(self.node.kings, target_pnbr),
        )

    def ordered_moves(self):
        return (m for m in itertools.chain(
            # self.winning_captures(),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.queens),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.rooks),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.bishops),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.knights),
            self.captures_piece(
                self.node.occupied_co[not self.node.turn] & self.node.pawns),
            self.node.generate_pseudo_legal_moves(
                0xffff_ffff_ffff_ffff, ~self.node.occupied_co[not self.node.turn]),
            # self.losing_captures()
        ) if self.node.is_legal(m))

    def pass_turn(self) -> None:
        self.node.push(Move.from_uci("0000"))

    #@profile
    def qsearch(self, alpha: float, beta: float, depth: float, colour: int, gameover: bool, checkmate: bool, draw: bool) -> float:

        val = self.evaluate(1, checkmate, draw) * colour
        if gameover:
            return val
        if val >= beta:
            return beta
        if (val < alpha - QUEEN_VALUE):
            return alpha
        
        alpha = max(val, alpha)

        for capture in self.captures():
            self.node.push(capture)
            gameover, checkmate, draw = self.gameover_check_info()
            score = -self.qsearch(-beta, -alpha, depth - 1, -colour, gameover, checkmate, draw)
            self.node.pop()
            if score >= beta:
                return score
            alpha = max(score, alpha)

        return alpha

    def tt_lookup(self, board: Board) -> "TTEntry":
        key = board._transposition_key()
        return self.hashtable.get(key, TTEntry.default())

    def tt_store(self, board: Board, entry: TTEntry):
        key = board._transposition_key()    
        self.hashtable[key] = entry

    #@profile
    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

    def pruning(self, depth, colour, alpha, beta, current_pos_is_check):
        if not (not current_pos_is_check and abs(
                alpha) < MATE_VALUE / 2 and abs(beta) < MATE_VALUE / 2) or depth > 2:
            return False

        see = see_eval(self.node) * colour

        DO_D1_PRUNING = depth <= 1 and see + FUTILITY_MARGIN < alpha

        DO_D2_PRUNING = depth <= 2 and see + FUTILITY_MARGIN_2 < alpha

        return DO_D1_PRUNING or DO_D2_PRUNING

    def move_sort(self, moves: list, ratings: list):
        pairs = zip(*sorted(zip(moves, ratings), key=operator.itemgetter(1)))
        moves, ratings = [list(pair) for pair in pairs]
        return moves, ratings

    def pv_string(self):
        count = 0
        moves = []
        while True:
            e = self.tt_lookup(self.node)
            if e.is_null() or not self.node.is_legal(e.best):
                break

            # print(self.node.__str__())
            # print(self.node.__repr__())
            # print(e.best)
            # print(self.node.san(e.best))

            moves.append(self.node.san(e.best))
            self.node.push(e.best)
            count += 1
        
        for _ in moves:
            self.node.pop()

        if count == 0: return ""
        return " ".join(moves)

    def turnmod(self) -> int:
        return -1 if self.node.turn else 1

    def show_iteration_data(self, moves: list, values: list, depth: float, start: float) -> tuple:
        t = round(time.time()-start, 2)
        print(f"{self.node.san(moves[0])} | {-round((self.turnmod()*values[0])/1000, 3)} | {str(t)}s at depth {str(depth + 1)}, {str(self.nodes)} nodes processed, at {str(int(self.nodes / (t+0.00001)))}NPS.\n", f"PV: {self.pv_string()}\n", end="")
        return (self.node.san(moves[0]), self.turnmod()*values[0], self.nodes, depth+1, t)

    def search(self, ponder: bool = False, readout: bool = True):
        val = float("-inf")
        start_time = time.time()
        self.nodes = 0
        moves = [next(self.ordered_moves())]
        saved_position = deepcopy(self.node)

        alpha, beta = float("-inf"), float("inf")
        valWINDOW = PAWN_VALUE / 4

        WINDOW_FAILED = False

        try:
            depth = 1
            while depth < 40:
                best = self.tt_lookup(self.node).best
                time_elapsed = time.time() - start_time
                # check if we aren't going to finish the next search in time
                if time_elapsed > 0.5 * self.time_limit and not ponder and not WINDOW_FAILED:
                    return best, val

                val = self.negamax(
                    depth, self.turnmod(), alpha=alpha, beta=beta)
                # print(val)
                if ((val <= alpha) or (val >= beta)):
                    # We fell outside the window, so try again with a
                    # full-width window (and the same depth).
                    alpha = float("-inf")
                    beta = float("inf")
                    WINDOW_FAILED = True
                    continue
                WINDOW_FAILED = False

                best = self.tt_lookup(self.node).best
                # check if we've run out of time
                if time_elapsed > self.time_limit and not ponder:
                    return best, val

                moves = [self.tt_lookup(self.node).best]
                values = [self.tt_lookup(self.node).value]

                if readout:
                    self.show_iteration_data(moves, values, depth, start_time)

                alpha = val - valWINDOW # Set up the window for the next iteration.
                beta = val + valWINDOW
                depth += 1
        except KeyboardInterrupt:
            self.node = saved_position
            pass
        return moves[0], val

    def ponder(self) -> None:
        self.origin = self.node.copy()
        self.search(ponder=True)

    def get_book_move(self):
        # book = chess.polyglot.open_reader(
        #     r"ProDeo292/ProDeo292/books/elo2500.bin")
        book = chess.polyglot.open_reader(
            r"books/elo2500.bin")
        main_entry = book.find(self.node)
        choice = book.weighted_choice(self.node)
        book.close()
        return main_entry.move, choice.move

    def engine_move(self) -> Move:
        # add flag_func for egtb mode
        if self.advancedTC:
            self.time_limit = (self.endpoint-time.time())/20
        print("Time for move: " + str(round(self.time_limit, 2)) + "s")
        if self.inbook:
            try:
                best, choice = self.get_book_move()
                if self.fun:
                    self.node.push(choice)
                    return choice
                else:
                    self.node.push(best)
                print(chess.pgn.Game.from_board(self.node)[-1])
            except IndexError:
                self.time_limit = self.time_limit*2
                best, _ = self.search()
                self.node.push(best)
                print(chess.pgn.Game.from_board(self.node)[-1])
                self.inbook = False
                self.time_limit = self.time_limit/2
        else:
            best, _ = self.search()
            self.node.push(best)
            print(chess.pgn.Game.from_board(self.node)[-1])
        # self.record_stack()
        self.endpoint += self.increment
        return best

    def user_move(self) -> str:
        move = input("enter move: ")
        while True:
            try:
                self.node.push_san(move)
                break
            except Exception:
                move = input("enter move: ")
        return move

    def display_ending(self) -> None:
        if self.node.is_stalemate():
            print('END BY STALEMATE')
        elif self.node.is_insufficient_material():
            print('END BY INSUFFICIENT MATERIAL')
        elif self.node.is_fivefold_repetition():
            print('END BY FIVEFOLD REPETITION')
        elif self.node.is_checkmate:
            print("BLACK" if self.node.turn == BLACK else "WHITE", 'WINS ON TURN',
                  self.node.fullmove_number)
        else:
            print('END BY UNKNOWN REASON')

    def run_game(self, indefinite=True) -> str:
        while not self.node.is_game_over():
            print(self.__repr__())
            if self.human and self.node.turn:
                try:
                    self.ponder()
                except KeyboardInterrupt:
                    self.node = self.origin
                    self.user_move()

            else:  # SWAP THESE ASAP

                self.engine_move()
            if not indefinite:
                break
        self.display_ending()
        try:
            return str(chess.pgn.Game.from_board(self.node)[-1])
        except Exception:
            return "PGN ERROR"

    def play_viri(self, fen=None):
        player_colour = input(
            "Enter the human player's colour in the form b/w\n--> ")
        while player_colour not in ['b', 'w']:
            player_colour = input(
                "Enter the human player's colour in the form b/w\n--> ")
        player_colour = WHITE if player_colour == 'w' else BLACK
        timeControl = int(
            input("how many seconds should viri get per move?\n--> "))
        self.__init__(human=True, time_limit=timeControl, fen=fen, book=True, fun=False)
        self.fun = False
        while not self.node.is_game_over():
            print(self.__repr__())
            if player_colour == self.node.turn:
                # try:
                #     self.ponder()
                # except KeyboardInterrupt:
                #     self.node = self.origin
                #     self.user_move()
                self.user_move()
            else:
                self.engine_move()
        self.display_ending()

    def perftx(self, n):
        if n == 0:
            self.nodes += 1
        else:
            for move in self.ordered_moves():
                self.node.push(move)
                self.perftx(n - 1)
                self.node.pop()

    def perft(self, n):
        self.nodes = 0
        self.perftx(n)
        print(self.nodes)

    def uci(self):
        start = input()
        while start != "uci":
            start = input()
        print("id", end="")
        while True:
            command = input()
            if command == "ucinewgame":
                board = Board()
            elif command.split()[0] == "position":
                fen = command[command.index(
                    "fen") + 3: command.index("moves") - 1]
                moves = command[command.index("moves"):].split()
                if fen == "startpos":
                    fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
                board = Board(fen)
                for move in moves:
                    board.push(Move.from_uci(move))
Example #17
0
File: move.py Project: TheoLvs/beth
    def __init__(self, value: str, board: Board):
        """Helper class to parse moves on a given board status
        The base class from python-chess miss information like piece value, capture and checkmate
        Used across the library to standardize move parsing and evaluation

        Args:
            value (str): The input move as string (SAN, LAN) or chess Move object
            board (Board): The board status at the right moment, moves and notations depend on the context
        """

        # Parse the string input if input value is not already a move object
        if isinstance(value, str):
            self.move = board.parse_san(value)
        else:
            self.move = value

        # Convert to real SAN value to have a uniformed string version
        self.move_str = board.san(self.move)

        # Store board and future board
        self.board_from = board.deepcopy()
        self.board_to = board.deepcopy()
        self.board_to.push_san(self.move_str)

        # Evaluate move characteristics, capture, from and to square
        self.trajectory = (self.move.from_square, self.move.to_square)
        self.is_capture = board.is_capture(self.move)
        self.from_piece = parse_piece(board.piece_at(self.from_square))

        # Prepare features for display and evaluation
        self.color = self.from_piece["color"].upper()
        self.color_factor = 1 if self.color == "WHITE" else -1
        self.name = self.from_piece["name"].upper()

        # Capture rules
        # Parse piece value of the capture
        if self.is_capture:
            try:
                self.to_piece = parse_piece(board.piece_at(self.to_square))
                self.value = self.to_piece["value"]
            except:
                self.to_piece = {}
                self.value = 0
        else:
            self.to_piece = {}
            self.value = 0

        # Use SAN to determine is the move is a checkmate
        # Modify move value if there is a checkmate
        self.checkmate = "#" in self.move_str
        if self.checkmate:
            self.value = CHECKMATE_VALUE

        # Promotion rules
        # Parsing the SAN name to get promotion piece (With a check the san value is appended by +)
        # Move value increased by the promotion piece minus the pawn value we exchanged
        if "=" in self.move_str:
            self.promoted_piece = PROMOTED_PIECES[self.move_str.split(
                "=")[-1].replace("+", "").replace("#", "")]
            bonus_promotion = PIECE_VALUES_BY_NAME[self.promoted_piece] - 1
            self.value += bonus_promotion