Add some basic algorithms and eval functions for an AI
All checks were successful
Python tests (make) / test (push) Successful in 11s
All checks were successful
Python tests (make) / test (push) Successful in 11s
This commit is contained in:
149
scripts/simulation.py
Normal file
149
scripts/simulation.py
Normal file
@@ -0,0 +1,149 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user