extends Node class_name FairyStockfishVariantGenerator const STANDARD_PIECE_CHARS = "PNBRQK" const files = "abcdefghijkl" const VARIANT_CHARS = "ACDEFGHIJLMOSTVWXYZ" func generate_variant_string(chess_game: ChessGame) -> String: var width = chess_game.boardXSize var height = chess_game.boardYSize var prefix = "chessbuilder" + str(width) + "x" + str(height) var piece_data = gather_piece_data(chess_game) var piece_definitions = piece_data.definitions var piece_mappings = piece_data.mappings var used_variant_chars = piece_data.used_chars var variant_string = "[" + prefix + ":chess]\n" # Build the pieceToCharTable with all used variant characters var char_table = build_piece_char_table(used_variant_chars) variant_string += "pieceToCharTable = " + char_table + "\n" variant_string += "maxRank = " + str(height) + "\n" variant_string += "maxFile = " + files[width - 1] + "\n" var current_fen = chess_game.getCurrentFen() variant_string += "startFen = " + current_fen + "\n\n" # Add wall types variant_string += "walltype = * # Duck wall\n" variant_string += "walltype = @ # Stone wall\n\n" # Add walling rules variant_string += "wallingRule = static\n" variant_string += "wallOrMove = false\n" variant_string += "mobilityRegion = *:@\n" variant_string += "prohibitedMove = *@\n\n" # Add the piece definitions variant_string += "# Custom piece movement definitions\n" for piece_key in piece_definitions: variant_string += "piece " + piece_key + " = " + piece_definitions[piece_key] + "\n" # Add pieceDrops if needed for capturing variant_string += "\n# Allow capturing with custom pieces\n" variant_string += "pieceDrops = true\n" return variant_string # Build a complete pieceToCharTable that includes all variant characters func build_piece_char_table(used_variant_chars: Array) -> String: var char_table = STANDARD_PIECE_CHARS for variant_char in used_variant_chars: char_table += variant_char.to_upper() char_table += "..*@..........." # Add lowercase versions char_table += STANDARD_PIECE_CHARS.to_lower() for variant_char in used_variant_chars: char_table += variant_char.to_lower() char_table += "..*@..........." return char_table # Gather all piece data from the current game state func gather_piece_data(chess_game: ChessGame) -> Dictionary: # Dictionary to track unique movement patterns # Key format: "piece_char:color:movement_string" var unique_movements = {} var modified_piece_types = {} # First pass: collect all unique movement patterns for y in range(chess_game.boardYSize): for x in range(chess_game.boardXSize): var container = chess_game.boardContainer.get_node(str(x) + "-" + str(y)) as PieceContainer if container && container.has_piece(): var piece = container.get_piece() as Pawn if piece.current_movement_string != piece.original_movement_string: var piece_char = get_piece_char(piece) var color_key = "white" if piece.Item_Color == 0 else "black" var piece_key = piece_char + ":" + color_key if !modified_piece_types.has(piece_key): modified_piece_types[piece_key] = true var movement_key = piece_key + ":" + piece.current_movement_string if !unique_movements.has(movement_key): unique_movements[movement_key] = { "piece_char": piece_char, "color": color_key, "betza_notation": piece.current_movement_string, "fairy_notation": convert_betza_to_fairy_stockfish(piece.current_movement_string), "positions": [] } unique_movements[movement_key].positions.append(str(x) + "-" + str(y)) var variant_char_index = 0 var piece_definitions = {} var piece_mappings = {} var used_variant_chars = [] for movement_key in unique_movements: var movement_data = unique_movements[movement_key] var piece_char = movement_data.piece_char var color = movement_data.color var fs_notation = movement_data.fairy_notation var variant_char if variant_char_index < VARIANT_CHARS.length(): variant_char = VARIANT_CHARS[variant_char_index] used_variant_chars.append(variant_char) variant_char_index += 1 else: # If we run out of variant chars, we'll need to add more or implement another solution print("Warning: Ran out of variant characters for piece definitions") break # Store the piece definition var definition_key = variant_char.to_upper() if color == "white" else variant_char.to_lower() piece_definitions[definition_key] = fs_notation for pos in movement_data.positions: piece_mappings[pos] = definition_key return { "definitions": piece_definitions, "mappings": piece_mappings, "used_chars": used_variant_chars } func get_piece_char(piece: Pawn) -> String: match piece.name: "Pawn": return "P" "Knight": return "N" "Bishop": return "B" "Rook": return "R" "Queen": return "Q" "King": return "K" _: return "P" # Default to pawn func convert_betza_to_fairy_stockfish(betza_notation: String) -> String: var atoms = parse_betza_notation(betza_notation) var fairy_stockfish_moves = [] for atom in atoms: var move_type = atom.type var modifiers = atom.modifiers var range_limit = atom.range var move_only = "m" in modifiers var capture_only = "c" in modifiers # Process direction modifiers var direction_modifiers = [] for modifier in modifiers: if modifier.length() > 1 || ["f", "b", "l", "r"].has(modifier): for char in modifier: if ["f", "b", "l", "r"].has(char): direction_modifiers.append(char) var fs_move = "" match move_type: "W": fs_move += "W" # Wazir (orthogonal step) "F": fs_move += "F" # Ferz (diagonal step) "R": fs_move += "R" # Rook (orthogonal slider) "B": fs_move += "B" # Bishop (diagonal slider) "N": fs_move += "N" # Knight "K": fs_move += "K" # King (one step in any direction) "Q": fs_move += "Q" # Queen (combination of R and B) if range_limit > 0: fs_move += str(range_limit) if direction_modifiers.size() > 0: fs_move += "/" for dir in direction_modifiers: match dir: "f": fs_move += "f" "b": fs_move += "b" "l": fs_move += "l" "r": fs_move += "r" if move_only: fs_move += "m" elif capture_only: fs_move += "c" fairy_stockfish_moves.append(fs_move) var ret_str = "" for moves in fairy_stockfish_moves: ret_str += str(moves) + " " return ret_str # Parse Betza notation into atoms func parse_betza_notation(notation: String) -> Array: var atoms = [] var i = 0 while i < notation.length(): var atom = { "type": "", "modifiers": [], "range": -1 # -1 means unlimited } while i < notation.length() && ["m", "c"].has(notation[i]): atom.modifiers.append(notation[i]) i += 1 var directions = "" while i < notation.length() && ["f", "b", "l", "r"].has(notation[i]): directions += notation[i] i += 1 if directions: atom.modifiers.append(directions) # Get the atom type if i < notation.length(): atom.type = notation[i] i += 1 # Check for range specification var range_str = "" while i < notation.length() && notation[i].is_valid_int(): range_str += notation[i] i += 1 if range_str: atom.range = int(range_str) atoms.append(atom) return atoms # Generate a modified FEN string with custom piece characters func generate_modified_fen(chess_game: ChessGame, piece_mappings: Dictionary) -> String: var fen_parts = chess_game.getCurrentFen().split(" ") var board_part = fen_parts[0] var rows = board_part.split("/") var new_rows = [] for y in range(rows.size()): var new_row = "" var x = 0 for c in rows[y]: if c.is_valid_int(): # Handle empty squares new_row += c x += int(c) else: var pos = str(x) + "-" + str(y) if piece_mappings.has(pos): new_row += piece_mappings[pos] else: new_row += c x += 1 new_rows.append(new_row) fen_parts[0] = new_rows.join("/") return fen_parts.join(" ") # Save the variant definition to a file func save_variant_to_file(variant_string: String) -> bool: print("VARIANT ", variant_string) ServerManager.write_variant(variant_string) return true # Helper function to get a complete variant definition for the current game state func generate_and_save_variant(chess_game: ChessGame) -> String: var variant_string = generate_variant_string(chess_game) save_variant_to_file(variant_string) return variant_string