diff --git a/Assets/ChessEngines/fairy-chess-server/index.js b/Assets/ChessEngines/fairy-chess-server/index.js index c9c4f79..ff54931 100644 --- a/Assets/ChessEngines/fairy-chess-server/index.js +++ b/Assets/ChessEngines/fairy-chess-server/index.js @@ -16,7 +16,7 @@ function getLogPath() { return path.join(os.homedir(), 'AppData', 'Roaming', 'ChessBuilder', 'logs'); } else { // macOS: ~/Library/Logs/ChessBuilder - return path.join(os.homedir(), 'Library', 'Logs', 'ChessBuilder'); + return path.join(os.homedir(), 'Library', 'ChessBuilder', 'logs'); } } @@ -69,7 +69,7 @@ let board = null; let engine = null; let isReady = false; let lastResponse = null -const SERVER_WAIT_THRESHOLD = 10 * 60 * 1000; +const SERVER_WAIT_THRESHOLD = 2 * 60 * 1000; const CHECK_INTERVAL = 5000; // Initialize ffish and engine @@ -104,6 +104,10 @@ app.use(express.json()); // Health check endpoint app.get('/health', (req, res) => { + console.log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + console.log("@@@@@@@@@@@@@@@@@HEALTH CHECK@@@@@@@@@@@@@@@@@@@@@@") + console.log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + console.log(JSON.stringify(ffish.variants())) lastResponse = new Date().getTime() res.json({ status: 'ok', @@ -382,11 +386,12 @@ app.post('/enginemove', async (req, res) => { const { depth = 15, movetime = 1000, - nodes = null + nodes = null, + fen } = req.body; try { - const fen = board.fen(); + // const fen = board.fen(); const analysis = await engine.getAnalysis(fen, { depth, movetime, diff --git a/Assets/ChessEngines/fairy-chess-server/test.js b/Assets/ChessEngines/fairy-chess-server/test.js index d04bbc3..3fcf25c 100644 --- a/Assets/ChessEngines/fairy-chess-server/test.js +++ b/Assets/ChessEngines/fairy-chess-server/test.js @@ -8,6 +8,9 @@ async function delay(ms) { async function testServer() { try { + + await axios.post(`${baseURL}/shutdown`); + return // Test 1: Health check console.log('\nTest 1: Health Check'); const health = await axios.get(`${baseURL}/health`); @@ -15,6 +18,14 @@ async function testServer() { // Wait for engine initialization 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 console.log('\nTest 2: Validate FEN'); diff --git a/Assets/test/simple.txt b/Assets/test/simple.txt deleted file mode 100644 index e69de29..0000000 diff --git a/Systems/FairyStockfish/ServerManager.gd b/Systems/FairyStockfish/ServerManager.gd index a6d1cff..a4b444a 100644 --- a/Systems/FairyStockfish/ServerManager.gd +++ b/Systems/FairyStockfish/ServerManager.gd @@ -7,6 +7,8 @@ var log_string: String = "" var running := false var server_process_id: int = -50 var request_timer: Timer +var server_pinger: Timer +var ping_http: HTTPRequest var server_url = "http://localhost:27531" @@ -37,24 +39,39 @@ func write_log(message: String): file.close() 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" setup_logging() check_server_files(server_path) start_server() 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): var dir = DirAccess.open(path) @@ -109,18 +126,15 @@ func _notification(what): if what == NOTIFICATION_WM_CLOSE_REQUEST: stop_server() get_tree().quit() + + func start_server() -> bool: if running: return true write_log("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) request_timer = Timer.new() diff --git a/Systems/FairyStockfish/Stockfish.gd b/Systems/FairyStockfish/Stockfish.gd deleted file mode 100644 index 9e2b279..0000000 --- a/Systems/FairyStockfish/Stockfish.gd +++ /dev/null @@ -1,390 +0,0 @@ -# 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() - diff --git a/Systems/FairyStockfish/StockfishClient.gd b/Systems/FairyStockfish/StockfishClient.gd index 9dc8c92..126de30 100644 --- a/Systems/FairyStockfish/StockfishClient.gd +++ b/Systems/FairyStockfish/StockfishClient.gd @@ -34,8 +34,7 @@ func connect_to_engine(_path: String, g: ChessGame) -> bool: if ServerManager.is_server_running(): print("**************SERVER RUNNING ****************") running = true - start_board(); - load_fen(game.getCurrentFen()) + start_game(1350) return true await get_tree().create_timer(delay).timeout @@ -44,23 +43,16 @@ func connect_to_engine(_path: String, g: ChessGame) -> bool: return false + +func start_game(elo: int) -> void: + start_board(elo) + load_fen(game.getCurrentFen()) + + + 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 @@ -82,7 +74,7 @@ func load_fen(fen: String): http_request.request(server_url + "/position", headers, HTTPClient.METHOD_POST, body) await http_request.request_completed -func start_board(): +func start_board(elo: int): if not running: return var headers = ["Content-Type: application/json"] @@ -93,7 +85,7 @@ func start_board(): print(body) http_request.request(server_url + "/new", headers, HTTPClient.METHOD_POST, body) await http_request.request_completed - setElo(1350) + setElo(elo) func _exit_tree(): ServerManager.stop_server() @@ -103,12 +95,28 @@ func _exit_tree(): 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" @@ -116,7 +124,11 @@ func setElo(elo: int = 1350) -> void: { "name": "UCI_Elo", "value": str(elo) # Convert int to string - } + }, + { + "name": "UCI_Variant", + "value": "chessbuilder" # Convert int to string + }, ] } @@ -143,7 +155,8 @@ func generateMove(think_time_ms: int = 1000) -> void: var headers = ["Content-Type: application/json"] var body = JSON.stringify({ "movetime": think_time_ms, - "depth": 15 + "depth": 15, + "fen": str(game.getCurrentFen()) }) # Request engine move diff --git a/Systems/Game/ChessGame.gd b/Systems/Game/ChessGame.gd index 1fd3cf3..d87e347 100644 --- a/Systems/Game/ChessGame.gd +++ b/Systems/Game/ChessGame.gd @@ -72,7 +72,8 @@ func _ready() -> void: stockfishController.connect_to_engine(stockfishPath, self) # if stockfishController.connect_to_engine(stockfishPath): # stockfishController.limit_strength_to(cpuElo) - + + func _exit_tree(): stockfishController.disconnect_engine() @@ -103,7 +104,44 @@ func initializeCardPreview() -> void: 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: var fen = "" @@ -115,17 +153,29 @@ func getCurrentFen() -> String: for x in range(boardXSize): # print("CHECKING ", str(x) + "-" + str(y)) var container = boardContainer.get_node(str(x) + "-" + str(y)) as PieceContainer - var piece = container.get_piece() - if piece == null: - emptySquares += 1 - else: + + if tileManager.active_tiles.has(str(x) + "-" + str(y)): + if emptySquares > 0: fen += str(emptySquares) emptySquares = 0 - # Convert piece to FEN notation - var fenChar = getPieceFenChar(piece) - fen += fenChar - + var tile = tileManager.active_tiles[str(x) + "-" + str(y)] + if tile is WallTile or tile is DoubleWallTile: + if tile is DoubleWallTile: + fen += "*" + else: + fen += "*" + else: + var piece = container.get_piece() + if piece == null: + emptySquares += 1 + else: + if emptySquares > 0: + fen += str(emptySquares) + emptySquares = 0 + # Convert piece to FEN notation + var fenChar = getPieceFenChar(piece) + fen += fenChar # Add any remaining empty squares at the end of the rank if emptySquares > 0: fen += str(emptySquares) @@ -142,7 +192,12 @@ func getCurrentFen() -> String: halfMoveClock, moveCount ] - + var specialChars = getSpecialTilesFen(); + print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + print(specialChars) + print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + # fen += specialChars; + currentFen = fen; return fen func initializeBoard() -> void: # Parse FEN to get board dimensions diff --git a/Systems/StateMachine/GameStates/Movement.gd b/Systems/StateMachine/GameStates/Movement.gd index abec343..b77a4a1 100644 --- a/Systems/StateMachine/GameStates/Movement.gd +++ b/Systems/StateMachine/GameStates/Movement.gd @@ -52,12 +52,12 @@ func enter(_previous: String, _data := {}) -> void: var target_square = move_str.substr(2, 2) # "e4" # First select the piece - var source_location = convert_algebraic_to_location(source_square) + var source_location = Utils.convert_algebraic_to_location(source_square) game.selectedNode = source_location print("source_location ", source_location) # Then make the move - var target_location = convert_algebraic_to_location(target_square) + var target_location = Utils.convert_algebraic_to_location(target_square) print("target_location ", target_location) handleMovement(target_location, true) return @@ -67,21 +67,6 @@ func exit() -> void: 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: # we need to prevent swapping of focus between peices after the double move process has started diff --git a/Systems/TileManager.gd b/Systems/TileManager.gd index b6b6327..32f81b8 100644 --- a/Systems/TileManager.gd +++ b/Systems/TileManager.gd @@ -98,6 +98,39 @@ func place_random_game_tiles(num_tiles: int = 0) -> void: available_positions.shuffle() 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 = '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 = WallTile.new(cntr, w, -1) + add_tile(p, wall) + # Skip over an iteration for a paired tile for i in range(min(num_tiles, available_positions.size())): if skipNext: @@ -114,16 +147,17 @@ func place_random_game_tiles(num_tiles: int = 0) -> void: var rng = RandomNumberGenerator.new() rng.randomize() var tile_type = rng.randi() % 3 - + # tile_type = 4; + # remove the set 2 var tile: Tile match tile_type: 0: # Wall tile - # tile = WallTile.new(container, is_white, -1) - # add_tile(pos, tile) + tile = WallTile.new(container, is_white, -1) + add_tile(pos, tile) continue 1: # Double Wall - # tile = DoubleWallTile.new(container, is_white, -1) - # add_tile(pos, tile) + tile = DoubleWallTile.new(container, is_white, -1) + add_tile(pos, tile) continue 2: # Portal pair # Only create portal pair if this isn't the last tile diff --git a/Systems/Tiles/Portal.gd b/Systems/Tiles/Portal.gd index f443364..1d1f0f4 100644 --- a/Systems/Tiles/Portal.gd +++ b/Systems/Tiles/Portal.gd @@ -19,7 +19,7 @@ func _init(button: Button, is_white: bool, d: int, id: int, color: Color) -> voi jumpable = true func apply_effect(piece: Pawn = null) -> void: - if !piece || !is_effect_active() || !other_portal || !other_portal.is_effect_active(): + if !piece || piece == null || !is_effect_active() || !other_portal || !other_portal.is_effect_active(): return if last_piece and last_piece.id == piece.id: return diff --git a/Utils/Utils.gd b/Utils/Utils.gd index ca891bc..848ed07 100644 --- a/Utils/Utils.gd +++ b/Utils/Utils.gd @@ -23,6 +23,35 @@ static func generate_guid() -> String: 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 DARK_CELL = Color(0.2, 0.2, 0.2, 1)