286 lines
10 KiB
GDScript
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
|