ChessBuilder/Systems/FairyStockfish/Stockfish.gd
2025-02-11 16:22:40 -06:00

181 lines
No EOL
4.7 KiB
GDScript

# 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