1063 lines
35 KiB
GDScript
1063 lines
35 KiB
GDScript
class_name ChessGame extends Control
|
|
|
|
# Constants
|
|
const WHITE = "white"
|
|
const BLACK = "black"
|
|
const StockfishController = preload("res://Systems/FairyStockfish/StockfishClient.gd")
|
|
|
|
# Signals
|
|
signal tile_pressed(location: String)
|
|
signal send_location(location: String)
|
|
signal map_open_requested(options)
|
|
signal node_completed(options)
|
|
signal turn_changed
|
|
signal game_initialized
|
|
|
|
# Game state variables
|
|
var currentPlayer: String = WHITE
|
|
var board: Array
|
|
var isWhiteToMove: bool = true
|
|
var castlingRights: String = "KQkq"
|
|
var enPassantTarget: String = "-"
|
|
var halfMoveClock: int = 0
|
|
var moveCount: int = 1
|
|
var currentHand: Array
|
|
var selectedNode: String = ""
|
|
var locationX: String = ""
|
|
var locationY: String = ""
|
|
var areas: PackedStringArray
|
|
var specialArea: PackedStringArray
|
|
var gamecheckMate: bool = false
|
|
var gamedraw: bool = false
|
|
var currentlyMovingPiece = null
|
|
var p1Points: int = 0
|
|
var p2Points: int = 0
|
|
var Turn: int = 0
|
|
var stockfishController: StockfishController
|
|
var stockfishPath = "res://Assets/ChessEngines/stockfish/stockfish.exe"
|
|
var currentFen = ""
|
|
var lightStyle = null
|
|
var darkStyle = null
|
|
var highlightStyle = null
|
|
var cpuElo = 1500
|
|
var is_initialized: bool = false
|
|
var currentNode = null;
|
|
|
|
# Node references
|
|
@onready var turnIndicator: ColorRect = $TurnIndicator
|
|
@onready var p1String: RichTextLabel = $Player1Points
|
|
@onready var p2String: RichTextLabel = $Player2Points
|
|
@onready var gold: int = 75
|
|
@onready var deckManager: DeckManager
|
|
@onready var tileManager: TileManager
|
|
@onready var cameraController: CameraController
|
|
@onready var cardDisplay: CardDisplay
|
|
@onready var cardPreview: CardPreview
|
|
@onready var boardContainer: FlowContainer = $Flow
|
|
@onready var stateMachine: StateMachine = $StateMachine
|
|
@onready var menuContainer = get_node_or_null("/root/Board/MenuContainer")
|
|
@onready var mapContainer = get_node_or_null("/root/Board/MapScreen")
|
|
|
|
var captured_pieces_this_turn: Array = []
|
|
var player_pieces_lost_this_turn: Array = []
|
|
var winConditionManager: WinConditionManager
|
|
var currentWinCondition = Utils.WinConditionType.CAPTURE_UNIT
|
|
var currentLossCondition = Utils.LossConditionType.UNIT_LOST
|
|
|
|
|
|
# Export parameters
|
|
@export var boardXSize = 12
|
|
@export var boardYSize = 12
|
|
@export var tileXSize: int = 50
|
|
@export var tileYSize: int = 50
|
|
@export var windowXSize: int = 1280
|
|
@export var windowYSize: int = 720
|
|
@export var FEN: String = "2rnbqkbnr2/2pppppppp2/6u5/12/2********U1/12/2u9/12/7U4/12/2PPPPPPPP2/2RNBQKBNR2 w KQkq - 0 1"
|
|
# Standard chess FEN: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
|
# Modified FEN: "2rnbqkbnr2/2pppppppp2/12/12/2********2/12/12/12/12/12/2PPPPPPPP2/2RNBQKBNR2 w KQkq - 0 1"
|
|
|
|
# ===========================================================================
|
|
# INITIALIZATION FUNCTIONS
|
|
# ===========================================================================
|
|
|
|
func _ready() -> void:
|
|
print("ChessGame _ready() called")
|
|
# Only set up paths and window size initially
|
|
if OS.get_name() == "Windows":
|
|
stockfishPath = "res://Assets/ChessEngines/stockfish/stockfish.exe"
|
|
else:
|
|
stockfishPath = ProjectSettings.globalize_path("res://Assets/ChessEngines/Fairy-Stockfish/src/stockfish")
|
|
|
|
add_to_group("ChessGame")
|
|
currentFen = FEN
|
|
DisplayServer.window_set_size(Vector2i(windowXSize, windowYSize))
|
|
|
|
# Set up the menu signal connection
|
|
if menuContainer:
|
|
print("Found MenuContainer, connecting signal")
|
|
if !menuContainer.is_connected("new_game_requested", Callable(self, "_on_new_game_requested")):
|
|
print("Signal Connected")
|
|
menuContainer.connect("new_game_requested", Callable(self, "_on_new_game_requested"))
|
|
else:
|
|
print("MenuContainer not found, will initialize game now")
|
|
call_deferred("initialize_game_system")
|
|
turnIndicator.visible = false
|
|
deckManager = DeckManager.new()
|
|
# 2rnbqkbnr1R/2ppp1pppp2/5p6/75/66/66/66/66/66/66/2PPPPPPPP2/2RNBQKBN3 b KQkq - 0 3
|
|
func _on_new_game_requested(options = {}):
|
|
print("ChessGame received new_game_requested signal ", is_initialized)
|
|
captured_pieces_this_turn = []
|
|
player_pieces_lost_this_turn = []
|
|
turnIndicator.visible = true
|
|
if options and "fen" in options.metadata:
|
|
currentFen = options.metadata.fen
|
|
if "elo" in options:
|
|
cpuElo = options.metadata.elo
|
|
if cameraController:
|
|
cameraController.reset_view()
|
|
if options and "metadata" in options:
|
|
if winConditionManager:
|
|
winConditionManager.load_condition_from_metadata(options.metadata)
|
|
|
|
currentNode = options
|
|
print("ChessGame FEN ", currentFen)
|
|
print("ChessGame DIMENSIONS ", get_board_dimensions(currentFen))
|
|
if is_initialized:
|
|
resetBoard()
|
|
initializeDeckSystem()
|
|
initializeBoard()
|
|
if cardDisplay:
|
|
cardDisplay.visible = true
|
|
if stateMachine:
|
|
stateMachine.transitionToNextState(Constants.WHITE_TURN)
|
|
else:
|
|
initialize_game_system()
|
|
if currentFen:
|
|
stockfishController.start_board(cpuElo, get_board_dimensions(currentFen))
|
|
else:
|
|
stockfishController.start_board(cpuElo, "8x8")
|
|
resetHighlights()
|
|
func initialize_game_system():
|
|
print("Initializing game system")
|
|
# Set up basic styles first
|
|
setupStyles()
|
|
|
|
# Initialize the game components
|
|
initializeGame()
|
|
initializeTiles()
|
|
|
|
# Initialize Stockfish controller
|
|
stockfishController = StockfishController.new()
|
|
add_child(stockfishController)
|
|
stockfishController.connect_to_engine(stockfishPath, self)
|
|
|
|
winConditionManager = WinConditionManager.new(self)
|
|
add_child(winConditionManager)
|
|
winConditionManager.connect("win_condition_met", Callable(self, "_on_win_condition_met"))
|
|
winConditionManager.connect("loss_condition_met", Callable(self, "_on_loss_condition_met"))
|
|
|
|
# Start the state machine
|
|
if stateMachine:
|
|
stateMachine.start()
|
|
stateMachine.transitionToNextState(Constants.WHITE_TURN)
|
|
|
|
# Mark as initialized
|
|
is_initialized = true
|
|
|
|
# Emit signal that game is initialized
|
|
emit_signal("game_initialized")
|
|
|
|
|
|
func _exit_tree():
|
|
# Clean up the Stockfish controller when exiting
|
|
if stockfishController:
|
|
stockfishController.disconnect_engine()
|
|
|
|
|
|
func initializeGame() -> void:
|
|
# Initialize all game components in the correct order
|
|
setupStyles()
|
|
initializeDeckSystem()
|
|
initializeCardPreview()
|
|
initializeBoard()
|
|
setupCameraController()
|
|
setupUI()
|
|
|
|
func initializeTiles() -> void:
|
|
# Initialize tile manager and setup tiles
|
|
tileManager = TileManager.new($Flow, self)
|
|
add_child(tileManager)
|
|
await get_tree().process_frame
|
|
tileManager.initialize(boardContainer)
|
|
setupTilesFromFEN()
|
|
# tileManager.place_random_game_tiles()
|
|
|
|
|
|
func initializeBoard() -> void:
|
|
# Parse FEN
|
|
var fen_parts = currentFen.split(" ")
|
|
var rows = fen_parts[0].split("/")
|
|
boardYSize = rows.size()
|
|
boardXSize = 0
|
|
|
|
# Calculate width from first row by counting both pieces numbers and tiles
|
|
for c in rows[0]:
|
|
if c.is_valid_int():
|
|
boardXSize += int(c) # Add the number of empty squares
|
|
else:
|
|
boardXSize += 1
|
|
|
|
# Initialize board array
|
|
board = []
|
|
for i in range(boardYSize):
|
|
var row = []
|
|
for j in range(boardXSize):
|
|
row.append(null)
|
|
board.append(row)
|
|
|
|
# Create the visual board and setup pieces
|
|
createBoard()
|
|
setupPiecesFromFEN()
|
|
# Set up signals for board interaction
|
|
if !boardContainer.has_user_signal("tile_pressed"):
|
|
boardContainer.add_user_signal("tile_pressed")
|
|
if !boardContainer.has_user_signal("send_location"):
|
|
boardContainer.add_user_signal("send_location")
|
|
|
|
func initializeCardPreview() -> void:
|
|
# Initialize the card preview component
|
|
cardPreview = CardPreview.new()
|
|
add_child(cardPreview)
|
|
|
|
func initializeDeckSystem() -> void:
|
|
deckManager.shuffleDeck()
|
|
deckManager.drawStartingHand()
|
|
# Initialize the deck manager and card display
|
|
if cardDisplay == null:
|
|
cardDisplay = CardDisplay.new()
|
|
add_child(cardDisplay)
|
|
add_child(deckManager)
|
|
|
|
# Set up signals for card interaction
|
|
if !deckManager.has_user_signal("card_pressed"):
|
|
deckManager.add_user_signal("card_pressed")
|
|
|
|
if !deckManager.has_user_signal("hand_updated"):
|
|
deckManager.add_user_signal("hand_updated")
|
|
|
|
# Update card display with initial hand
|
|
cardDisplay.update_hand(deckManager.hand)
|
|
deckManager.connect("hand_updated", func(hand): cardDisplay.update_hand(hand))
|
|
|
|
func setupCameraController() -> void:
|
|
cameraController = CameraController.new(boardContainer)
|
|
cameraController.name = "CameraController"
|
|
add_child(cameraController)
|
|
|
|
cameraController.zoom_changed.connect(_on_zoom_changed)
|
|
|
|
|
|
func _on_zoom_changed(zoom_level: float) -> void:
|
|
# manuall adjustements try to avoid
|
|
pass
|
|
|
|
|
|
|
|
func setupStyles() -> void:
|
|
lightStyle = StyleBoxFlat.new()
|
|
lightStyle.bg_color = Utils.LIGHT_CELL
|
|
darkStyle = StyleBoxFlat.new()
|
|
darkStyle.bg_color = Utils.DARK_CELL
|
|
|
|
highlightStyle = StyleBoxFlat.new()
|
|
highlightStyle.bg_color = Color(0, 0.3, 0, 1)
|
|
|
|
func setupUI() -> void:
|
|
p1String.text = "0"
|
|
p2String.text = "0"
|
|
updateTurnIndicator()
|
|
|
|
# Create control buttons
|
|
var zoom_in_button = Button.new()
|
|
zoom_in_button.text = "+"
|
|
zoom_in_button.size = Vector2(30, 30)
|
|
zoom_in_button.pressed.connect(func(): cameraController.zoom_in(zoom_in_button.global_position))
|
|
|
|
var zoom_out_button = Button.new()
|
|
zoom_out_button.text = "-"
|
|
zoom_out_button.size = Vector2(30, 30)
|
|
zoom_out_button.pressed.connect(func(): cameraController.zoom_out(zoom_out_button.global_position))
|
|
|
|
var reset_button = Button.new()
|
|
reset_button.text = "Reset View"
|
|
reset_button.size = Vector2(0, 30)
|
|
reset_button.pressed.connect(func(): cameraController.reset_view())
|
|
|
|
var control_container = VBoxContainer.new()
|
|
control_container.name = "ZoomControls"
|
|
|
|
control_container.position = Vector2(windowXSize - 100, windowYSize - 80)
|
|
|
|
var h_container = HBoxContainer.new()
|
|
h_container.add_child(zoom_out_button)
|
|
h_container.add_child(zoom_in_button)
|
|
|
|
control_container.add_child(h_container)
|
|
control_container.add_child(reset_button)
|
|
|
|
# Add the container to the scene
|
|
add_child(control_container)
|
|
|
|
|
|
func get_base_style(is_white: bool) -> StyleBoxFlat:
|
|
return lightStyle if is_white else darkStyle
|
|
|
|
|
|
# ===========================================================================
|
|
# BOARD CREATION AND SETUP
|
|
# ===========================================================================
|
|
|
|
func createBoard() -> void:
|
|
|
|
for child in boardContainer.get_children():
|
|
boardContainer.remove_child(child)
|
|
child.queue_free()
|
|
|
|
# Reset container size
|
|
boardContainer.size = Vector2.ZERO
|
|
|
|
# Add to group if not already in it
|
|
if not boardContainer.is_in_group("Flow"):
|
|
boardContainer.add_to_group("Flow")
|
|
var numberX = 0
|
|
var numberY = 0
|
|
var isWhite = true
|
|
print("CREATING BOARD X " + str(boardXSize) + " Y " + str(boardYSize))
|
|
while numberY != boardYSize:
|
|
boardContainer.size.y += tileYSize + 5
|
|
|
|
while numberX != boardXSize:
|
|
if numberY == 0:
|
|
boardContainer.size.x += tileXSize + 5
|
|
createTile(numberX, numberY, isWhite)
|
|
isWhite = !isWhite
|
|
numberX += 1
|
|
|
|
isWhite = !isWhite
|
|
numberY += 1
|
|
numberX = 0
|
|
|
|
func createTile(x: int, y: int, isWhite: bool) -> void:
|
|
# Create a single tile for the chess board
|
|
var tile = PieceContainer.new(str(x) + "-" + str(y))
|
|
tile.set_custom_minimum_size(Vector2(tileXSize, tileYSize))
|
|
# if x == 0:
|
|
# var style = StyleBoxFlat.new()
|
|
# style.bg_color = Utils.GREEN_CELL
|
|
# tile.add_theme_stylebox_override("normal", style )
|
|
# else:
|
|
tile.add_theme_stylebox_override("normal", lightStyle if isWhite else darkStyle)
|
|
# print(" Create Tile " + str(x) + "-" + str(y) )
|
|
tile.set_name(str(x) + "-" + str(y))
|
|
# tile.pressed.connect(func(): handleTileSelection(tile.name))
|
|
tile.pressed.connect(func():
|
|
# tile_pressed.emit(tile.name)
|
|
boardContainer.emit_signal("tile_pressed", tile.name)
|
|
)
|
|
|
|
boardContainer.add_child(tile)
|
|
|
|
func setupPiecesFromFEN() -> void:
|
|
# Set up chess pieces from the FEN string
|
|
var fen_parts = currentFen.split(" ")
|
|
var rows = fen_parts[0].split("/")
|
|
|
|
# Iterate through rows in reverse to place black pieces at top
|
|
for y in range(rows.size()):
|
|
var x = 0
|
|
# Convert y coordinate to flip the board (boardY-y puts white at bottom)
|
|
var board_y = (boardYSize - 1) - y
|
|
for c in rows[y]:
|
|
if c.is_valid_int():
|
|
# Skip empty squares
|
|
x += int(c)
|
|
else:
|
|
var piece_info = getFENPieceInfo(c)
|
|
if piece_info.name != "":
|
|
placePiece(str(x) + "-" + str(board_y), piece_info.name, piece_info.color)
|
|
x += 1
|
|
|
|
func setupTilesFromFEN() -> void:
|
|
# Set up special tiles from the FEN string
|
|
var fen_parts = currentFen.split(" ")
|
|
var rows = fen_parts[0].split("/")
|
|
var matchedPortals = {}
|
|
var portalCnt = 0;
|
|
|
|
# Iterate through rows in reverse to place black pieces at top
|
|
for y in range(rows.size()):
|
|
var x = 0
|
|
# Convert y coordinate to flip the board (7-y puts white at bottom)
|
|
var board_y = (boardYSize - 1) - y # For an 8x8 board
|
|
for c in rows[y]:
|
|
if c.is_valid_int():
|
|
# Skip empty squares
|
|
x += int(c)
|
|
else:
|
|
var loc = str(x) + "-" + str(board_y);
|
|
if tileManager.portalString.find(c) > -1:
|
|
var chr = c
|
|
# shjould we lowercase?
|
|
if chr in matchedPortals:
|
|
if !("p2" in matchedPortals[chr]):
|
|
matchedPortals[chr].p2 = loc
|
|
tileManager.place_portal_pair(matchedPortals[chr].p1, matchedPortals[chr].p2, portalCnt)
|
|
portalCnt += 1;
|
|
else:
|
|
matchedPortals[chr] = { "p1": loc }
|
|
x += 1
|
|
else:
|
|
var tile = getFENTile(c, loc)
|
|
if tile != null:
|
|
tileManager.add_tile(loc, tile)
|
|
x += 1
|
|
|
|
func placePiece(position: String, pieceName: String, color: int) -> void:
|
|
# print("Placing Piece ", position)
|
|
var piece = summonPiece(pieceName, color)
|
|
var container = boardContainer.get_node(position) as PieceContainer
|
|
await container.set_piece(piece, false)
|
|
container.remove_piece(true)
|
|
container.set_piece(piece, false)
|
|
|
|
var coords = position.split("-")
|
|
board[int(coords[1])][int(coords[0])] = piece
|
|
|
|
|
|
func summonPiece(pieceName: String, color: int) -> Node:
|
|
# Create a chess piece based on its name and color
|
|
var piece
|
|
match pieceName:
|
|
"Pawn":
|
|
piece = Pawn.new(self)
|
|
piece.name = "Pawn"
|
|
"King":
|
|
piece = King.new(self)
|
|
piece.name = "King"
|
|
"Queen":
|
|
piece = Queen.new(self)
|
|
piece.name = "Queen"
|
|
"Knight":
|
|
piece = Knight.new(self)
|
|
piece.name = "Knight"
|
|
"Rook":
|
|
piece = Rook.new(self)
|
|
piece.name = "Rook"
|
|
"Bishop":
|
|
piece = Bishop.new(self)
|
|
piece.name = "Bishop"
|
|
|
|
piece.Item_Color = color
|
|
piece.position = Vector2(tileXSize / 2, tileYSize / 2)
|
|
return piece
|
|
|
|
# ===========================================================================
|
|
# GAME FLOW AND STATE MANAGEMENT
|
|
# ===========================================================================
|
|
|
|
func isPlayerTurn() -> bool:
|
|
return currentPlayer == WHITE
|
|
|
|
func _process(delta: float) -> void:
|
|
# Process the state machine
|
|
stateMachine.process(delta)
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
# Handle input events not handled by other nodes
|
|
stateMachine.unhandledInput(event)
|
|
if event is InputEventKey:
|
|
if event.pressed:
|
|
if event.keycode == KEY_EQUAL or event.keycode == KEY_PLUS: # Zoom in with + key
|
|
cameraController.zoom_in(get_viewport().get_mouse_position())
|
|
elif event.keycode == KEY_MINUS: # Zoom out with - key
|
|
cameraController.zoom_out(get_viewport().get_mouse_position())
|
|
elif event.keycode == KEY_R: # Reset view with R key
|
|
cameraController.reset_view()
|
|
|
|
|
|
func resetBoard() -> void:
|
|
clearSelection()
|
|
clearBoard()
|
|
Turn = 0
|
|
currentPlayer = WHITE
|
|
p1Points = 0
|
|
p1String.text = str(p1Points)
|
|
p2Points = 0
|
|
p2String.text = str(p2Points)
|
|
gamecheckMate = false;
|
|
gamedraw = false;
|
|
|
|
areas.clear()
|
|
specialArea.clear()
|
|
updateTurnIndicator()
|
|
|
|
|
|
|
|
func clearBoard() -> void:
|
|
# Clear all pieces from the board
|
|
for child in boardContainer.get_children():
|
|
if child is PieceContainer:
|
|
child.remove_piece()
|
|
|
|
func updateTurnIndicator():
|
|
if Turn == 0: # White's turn
|
|
turnIndicator.color = Color(1, 1, 1, 1) # White
|
|
else: # Black's turn
|
|
turnIndicator.color = Color(0, 0, 0, 1) # Black
|
|
|
|
func cleanupPhase() -> void:
|
|
pass
|
|
|
|
|
|
func updateEffectDurations() -> void:
|
|
# Update the duration of active effects
|
|
deckManager.updateCardDurations()
|
|
tileManager.update_tile_durations()
|
|
|
|
func applyTileEffects() -> void:
|
|
# Apply effects from active tiles
|
|
tileManager.apply_alltile_effects()
|
|
|
|
|
|
func applyCardEffects() -> void:
|
|
pass
|
|
|
|
func evaluatePosition() -> Dictionary:
|
|
var status = {
|
|
"checkmate": isCheckmate(),
|
|
"draw": isDraw(),
|
|
}
|
|
return status
|
|
|
|
|
|
func isCheckmate() -> bool:
|
|
return gamecheckMate
|
|
|
|
func endRound() -> void:
|
|
print("****************ENDROUND*************")
|
|
deckManager.cleanup()
|
|
cardDisplay.visible = false
|
|
mapContainer.visible = true
|
|
resetBoard()
|
|
|
|
func isDraw() -> bool:
|
|
return gamedraw
|
|
|
|
func endGame(reason: String) -> void:
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# ===========================================================================
|
|
# MOVE EXECUTION AND VALIDATION
|
|
# ===========================================================================
|
|
|
|
|
|
func executeMove(targetLocation: String) -> void:
|
|
print("executeMove ", targetLocation)
|
|
var targetContainer = get_node("Flow/" + targetLocation) as PieceContainer
|
|
var sourceContainer = get_node("Flow/" + selectedNode) as PieceContainer
|
|
var piece = sourceContainer.get_piece()
|
|
var old_location = selectedNode
|
|
|
|
# Handle capture if there's a piece in target location
|
|
if targetContainer.has_piece():
|
|
var capturedPiece = targetContainer.get_piece()
|
|
updatePointsAndCapture(capturedPiece, true)
|
|
|
|
# Move piece to new location
|
|
sourceContainer.remove_piece(true)
|
|
targetContainer.set_piece(piece)
|
|
|
|
currentlyMovingPiece = piece
|
|
resetHighlights()
|
|
if winConditionManager:
|
|
winConditionManager.on_piece_moved(piece, old_location, targetLocation)
|
|
|
|
|
|
# Additional helper function to check if a specific tile has been reached
|
|
func is_tile_reached(location: String, piece_name: String = "") -> bool:
|
|
var container = get_node("Flow/" + location) as PieceContainer
|
|
if container and container.has_piece():
|
|
var piece = container.get_piece()
|
|
if piece_name == "" or piece.name == piece_name:
|
|
return piece.Item_Color == 0 # Player's piece
|
|
return false
|
|
|
|
|
|
# func resolveMoveEffects() -> void:
|
|
# # Resolve effects after a move is made
|
|
# print("resolveMoveEffects", currentlyMovingPiece)
|
|
# togglePieceChessEffect()
|
|
|
|
# selectedNode = ""
|
|
# Turn = 1 if Turn == 0 else 0
|
|
# updateTurnIndicator()
|
|
# resetHighlights()
|
|
# currentlyMovingPiece = null
|
|
# emit_signal("turn_changed")
|
|
|
|
|
|
func resolveMoveEffects() -> void:
|
|
# Resolve effects after a move is made
|
|
print("resolveMoveEffects", currentlyMovingPiece)
|
|
togglePieceChessEffect()
|
|
selectedNode = ""
|
|
resetHighlights()
|
|
currentlyMovingPiece = null
|
|
|
|
func endTurn() -> void:
|
|
print("turn_changed")
|
|
emit_signal("turn_changed")
|
|
Turn = 1 if Turn == 0 else 0
|
|
updateTurnIndicator()
|
|
if (isPlayerTurn()):
|
|
updateEffectDurations()
|
|
check_captured_pieces_conditions()
|
|
|
|
|
|
func togglePieceChessEffect() -> void:
|
|
# Update piece-specific effects after a move
|
|
var piece = currentlyMovingPiece
|
|
if piece.name == "Pawn":
|
|
if piece.Double_Start:
|
|
piece.En_Passant = true
|
|
piece.Double_Start = false
|
|
elif piece.name == "King":
|
|
piece.Castling = false
|
|
elif piece.name == "Rook":
|
|
piece.Castling = false
|
|
|
|
|
|
|
|
func isValidMove(location: String) -> bool:
|
|
# Check if a move to the given location is valid
|
|
var node = get_node("Flow/" + location) as PieceContainer
|
|
var piece = node.get_piece()
|
|
if piece == null || piece.Item_Color != Turn:
|
|
for area in areas:
|
|
if area == node.name:
|
|
return true
|
|
return false
|
|
|
|
|
|
func clearSelection() :
|
|
# Clear the current selection
|
|
resetHighlights()
|
|
selectedNode = ""
|
|
if cardPreview:
|
|
cardPreview.hide_preview()
|
|
return
|
|
|
|
|
|
|
|
func getMovableAreas() -> void:
|
|
# Get all valid moves for the selected piece
|
|
print("HIGHLIGHTING getMovableAreas 1", selectedNode)
|
|
resetHighlights()
|
|
areas.clear()
|
|
specialArea.clear()
|
|
var container = get_node("Flow/" + selectedNode) as PieceContainer
|
|
var piece = container.get_piece()
|
|
if piece == null:
|
|
return
|
|
var piece_id = piece.get_instance_id()
|
|
# print("HIGHLIGHTING getMovableAreas 2")
|
|
if deckManager.attached_cards.has(piece_id):
|
|
var card = deckManager.attached_cards[piece_id]
|
|
cardPreview.show_card_preview(card)
|
|
if stateMachine.state.name == Constants.MOVEMENT:
|
|
var movement_state = stateMachine.state
|
|
if piece_id in movement_state.moves_remaining:
|
|
var moves_left = movement_state.moves_remaining[piece_id] - 1
|
|
cardPreview.update_moves_remaining(moves_left)
|
|
else:
|
|
cardPreview.hide_preview()
|
|
# print("HIGHLIGHTING getMovableAreas 3")
|
|
var moves = piece.getValidMoves(boardContainer, selectedNode)
|
|
areas = moves.regular_moves
|
|
specialArea = moves.special_moves
|
|
# print("HIGHLIGHTING getMovableAreas 4")
|
|
highlightValidMoves()
|
|
|
|
func highlightValidMoves() -> void:
|
|
for move in areas:
|
|
var button = boardContainer.get_node(move)
|
|
|
|
# If there's an active tile effect, combine with its current style instead
|
|
if tileManager && tileManager.get_tile(move):
|
|
var current_style = button.get_theme_stylebox("normal")
|
|
var highlightedStyle = StyleBoxFlat.new()
|
|
highlightedStyle.bg_color = current_style.bg_color + highlightStyle.bg_color
|
|
button.add_theme_stylebox_override("normal", highlightedStyle)
|
|
else:
|
|
# Default chess pattern highlighting
|
|
var isWhiteSquare = (int(move.split("-")[0]) + int(move.split("-")[1])) % 2 == 0
|
|
var baseStyle = lightStyle if isWhiteSquare else darkStyle
|
|
var combinedStyle = StyleBoxFlat.new()
|
|
combinedStyle.bg_color = baseStyle.bg_color + highlightStyle.bg_color
|
|
button.add_theme_stylebox_override("normal", combinedStyle)
|
|
|
|
|
|
|
|
func resetHighlights():
|
|
# Reset all highlights on the board
|
|
for button in boardContainer.get_children():
|
|
if !button.name.contains("-"):
|
|
continue
|
|
|
|
# Skip if this tile has an active effect
|
|
if tileManager && tileManager.get_tile(button.name):
|
|
continue
|
|
|
|
var coord = button.name.split("-")
|
|
var isWhiteSquare = (int(coord[0]) + int(coord[1])) % 2 == 0
|
|
if isWhiteSquare:
|
|
button.add_theme_stylebox_override("normal", lightStyle)
|
|
else:
|
|
button.add_theme_stylebox_override("normal", darkStyle)
|
|
|
|
|
|
# ===========================================================================
|
|
# CAPTURING AND PIECE MANAGEMENT
|
|
# ===========================================================================
|
|
|
|
|
|
func isNull(location: String) -> bool:
|
|
return get_node_or_null("Flow/" + location) == null
|
|
|
|
|
|
func updatePointsAndCapture(capturedPiece: Pawn, animate: bool = true) -> void:
|
|
# Update points and handle piece capture
|
|
print(Turn, " updatePointsAndCapture ", capturedPiece.Item_Color)
|
|
if animate:
|
|
animatePieceCapture(capturedPiece)
|
|
if Turn == 0: # Player's turn
|
|
gold += capturedPiece.Points;
|
|
p1Points += capturedPiece.Points
|
|
p1String.text = str(p1Points)
|
|
|
|
# Player captured an enemy piece
|
|
print("winConditionManager and capturedPiece.Item_Color == 1")
|
|
if winConditionManager and capturedPiece.Item_Color == 1: # Enemy piece (black)
|
|
# winConditionManager.on_piece_captured(capturedPiece)
|
|
captured_pieces_this_turn.append(capturedPiece)
|
|
print("Player captured an enemy piece ", captured_pieces_this_turn)
|
|
|
|
else: # Enemy's turn
|
|
p2Points += capturedPiece.Points
|
|
p2String.text = str(p2Points)
|
|
|
|
# Enemy captured a player piece
|
|
print("winConditionManager and capturedPiece.Item_Color == 0")
|
|
# recover_cards_from_player_piece(capturedPiece)
|
|
|
|
# Add to player pieces lost array for loss condition checking
|
|
|
|
if winConditionManager and capturedPiece.Item_Color == 0: # Player piece (white)
|
|
player_pieces_lost_this_turn.append(capturedPiece)
|
|
recover_cards_from_player_piece(capturedPiece)
|
|
print("ENemy captured an player piece ", player_pieces_lost_this_turn)
|
|
# winConditionManager.on_piece_lost(capturedPiece)
|
|
|
|
|
|
if capturedPiece.name == "King":
|
|
print("Game Over!")
|
|
gamecheckMate = true
|
|
|
|
|
|
func check_captured_pieces_conditions():
|
|
# Process captured enemy pieces (possible win condition)
|
|
print("&&&&&&&&&&&&&&&&77check_captured_pieces_conditions&&&&&&&&&&&&&&&")
|
|
for piece in captured_pieces_this_turn:
|
|
if winConditionManager:
|
|
winConditionManager.on_piece_captured(piece)
|
|
print(captured_pieces_this_turn)
|
|
# Process captured player pieces (possible loss condition)
|
|
for piece in player_pieces_lost_this_turn:
|
|
if winConditionManager:
|
|
winConditionManager.on_piece_lost(piece)
|
|
print(player_pieces_lost_this_turn)
|
|
|
|
func _on_win_condition_met(condition_data):
|
|
print("Win condition met: ", condition_data)
|
|
recover_cards_from_player_pieces()
|
|
emit_signal("node_completed", {
|
|
"node": currentNode,
|
|
"completed": true,
|
|
"success": true,
|
|
"condition": condition_data
|
|
})
|
|
if stateMachine:
|
|
stateMachine.transitionToNextState(Constants.CLEANUP, {"endCondition": "win_condition_met"})
|
|
|
|
# endRound()
|
|
|
|
func _on_loss_condition_met(condition_data):
|
|
print("Loss condition met: ", condition_data)
|
|
recover_cards_from_player_pieces()
|
|
emit_signal("node_completed", {
|
|
"node": currentNode,
|
|
"completed": true,
|
|
"success": false,
|
|
"condition": condition_data
|
|
})
|
|
if stateMachine:
|
|
stateMachine.transitionToNextState(Constants.CLEANUP, {"endCondition": "win_condition_met"})
|
|
# endRound()
|
|
|
|
func animatePieceCapture(capturedPiece: Pawn) -> void:
|
|
# Animate the capture of a piece
|
|
var container = capturedPiece.get_parent() as PieceContainer
|
|
await capturedPiece.animate_capture()
|
|
container.remove_piece()
|
|
|
|
|
|
func recover_cards_from_player_piece(piece: Pawn):
|
|
if piece != null and piece.Item_Color == 0:
|
|
var piece_id = piece.get_instance_id()
|
|
|
|
if deckManager.attached_cards.has(piece_id):
|
|
var card = deckManager.attached_cards[piece_id]
|
|
card.reset()
|
|
|
|
match card.rank:
|
|
Card.Rank.RANK_2:
|
|
deckManager.deck.append(card)
|
|
print("Recovered Rank 2 card pile: ", card.cardName)
|
|
Card.Rank.RANK_3:
|
|
deckManager.deck.append(card)
|
|
print("Recovered Rank 3 card to deck: ", card.cardName)
|
|
|
|
deckManager.attached_cards.erase(piece_id)
|
|
|
|
|
|
func recover_cards_from_player_pieces():
|
|
for y in range(boardYSize):
|
|
for x in range(boardXSize):
|
|
var piece = (boardContainer.get_node(str(x) + "-" + str(y)) as PieceContainer).get_piece()
|
|
recover_cards_from_player_piece(piece)
|
|
|
|
# ===========================================================================
|
|
# FEN NOTATION AND POSITION TRACKING
|
|
# ===========================================================================
|
|
|
|
|
|
func parseLocation(location: String) -> void:
|
|
# Parse a location string into X and Y coordinates
|
|
var number = 0
|
|
locationX = ""
|
|
while location.substr(number, 1) != "-":
|
|
locationX += location.substr(number, 1)
|
|
number += 1
|
|
locationY = location.substr(number + 1)
|
|
|
|
func getCurrentFen() -> String:
|
|
# Generate FEN string from the current board position
|
|
var fen = ""
|
|
|
|
# For a standard chess board, we want to generate FEN from top (black side, rank 8)
|
|
# to bottom (white side, rank 1)
|
|
for y in range(boardYSize):
|
|
var emptySquares = 0
|
|
for x in range(boardXSize):
|
|
# print("CHECKING ", str(x) + "-" + str(y))
|
|
var container = boardContainer.get_node(str(x) + "-" + str(y)) as PieceContainer
|
|
|
|
if tileManager.active_tiles.has(str(x) + "-" + str(y)):
|
|
|
|
var tile = tileManager.active_tiles[str(x) + "-" + str(y)]
|
|
if tile is WallTile or tile is DoubleWallTile:
|
|
if emptySquares > 0:
|
|
fen += str(emptySquares)
|
|
emptySquares = 0
|
|
if tile.tile_name == "Double Wall":
|
|
fen += "*"
|
|
else:
|
|
fen += "*"
|
|
elif tile is PortalTile:
|
|
var piece = container.get_piece()
|
|
if piece == null:
|
|
emptySquares += 1
|
|
else:
|
|
# Convert piece to FEN notation
|
|
var fenChar = getPieceFenChar(piece)
|
|
fen += fenChar
|
|
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)
|
|
|
|
# Add rank separator (except for the last rank)
|
|
if y < boardYSize - 1:
|
|
fen += "/"
|
|
|
|
# Add the rest of the FEN string components
|
|
fen += " %s %s %s %d %d" % [
|
|
"b" if currentPlayer == BLACK else "w",
|
|
castlingRights,
|
|
enPassantTarget,
|
|
halfMoveClock,
|
|
moveCount
|
|
]
|
|
# fen += specialChars;
|
|
currentFen = fen;
|
|
return fen
|
|
|
|
|
|
func getFENPieceInfo(fen_char: String) -> Dictionary:
|
|
# Get piece information from a FEN character
|
|
var piece_info = {
|
|
"name": "",
|
|
"color": 0 # Black
|
|
}
|
|
|
|
# If uppercase, it's white
|
|
if fen_char.to_upper() == fen_char:
|
|
piece_info.color = 1 # White
|
|
|
|
# Map FEN characters to piece names
|
|
match fen_char.to_upper():
|
|
"P": piece_info.name = "Pawn"
|
|
"R": piece_info.name = "Rook"
|
|
"N": piece_info.name = "Knight"
|
|
"B": piece_info.name = "Bishop"
|
|
"Q": piece_info.name = "Queen"
|
|
"K": piece_info.name = "King"
|
|
|
|
return piece_info
|
|
|
|
|
|
|
|
func getFENTile(fen_char: String, location: String) -> Tile:
|
|
# Get tile information from a FEN character
|
|
var tile = null
|
|
var container = boardContainer.get_node(location) as PieceContainer
|
|
|
|
var is_white = (int(location.split("-")[0]) + int(location.split("-")[1])) % 2 == 0
|
|
# Map FEN characters to piece names
|
|
match fen_char.to_upper():
|
|
"*": tile = WallTile.new(container, is_white, -1)
|
|
|
|
return tile
|
|
|
|
|
|
func getPieceFenChar(piece: Pawn) -> String:
|
|
# Get FEN character from a piece
|
|
if piece == null:
|
|
return ""
|
|
var fenChar = ""
|
|
match piece.name:
|
|
"Pawn": fenChar = "p"
|
|
"Knight": fenChar = "n"
|
|
"Bishop": fenChar = "b"
|
|
"Rook": fenChar = "r"
|
|
"Queen": fenChar = "q"
|
|
"King": fenChar = "k"
|
|
|
|
# In our system, Item_Color == 1 is white, 0 is black
|
|
return fenChar.to_upper() if piece.Item_Color == 0 else fenChar
|
|
|
|
# Unused
|
|
func updateStateFromMove(fromIdx: int, toIdx: int) -> void:
|
|
# Update game state based on the move
|
|
isWhiteToMove = !isWhiteToMove
|
|
if isWhiteToMove:
|
|
moveCount += 1
|
|
|
|
# Update castling rights if needed
|
|
updateCastlingRights(fromIdx, toIdx)
|
|
|
|
# Update en passant target
|
|
updateEnPassantTarget(fromIdx, toIdx)
|
|
|
|
# Update halfmove clock
|
|
updateHalfMoveClock(fromIdx, toIdx)
|
|
|
|
func updateCastlingRights(fromIdx: int, toIdx: int) -> void:
|
|
var piece = board[fromIdx]
|
|
if piece == null:
|
|
return
|
|
|
|
# Remove castling rights when king or rook moves
|
|
match piece.type:
|
|
"king":
|
|
if piece.isWhite:
|
|
castlingRights = castlingRights.replace("K", "").replace("Q", "")
|
|
else:
|
|
castlingRights = castlingRights.replace("k", "").replace("q", "")
|
|
"rook":
|
|
var startRank = boardYSize - 1 if piece.isWhite else 0
|
|
if fromIdx == startRank * boardYSize: # Queen-side rook
|
|
castlingRights = castlingRights.replace("Q" if piece.isWhite else "q", "")
|
|
elif fromIdx == startRank * boardYSize + 7: # King-side rook
|
|
castlingRights = castlingRights.replace("K" if piece.isWhite else "k", "")
|
|
|
|
if castlingRights == "":
|
|
castlingRights = "-"
|
|
|
|
func updateEnPassantTarget(fromIdx: int, toIdx: int) -> void:
|
|
var piece = board[fromIdx]
|
|
if piece == null or piece.type != "pawn":
|
|
enPassantTarget = "-"
|
|
return
|
|
|
|
# Check for double pawn move
|
|
var fromRank = fromIdx / boardYSize
|
|
var toRank = toIdx / boardYSize
|
|
if abs(fromRank - toRank) == 2:
|
|
var file = fromIdx % boardYSize
|
|
var targetRank = (fromRank + toRank) / 2
|
|
enPassantTarget = "%s%d" % [char(97 + file), boardYSize - targetRank]
|
|
else:
|
|
enPassantTarget = "-"
|
|
|
|
func updateHalfMoveClock(fromIdx: int, toIdx: int) -> void:
|
|
var piece = board[fromIdx]
|
|
if piece == null:
|
|
return
|
|
|
|
# Reset on pawn move or capture
|
|
if piece.type == "pawn" or board[toIdx] != null:
|
|
halfMoveClock = 0
|
|
else:
|
|
halfMoveClock += 1
|
|
|
|
|
|
|
|
func get_board_dimensions(fen_string: String) -> String:
|
|
var board_part: String = fen_string.split(" ")[0]
|
|
var ranks: Array = board_part.split("/")
|
|
var height: int = ranks.size()
|
|
var width: int = 0
|
|
var first_rank: String = ranks[0]
|
|
|
|
for character in first_rank:
|
|
if character.is_valid_int():
|
|
width += int(character)
|
|
else:
|
|
width += 1
|
|
|
|
return str(width) + "x" + str(height)
|