Stabily integrated ai
This commit is contained in:
parent
086b114853
commit
b3d58f325f
8 changed files with 592 additions and 72 deletions
96
Systems/FairyStockfish/ServerManager.gd
Normal file
96
Systems/FairyStockfish/ServerManager.gd
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# ServerManager.gd
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var server_path: String = ""
|
||||||
|
var running := false
|
||||||
|
var server_process_id: int = -50
|
||||||
|
|
||||||
|
var server_url = "http://localhost:27531"
|
||||||
|
func _ready():
|
||||||
|
# Get the path to the server directory relative to the project
|
||||||
|
server_path = ProjectSettings.globalize_path("res://Assets/ChessEngines/fairy-chess-server")
|
||||||
|
start_server()
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
stop_server()
|
||||||
|
|
||||||
|
func start_server() -> bool:
|
||||||
|
if running:
|
||||||
|
return true
|
||||||
|
|
||||||
|
print("Starting chess server...", server_path)
|
||||||
|
|
||||||
|
# Make sure we're in the correct directory
|
||||||
|
|
||||||
|
|
||||||
|
var http_request = HTTPRequest.new()
|
||||||
|
add_child(http_request)
|
||||||
|
# http_request.connect("request_completed", self._on_init_request_completed)
|
||||||
|
http_request.request_completed.connect(self._on_init_request_completed)
|
||||||
|
http_request.request(server_url + "/health")
|
||||||
|
|
||||||
|
# var http = HTTPClient.new()
|
||||||
|
# var err = http.connect_to_host("localhost", 27531)
|
||||||
|
# print("is_server_running", err, OK)
|
||||||
|
# if err != OK:
|
||||||
|
# server_process_id = OS.create_process("node", [server_path + "/index.js"])
|
||||||
|
|
||||||
|
# if server_process_id <= 0:
|
||||||
|
# printerr("Failed to start server")
|
||||||
|
# return false
|
||||||
|
|
||||||
|
|
||||||
|
# if exit_code != 0:
|
||||||
|
# printerr("Failed to start server: ", output)
|
||||||
|
# return false
|
||||||
|
|
||||||
|
running = true
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _on_init_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||||
|
var json = JSON.new()
|
||||||
|
json.parse(body.get_string_from_utf8())
|
||||||
|
var response = json.get_data()
|
||||||
|
# Will print the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||||
|
print(response)
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
print("HTTP Request failed")
|
||||||
|
server_process_id = OS.create_process("node", [server_path + "/index.js"])
|
||||||
|
|
||||||
|
if server_process_id <= 0:
|
||||||
|
printerr("Failed to start server")
|
||||||
|
return false
|
||||||
|
print("Chess server started")
|
||||||
|
return
|
||||||
|
|
||||||
|
if response.status != "ok":
|
||||||
|
print("Server error : ", response_code, json.parse(body.get_string_from_utf8()),)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
func stop_server():
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Stopping chess server...")
|
||||||
|
# Send a stop request to the server
|
||||||
|
var http = HTTPClient.new()
|
||||||
|
var err = http.connect_to_host("localhost", 27531)
|
||||||
|
if err == OK:
|
||||||
|
# Send a POST request to a shutdown endpoint
|
||||||
|
var headers = ["Content-Type: application/json"]
|
||||||
|
var data = JSON.stringify({"command": "shutdown"})
|
||||||
|
http.request(HTTPClient.METHOD_POST, "/shutdown", headers, data)
|
||||||
|
|
||||||
|
running = false
|
||||||
|
print("Chess server stopped")
|
||||||
|
|
||||||
|
func is_server_running() -> bool:
|
||||||
|
if not running:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Try to connect to the server
|
||||||
|
var http = HTTPClient.new()
|
||||||
|
var err = http.connect_to_host("localhost", 27531)
|
||||||
|
print("is_server_running", err, OK)
|
||||||
|
return err == OK
|
||||||
|
|
@ -6,10 +6,18 @@ var board: Array
|
||||||
var game: ChessGame
|
var game: ChessGame
|
||||||
|
|
||||||
# Engine state
|
# Engine state
|
||||||
var engine_path: String = ""
|
var process_id: int = -1
|
||||||
|
var engine_thread: Thread
|
||||||
|
var pipe_info: Dictionary
|
||||||
|
var command_queue: Array = []
|
||||||
|
var response_queue: Array = []
|
||||||
var mutex: Mutex
|
var mutex: Mutex
|
||||||
|
var semaphore: Semaphore
|
||||||
var running := false
|
var running := false
|
||||||
|
var engine_path: String = ""
|
||||||
|
var _cleanup_in_progress := false
|
||||||
|
var threadTracker: Array = []
|
||||||
|
var last_file_size = 0
|
||||||
# Game state
|
# Game state
|
||||||
var moves: Array = []
|
var moves: Array = []
|
||||||
var move_time: int = 1000 # in ms
|
var move_time: int = 1000 # in ms
|
||||||
|
|
@ -29,6 +37,7 @@ var piece_type_from_symbol := {
|
||||||
func _init(boardRef: Array):
|
func _init(boardRef: Array):
|
||||||
board = boardRef
|
board = boardRef
|
||||||
mutex = Mutex.new()
|
mutex = Mutex.new()
|
||||||
|
semaphore = Semaphore.new()
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
game = get_parent() as ChessGame
|
game = get_parent() as ChessGame
|
||||||
|
|
@ -36,36 +45,82 @@ func _ready():
|
||||||
func _exit_tree():
|
func _exit_tree():
|
||||||
disconnect_engine()
|
disconnect_engine()
|
||||||
|
|
||||||
|
func _notification(what):
|
||||||
|
if what == NOTIFICATION_PREDELETE:
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
func cleanup():
|
||||||
|
if _cleanup_in_progress:
|
||||||
|
return
|
||||||
|
_cleanup_in_progress = true
|
||||||
|
|
||||||
|
disconnect_engine()
|
||||||
|
_cleanup_in_progress = false
|
||||||
|
|
||||||
|
func safe_cleanup():
|
||||||
|
if engine_thread:
|
||||||
|
if engine_thread.is_alive():
|
||||||
|
var timeout = 5000 # 5 seconds
|
||||||
|
var start_time = Time.get_ticks_msec()
|
||||||
|
while engine_thread.is_alive():
|
||||||
|
if Time.get_ticks_msec() - start_time > timeout:
|
||||||
|
printerr("Thread cleanup timeout")
|
||||||
|
break
|
||||||
|
OS.delay_msec(10)
|
||||||
|
var result = engine_thread.wait_to_finish()
|
||||||
|
if result != OK:
|
||||||
|
printerr("Failed to cleanup thread: ", result)
|
||||||
|
engine_thread = null
|
||||||
|
|
||||||
func connect_to_engine(path: String) -> bool:
|
func connect_to_engine(path: String) -> bool:
|
||||||
if running:
|
if running:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
engine_path = path
|
engine_path = path
|
||||||
|
|
||||||
# Test if we can execute stockfish
|
pipe_info = OS.execute_with_pipe(engine_path, ["uci"])
|
||||||
var output = []
|
process_id = pipe_info.get("pid", -1)
|
||||||
var exit_code = OS.execute(engine_path, ["uci"], output, true)
|
if process_id <= 0:
|
||||||
print("Exit code: ", exit_code)
|
printerr("Failed to start engine process")
|
||||||
print("Output: ", output)
|
|
||||||
if exit_code != OK:
|
|
||||||
printerr("Failed to start Stockfish engine: ", exit_code)
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
running = true
|
running = true
|
||||||
print("Connected to engine: ", engine_path)
|
print("PID ", process_id, " Connected to engine: ", engine_path)
|
||||||
|
|
||||||
# Initialize with current game state
|
# Start communication thread
|
||||||
|
engine_thread = Thread.new()
|
||||||
|
engine_thread.start(_engine_thread_function)
|
||||||
|
|
||||||
|
# # Initialize UCI mode
|
||||||
|
# _send_command_wait("uci", "uciok")
|
||||||
|
# _send_command_wait("isready", "readyok")
|
||||||
|
|
||||||
|
# # Initialize with current game state
|
||||||
|
_send_command("ucinewgame")
|
||||||
|
_send_command_wait("isready", "readyok")
|
||||||
load_fen(game.getCurrentFen())
|
load_fen(game.getCurrentFen())
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
func disconnect_engine():
|
func disconnect_engine():
|
||||||
if running:
|
if running:
|
||||||
mutex.lock()
|
_send_command("quit")
|
||||||
var output = []
|
|
||||||
OS.execute(engine_path, ["quit"], output, true)
|
|
||||||
mutex.unlock()
|
|
||||||
running = false
|
running = false
|
||||||
engine_path = ""
|
|
||||||
|
# Wait for thread to finish
|
||||||
|
if engine_thread and engine_thread.is_started():
|
||||||
|
engine_thread.wait_to_finish()
|
||||||
|
|
||||||
|
# Clean up process
|
||||||
|
if pipe_info:
|
||||||
|
if pipe_info.get("stdio"):
|
||||||
|
pipe_info["stdio"].close()
|
||||||
|
if pipe_info.get("stderr"):
|
||||||
|
pipe_info["stderr"].close()
|
||||||
|
if process_id > 0:
|
||||||
|
OS.kill(process_id)
|
||||||
|
process_id = -1
|
||||||
|
|
||||||
print("Disconnected from engine")
|
print("Disconnected from engine")
|
||||||
|
|
||||||
func limit_strength_to(elo_value: int):
|
func limit_strength_to(elo_value: int):
|
||||||
|
|
@ -87,40 +142,35 @@ func load_fen(fen: String):
|
||||||
update_position(fen)
|
update_position(fen)
|
||||||
|
|
||||||
func update_position(fen: String):
|
func update_position(fen: String):
|
||||||
mutex.lock()
|
|
||||||
_send_command("position fen " + fen)
|
_send_command("position fen " + fen)
|
||||||
mutex.unlock()
|
# _send_command_wait("isready", "readyok")
|
||||||
|
|
||||||
func generateMove(think_time_ms: int = 1000) -> void:
|
func generateMove(think_time_ms: int = 1000) -> void:
|
||||||
if not running:
|
if not running:
|
||||||
return
|
return
|
||||||
|
print("------------=========GENERATE MOVE =========-----------------")
|
||||||
|
|
||||||
move_time = think_time_ms
|
move_time = think_time_ms
|
||||||
|
|
||||||
# Update position first
|
# Send position
|
||||||
mutex.lock()
|
|
||||||
var command = "position fen " + game.getCurrentFen()
|
var command = "position fen " + game.getCurrentFen()
|
||||||
if moves.size() > 0:
|
_send_command(command)
|
||||||
command += " moves " + " ".join(moves)
|
# _send_command_wait("isready", "readyok")
|
||||||
print(command)
|
# var response = _send_command_wait("isready", "readyok")
|
||||||
var output = _send_command(command)
|
# if not "readyok" in response:
|
||||||
|
# print("Engine not ready after setting position")
|
||||||
|
# return
|
||||||
|
|
||||||
# Then get move
|
# Get move with timeout for thinking
|
||||||
output = _send_command("go movetime " + str(move_time))
|
var response = _send_command_wait("go movetime " + str(move_time), "bestmove")
|
||||||
if output.size() == 0:
|
print("Move response: ", response)
|
||||||
return
|
|
||||||
print(type_string(typeof(output[0])))
|
# Parse response for bestmove
|
||||||
var lines = output[0].split("\n")
|
var lines = response.split("\n")
|
||||||
mutex.unlock()
|
|
||||||
# Parse the output
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
# print("-")
|
|
||||||
# print(line)
|
|
||||||
# print("-")
|
|
||||||
if line.begins_with("bestmove"):
|
if line.begins_with("bestmove"):
|
||||||
var parts = line.split(" ")
|
var parts = line.split(" ")
|
||||||
print( parts)
|
if parts.size() >= 4: # Should have "bestmove e2e4 ponder e7e5"
|
||||||
if parts.size() >= 2:
|
|
||||||
generated_move = {
|
generated_move = {
|
||||||
"move": parts[1],
|
"move": parts[1],
|
||||||
"ponder": parts[3]
|
"ponder": parts[3]
|
||||||
|
|
@ -172,10 +222,169 @@ func send_move(move_data: Dictionary):
|
||||||
_send_command(command)
|
_send_command(command)
|
||||||
mutex.unlock()
|
mutex.unlock()
|
||||||
|
|
||||||
func _send_command(command: String) -> Array:
|
|
||||||
if not running:
|
|
||||||
return []
|
func safe_get_line(stdio: FileAccess) -> Array:
|
||||||
|
var start_time = Time.get_ticks_msec()
|
||||||
|
var timeout = 500 # 50ms timeout
|
||||||
|
|
||||||
|
if stdio and not stdio.eof_reached():
|
||||||
|
var thread = Thread.new()
|
||||||
|
var mutex = Mutex.new()
|
||||||
|
var done = false
|
||||||
|
var result = ""
|
||||||
|
|
||||||
var output = []
|
thread.start(func():
|
||||||
OS.execute(engine_path, [command], output, true)
|
# Try to read a chunk of data
|
||||||
return output
|
var buffer = stdio.get_buffer(1024) # Read up to 1KB at a time
|
||||||
|
if buffer.size() > 0:
|
||||||
|
var content = buffer.get_string_from_utf8()
|
||||||
|
var lines = content.split("\n")
|
||||||
|
# Get last non-empty line
|
||||||
|
# var last_line = ""
|
||||||
|
# for i in range(lines.size() - 1, -1, -1):
|
||||||
|
# if not lines[i].strip_edges().is_empty():
|
||||||
|
# last_line = lines[i]
|
||||||
|
# break
|
||||||
|
|
||||||
|
mutex.lock()
|
||||||
|
# result = last_line
|
||||||
|
done = true
|
||||||
|
mutex.unlock()
|
||||||
|
print("THREAD: ", lines)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
mutex.lock()
|
||||||
|
done = true
|
||||||
|
mutex.unlock()
|
||||||
|
return []
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for either completion or timeout
|
||||||
|
while true:
|
||||||
|
mutex.lock()
|
||||||
|
var is_done = done
|
||||||
|
mutex.unlock()
|
||||||
|
|
||||||
|
if not thread.is_alive():
|
||||||
|
print("DeadThread")
|
||||||
|
var thread_result = thread.wait_to_finish()
|
||||||
|
print("******T-RES*********", thread_result)
|
||||||
|
return [thread_result, null]
|
||||||
|
# else:
|
||||||
|
# print("Alive THread Kill ME")
|
||||||
|
|
||||||
|
if Time.get_ticks_msec() - start_time > timeout:
|
||||||
|
break
|
||||||
|
|
||||||
|
OS.delay_msec(200)
|
||||||
|
|
||||||
|
# Handle thread cleanup and result capture
|
||||||
|
if thread.is_alive():
|
||||||
|
threadTracker.push_front(thread)
|
||||||
|
mutex.lock()
|
||||||
|
var final_result = [result, null]
|
||||||
|
mutex.unlock()
|
||||||
|
return final_result
|
||||||
|
else:
|
||||||
|
var thread_result = thread.wait_to_finish()
|
||||||
|
return [thread_result, null]
|
||||||
|
|
||||||
|
return ["", null]
|
||||||
|
|
||||||
|
func _engine_thread_function():
|
||||||
|
print("Engine thread started")
|
||||||
|
var stdio = pipe_info.get("stdio")
|
||||||
|
var stderr = pipe_info.get("stderr")
|
||||||
|
|
||||||
|
|
||||||
|
while running:
|
||||||
|
mutex.lock()
|
||||||
|
print("CMDS ", command_queue)
|
||||||
|
if command_queue.size() > 0:
|
||||||
|
var command = command_queue.pop_front()
|
||||||
|
mutex.unlock()
|
||||||
|
|
||||||
|
if stdio:
|
||||||
|
stdio.store_line(command)
|
||||||
|
print("************************Sent command: ", command)
|
||||||
|
OS.delay_msec(200)
|
||||||
|
else:
|
||||||
|
mutex.unlock()
|
||||||
|
|
||||||
|
print("safe_get_line Start")
|
||||||
|
var result = safe_get_line(stdio)
|
||||||
|
print("safe_get_line End", result)
|
||||||
|
|
||||||
|
# var line = result[0]
|
||||||
|
# if not line.is_empty():
|
||||||
|
# mutex.lock()
|
||||||
|
# response_queue.push_back(line)
|
||||||
|
# mutex.unlock()
|
||||||
|
# semaphore.post()
|
||||||
|
# print("Engine response: ", line)
|
||||||
|
# var line = result[0]
|
||||||
|
for line in result:
|
||||||
|
if line and not line.is_empty():
|
||||||
|
mutex.lock()
|
||||||
|
response_queue.append_array(line)
|
||||||
|
mutex.unlock()
|
||||||
|
semaphore.post()
|
||||||
|
print("Engine response: ", line)
|
||||||
|
|
||||||
|
OS.delay_msec(200)
|
||||||
|
|
||||||
|
print("################################left thread###########################")
|
||||||
|
|
||||||
|
func _send_command_wait(command: String, expected: String = "") -> String:
|
||||||
|
# Add command to queue more efficiently
|
||||||
|
mutex.lock()
|
||||||
|
command_queue.push_back(command)
|
||||||
|
mutex.unlock()
|
||||||
|
|
||||||
|
var response = ""
|
||||||
|
var start_time = Time.get_ticks_msec()
|
||||||
|
var timeout = 5000 # Reduced timeout to 5 seconds
|
||||||
|
|
||||||
|
# Use an exponential backoff for delays
|
||||||
|
var delay_time = 1
|
||||||
|
var max_delay = 16 # Maximum delay in milliseconds
|
||||||
|
|
||||||
|
while running:
|
||||||
|
var current_time = Time.get_ticks_msec()
|
||||||
|
if current_time - start_time > timeout:
|
||||||
|
printerr("Command timeout: ", command)
|
||||||
|
break
|
||||||
|
|
||||||
|
if semaphore.try_wait():
|
||||||
|
# Reset delay on successful response
|
||||||
|
delay_time = 1
|
||||||
|
|
||||||
|
mutex.lock()
|
||||||
|
var lines_to_process = response_queue.duplicate()
|
||||||
|
response_queue.clear()
|
||||||
|
mutex.unlock()
|
||||||
|
print("PROCESSING LINES ##################")
|
||||||
|
print(lines_to_process)
|
||||||
|
# if lines_to_process.is_empty():
|
||||||
|
# return
|
||||||
|
# Process all available lines
|
||||||
|
for line in lines_to_process:
|
||||||
|
if not line.is_empty():
|
||||||
|
print(expected, " =? ", line)
|
||||||
|
if expected.is_empty() or expected in line:
|
||||||
|
print("MATCHED LINES ##################", line, ' ', expected)
|
||||||
|
return line
|
||||||
|
# print("This should not print if we returned")
|
||||||
|
else:
|
||||||
|
# Use exponential backoff for delays
|
||||||
|
OS.delay_msec(delay_time)
|
||||||
|
delay_time = mini(delay_time * 2, max_delay)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
func _send_command(command: String):
|
||||||
|
mutex.lock()
|
||||||
|
command_queue.push_back(command)
|
||||||
|
mutex.unlock()
|
||||||
|
|
||||||
|
|
|
||||||
215
Systems/FairyStockfish/StockfishClient.gd
Normal file
215
Systems/FairyStockfish/StockfishClient.gd
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
# StockfishClient.gd
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var server_url = "http://localhost:27531"
|
||||||
|
var http_request: HTTPRequest
|
||||||
|
var running := false
|
||||||
|
var generated_move: Dictionary = {}
|
||||||
|
var move_time: int = 1000
|
||||||
|
var game: ChessGame
|
||||||
|
|
||||||
|
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():
|
||||||
|
print("STARTING SERVER CLIENT")
|
||||||
|
http_request = HTTPRequest.new()
|
||||||
|
add_child(http_request)
|
||||||
|
http_request.request_completed.connect(self._on_request_completed)
|
||||||
|
|
||||||
|
func connect_to_engine(_path: String, g: ChessGame) -> bool:
|
||||||
|
game = g
|
||||||
|
# Wait for server to be ready
|
||||||
|
var retries = 5
|
||||||
|
var delay = 1.0 # seconds
|
||||||
|
|
||||||
|
while retries > 0:
|
||||||
|
if ServerManager.is_server_running():
|
||||||
|
print("**************SERVER RUNNING ****************")
|
||||||
|
running = true
|
||||||
|
start_board();
|
||||||
|
load_fen(game.getCurrentFen())
|
||||||
|
return true
|
||||||
|
|
||||||
|
await get_tree().create_timer(delay).timeout
|
||||||
|
retries -= 1
|
||||||
|
print("**************ATTEMPTING SERVER CONNECTION****************")
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
func disconnect_engine():
|
||||||
|
running = false
|
||||||
|
|
||||||
|
func limit_strength_to(elo_value: int):
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
|
||||||
|
var headers = ["Content-Type: application/json"]
|
||||||
|
var body = JSON.stringify({
|
||||||
|
"options": {
|
||||||
|
"UCI_LimitStrength": "true",
|
||||||
|
"UCI_Elo": str(elo_value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
print(body)
|
||||||
|
http_request.request(server_url + "/setoption", headers, HTTPClient.METHOD_POST, body)
|
||||||
|
|
||||||
|
func stop_calculating():
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
# Send stop command to server
|
||||||
|
http_request.request(server_url + "/stop", [], HTTPClient.METHOD_POST)
|
||||||
|
|
||||||
|
func load_fen(fen: String):
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
# var http_request = HTTPRequest.new()
|
||||||
|
# add_child(http_request)
|
||||||
|
# http_request.request_completed.connect(self._on_request_completed)
|
||||||
|
var headers = ["Content-Type: application/json"]
|
||||||
|
var body = JSON.new().stringify({
|
||||||
|
"fen": fen
|
||||||
|
})
|
||||||
|
print(server_url + "/position")
|
||||||
|
print(body)
|
||||||
|
|
||||||
|
http_request.request(server_url + "/position", headers, HTTPClient.METHOD_POST, body)
|
||||||
|
|
||||||
|
func start_board():
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
var headers = ["Content-Type: application/json"]
|
||||||
|
var body = JSON.new().stringify({
|
||||||
|
"variant": 'chess'
|
||||||
|
})
|
||||||
|
print(server_url + "/new")
|
||||||
|
print(body)
|
||||||
|
http_request.request(server_url + "/new", headers, HTTPClient.METHOD_POST, body)
|
||||||
|
|
||||||
|
|
||||||
|
func update_position(fen: String):
|
||||||
|
load_fen(fen)
|
||||||
|
|
||||||
|
func generateMove(think_time_ms: int = 1000) -> void:
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
print("&&&&&&&&&&&&&&&GENERATING MOVE&&&&&&&&&&&&&&&&&&&&&&")
|
||||||
|
|
||||||
|
move_time = think_time_ms
|
||||||
|
|
||||||
|
var headers = ["Content-Type: application/json"]
|
||||||
|
var body = JSON.stringify({
|
||||||
|
"movetime": think_time_ms,
|
||||||
|
"depth": 15
|
||||||
|
})
|
||||||
|
|
||||||
|
# Request engine move
|
||||||
|
var move_request = HTTPRequest.new()
|
||||||
|
add_child(move_request)
|
||||||
|
move_request.request_completed.connect(self._on_bestmove_completed)
|
||||||
|
var error = move_request.request(
|
||||||
|
server_url + "/enginemove",
|
||||||
|
headers,
|
||||||
|
HTTPClient.METHOD_POST,
|
||||||
|
body
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func getGeneratedMove() -> Dictionary:
|
||||||
|
var move = generated_move.duplicate()
|
||||||
|
generated_move.clear()
|
||||||
|
return move
|
||||||
|
|
||||||
|
func send_move(move_data: Dictionary):
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
|
||||||
|
var headers = ["Content-Type: application/json"]
|
||||||
|
var move_str = from_move_to_string(move_data)
|
||||||
|
var body = JSON.stringify({
|
||||||
|
"move": move_str
|
||||||
|
})
|
||||||
|
|
||||||
|
http_request.request(server_url + "/move", headers, HTTPClient.METHOD_POST, body)
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
func from_move_to_string(move_data: Dictionary) -> String:
|
||||||
|
# Same implementation as original
|
||||||
|
var board_size = 8 # Standard chess board
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
var letters = "abcdefgh"
|
||||||
|
var str_move = "%s%d%s%d" % [
|
||||||
|
letters[source_i],
|
||||||
|
board_size - source_j,
|
||||||
|
letters[target_i],
|
||||||
|
board_size - target_j
|
||||||
|
]
|
||||||
|
|
||||||
|
if move_data.get("flags", "") == "PROMOTION":
|
||||||
|
str_move += symbol_from_piece_type[move_data.promotion_piece].to_lower()
|
||||||
|
|
||||||
|
return str_move
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func _on_bestmove_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||||
|
print("^^^^^^^^^^^^^^^_on_bestmove_completed^^^^^^^^^^^^^^^^^")
|
||||||
|
var json = JSON.new()
|
||||||
|
json.parse(body.get_string_from_utf8())
|
||||||
|
var response = json.get_data()
|
||||||
|
# Will print the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||||
|
print(response)
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
print("HTTP Request failed")
|
||||||
|
return
|
||||||
|
|
||||||
|
if response.status != "ok":
|
||||||
|
print("Server error:", response_code, response,)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
generated_move = {
|
||||||
|
"move": response.move,
|
||||||
|
"ponder": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||||
|
print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
|
||||||
|
var json = JSON.new()
|
||||||
|
json.parse(body.get_string_from_utf8())
|
||||||
|
var response = json.get_data()
|
||||||
|
# Will print the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||||
|
print(response)
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
print("HTTP Request failed")
|
||||||
|
return
|
||||||
|
|
||||||
|
if response.status != "ok":
|
||||||
|
print("Server error:", response_code, json.parse(body.get_string_from_utf8()),)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# Handle different response types
|
||||||
|
if "bestMove" in response:
|
||||||
|
generated_move = {
|
||||||
|
"move": response.bestMove,
|
||||||
|
"ponder": response.get("ponderMove", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
# You can add more response handling here
|
||||||
|
|
@ -26,7 +26,7 @@ var currentlyMovingPiece = null
|
||||||
var p1Points: int = 0
|
var p1Points: int = 0
|
||||||
var p2Points: int = 0
|
var p2Points: int = 0
|
||||||
var Turn: int = 0
|
var Turn: int = 0
|
||||||
const StockfishController = preload("res://Systems/FairyStockfish/Stockfish.gd")
|
const StockfishController = preload("res://Systems/FairyStockfish/StockfishClient.gd")
|
||||||
var stockfishController: StockfishController
|
var stockfishController: StockfishController
|
||||||
var stockfishPath = "res://Assets/ChessEngines/stockfish/stockfish.exe"
|
var stockfishPath = "res://Assets/ChessEngines/stockfish/stockfish.exe"
|
||||||
@onready var turnIndicator: ColorRect = $TurnIndicator
|
@onready var turnIndicator: ColorRect = $TurnIndicator
|
||||||
|
|
@ -56,6 +56,7 @@ var highlightStyle = null
|
||||||
var cpuElo = 1500
|
var cpuElo = 1500
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
print("Initi Local Stockfish")
|
||||||
if OS.get_name() == "Windows":
|
if OS.get_name() == "Windows":
|
||||||
stockfishPath = "res://Assets/ChessEngines/stockfish/stockfish.exe"
|
stockfishPath = "res://Assets/ChessEngines/stockfish/stockfish.exe"
|
||||||
else:
|
else:
|
||||||
|
|
@ -66,10 +67,11 @@ func _ready() -> void:
|
||||||
initializeGame()
|
initializeGame()
|
||||||
initializeTiles()
|
initializeTiles()
|
||||||
stateMachine.transitionToNextState(Constants.WHITE_TURN)
|
stateMachine.transitionToNextState(Constants.WHITE_TURN)
|
||||||
stockfishController = StockfishController.new(board)
|
stockfishController = StockfishController.new()
|
||||||
add_child(stockfishController)
|
add_child(stockfishController)
|
||||||
if stockfishController.connect_to_engine(stockfishPath):
|
stockfishController.connect_to_engine(stockfishPath, self)
|
||||||
stockfishController.limit_strength_to(cpuElo)
|
# if stockfishController.connect_to_engine(stockfishPath):
|
||||||
|
# stockfishController.limit_strength_to(cpuElo)
|
||||||
|
|
||||||
func _exit_tree():
|
func _exit_tree():
|
||||||
stockfishController.disconnect_engine()
|
stockfishController.disconnect_engine()
|
||||||
|
|
@ -134,7 +136,7 @@ func getCurrentFen() -> String:
|
||||||
|
|
||||||
# Add the rest of the FEN string components
|
# Add the rest of the FEN string components
|
||||||
fen += " %s %s %s %d %d" % [
|
fen += " %s %s %s %d %d" % [
|
||||||
"w" if isWhiteToMove else "b",
|
"b" if currentPlayer == BLACK else "w",
|
||||||
castlingRights,
|
castlingRights,
|
||||||
enPassantTarget,
|
enPassantTarget,
|
||||||
halfMoveClock,
|
halfMoveClock,
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,20 @@ extends "res://Systems/StateMachine/ChessGameState.gd"
|
||||||
|
|
||||||
|
|
||||||
var moveTimer: Timer
|
var moveTimer: Timer
|
||||||
|
var stateDelay: Timer
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
moveTimer = Timer.new()
|
moveTimer = Timer.new()
|
||||||
moveTimer.one_shot = true # Timer only fires once
|
moveTimer.one_shot = true # Timer only fires once
|
||||||
moveTimer.connect("timeout", _on_move_timer_timeout)
|
moveTimer.connect("timeout", _on_move_timer_timeout)
|
||||||
add_child(moveTimer)
|
add_child(moveTimer)
|
||||||
|
stateDelay = Timer.new()
|
||||||
|
stateDelay.one_shot = true # Timer only fires once
|
||||||
|
stateDelay.connect("timeout", _on_state_delay_timeout)
|
||||||
|
add_child(stateDelay)
|
||||||
|
|
||||||
func enter(_previous: String, _data := {}) -> void:
|
func enter(_previous: String, _data := {}) -> void:
|
||||||
|
game.moveCount += 1;
|
||||||
print("ENTERING STATE ", Constants.BLACK_TURN)
|
print("ENTERING STATE ", Constants.BLACK_TURN)
|
||||||
game.currentPlayer = game.BLACK
|
game.currentPlayer = game.BLACK
|
||||||
|
|
||||||
|
|
@ -22,9 +28,14 @@ func _on_move_timer_timeout() -> void:
|
||||||
print("------------------GENERATING MOVE --------------------")
|
print("------------------GENERATING MOVE --------------------")
|
||||||
if game.stockfishController:
|
if game.stockfishController:
|
||||||
print("------------------STARTING GENERATING MOVE --------------------")
|
print("------------------STARTING GENERATING MOVE --------------------")
|
||||||
game.stockfishController.generateMove(1000) # 1 second think time
|
|
||||||
finished.emit(Constants.HAND_SETUP)
|
|
||||||
|
|
||||||
|
game.stockfishController.load_fen(game.getCurrentFen())
|
||||||
|
OS.delay_msec(250)
|
||||||
|
game.stockfishController.generateMove(1000) # 1 second think time
|
||||||
|
stateDelay.start(2)
|
||||||
|
|
||||||
|
func _on_state_delay_timeout() -> void:
|
||||||
|
finished.emit(Constants.HAND_SETUP)
|
||||||
func exit() -> void:
|
func exit() -> void:
|
||||||
moveTimer.stop()
|
moveTimer.stop()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,11 @@ func convert_algebraic_to_location(square: String) -> String:
|
||||||
# Convert file letter to number (a=0, b=1, etc)
|
# Convert file letter to number (a=0, b=1, etc)
|
||||||
var file_num = file.unicode_at(0) - 'a'.unicode_at(0)
|
var file_num = file.unicode_at(0) - 'a'.unicode_at(0)
|
||||||
|
|
||||||
# Convert rank to 0-based index from bottom
|
# Since we're working with black's moves and our board is oriented with white at bottom:
|
||||||
var rank_num = rank - 1
|
# 1. Flip file: 7 - file_num to mirror horizontally
|
||||||
|
# 2. Flip rank: 8 - rank to mirror vertically
|
||||||
|
file_num = file_num
|
||||||
|
var rank_num = 8 - rank
|
||||||
|
|
||||||
# Return location in your game's format
|
# Return location in your game's format
|
||||||
return "%d-%d" % [file_num, rank_num]
|
return "%d-%d" % [file_num, rank_num]
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ func clear_tiles() -> void:
|
||||||
remove_tile(location)
|
remove_tile(location)
|
||||||
|
|
||||||
# Function to place random game tiles at the start of each match
|
# Function to place random game tiles at the start of each match
|
||||||
func place_random_game_tiles(num_tiles: int = 8) -> void:
|
func place_random_game_tiles(num_tiles: int = 0) -> void:
|
||||||
if !board_flow:
|
if !board_flow:
|
||||||
push_error("TileManager not initialized with board_flow")
|
push_error("TileManager not initialized with board_flow")
|
||||||
return
|
return
|
||||||
|
|
@ -131,24 +131,7 @@ func place_random_game_tiles(num_tiles: int = 8) -> void:
|
||||||
var next_pos = available_positions[i + 1]
|
var next_pos = available_positions[i + 1]
|
||||||
var next_container = board_flow.get_node(next_pos) as PieceContainer
|
var next_container = board_flow.get_node(next_pos) as PieceContainer
|
||||||
if next_container:
|
if next_container:
|
||||||
# var next_is_white = (int(next_pos.split("-")[0]) + int(next_pos.split("-")[1])) % 2 == 0
|
|
||||||
|
|
||||||
# # Create portal pair with alternating blue/orange colors
|
|
||||||
# # var portal_color = Color.ORANGE if (i % 2 == 0) else Color.BLUE
|
|
||||||
# var portals = PortalTile.create_portal_pair(
|
|
||||||
# container, is_white,
|
|
||||||
# next_container, next_is_white,
|
|
||||||
# -1, # permanent duration
|
|
||||||
# i, # pair id
|
|
||||||
# Color.ORANGE,
|
|
||||||
# Color.BLUE
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Add both portals
|
|
||||||
# add_tile(pos, portals[0])
|
|
||||||
# add_tile(next_pos, portals[1])
|
|
||||||
place_portal_pair(pos, i, available_positions, container, is_white)
|
place_portal_pair(pos, i, available_positions, container, is_white)
|
||||||
# Skip the next position since we used it for the portal pair
|
|
||||||
skipNext = true
|
skipNext = true
|
||||||
else:
|
else:
|
||||||
# If we can't make a pair, fall back to a wall tile
|
# If we can't make a pair, fall back to a wall tile
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ config/icon="res://icon.svg"
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
Constants="*res://Systems/StateMachine/Constants.gd"
|
Constants="*res://Systems/StateMachine/Constants.gd"
|
||||||
|
ServerManager="*res://Systems/FairyStockfish/ServerManager.gd"
|
||||||
|
|
||||||
[editor]
|
[editor]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue