# Stockfish.gd extends Node # References to game objects var board: Array var game: ChessGame # Engine state var engine_path: String = "" var mutex: Mutex var running := false # Game state var moves: Array = [] var move_time: int = 1000 # in ms var generated_move: Dictionary = {} # Stores the last generated move # Piece type mapping var symbol_from_piece_type := { "PAWN": "p", "KNIGHT": "n", "BISHOP": "b", "ROOK": "r", "QUEEN": "q", "KING": "k" } var piece_type_from_symbol := { "p": "PAWN", "n": "KNIGHT", "b": "BISHOP", "r": "ROOK", "q": "QUEEN", "k": "KING" } func _init(boardRef: Array): board = boardRef mutex = Mutex.new() func _ready(): game = get_parent() as ChessGame func _exit_tree(): disconnect_engine() func connect_to_engine(path: String) -> bool: if running: return false engine_path = path # Test if we can execute stockfish var output = [] var exit_code = OS.execute(engine_path, ["uci"], output, true) print("Exit code: ", exit_code) print("Output: ", output) if exit_code != OK: printerr("Failed to start Stockfish engine: ", exit_code) return false running = true print("Connected to engine: ", engine_path) # Initialize with current game state load_fen(game.getCurrentFen()) return true func disconnect_engine(): if running: mutex.lock() var output = [] OS.execute(engine_path, ["quit"], output, true) mutex.unlock() running = false engine_path = "" print("Disconnected from engine") func limit_strength_to(elo_value: int): mutex.lock() if elo_value != -1: # Using -1 instead of int.MaxValue _send_command("setoption name UCI_LimitStrength value true") _send_command("setoption name UCI_Elo value " + str(elo_value)) else: _send_command("setoption name UCI_LimitStrength value false") mutex.unlock() func stop_calculating(): mutex.lock() _send_command("stop") mutex.unlock() func load_fen(fen: String): moves.clear() update_position(fen) func update_position(fen: String): mutex.lock() _send_command("position fen " + fen) mutex.unlock() func generateMove(think_time_ms: int = 1000) -> void: if not running: return move_time = think_time_ms # Update position first mutex.lock() var command = "position fen " + game.getCurrentFen() if moves.size() > 0: command += " moves " + " ".join(moves) print(command) var output = _send_command(command) # Then get move output = _send_command("go movetime " + str(move_time)) if output.size() == 0: return print(type_string(typeof(output[0]))) var lines = output[0].split("\n") mutex.unlock() # Parse the output for line in lines: # print("-") # print(line) # print("-") if line.begins_with("bestmove"): var parts = line.split(" ") print( parts) if parts.size() >= 2: generated_move = { "move": parts[1], "ponder": parts[3] } print("Generated move: ", generated_move) return generated_move = {} func getGeneratedMove() -> Dictionary: var move = generated_move.duplicate() generated_move.clear() # Clear after retrieving return move func from_move_to_string(move_data: Dictionary) -> String: var board_size = len(board) # Get source and target squares var source_i = move_data.source % board_size var source_j = move_data.source / board_size var target_i = move_data.target % board_size var target_j = move_data.target / board_size # Convert to algebraic notation var letters = "abcdefghijklmnopqrstuvwxyz".substr(0, board_size) var str_move = "%s%d%s%d" % [ letters[source_i], board_size - source_j, letters[target_i], board_size - target_j ] # Add promotion piece if needed if move_data.get("flags", "") == "PROMOTION": str_move += symbol_from_piece_type[move_data.promotion_piece] return str_move func send_move(move_data: Dictionary): var str_move = from_move_to_string(move_data) moves.append(str_move) print("move: ", str_move) # Update engine with the new move mutex.lock() var command = "position fen " + game.getCurrentFen() if moves.size() > 0: command += " moves " + " ".join(moves) _send_command(command) mutex.unlock() func _send_command(command: String) -> Array: if not running: return [] var output = [] OS.execute(engine_path, [command], output, true) return output