#!/usr/bin/env python3 import sys import torch import chess from transformers import AutoTokenizer, AutoModelForCausalLM MODEL_ID = "zhoudoe23/ChessQween3-base-puzzled" print(f"[Engine] 正在加载模型: {MODEL_ID} ...", file=sys.stderr) tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32) model.eval() print("[Engine] 模型加载成功,准备下棋!", file=sys.stderr) def format_fen_for_model(fen_str: str) -> str: """将标准 FEN 转化为模型训练时的格式""" parts = fen_str.split(" ") # 1. 棋盘字符间加空格 ranks = parts[0].split("/") formatted_ranks = [" ".join(list(rank)) for rank in ranks] formatted_board = " / ".join(formatted_ranks) # 2. 颜色转换 color = "<|WHITE|>" if parts[1] == "w" else "<|BLACK|>" # 3. 组合其他字段 castling = parts[2] ep = parts[3] halfmove = parts[4] fullmove = parts[5] return f"{formatted_board} {color} {castling} {ep} 0 1 >" def is_suicide_move(board: chess.Board, move: chess.Move) -> bool: """精准判断:这一步是不是把重子(车/后)送给对方白吃(Hanging Piece)""" moving_piece = board.piece_at(move.from_square) if not moving_piece: return False # 只针对车 (Rook) 和 后 (Queen) 这种大子做防御(送兵/送马有时是战术弃子,但送车后绝对崩盘) if moving_piece.piece_type not in [chess.ROOK, chess.QUEEN]: return False my_color = board.turn dest_square = move.to_square # 模拟走这步棋 board.push(move) # 1. 如果走完这步直接【绝杀(Checkmate)】,那送子也无所谓,这是好棋! if board.is_checkmate(): board.pop() return False opponent_color = board.turn # 走完后轮到对方 # 2. 检查落子点:落子后,这个格子是否在对方的攻击火力下? is_under_attack = board.is_attacked_by(opponent_color, dest_square) # 3. 检查落子点:落子后,这个格子是否有我方其他棋子在保护? is_defended = board.is_attacked_by(my_color, dest_square) board.pop() # 【核心逻辑】:如果落子点【暴露在对方火力下】且【我方没有任何棋子守护】,这就是纯送死! if is_under_attack and not is_defended: return True # 判定为纯送死(自杀) return False def get_best_move(board: chess.Board) -> chess.Move: raw_fen = board.fen() prompt = format_fen_for_model(raw_fen) inputs = tokenizer(prompt, return_tensors="pt") with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits[0, -1, :] # 将所有 Token 按模型预测的概率从高到低排序 sorted_indices = torch.argsort(logits, descending=True) candidate_moves = [] for token_id in sorted_indices: move_str = tokenizer.decode(token_id.item()).strip() try: move = chess.Move.from_uci(move_str) if move in board.legal_moves: candidate_moves.append(move) except ValueError: continue # 🌟 第一优先级:找【概率高】且【不是自杀送车送后】的走法 for move in candidate_moves: if not is_suicide_move(board, move): return move # 找到了兼具战术直觉与安全的完美走法! # 🌟 保底:如果模型脑子抽风,认为所有走法都是自杀,则被迫返回概率最高的合法走法 return candidate_moves[0] def main(): board = chess.Board() while True: line = sys.stdin.readline() if not line: break line = line.strip() if not line: continue if line == "uci": print("id name ChessQween3-base") print("id author zhoudoe23") print("uciok") sys.stdout.flush() elif line == "isready": print("readyok") sys.stdout.flush() elif line == "ucinewgame": board = chess.Board() elif line.startswith("position"): tokens = line.split() if "startpos" in tokens: board = chess.Board() if "moves" in tokens: move_idx = tokens.index("moves") + 1 for m in tokens[move_idx:]: board.push_uci(m) elif "fen" in tokens: fen_idx = tokens.index("fen") + 1 if "moves" in tokens: moves_idx = tokens.index("moves") fen_str = " ".join(tokens[fen_idx:moves_idx]) board = chess.Board(fen_str) for m in tokens[moves_idx + 1:]: board.push_uci(m) else: fen_str = " ".join(tokens[fen_idx:]) board = chess.Board(fen_str) elif line.startswith("go"): best_move = get_best_move(board) print(f"bestmove {best_move.uci()}") sys.stdout.flush() elif line == "quit": break if __name__ == "__main__": main()