ChessBuilder/Systems/FairyStockfish/StockfishClient.gd

255 lines
7 KiB
GDScript

# 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_game(2100)
return true
await get_tree().create_timer(delay).timeout
retries -= 1
print("**************ATTEMPTING SERVER CONNECTION****************")
return false
func start_game(elo: int) -> void:
start_board(elo)
load_fen(game.getCurrentFen())
func disconnect_engine():
running = false
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)
await http_request.request_completed
func start_board(elo: int):
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)
await http_request.request_completed
setElo(elo)
func _exit_tree():
ServerManager.stop_server()
disconnect_engine();
func update_position(fen: String):
load_fen(fen)
func get_globalDir() -> String:
if OS.get_name() == "Linux":
return OS.get_environment("HOME").path_join(".local/share/ChessBuilder")
elif OS.get_name() == "Windows":
return OS.get_environment("APPDATA").path_join("Roaming/ChessBuilder")
else: # macOS
return OS.get_environment("HOME").path_join("Library/ChessBuilder")
func setElo(elo: int = 1350) -> void:
if not running:
return
var headers = ["Content-Type: application/json"]
var data = {
"options": [
{
"name": "VariantPath",
"value": get_globalDir() + "/Assets" + "/ChessEngines/Fairy-Stockfish/src/variants.ini"
},
{
"name": "UCI_LimitStrength",
"value": "true"
},
{
"name": "UCI_Elo",
"value": str(elo) # Convert int to string
},
{
"name": "UCI_Variant",
"value": "chessbuilder" # Convert int to string
},
]
}
var body = JSON.new().stringify(data)
# Request engine move
var elo_req = HTTPRequest.new()
add_child(elo_req)
elo_req.request_completed.connect(self._on_request_completed)
elo_req.request(
server_url + "/setoptions",
headers,
HTTPClient.METHOD_POST,
body
)
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,
"fen": str(game.getCurrentFen())
})
# 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 response == null or 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