ChessBuilder/Systems/FairyStockfish/FairyStockfishVariantGenerator.gd

286 lines
10 KiB
GDScript

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