Download uci_engine.py from mondk/my-chess-engine-test: direct link, hf CLI and curl.
- Browser
- Download file 5.42 kB
-
https://huggingface.co/mondk/my-chess-engine-test/resolve/main/uci_engine.py
- Command line
-
hf download hf://mondk/my-chess-engine-test/uci_engine.py
-
curl -L -o uci_engine.py https://huggingface.co/mondk/my-chess-engine-test/resolve/main/uci_engine.py
5.42 kB
| """Engine UCI don gian, dung model CNN (policy + value) train tu dataset random-vs-stockfish. | |
| Chay: python uci_engine.py | |
| Yeu cau: pip install torch python-chess | |
| File chess_model.pt phai cung thu muc (hoac sua MODEL_PATH ben duoi).""" | |
| import sys | |
| import os | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import chess | |
| import numpy as np | |
| MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chess_model.pt") | |
| ENGINE_NAME = "RandomVsSF-Net" | |
| ENGINE_AUTHOR = "Ban tu train" | |
| PIECE_ORDER = [chess.PAWN, chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN, chess.KING] | |
| class ChessNet(nn.Module): | |
| def __init__(self, in_planes, channels, num_blocks): | |
| super().__init__() | |
| self.conv_in = nn.Conv2d(in_planes, channels, 3, padding=1) | |
| self.bn_in = nn.BatchNorm2d(channels) | |
| self.blocks = nn.ModuleList([ | |
| nn.Sequential( | |
| nn.Conv2d(channels, channels, 3, padding=1), | |
| nn.BatchNorm2d(channels), | |
| nn.ReLU(), | |
| nn.Conv2d(channels, channels, 3, padding=1), | |
| nn.BatchNorm2d(channels), | |
| ) for _ in range(num_blocks) | |
| ]) | |
| self.policy_conv = nn.Conv2d(channels, 32, 1) | |
| self.policy_fc = nn.Linear(32 * 8 * 8, 4096) | |
| self.value_conv = nn.Conv2d(channels, 8, 1) | |
| self.value_fc1 = nn.Linear(8 * 8 * 8, 64) | |
| self.value_fc2 = nn.Linear(64, 1) | |
| def forward(self, x): | |
| x = F.relu(self.bn_in(self.conv_in(x))) | |
| for block in self.blocks: | |
| x = F.relu(block(x) + x) | |
| p = F.relu(self.policy_conv(x)).flatten(1) | |
| policy_logits = self.policy_fc(p) | |
| v = F.relu(self.value_conv(x)).flatten(1) | |
| v = F.relu(self.value_fc1(v)) | |
| value = torch.tanh(self.value_fc2(v)) | |
| return policy_logits, value | |
| def board_to_tensor(board, num_planes): | |
| planes = np.zeros((num_planes, 8, 8), dtype=np.float32) | |
| for color_idx, color in enumerate([chess.WHITE, chess.BLACK]): | |
| for piece_idx, piece_type in enumerate(PIECE_ORDER): | |
| for sq in board.pieces(piece_type, color): | |
| r, c = chess.square_rank(sq), chess.square_file(sq) | |
| planes[color_idx * 6 + piece_idx, r, c] = 1.0 | |
| if board.turn == chess.WHITE: | |
| planes[12, :, :] = 1.0 | |
| planes[13, :, :] = 1.0 if board.has_kingside_castling_rights(chess.WHITE) else 0.0 | |
| planes[14, :, :] = 1.0 if board.has_queenside_castling_rights(chess.WHITE) else 0.0 | |
| planes[15, :, :] = 1.0 if board.has_kingside_castling_rights(chess.BLACK) else 0.0 | |
| planes[16, :, :] = 1.0 if board.has_queenside_castling_rights(chess.BLACK) else 0.0 | |
| return planes | |
| def select_move(model, board, device, num_planes, top_k=5): | |
| model.eval() | |
| x = torch.from_numpy(board_to_tensor(board, num_planes)).unsqueeze(0).to(device) | |
| with torch.no_grad(): | |
| policy_logits, _ = model(x) | |
| policy_logits = policy_logits.squeeze(0).cpu().numpy() | |
| legal_moves = list(board.legal_moves) | |
| if not legal_moves: | |
| return None | |
| scored = [(mv, policy_logits[mv.from_square * 64 + mv.to_square]) for mv in legal_moves] | |
| scored.sort(key=lambda t: t[1], reverse=True) | |
| candidates = scored[:top_k] | |
| mover_is_white = board.turn == chess.WHITE | |
| best_move, best_value = None, -float("inf") | |
| for mv, _ in candidates: | |
| board.push(mv) | |
| x2 = torch.from_numpy(board_to_tensor(board, num_planes)).unsqueeze(0).to(device) | |
| with torch.no_grad(): | |
| _, value = model(x2) | |
| v = value.item() | |
| v_for_mover = v if mover_is_white else -v | |
| board.pop() | |
| if v_for_mover > best_value: | |
| best_value = v_for_mover | |
| best_move = mv | |
| return best_move | |
| def main(): | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| ckpt = torch.load(MODEL_PATH, map_location=device) | |
| model = ChessNet(ckpt["in_planes"], ckpt["channels"], ckpt["num_blocks"]).to(device) | |
| model.load_state_dict(ckpt["model_state"]) | |
| model.eval() | |
| num_planes = ckpt["in_planes"] | |
| board = chess.Board() | |
| for line in sys.stdin: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| tokens = line.split() | |
| cmd = tokens[0] | |
| if cmd == "uci": | |
| print(f"id name {ENGINE_NAME}") | |
| print(f"id author {ENGINE_AUTHOR}") | |
| print("uciok") | |
| sys.stdout.flush() | |
| elif cmd == "isready": | |
| print("readyok") | |
| sys.stdout.flush() | |
| elif cmd in ("ucinewgame", "ucinewchess"): | |
| board = chess.Board() | |
| elif cmd == "position": | |
| idx = 1 | |
| if tokens[idx] == "startpos": | |
| board = chess.Board() | |
| idx += 1 | |
| elif tokens[idx] == "fen": | |
| fen = " ".join(tokens[idx + 1: idx + 7]) | |
| board = chess.Board(fen) | |
| idx += 7 | |
| if idx < len(tokens) and tokens[idx] == "moves": | |
| for mv_str in tokens[idx + 1:]: | |
| board.push(chess.Move.from_uci(mv_str)) | |
| elif cmd == "go": | |
| mv = select_move(model, board, device, num_planes) | |
| if mv is None: | |
| print("bestmove 0000") | |
| else: | |
| print(f"bestmove {mv.uci()}") | |
| sys.stdout.flush() | |
| elif cmd == "quit": | |
| break | |
| if __name__ == "__main__": | |
| main() | |