Compare commits
No commits in common. "4aa9085c9cb462216438d73a17cafcf23e64e838" and "e90591c306b62fec4060dfc4c3e93f598808c7fc" have entirely different histories.
4aa9085c9c
...
e90591c306
14 changed files with 578 additions and 475 deletions
|
|
@ -16,7 +16,7 @@ function getLogPath() {
|
||||||
return path.join(os.homedir(), 'AppData', 'Roaming', 'ChessBuilder', 'logs');
|
return path.join(os.homedir(), 'AppData', 'Roaming', 'ChessBuilder', 'logs');
|
||||||
} else {
|
} else {
|
||||||
// macOS: ~/Library/Logs/ChessBuilder
|
// macOS: ~/Library/Logs/ChessBuilder
|
||||||
return path.join(os.homedir(), 'Library', 'ChessBuilder', 'logs');
|
return path.join(os.homedir(), 'Library', 'Logs', 'ChessBuilder');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ let board = null;
|
||||||
let engine = null;
|
let engine = null;
|
||||||
let isReady = false;
|
let isReady = false;
|
||||||
let lastResponse = null
|
let lastResponse = null
|
||||||
const SERVER_WAIT_THRESHOLD = 2 * 60 * 1000;
|
const SERVER_WAIT_THRESHOLD = 10 * 60 * 1000;
|
||||||
const CHECK_INTERVAL = 5000;
|
const CHECK_INTERVAL = 5000;
|
||||||
|
|
||||||
// Initialize ffish and engine
|
// Initialize ffish and engine
|
||||||
|
|
@ -104,10 +104,6 @@ app.use(express.json());
|
||||||
|
|
||||||
// Health check endpoint
|
// Health check endpoint
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
console.log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
|
|
||||||
console.log("@@@@@@@@@@@@@@@@@HEALTH CHECK@@@@@@@@@@@@@@@@@@@@@@")
|
|
||||||
console.log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
|
|
||||||
console.log(JSON.stringify(ffish.variants()))
|
|
||||||
lastResponse = new Date().getTime()
|
lastResponse = new Date().getTime()
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
|
|
@ -386,12 +382,11 @@ app.post('/enginemove', async (req, res) => {
|
||||||
const {
|
const {
|
||||||
depth = 15,
|
depth = 15,
|
||||||
movetime = 1000,
|
movetime = 1000,
|
||||||
nodes = null,
|
nodes = null
|
||||||
fen
|
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// const fen = board.fen();
|
const fen = board.fen();
|
||||||
const analysis = await engine.getAnalysis(fen, {
|
const analysis = await engine.getAnalysis(fen, {
|
||||||
depth,
|
depth,
|
||||||
movetime,
|
movetime,
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ async function delay(ms) {
|
||||||
|
|
||||||
async function testServer() {
|
async function testServer() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await axios.post(`${baseURL}/shutdown`);
|
|
||||||
return
|
|
||||||
// Test 1: Health check
|
// Test 1: Health check
|
||||||
console.log('\nTest 1: Health Check');
|
console.log('\nTest 1: Health Check');
|
||||||
const health = await axios.get(`${baseURL}/health`);
|
const health = await axios.get(`${baseURL}/health`);
|
||||||
|
|
@ -18,14 +15,6 @@ async function testServer() {
|
||||||
|
|
||||||
// Wait for engine initialization
|
// Wait for engine initialization
|
||||||
await delay(2000);
|
await delay(2000);
|
||||||
// Test 2: Validate FEN
|
|
||||||
console.log('\nTest 1.5: Validate MOVE');
|
|
||||||
const moveVal = await axios.post(`${baseURL}/move`, {
|
|
||||||
move: '@a6'
|
|
||||||
});
|
|
||||||
console.log('MOVE validation:', moveVal);
|
|
||||||
await delay(2000);
|
|
||||||
|
|
||||||
|
|
||||||
// Test 2: Validate FEN
|
// Test 2: Validate FEN
|
||||||
console.log('\nTest 2: Validate FEN');
|
console.log('\nTest 2: Validate FEN');
|
||||||
|
|
|
||||||
0
Assets/test/simple.txt
Normal file
0
Assets/test/simple.txt
Normal file
|
|
@ -7,8 +7,6 @@ var log_string: String = ""
|
||||||
var running := false
|
var running := false
|
||||||
var server_process_id: int = -50
|
var server_process_id: int = -50
|
||||||
var request_timer: Timer
|
var request_timer: Timer
|
||||||
var server_pinger: Timer
|
|
||||||
var ping_http: HTTPRequest
|
|
||||||
|
|
||||||
var server_url = "http://localhost:27531"
|
var server_url = "http://localhost:27531"
|
||||||
|
|
||||||
|
|
@ -39,39 +37,24 @@ func write_log(message: String):
|
||||||
|
|
||||||
file.close()
|
file.close()
|
||||||
func _ready():
|
func _ready():
|
||||||
|
# Get the path to the server directory relative to the project
|
||||||
|
# server_path = OS.get_executable_path().get_base_dir().path_join("Assets/ChessEngines/fairy-chess-server")
|
||||||
|
# server_path = "res://Assets/ChessEngines/fairy-chess-server"
|
||||||
|
# server_path = ProjectSettings.globalize_path("res://Assets/ChessEngines/fairy-chess-server")
|
||||||
|
# if OS.has_feature("editor"):
|
||||||
|
# server_path = ProjectSettings.globalize_path("res://Assets/ChessEngines/fairy-chess-server")
|
||||||
|
# else:
|
||||||
|
# # Running from an exported project.
|
||||||
|
# # `path` will contain the absolute path to `hello.txt` next to the executable.
|
||||||
|
# # This is *not* identical to using `ProjectSettings.globalize_path()` with a `res://` path,
|
||||||
|
# # but is close enough in spirit.
|
||||||
|
# server_path = OS.get_executable_path().get_base_dir().path_join("Assets/ChessEngines/fairy-chess-server")
|
||||||
|
|
||||||
server_path = extract_server_files() + "/ChessEngines/fairy-chess-server"
|
server_path = extract_server_files() + "/ChessEngines/fairy-chess-server"
|
||||||
setup_logging()
|
setup_logging()
|
||||||
check_server_files(server_path)
|
check_server_files(server_path)
|
||||||
start_server()
|
start_server()
|
||||||
get_tree().set_auto_accept_quit(false)
|
get_tree().set_auto_accept_quit(false)
|
||||||
setup_server_pinger()
|
|
||||||
|
|
||||||
func setup_server_pinger():
|
|
||||||
server_pinger = Timer.new()
|
|
||||||
add_child(server_pinger)
|
|
||||||
server_pinger.wait_time = 60.0
|
|
||||||
server_pinger.one_shot = false
|
|
||||||
server_pinger.timeout.connect(self._on_ping_timer_timeout)
|
|
||||||
ping_http = HTTPRequest.new()
|
|
||||||
add_child(ping_http)
|
|
||||||
ping_http.request_completed.connect(self._on_ping_completed)
|
|
||||||
|
|
||||||
server_pinger.start()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_ping_timer_timeout():
|
|
||||||
write_log("Pinging server health endpoint...")
|
|
||||||
var error = ping_http.request(server_url + "/health")
|
|
||||||
if error != OK:
|
|
||||||
write_log("Error sending ping request: " + str(error))
|
|
||||||
|
|
||||||
|
|
||||||
func _on_ping_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
|
||||||
if result == HTTPRequest.RESULT_SUCCESS:
|
|
||||||
write_log("Server ping successful")
|
|
||||||
else:
|
|
||||||
write_log("Server ping failed - may need to restart server")
|
|
||||||
|
|
||||||
|
|
||||||
func write_log_dir_contents(path: String):
|
func write_log_dir_contents(path: String):
|
||||||
var dir = DirAccess.open(path)
|
var dir = DirAccess.open(path)
|
||||||
|
|
@ -126,15 +109,18 @@ func _notification(what):
|
||||||
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||||
stop_server()
|
stop_server()
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|
||||||
|
|
||||||
func start_server() -> bool:
|
func start_server() -> bool:
|
||||||
if running:
|
if running:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
write_log("Starting chess server... " + server_path)
|
write_log("Starting chess server... " + server_path)
|
||||||
|
|
||||||
|
# Make sure we're in the correct directory
|
||||||
|
|
||||||
|
|
||||||
var http_request = HTTPRequest.new()
|
var http_request = HTTPRequest.new()
|
||||||
add_child(http_request)
|
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_completed.connect(self._on_init_request_completed)
|
||||||
|
|
||||||
request_timer = Timer.new()
|
request_timer = Timer.new()
|
||||||
|
|
|
||||||
390
Systems/FairyStockfish/Stockfish.gd
Normal file
390
Systems/FairyStockfish/Stockfish.gd
Normal file
|
|
@ -0,0 +1,390 @@
|
||||||
|
# Stockfish.gd
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
# References to game objects
|
||||||
|
var board: Array
|
||||||
|
var game: ChessGame
|
||||||
|
|
||||||
|
# Engine state
|
||||||
|
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 semaphore: Semaphore
|
||||||
|
var running := false
|
||||||
|
var engine_path: String = ""
|
||||||
|
var _cleanup_in_progress := false
|
||||||
|
var threadTracker: Array = []
|
||||||
|
var last_file_size = 0
|
||||||
|
# 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()
|
||||||
|
semaphore = Semaphore.new()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
game = get_parent() as ChessGame
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
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:
|
||||||
|
if running:
|
||||||
|
return false
|
||||||
|
|
||||||
|
engine_path = path
|
||||||
|
|
||||||
|
pipe_info = OS.execute_with_pipe(engine_path, ["uci"])
|
||||||
|
process_id = pipe_info.get("pid", -1)
|
||||||
|
if process_id <= 0:
|
||||||
|
printerr("Failed to start engine process")
|
||||||
|
return false
|
||||||
|
|
||||||
|
running = true
|
||||||
|
print("PID ", process_id, " Connected to engine: ", engine_path)
|
||||||
|
|
||||||
|
# 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())
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func disconnect_engine():
|
||||||
|
if running:
|
||||||
|
_send_command("quit")
|
||||||
|
running = false
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
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):
|
||||||
|
_send_command("position fen " + fen)
|
||||||
|
# _send_command_wait("isready", "readyok")
|
||||||
|
|
||||||
|
func generateMove(think_time_ms: int = 1000) -> void:
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
print("------------=========GENERATE MOVE =========-----------------")
|
||||||
|
|
||||||
|
move_time = think_time_ms
|
||||||
|
|
||||||
|
# Send position
|
||||||
|
var command = "position fen " + game.getCurrentFen()
|
||||||
|
_send_command(command)
|
||||||
|
# _send_command_wait("isready", "readyok")
|
||||||
|
# var response = _send_command_wait("isready", "readyok")
|
||||||
|
# if not "readyok" in response:
|
||||||
|
# print("Engine not ready after setting position")
|
||||||
|
# return
|
||||||
|
|
||||||
|
# Get move with timeout for thinking
|
||||||
|
var response = _send_command_wait("go movetime " + str(move_time), "bestmove")
|
||||||
|
print("Move response: ", response)
|
||||||
|
|
||||||
|
# Parse response for bestmove
|
||||||
|
var lines = response.split("\n")
|
||||||
|
for line in lines:
|
||||||
|
if line.begins_with("bestmove"):
|
||||||
|
var parts = line.split(" ")
|
||||||
|
if parts.size() >= 4: # Should have "bestmove e2e4 ponder e7e5"
|
||||||
|
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 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 = ""
|
||||||
|
|
||||||
|
thread.start(func():
|
||||||
|
# Try to read a chunk of data
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
@ -34,7 +34,8 @@ func connect_to_engine(_path: String, g: ChessGame) -> bool:
|
||||||
if ServerManager.is_server_running():
|
if ServerManager.is_server_running():
|
||||||
print("**************SERVER RUNNING ****************")
|
print("**************SERVER RUNNING ****************")
|
||||||
running = true
|
running = true
|
||||||
start_game(1350)
|
start_board();
|
||||||
|
load_fen(game.getCurrentFen())
|
||||||
return true
|
return true
|
||||||
|
|
||||||
await get_tree().create_timer(delay).timeout
|
await get_tree().create_timer(delay).timeout
|
||||||
|
|
@ -43,16 +44,23 @@ func connect_to_engine(_path: String, g: ChessGame) -> bool:
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
func start_game(elo: int) -> void:
|
|
||||||
start_board(elo)
|
|
||||||
load_fen(game.getCurrentFen())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func disconnect_engine():
|
func disconnect_engine():
|
||||||
running = false
|
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():
|
func stop_calculating():
|
||||||
if not running:
|
if not running:
|
||||||
return
|
return
|
||||||
|
|
@ -74,7 +82,7 @@ func load_fen(fen: String):
|
||||||
|
|
||||||
http_request.request(server_url + "/position", headers, HTTPClient.METHOD_POST, body)
|
http_request.request(server_url + "/position", headers, HTTPClient.METHOD_POST, body)
|
||||||
await http_request.request_completed
|
await http_request.request_completed
|
||||||
func start_board(elo: int):
|
func start_board():
|
||||||
if not running:
|
if not running:
|
||||||
return
|
return
|
||||||
var headers = ["Content-Type: application/json"]
|
var headers = ["Content-Type: application/json"]
|
||||||
|
|
@ -85,7 +93,7 @@ func start_board(elo: int):
|
||||||
print(body)
|
print(body)
|
||||||
http_request.request(server_url + "/new", headers, HTTPClient.METHOD_POST, body)
|
http_request.request(server_url + "/new", headers, HTTPClient.METHOD_POST, body)
|
||||||
await http_request.request_completed
|
await http_request.request_completed
|
||||||
setElo(elo)
|
setElo(1350)
|
||||||
|
|
||||||
func _exit_tree():
|
func _exit_tree():
|
||||||
ServerManager.stop_server()
|
ServerManager.stop_server()
|
||||||
|
|
@ -95,28 +103,12 @@ func _exit_tree():
|
||||||
func update_position(fen: String):
|
func update_position(fen: String):
|
||||||
load_fen(fen)
|
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:
|
func setElo(elo: int = 1350) -> void:
|
||||||
if not running:
|
if not running:
|
||||||
return
|
return
|
||||||
var headers = ["Content-Type: application/json"]
|
var headers = ["Content-Type: application/json"]
|
||||||
var data = {
|
var data = {
|
||||||
"options": [
|
"options": [
|
||||||
{
|
|
||||||
"name": "VariantPath",
|
|
||||||
"value": get_globalDir() + "/Assets" + "/ChessEngines/Fairy-Stockfish/src/variants.ini"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "UCI_LimitStrength",
|
"name": "UCI_LimitStrength",
|
||||||
"value": "true"
|
"value": "true"
|
||||||
|
|
@ -124,11 +116,7 @@ func setElo(elo: int = 1350) -> void:
|
||||||
{
|
{
|
||||||
"name": "UCI_Elo",
|
"name": "UCI_Elo",
|
||||||
"value": str(elo) # Convert int to string
|
"value": str(elo) # Convert int to string
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"name": "UCI_Variant",
|
|
||||||
"value": "chessbuilder" # Convert int to string
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,8 +143,7 @@ func generateMove(think_time_ms: int = 1000) -> void:
|
||||||
var headers = ["Content-Type: application/json"]
|
var headers = ["Content-Type: application/json"]
|
||||||
var body = JSON.stringify({
|
var body = JSON.stringify({
|
||||||
"movetime": think_time_ms,
|
"movetime": think_time_ms,
|
||||||
"depth": 15,
|
"depth": 15
|
||||||
"fen": str(game.getCurrentFen())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Request engine move
|
# Request engine move
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,6 @@ func _ready() -> void:
|
||||||
# if stockfishController.connect_to_engine(stockfishPath):
|
# if stockfishController.connect_to_engine(stockfishPath):
|
||||||
# stockfishController.limit_strength_to(cpuElo)
|
# stockfishController.limit_strength_to(cpuElo)
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree():
|
func _exit_tree():
|
||||||
stockfishController.disconnect_engine()
|
stockfishController.disconnect_engine()
|
||||||
|
|
||||||
|
|
@ -104,44 +103,7 @@ func initializeCardPreview() -> void:
|
||||||
add_child(cardPreview)
|
add_child(cardPreview)
|
||||||
|
|
||||||
|
|
||||||
func getSpecialTilesFen() -> String:
|
|
||||||
if tileManager.active_tiles.is_empty():
|
|
||||||
return ""
|
|
||||||
|
|
||||||
var special_fen = " moves"
|
|
||||||
var portal_pairs = {} # Dictionary to track portal pairs by pair_id
|
|
||||||
|
|
||||||
# First pass: Collect portal pairs
|
|
||||||
for pos in tileManager.active_tiles:
|
|
||||||
var tile = tileManager.active_tiles[pos]
|
|
||||||
if tile is PortalTile:
|
|
||||||
var pair_id = tile.pair_id
|
|
||||||
if !portal_pairs.has(pair_id):
|
|
||||||
portal_pairs[pair_id] = []
|
|
||||||
portal_pairs[pair_id].append(pos)
|
|
||||||
|
|
||||||
# Second pass: Add walls and portal pairs to FEN
|
|
||||||
for pos in tileManager.active_tiles:
|
|
||||||
var tile = tileManager.active_tiles[pos]
|
|
||||||
var algebraic = Utils.location_to_algebraic(pos)
|
|
||||||
|
|
||||||
if tile is WallTile or tile is DoubleWallTile:
|
|
||||||
if tile is DoubleWallTile:
|
|
||||||
special_fen += " @%s" % algebraic
|
|
||||||
else:
|
|
||||||
special_fen += " @%s" % algebraic
|
|
||||||
# elif tile is PortalTile:
|
|
||||||
# # Only process each portal pair once
|
|
||||||
# var pair_id = tile.pair_id
|
|
||||||
# if portal_pairs.has(pair_id):
|
|
||||||
# var pair = portal_pairs[pair_id]
|
|
||||||
# if pair.size() == 2:
|
|
||||||
# var alg1 = Utils.location_to_algebraic(pair[0])
|
|
||||||
# var alg2 = Utils.location_to_algebraic(pair[1])
|
|
||||||
# special_fen += " portal %s=p1 portal %s=p2" % [alg1, alg2]
|
|
||||||
# portal_pairs.erase(pair_id) # Process each pair only once
|
|
||||||
|
|
||||||
return special_fen
|
|
||||||
|
|
||||||
func getCurrentFen() -> String:
|
func getCurrentFen() -> String:
|
||||||
var fen = ""
|
var fen = ""
|
||||||
|
|
@ -153,19 +115,6 @@ func getCurrentFen() -> String:
|
||||||
for x in range(boardXSize):
|
for x in range(boardXSize):
|
||||||
# print("CHECKING ", str(x) + "-" + str(y))
|
# print("CHECKING ", str(x) + "-" + str(y))
|
||||||
var container = boardContainer.get_node(str(x) + "-" + str(y)) as PieceContainer
|
var container = boardContainer.get_node(str(x) + "-" + str(y)) as PieceContainer
|
||||||
|
|
||||||
if tileManager.active_tiles.has(str(x) + "-" + str(y)):
|
|
||||||
|
|
||||||
if emptySquares > 0:
|
|
||||||
fen += str(emptySquares)
|
|
||||||
emptySquares = 0
|
|
||||||
var tile = tileManager.active_tiles[str(x) + "-" + str(y)]
|
|
||||||
if tile is WallTile or tile is DoubleWallTile:
|
|
||||||
if tile.tile_name == "Double Wall":
|
|
||||||
fen += "*"
|
|
||||||
else:
|
|
||||||
fen += "*"
|
|
||||||
else:
|
|
||||||
var piece = container.get_piece()
|
var piece = container.get_piece()
|
||||||
if piece == null:
|
if piece == null:
|
||||||
emptySquares += 1
|
emptySquares += 1
|
||||||
|
|
@ -176,6 +125,7 @@ func getCurrentFen() -> String:
|
||||||
# Convert piece to FEN notation
|
# Convert piece to FEN notation
|
||||||
var fenChar = getPieceFenChar(piece)
|
var fenChar = getPieceFenChar(piece)
|
||||||
fen += fenChar
|
fen += fenChar
|
||||||
|
|
||||||
# Add any remaining empty squares at the end of the rank
|
# Add any remaining empty squares at the end of the rank
|
||||||
if emptySquares > 0:
|
if emptySquares > 0:
|
||||||
fen += str(emptySquares)
|
fen += str(emptySquares)
|
||||||
|
|
@ -192,12 +142,7 @@ func getCurrentFen() -> String:
|
||||||
halfMoveClock,
|
halfMoveClock,
|
||||||
moveCount
|
moveCount
|
||||||
]
|
]
|
||||||
var specialChars = getSpecialTilesFen();
|
|
||||||
print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
|
|
||||||
print(specialChars)
|
|
||||||
print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
|
|
||||||
# fen += specialChars;
|
|
||||||
currentFen = fen;
|
|
||||||
return fen
|
return fen
|
||||||
func initializeBoard() -> void:
|
func initializeBoard() -> void:
|
||||||
# Parse FEN to get board dimensions
|
# Parse FEN to get board dimensions
|
||||||
|
|
@ -395,22 +340,22 @@ func summonPiece(pieceName: String, color: int) -> Node:
|
||||||
var piece
|
var piece
|
||||||
match pieceName:
|
match pieceName:
|
||||||
"Pawn":
|
"Pawn":
|
||||||
piece = Pawn.new(self)
|
piece = Pawn.new()
|
||||||
piece.name = "Pawn"
|
piece.name = "Pawn"
|
||||||
"King":
|
"King":
|
||||||
piece = King.new(self)
|
piece = King.new()
|
||||||
piece.name = "King"
|
piece.name = "King"
|
||||||
"Queen":
|
"Queen":
|
||||||
piece = Queen.new(self)
|
piece = Queen.new()
|
||||||
piece.name = "Queen"
|
piece.name = "Queen"
|
||||||
"Knight":
|
"Knight":
|
||||||
piece = Knight.new(self)
|
piece = Knight.new()
|
||||||
piece.name = "Knight"
|
piece.name = "Knight"
|
||||||
"Rook":
|
"Rook":
|
||||||
piece = Rook.new(self)
|
piece = Rook.new()
|
||||||
piece.name = "Rook"
|
piece.name = "Rook"
|
||||||
"Bishop":
|
"Bishop":
|
||||||
piece = Bishop.new(self)
|
piece = Bishop.new()
|
||||||
piece.name = "Bishop"
|
piece.name = "Bishop"
|
||||||
|
|
||||||
piece.Item_Color = color
|
piece.Item_Color = color
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,4 @@ func get_overlay(overlay_name: String) -> Node:
|
||||||
|
|
||||||
func has_piece() -> bool:
|
func has_piece() -> bool:
|
||||||
return piece != null
|
return piece != null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,12 @@ func enter(_previous: String, _data := {}) -> void:
|
||||||
var target_square = move_str.substr(2, 2) # "e4"
|
var target_square = move_str.substr(2, 2) # "e4"
|
||||||
|
|
||||||
# First select the piece
|
# First select the piece
|
||||||
var source_location = Utils.convert_algebraic_to_location(source_square)
|
var source_location = convert_algebraic_to_location(source_square)
|
||||||
game.selectedNode = source_location
|
game.selectedNode = source_location
|
||||||
print("source_location ", source_location)
|
print("source_location ", source_location)
|
||||||
|
|
||||||
# Then make the move
|
# Then make the move
|
||||||
var target_location = Utils.convert_algebraic_to_location(target_square)
|
var target_location = convert_algebraic_to_location(target_square)
|
||||||
print("target_location ", target_location)
|
print("target_location ", target_location)
|
||||||
handleMovement(target_location, true)
|
handleMovement(target_location, true)
|
||||||
return
|
return
|
||||||
|
|
@ -67,6 +67,21 @@ func exit() -> void:
|
||||||
game.boardContainer.disconnect("tile_pressed", handleMovement)
|
game.boardContainer.disconnect("tile_pressed", handleMovement)
|
||||||
|
|
||||||
|
|
||||||
|
func convert_algebraic_to_location(square: String) -> String:
|
||||||
|
var file = square[0] # letter (a-h)
|
||||||
|
var rank = int(square[1]) # number (1-8)
|
||||||
|
|
||||||
|
# Convert file letter to number (a=0, b=1, etc)
|
||||||
|
var file_num = file.unicode_at(0) - 'a'.unicode_at(0)
|
||||||
|
|
||||||
|
# Since we're working with black's moves and our board is oriented with white at bottom:
|
||||||
|
# 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 "%d-%d" % [file_num, rank_num]
|
||||||
|
|
||||||
func handleMovement(location: String, generated: bool = false) -> void:
|
func handleMovement(location: String, generated: bool = false) -> void:
|
||||||
# we need to prevent swapping of focus between peices after the double move process has started
|
# we need to prevent swapping of focus between peices after the double move process has started
|
||||||
|
|
|
||||||
|
|
@ -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 = 5) -> 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
|
||||||
|
|
@ -98,49 +98,6 @@ func place_random_game_tiles(num_tiles: int = 5) -> void:
|
||||||
available_positions.shuffle()
|
available_positions.shuffle()
|
||||||
|
|
||||||
var skipNext = false;
|
var skipNext = false;
|
||||||
|
|
||||||
# var wall: Tile
|
|
||||||
# var p = '0-2'
|
|
||||||
# var cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# var w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# wall = WallTile.new(cntr, w, -1)
|
|
||||||
# add_tile(p, wall)
|
|
||||||
# # p = '1-2'
|
|
||||||
# # cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# # w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# # wall = WallTile.new(cntr, w, -1)
|
|
||||||
# # add_tile(p, wall)
|
|
||||||
# p = '2-2'
|
|
||||||
# cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# wall = WallTile.new(cntr, w, -1)
|
|
||||||
# add_tile(p, wall)
|
|
||||||
# p = '3-2'
|
|
||||||
# cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# wall = WallTile.new(cntr, w, -1)
|
|
||||||
# add_tile(p, wall)
|
|
||||||
# p = '4-2'
|
|
||||||
# cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# wall = WallTile.new(cntr, w, -1)
|
|
||||||
# add_tile(p, wall)
|
|
||||||
# p = '5-2'
|
|
||||||
# cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# wall = WallTile.new(cntr, w, -1)
|
|
||||||
# add_tile(p, wall)
|
|
||||||
# # p = '6-2'
|
|
||||||
# # cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# # w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# # wall = WallTile.new(cntr, w, -1)
|
|
||||||
# # add_tile(p, wall)
|
|
||||||
# p = '7-2'
|
|
||||||
# cntr = board_flow.get_node(p) as PieceContainer
|
|
||||||
# w = (int(p.split("-")[0]) + int(p.split("-")[1])) % 2 == 0
|
|
||||||
# wall = DoubleWallTile.new(cntr, w, -1)
|
|
||||||
# add_tile(p, wall)
|
|
||||||
|
|
||||||
# Skip over an iteration for a paired tile
|
# Skip over an iteration for a paired tile
|
||||||
for i in range(min(num_tiles, available_positions.size())):
|
for i in range(min(num_tiles, available_positions.size())):
|
||||||
if skipNext:
|
if skipNext:
|
||||||
|
|
@ -157,13 +114,12 @@ func place_random_game_tiles(num_tiles: int = 5) -> void:
|
||||||
var rng = RandomNumberGenerator.new()
|
var rng = RandomNumberGenerator.new()
|
||||||
rng.randomize()
|
rng.randomize()
|
||||||
var tile_type = rng.randi() % 3
|
var tile_type = rng.randi() % 3
|
||||||
# tile_type = 4;
|
|
||||||
# remove the set 2
|
|
||||||
var tile: Tile
|
var tile: Tile
|
||||||
match tile_type:
|
match tile_type:
|
||||||
0: # Wall tile
|
0: # Wall tile
|
||||||
tile = WallTile.new(container, is_white, -1)
|
# tile = WallTile.new(container, is_white, -1)
|
||||||
add_tile(pos, tile)
|
# add_tile(pos, tile)
|
||||||
continue
|
continue
|
||||||
1: # Double Wall
|
1: # Double Wall
|
||||||
# tile = DoubleWallTile.new(container, is_white, -1)
|
# tile = DoubleWallTile.new(container, is_white, -1)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ func _init(button: Button, is_white: bool, d: int, id: int, color: Color) -> voi
|
||||||
jumpable = true
|
jumpable = true
|
||||||
|
|
||||||
func apply_effect(piece: Pawn = null) -> void:
|
func apply_effect(piece: Pawn = null) -> void:
|
||||||
if !piece || piece == null || !is_effect_active() || !other_portal || !other_portal.is_effect_active():
|
if !piece || !is_effect_active() || !other_portal || !other_portal.is_effect_active():
|
||||||
return
|
return
|
||||||
if last_piece and last_piece.id == piece.id:
|
if last_piece and last_piece.id == piece.id:
|
||||||
return
|
return
|
||||||
|
|
@ -33,61 +33,10 @@ func apply_effect(piece: Pawn = null) -> void:
|
||||||
if current_container:
|
if current_container:
|
||||||
var target_container = other_portal.base_button as PieceContainer
|
var target_container = other_portal.base_button as PieceContainer
|
||||||
if target_container:
|
if target_container:
|
||||||
last_piece = piece
|
# last_piece = piece
|
||||||
other_portal.last_piece = piece
|
other_portal.last_piece = piece
|
||||||
# Move the piece
|
# Move the piece
|
||||||
animate_portal_teleport(piece, current_container, target_container)
|
target_container.animate_movement(current_container, piece);
|
||||||
# target_container.animate_movement(current_container, piece);
|
|
||||||
|
|
||||||
func animate_portal_teleport(piece: Pawn, origin_container: PieceContainer, destination_container: PieceContainer) -> void:
|
|
||||||
# Save original properties
|
|
||||||
var original_scale = piece.scale
|
|
||||||
var original_modulate = piece.modulate
|
|
||||||
var original_z_index = piece.z_index
|
|
||||||
|
|
||||||
# Raise z-index during animation
|
|
||||||
piece.z_index = 2
|
|
||||||
|
|
||||||
# Step 1: Move to portal center
|
|
||||||
var portal_center = base_button.global_position + (base_button.size / 2)
|
|
||||||
var step1_tween = piece.create_tween()
|
|
||||||
step1_tween.set_trans(Tween.TRANS_LINEAR)
|
|
||||||
step1_tween.tween_property(piece, "global_position", portal_center, 0.2)
|
|
||||||
await step1_tween.finished
|
|
||||||
|
|
||||||
# Step 2: Shrink and fade out
|
|
||||||
var step2_tween = piece.create_tween()
|
|
||||||
step2_tween.set_trans(Tween.TRANS_QUAD)
|
|
||||||
step2_tween.set_ease(Tween.EASE_OUT)
|
|
||||||
# Ensure modulate maintains the piece's color (white or black)
|
|
||||||
var fade_color = original_modulate.darkened(0.5)
|
|
||||||
fade_color.a = 0.0 # Fully transparent
|
|
||||||
|
|
||||||
# Animate scale and transparency
|
|
||||||
step2_tween.tween_property(piece, "scale", Vector2.ZERO, 0.3)
|
|
||||||
step2_tween.parallel().tween_property(piece, "modulate", fade_color, 0.3)
|
|
||||||
await step2_tween.finished
|
|
||||||
|
|
||||||
# Step 3: Reparent to destination container
|
|
||||||
# origin_container.remove_child(piece)
|
|
||||||
# destination_container.add_child(piece)
|
|
||||||
|
|
||||||
# Position at target portal center
|
|
||||||
var target_portal_center = other_portal.base_button.global_position + (other_portal.base_button.size / 2)
|
|
||||||
piece.global_position = target_portal_center
|
|
||||||
|
|
||||||
# Step 4: Grow and fade in at destination
|
|
||||||
var step4_tween = piece.create_tween()
|
|
||||||
step4_tween.set_trans(Tween.TRANS_BACK) # A bit of bounce
|
|
||||||
step4_tween.set_ease(Tween.EASE_OUT)
|
|
||||||
|
|
||||||
# Reset scale and opacity
|
|
||||||
step4_tween.tween_property(piece, "scale", original_scale, 0.3)
|
|
||||||
step4_tween.parallel().tween_property(piece, "modulate", original_modulate, 0.3)
|
|
||||||
await step4_tween.finished
|
|
||||||
|
|
||||||
# Reset z-index
|
|
||||||
piece.z_index = original_z_index
|
|
||||||
|
|
||||||
func update_appearance() -> void:
|
func update_appearance() -> void:
|
||||||
if is_effect_active() && base_button:
|
if is_effect_active() && base_button:
|
||||||
|
|
|
||||||
|
|
@ -23,35 +23,6 @@ static func generate_guid() -> String:
|
||||||
return guid
|
return guid
|
||||||
|
|
||||||
|
|
||||||
static func convert_algebraic_to_location(square: String) -> String:
|
|
||||||
var file = square[0] # letter (a-h)
|
|
||||||
var rank = int(square[1]) # number (1-8)
|
|
||||||
|
|
||||||
# Convert file letter to number (a=0, b=1, etc)
|
|
||||||
var file_num = file.unicode_at(0) - 'a'.unicode_at(0)
|
|
||||||
|
|
||||||
# Since we're working with black's moves and our board is oriented with white at bottom:
|
|
||||||
# 1. Flip rank: 8 - rank to mirror vertically
|
|
||||||
file_num = file_num
|
|
||||||
var rank_num = 8 - rank
|
|
||||||
|
|
||||||
# Return location in your game's format
|
|
||||||
return "%d-%d" % [file_num, rank_num]
|
|
||||||
|
|
||||||
|
|
||||||
static func location_to_algebraic(location: String) -> String:
|
|
||||||
# Convert from "x-y" format to algebraic notation
|
|
||||||
var coords = location.split("-")
|
|
||||||
var x = int(coords[0])
|
|
||||||
var y = int(coords[1])
|
|
||||||
|
|
||||||
# Convert x to file letter (0 = 'a', 1 = 'b', etc.)
|
|
||||||
var file = char(97 + x) # 97 is ASCII 'a'
|
|
||||||
|
|
||||||
# Convert y to rank number (flip since our board has 0 at top)
|
|
||||||
var rank = str(8 - y)
|
|
||||||
|
|
||||||
return file + rank
|
|
||||||
|
|
||||||
static var LIGHT_CELL = Color(0.5, 0.5, 0.5, 1)
|
static var LIGHT_CELL = Color(0.5, 0.5, 0.5, 1)
|
||||||
static var DARK_CELL = Color(0.2, 0.2, 0.2, 1)
|
static var DARK_CELL = Color(0.2, 0.2, 0.2, 1)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
@tool
|
@tool
|
||||||
extends Pawn
|
extends Pawn
|
||||||
class_name Knight
|
class_name Knight
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
self.texture = load("res://addons/Chess/Textures/WKnight.svg")
|
self.texture = load("res://addons/Chess/Textures/WKnight.svg")
|
||||||
Points = 3
|
Points = 3
|
||||||
|
|
@ -22,7 +23,7 @@ func getValidMoves(board_flow, current_location: String) -> Dictionary:
|
||||||
var loc = current_location.split("-")
|
var loc = current_location.split("-")
|
||||||
var x = int(loc[0])
|
var x = int(loc[0])
|
||||||
var y = int(loc[1])
|
var y = int(loc[1])
|
||||||
game = board_flow.get_parent() as ChessGame
|
var game = board_flow.get_parent() as ChessGame
|
||||||
|
|
||||||
# All possible L-shaped moves
|
# All possible L-shaped moves
|
||||||
var knight_moves = [
|
var knight_moves = [
|
||||||
|
|
@ -38,88 +39,54 @@ func getValidMoves(board_flow, current_location: String) -> Dictionary:
|
||||||
var new_loc = str(target_x) + "-" + str(target_y)
|
var new_loc = str(target_x) + "-" + str(target_y)
|
||||||
|
|
||||||
if is_valid_cell(board_flow, new_loc):
|
if is_valid_cell(board_flow, new_loc):
|
||||||
# Check both possible paths to the target
|
# Check tiles in the path
|
||||||
var horizontal_first_clear = true
|
var path_clear = true
|
||||||
var vertical_first_clear = true
|
|
||||||
|
|
||||||
# Path 1: Check horizontal first, then vertical
|
# Check horizontally first, then vertically (or vice versa)
|
||||||
|
var check_horizontal_first = abs(move[0]) > abs(move[1])
|
||||||
|
|
||||||
|
if check_horizontal_first:
|
||||||
# Check horizontal movement
|
# Check horizontal movement
|
||||||
var step_x = sign(move[0])
|
var step_x = sign(move[0])
|
||||||
for i in range(1, abs(move[0]) + 1):
|
for i in range(1, abs(move[0]) + 1):
|
||||||
var path_tile_loc = str(x + (i * step_x)) + "-" + str(y)
|
var path_tile_loc = str(x + (i * step_x)) + "-" + str(y)
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
var tile = game.tileManager.get_tile(path_tile_loc)
|
||||||
if tile && !tile.jumpable:
|
if tile && !tile.jumpable:
|
||||||
horizontal_first_clear = false
|
path_clear = false
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check vertical movement if horizontal was clear
|
# Check vertical movement if path still clear
|
||||||
if horizontal_first_clear:
|
if path_clear:
|
||||||
var step_y = sign(move[1])
|
var step_y = sign(move[1])
|
||||||
var path_tile_loc = str(target_x) + "-" + str(y + step_y)
|
var path_tile_loc = str(target_x) + "-" + str(y + step_y)
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
var tile = game.tileManager.get_tile(path_tile_loc)
|
||||||
if tile && !tile.jumpable:
|
if tile && !tile.jumpable:
|
||||||
horizontal_first_clear = false
|
path_clear = false
|
||||||
|
else:
|
||||||
# Path 2: Check vertical first, then horizontal
|
|
||||||
# Check vertical movement
|
# Check vertical movement
|
||||||
var step_y = sign(move[1])
|
var step_y = sign(move[1])
|
||||||
for i in range(1, abs(move[1]) + 1):
|
for i in range(1, abs(move[1]) + 1):
|
||||||
var path_tile_loc = str(x) + "-" + str(y + (i * step_y))
|
var path_tile_loc = str(x) + "-" + str(y + (i * step_y))
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
var tile = game.tileManager.get_tile(path_tile_loc)
|
||||||
if tile && !tile.jumpable:
|
if tile && !tile.jumpable:
|
||||||
vertical_first_clear = false
|
path_clear = false
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check horizontal movement if vertical was clear
|
# Check horizontal movement if path still clear
|
||||||
if vertical_first_clear:
|
if path_clear:
|
||||||
|
var step_x = sign(move[0])
|
||||||
var path_tile_loc = str(x + step_x) + "-" + str(target_y)
|
var path_tile_loc = str(x + step_x) + "-" + str(target_y)
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
var tile = game.tileManager.get_tile(path_tile_loc)
|
||||||
if tile && !tile.jumpable:
|
if tile && !tile.jumpable:
|
||||||
vertical_first_clear = false
|
path_clear = false
|
||||||
|
|
||||||
# Move is valid if EITHER path is clear
|
# Only add the move if the path is clear and the destination is valid
|
||||||
if (horizontal_first_clear || vertical_first_clear) && (can_move_to_cell(board_flow, new_loc) || can_move_to_cell(board_flow, new_loc, true)):
|
if path_clear && (can_move_to_cell(board_flow, new_loc) || can_move_to_cell(board_flow, new_loc, true)):
|
||||||
moves.regular_moves.append(new_loc)
|
moves.regular_moves.append(new_loc)
|
||||||
|
|
||||||
return moves
|
return moves
|
||||||
|
|
||||||
|
|
||||||
# func animate_movement(target_position: Vector2, duration: float = 0.5) -> void:
|
|
||||||
# z_index = 1
|
|
||||||
# var tween = create_tween()
|
|
||||||
# tween.set_trans(Tween.TRANS_LINEAR)
|
|
||||||
# tween.set_ease(Tween.EASE_IN_OUT)
|
|
||||||
|
|
||||||
# var start_pos = global_position
|
|
||||||
# var total_delta = target_position - start_pos
|
|
||||||
# var mid_pos: Vector2
|
|
||||||
|
|
||||||
# var game = get_tree().get_first_node_in_group("ChessGame") as ChessGame
|
|
||||||
# var cell_delta_x = int(total_delta.x / game.boardXSize)
|
|
||||||
# var cell_delta_y = int(total_delta.y / game.boardYSize)
|
|
||||||
|
|
||||||
# if abs(cell_delta_x) > abs(cell_delta_y):
|
|
||||||
# # Moving more horizontally ([-2, ±1])
|
|
||||||
# mid_pos = Vector2(
|
|
||||||
# start_pos.x + (cell_delta_x * game.boardXSize),
|
|
||||||
# start_pos.y
|
|
||||||
# )
|
|
||||||
# else:
|
|
||||||
# # Moving more vertically ([±1, -2])
|
|
||||||
# mid_pos = Vector2(
|
|
||||||
# start_pos.x,
|
|
||||||
# start_pos.y + (cell_delta_y * game.boardYSize)
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # First move (longer distance)
|
|
||||||
# tween.tween_property(self, "global_position", mid_pos, duration / 3 * 2)
|
|
||||||
# # Second move (shorter distance)
|
|
||||||
# tween.tween_property(self, "global_position", target_position, duration / 3 * 1)
|
|
||||||
|
|
||||||
# await tween.finished
|
|
||||||
# z_index = 0
|
|
||||||
|
|
||||||
|
|
||||||
func animate_movement(target_position: Vector2, duration: float = 0.5) -> void:
|
func animate_movement(target_position: Vector2, duration: float = 0.5) -> void:
|
||||||
z_index = 1
|
z_index = 1
|
||||||
var tween = create_tween()
|
var tween = create_tween()
|
||||||
|
|
@ -130,72 +97,26 @@ func animate_movement(target_position: Vector2, duration: float = 0.5) -> void:
|
||||||
var total_delta = target_position - start_pos
|
var total_delta = target_position - start_pos
|
||||||
var mid_pos: Vector2
|
var mid_pos: Vector2
|
||||||
|
|
||||||
var cell_delta_x = int(total_delta.x / game.boardXSize)
|
var game = get_tree().get_first_node_in_group("ChessGame") as ChessGame
|
||||||
var cell_delta_y = int(total_delta.y / game.boardYSize)
|
var cell_delta_x = int(total_delta.x / game.tileXSize)
|
||||||
|
var cell_delta_y = int(total_delta.y / game.tileYSize)
|
||||||
|
|
||||||
# Convert current and target positions to board coordinates
|
if abs(cell_delta_x) > abs(cell_delta_y):
|
||||||
var current_x = int(start_pos.x / game.boardXSize)
|
# Moving more horizontally ([-2, ±1])
|
||||||
var current_y = int(start_pos.y / game.boardYSize)
|
|
||||||
|
|
||||||
# Check both possible paths
|
|
||||||
var horizontal_first_clear = true
|
|
||||||
var vertical_first_clear = true
|
|
||||||
|
|
||||||
# Check horizontal-first path
|
|
||||||
for i in range(1, abs(cell_delta_x) + 1):
|
|
||||||
var step_x = sign(cell_delta_x)
|
|
||||||
var path_tile_loc = str(current_x + (i * step_x)) + "-" + str(current_y)
|
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
|
||||||
if tile && !tile.jumpable:
|
|
||||||
horizontal_first_clear = false
|
|
||||||
break
|
|
||||||
|
|
||||||
if horizontal_first_clear:
|
|
||||||
var step_y = sign(cell_delta_y)
|
|
||||||
var path_tile_loc = str(current_x + cell_delta_x) + "-" + str(current_y + step_y)
|
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
|
||||||
if tile && !tile.jumpable:
|
|
||||||
horizontal_first_clear = false
|
|
||||||
|
|
||||||
# Check vertical-first path
|
|
||||||
for i in range(1, abs(cell_delta_y) + 1):
|
|
||||||
var step_y = sign(cell_delta_y)
|
|
||||||
var path_tile_loc = str(current_x) + "-" + str(current_y + (i * step_y))
|
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
|
||||||
if tile && !tile.jumpable:
|
|
||||||
vertical_first_clear = false
|
|
||||||
break
|
|
||||||
|
|
||||||
if vertical_first_clear:
|
|
||||||
var step_x = sign(cell_delta_x)
|
|
||||||
var path_tile_loc = str(current_x + step_x) + "-" + str(current_y + cell_delta_y)
|
|
||||||
var tile = game.tileManager.get_tile(path_tile_loc)
|
|
||||||
if tile && !tile.jumpable:
|
|
||||||
vertical_first_clear = false
|
|
||||||
|
|
||||||
# Choose the valid path for animation
|
|
||||||
var use_horizontal_first = true
|
|
||||||
if !horizontal_first_clear && vertical_first_clear:
|
|
||||||
use_horizontal_first = false
|
|
||||||
elif horizontal_first_clear && vertical_first_clear:
|
|
||||||
# If both paths are valid, use the traditional logic
|
|
||||||
use_horizontal_first = abs(cell_delta_x) > abs(cell_delta_y)
|
|
||||||
elif !horizontal_first_clear && !vertical_first_clear:
|
|
||||||
push_error("Attempting to animate an invalid move!")
|
|
||||||
return
|
|
||||||
|
|
||||||
if use_horizontal_first:
|
|
||||||
mid_pos = Vector2(
|
mid_pos = Vector2(
|
||||||
start_pos.x + (cell_delta_x * game.boardXSize),
|
start_pos.x + (cell_delta_x * game.tileXSize),
|
||||||
start_pos.y
|
start_pos.y
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# Moving more vertically ([±1, -2])
|
||||||
mid_pos = Vector2(
|
mid_pos = Vector2(
|
||||||
start_pos.x,
|
start_pos.x,
|
||||||
start_pos.y + (cell_delta_y * game.boardYSize)
|
start_pos.y + (cell_delta_y * game.tileYSize)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# First move (longer distance)
|
||||||
tween.tween_property(self, "global_position", mid_pos, duration / 3 * 2)
|
tween.tween_property(self, "global_position", mid_pos, duration / 3 * 2)
|
||||||
|
# Second move (shorter distance)
|
||||||
tween.tween_property(self, "global_position", target_position, duration / 3 * 1)
|
tween.tween_property(self, "global_position", target_position, duration / 3 * 1)
|
||||||
|
|
||||||
await tween.finished
|
await tween.finished
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ class_name Pawn
|
||||||
|
|
||||||
@export var Points = 1
|
@export var Points = 1
|
||||||
|
|
||||||
var game: ChessGame = null
|
|
||||||
var duration_label: Label
|
var duration_label: Label
|
||||||
var Temp_Color = 0
|
var Temp_Color = 0
|
||||||
var Double_Start = true
|
var Double_Start = true
|
||||||
|
|
@ -24,8 +23,7 @@ var id: String = Utils.generate_guid()
|
||||||
func _ready():
|
func _ready():
|
||||||
modulate = Color.WHITE if Item_Color == 0 else Color.BLACK
|
modulate = Color.WHITE if Item_Color == 0 else Color.BLACK
|
||||||
|
|
||||||
func _init(chess: ChessGame) -> void:
|
func _init() -> void:
|
||||||
game = chess
|
|
||||||
self.texture = load("res://addons/Chess/Textures/WPawn.svg")
|
self.texture = load("res://addons/Chess/Textures/WPawn.svg")
|
||||||
var background_style = StyleBoxFlat.new()
|
var background_style = StyleBoxFlat.new()
|
||||||
background_style.bg_color = Color.WHITE
|
background_style.bg_color = Color.WHITE
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue