181 lines
No EOL
4.7 KiB
GDScript
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 |