my-chess-engine-test / uci_engine.py
mondk's picture
Upload 3 files
55a8a4c verified
Raw History Blame Contribute Delete
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()