from binding.python_c_ffi import Board from binding.python_c_ffi import Move from binding.python_c_ffi import ChessFFI from binding.python_c_ffi import WHITE from binding.python_c_ffi import print_board from binding.python_c_ffi import sq_to_coord from scripts.evaluation import RandomEval from scripts.evaluation import NegaMaxEval YMD_HM = "%Y-%m-%d-%H-%M" MAX_MOVES = 256 DATA_PATH = "./data" class Engine: def __init__(self, strat_white=None, strat_black=None, max_plys=500): self.chess_ffi = ChessFFI() self.board = Board() self.max_plys = max_plys self.moves = [] self._load_attack_cache() self._seed_engine() random_strat = RandomEval(chess_ffi=self.chess_ffi) nega_strat = NegaMaxEval(chess_ffi=self.chess_ffi, depth=2, cp_window=30) if not strat_white: self.strat_white = random_strat else: self.strat_white = strat_white if not strat_black: self.strat_black = random_strat else: self.strat_black = strat_black def run(self, fen): self.chess_ffi.load_fen(self.board, fen) self._clear_moves() plys = 0 while plys <= self.max_plys: print_board(self.board) moves_buf = (Move * MAX_MOVES)() n_legal = self.chess_ffi.get_legal_moves(self.board, moves_buf) game_over, ending = self._is_game_over(n_legal) if game_over: print(ending) break legal = [moves_buf[i] for i in range(n_legal)] if self.board.side_to_move == WHITE: best_move = self.strat_white.get_best_move(self.board, legal) else: best_move = self.strat_black.get_best_move(self.board, legal) new_board = Board() if not self.chess_ffi.apply_move_on_copy(self.board, new_board, best_move): print("ERROR: apply_move_on_copy failed") break self.board = new_board plys += 1 move = self.to_uci(best_move) self.moves.append(move) if self.board.halfmove_clock >= 100: print("draw (50-move rule)") break for move in self.moves: # print(move) pass print("done") print(f"winner: {self.side_tag(not self.board.side_to_move)}") print(len(self.moves)) def side_tag(self, side): return 'w' if side == WHITE else 'b' def load_fen(self, fen): self.chess_ffi.load_fen(self.board, fen) def _clear_moves(self): self.moves = [] def to_uci(self, move): fr = sq_to_coord(getattr(move, "from")) to = sq_to_coord(move.to) promo = getattr(move, "promo", 0) or "" if promo: MAP = { 1:"n", 2:"b", 3:"r", 4:"q", "n":"n", "b":"b", "r":"r", "q":"q" } promo = MAP.get(promo, "").lower() return f"{fr}{to}{promo}" def _load_attack_cache(self): self.chess_ffi.init_attack_cache() def _seed_engine(self): import ctypes as C, time seed = time.time_ns() s32 = int(seed) & 0xFFFFFFFF libc = C.CDLL("libc.so.6") # on Ubuntu libc.srand.argtypes = (C.c_uint,) libc.srand.restype = None libc.srand(C.c_uint(s32)) return s32 def _is_game_over(self, legal_moves): if legal_moves == 0: if self.chess_ffi.in_check(self.board, self.board.side_to_move): ending = "checkmate" else: ending = "stalemate" return True, ending return False, None if __name__ == "__main__": engine = Engine() fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" engine.run(fen)