INTEGRADED AI
This commit is contained in:
parent
42ea425656
commit
086b114853
12 changed files with 502 additions and 81 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
# Godot 4+ specific ignores
|
# Godot 4+ specific ignores
|
||||||
.godot/
|
.godot/
|
||||||
/android/
|
/android/
|
||||||
|
stockfish
|
||||||
|
Assets/ChessEngines
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,15 @@ stateDiagram
|
||||||
HandSetup --> DrawPhase
|
HandSetup --> DrawPhase
|
||||||
DrawPhase --> ResolvePersistentEffects : Draw/Discard
|
DrawPhase --> ResolvePersistentEffects : Draw/Discard
|
||||||
|
|
||||||
ResolvePersistentEffects --> ApplyTileEffects : Update Durations
|
ResolvePersistentEffects --> PreMovePhase : Update Durations
|
||||||
ApplyTileEffects --> PreMovePhase
|
|
||||||
|
|
||||||
PreMovePhase --> AttachCards : Play Cards
|
PreMovePhase --> AttachCards : Play Cards
|
||||||
AttachCards --> ApplyCardEffects
|
AttachCards --> ApplyCardEffects
|
||||||
ApplyCardEffects --> Movement
|
ApplyCardEffects --> Movement
|
||||||
|
|
||||||
Movement --> PostMovePhase
|
Movement --> PostMovePhase
|
||||||
PostMovePhase --> EvaluatePosition : Resolve Move Effects
|
PostMovePhase --> ApplyTileEffects : Resolve Move Effects
|
||||||
|
ApplyTileEffects --> EvaluatePosition
|
||||||
|
|
||||||
state EvaluatePosition {
|
state EvaluatePosition {
|
||||||
[*] --> CheckStatus
|
[*] --> CheckStatus
|
||||||
|
|
|
||||||
181
Systems/FairyStockfish/Stockfish.gd
Normal file
181
Systems/FairyStockfish/Stockfish.gd
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
# Stockfish.gd
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
# References to game objects
|
||||||
|
var board: Array
|
||||||
|
var game: ChessGame
|
||||||
|
|
||||||
|
# Engine state
|
||||||
|
var engine_path: String = ""
|
||||||
|
var mutex: Mutex
|
||||||
|
var running := false
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
game = get_parent() as ChessGame
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
disconnect_engine()
|
||||||
|
|
||||||
|
func connect_to_engine(path: String) -> bool:
|
||||||
|
if running:
|
||||||
|
return false
|
||||||
|
|
||||||
|
engine_path = path
|
||||||
|
|
||||||
|
# Test if we can execute stockfish
|
||||||
|
var output = []
|
||||||
|
var exit_code = OS.execute(engine_path, ["uci"], output, true)
|
||||||
|
print("Exit code: ", exit_code)
|
||||||
|
print("Output: ", output)
|
||||||
|
if exit_code != OK:
|
||||||
|
printerr("Failed to start Stockfish engine: ", exit_code)
|
||||||
|
return false
|
||||||
|
|
||||||
|
running = true
|
||||||
|
print("Connected to engine: ", engine_path)
|
||||||
|
|
||||||
|
# Initialize with current game state
|
||||||
|
load_fen(game.getCurrentFen())
|
||||||
|
return true
|
||||||
|
|
||||||
|
func disconnect_engine():
|
||||||
|
if running:
|
||||||
|
mutex.lock()
|
||||||
|
var output = []
|
||||||
|
OS.execute(engine_path, ["quit"], output, true)
|
||||||
|
mutex.unlock()
|
||||||
|
running = false
|
||||||
|
engine_path = ""
|
||||||
|
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):
|
||||||
|
mutex.lock()
|
||||||
|
_send_command("position fen " + fen)
|
||||||
|
mutex.unlock()
|
||||||
|
|
||||||
|
func generateMove(think_time_ms: int = 1000) -> void:
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
|
||||||
|
move_time = think_time_ms
|
||||||
|
|
||||||
|
# Update position first
|
||||||
|
mutex.lock()
|
||||||
|
var command = "position fen " + game.getCurrentFen()
|
||||||
|
if moves.size() > 0:
|
||||||
|
command += " moves " + " ".join(moves)
|
||||||
|
print(command)
|
||||||
|
var output = _send_command(command)
|
||||||
|
|
||||||
|
# Then get move
|
||||||
|
output = _send_command("go movetime " + str(move_time))
|
||||||
|
if output.size() == 0:
|
||||||
|
return
|
||||||
|
print(type_string(typeof(output[0])))
|
||||||
|
var lines = output[0].split("\n")
|
||||||
|
mutex.unlock()
|
||||||
|
# Parse the output
|
||||||
|
for line in lines:
|
||||||
|
# print("-")
|
||||||
|
# print(line)
|
||||||
|
# print("-")
|
||||||
|
if line.begins_with("bestmove"):
|
||||||
|
var parts = line.split(" ")
|
||||||
|
print( parts)
|
||||||
|
if parts.size() >= 2:
|
||||||
|
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 _send_command(command: String) -> Array:
|
||||||
|
if not running:
|
||||||
|
return []
|
||||||
|
|
||||||
|
var output = []
|
||||||
|
OS.execute(engine_path, [command], output, true)
|
||||||
|
return output
|
||||||
|
|
@ -8,6 +8,11 @@ signal send_location(location: String)
|
||||||
signal turn_changed
|
signal turn_changed
|
||||||
var currentPlayer: String = WHITE
|
var currentPlayer: String = WHITE
|
||||||
var board: Array
|
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 currentHand: Array
|
||||||
var selectedNode: String = ""
|
var selectedNode: String = ""
|
||||||
var locationX: String = ""
|
var locationX: String = ""
|
||||||
|
|
@ -21,7 +26,9 @@ var currentlyMovingPiece = null
|
||||||
var p1Points: int = 0
|
var p1Points: int = 0
|
||||||
var p2Points: int = 0
|
var p2Points: int = 0
|
||||||
var Turn: int = 0
|
var Turn: int = 0
|
||||||
|
const StockfishController = preload("res://Systems/FairyStockfish/Stockfish.gd")
|
||||||
|
var stockfishController: StockfishController
|
||||||
|
var stockfishPath = "res://Assets/ChessEngines/stockfish/stockfish.exe"
|
||||||
@onready var turnIndicator: ColorRect = $TurnIndicator
|
@onready var turnIndicator: ColorRect = $TurnIndicator
|
||||||
@onready var p1String: RichTextLabel = $Player1Points
|
@onready var p1String: RichTextLabel = $Player1Points
|
||||||
@onready var p2String: RichTextLabel = $Player2Points
|
@onready var p2String: RichTextLabel = $Player2Points
|
||||||
|
|
@ -41,18 +48,31 @@ var Turn: int = 0
|
||||||
@onready var boardContainer: FlowContainer = $Flow
|
@onready var boardContainer: FlowContainer = $Flow
|
||||||
@onready var stateMachine: StateMachine = $StateMachine
|
@onready var stateMachine: StateMachine = $StateMachine
|
||||||
|
|
||||||
|
var currentFen = ""
|
||||||
var lightStyle = null
|
var lightStyle = null
|
||||||
|
|
||||||
var darkStyle = null
|
var darkStyle = null
|
||||||
var highlightStyle = null
|
var highlightStyle = null
|
||||||
|
var cpuElo = 1500
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
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")
|
add_to_group("ChessGame")
|
||||||
|
currentFen = FEN
|
||||||
DisplayServer.window_set_size(Vector2i(windowXSize, windowYSize))
|
DisplayServer.window_set_size(Vector2i(windowXSize, windowYSize))
|
||||||
initializeGame()
|
initializeGame()
|
||||||
initializeTiles()
|
initializeTiles()
|
||||||
stateMachine.transitionToNextState(Constants.WHITE_TURN)
|
stateMachine.transitionToNextState(Constants.WHITE_TURN)
|
||||||
|
stockfishController = StockfishController.new(board)
|
||||||
|
add_child(stockfishController)
|
||||||
|
if stockfishController.connect_to_engine(stockfishPath):
|
||||||
|
stockfishController.limit_strength_to(cpuElo)
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
stockfishController.disconnect_engine()
|
||||||
|
|
||||||
func initializeTiles() -> void:
|
func initializeTiles() -> void:
|
||||||
tileManager = TileManager.new($Flow, self)
|
tileManager = TileManager.new($Flow, self)
|
||||||
|
|
@ -83,6 +103,45 @@ func initializeCardPreview() -> void:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func getCurrentFen() -> String:
|
||||||
|
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
|
||||||
|
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" % [
|
||||||
|
"w" if isWhiteToMove else "b",
|
||||||
|
castlingRights,
|
||||||
|
enPassantTarget,
|
||||||
|
halfMoveClock,
|
||||||
|
moveCount
|
||||||
|
]
|
||||||
|
|
||||||
|
return fen
|
||||||
func initializeBoard() -> void:
|
func initializeBoard() -> void:
|
||||||
# Parse FEN to get board dimensions
|
# Parse FEN to get board dimensions
|
||||||
# rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
# rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
||||||
|
|
@ -485,3 +544,99 @@ func cleanupPhase() -> void:
|
||||||
|
|
||||||
func isNull(location: String) -> bool:
|
func isNull(location: String) -> bool:
|
||||||
return get_node_or_null("Flow/" + location) == null
|
return get_node_or_null("Flow/" + location) == null
|
||||||
|
|
||||||
|
|
||||||
|
func getPieceFenChar(piece: Pawn) -> String:
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = 7 if piece.isWhite else 0
|
||||||
|
if fromIdx == startRank * 8: # Queen-side rook
|
||||||
|
castlingRights = castlingRights.replace("Q" if piece.isWhite else "q", "")
|
||||||
|
elif fromIdx == startRank * 8 + 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 / 8
|
||||||
|
var toRank = toIdx / 8
|
||||||
|
if abs(fromRank - toRank) == 2:
|
||||||
|
var file = fromIdx % 8
|
||||||
|
var targetRank = (fromRank + toRank) / 2
|
||||||
|
enPassantTarget = "%s%d" % [char(97 + file), 8 - 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 _on_ai_move_generated(move: String) -> void:
|
||||||
|
if Turn == 1: # Only process AI moves during black's turn
|
||||||
|
# The move will be automatically handled by the Movement state
|
||||||
|
# which is listening for the moveGenerated signal
|
||||||
|
pass
|
||||||
|
|
||||||
|
func executeAiMove(fromLocation: String, toLocation: String) -> void:
|
||||||
|
selectedNode = fromLocation
|
||||||
|
executeMove(toLocation)
|
||||||
|
|
||||||
|
func convertNotationToLocation(notation: String) -> String:
|
||||||
|
var file = notation[0].unicode_at(0) - 'a'.unicode_at(0)
|
||||||
|
var rank = 8 - int(notation[1])
|
||||||
|
return str(file) + "-" + str(rank)
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,4 @@ func get_overlay(overlay_name: String) -> Node:
|
||||||
|
|
||||||
func has_piece() -> bool:
|
func has_piece() -> bool:
|
||||||
return piece != null
|
return piece != null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@ extends "res://Systems/StateMachine/ChessGameState.gd"
|
||||||
func enter(_previous: String, _data := {}) -> void:
|
func enter(_previous: String, _data := {}) -> void:
|
||||||
print("ENTERING STATE ", Constants.TILE_EFFECTS)
|
print("ENTERING STATE ", Constants.TILE_EFFECTS)
|
||||||
game.applyTileEffects()
|
game.applyTileEffects()
|
||||||
finished.emit(Constants.PRE_MOVE)
|
finished.emit(Constants.EVALUATE_POSITION)
|
||||||
|
|
@ -1,6 +1,31 @@
|
||||||
extends "res://Systems/StateMachine/ChessGameState.gd"
|
extends "res://Systems/StateMachine/ChessGameState.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var moveTimer: Timer
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
moveTimer = Timer.new()
|
||||||
|
moveTimer.one_shot = true # Timer only fires once
|
||||||
|
moveTimer.connect("timeout", _on_move_timer_timeout)
|
||||||
|
add_child(moveTimer)
|
||||||
|
|
||||||
func enter(_previous: String, _data := {}) -> void:
|
func enter(_previous: String, _data := {}) -> void:
|
||||||
print("ENTERING STATE ", Constants.BLACK_TURN)
|
print("ENTERING STATE ", Constants.BLACK_TURN)
|
||||||
game.currentPlayer = game.BLACK
|
game.currentPlayer = game.BLACK
|
||||||
finished.emit(Constants.HAND_SETUP)
|
|
||||||
|
# Delay to avoid duplication during animation
|
||||||
|
if game.stockfishController:
|
||||||
|
moveTimer.start(2)
|
||||||
|
|
||||||
|
func _on_move_timer_timeout() -> void:
|
||||||
|
# Generate AI move after delay
|
||||||
|
print("------------------GENERATING MOVE --------------------")
|
||||||
|
if game.stockfishController:
|
||||||
|
print("------------------STARTING GENERATING MOVE --------------------")
|
||||||
|
game.stockfishController.generateMove(1000) # 1 second think time
|
||||||
|
finished.emit(Constants.HAND_SETUP)
|
||||||
|
|
||||||
|
func exit() -> void:
|
||||||
|
moveTimer.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,19 @@ var multiMoving = ""
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
print("Movement state ready")
|
print("Movement state ready")
|
||||||
|
|
||||||
|
func _setup_movement_modifiers() -> void:
|
||||||
|
for piece_id in game.deckManager.attached_cards:
|
||||||
|
var card = game.deckManager.attached_cards[piece_id]
|
||||||
|
if card.effectType == Card.EffectType.MOVEMENT_MODIFIER:
|
||||||
|
var effects = card.modify_moves()
|
||||||
|
moves_remaining[piece_id] = effects.get("extra_moves", 0) + 1
|
||||||
|
|
||||||
|
|
||||||
func enter(_previous: String, _data := {}) -> void:
|
func enter(_previous: String, _data := {}) -> void:
|
||||||
print("ENTERING STATE ", Constants.MOVEMENT)
|
print("ENTERING STATE ", Constants.MOVEMENT, " ", game.currentPlayer)
|
||||||
|
|
||||||
|
|
||||||
|
_setup_movement_modifiers()
|
||||||
if !game.boardContainer.is_connected("tile_pressed", handleMovement):
|
if !game.boardContainer.is_connected("tile_pressed", handleMovement):
|
||||||
game.boardContainer.connect("tile_pressed", handleMovement)
|
game.boardContainer.connect("tile_pressed", handleMovement)
|
||||||
if game.selectedNode != "":
|
if game.selectedNode != "":
|
||||||
|
|
@ -32,15 +43,48 @@ func enter(_previous: String, _data := {}) -> void:
|
||||||
else:
|
else:
|
||||||
moves_remaining[piece_id] = effects.get("extra_moves", 0) + 1
|
moves_remaining[piece_id] = effects.get("extra_moves", 0) + 1
|
||||||
|
|
||||||
|
if game.currentPlayer == game.BLACK and game.stockfishController:
|
||||||
|
var move = game.stockfishController.getGeneratedMove()
|
||||||
|
print("GENERATED MOVE ", move)
|
||||||
|
if move:
|
||||||
|
var move_str = move.move # e.g., "e2e4"
|
||||||
|
var source_square = move_str.substr(0, 2) # "e2"
|
||||||
|
var target_square = move_str.substr(2, 2) # "e4"
|
||||||
|
|
||||||
|
# First select the piece
|
||||||
|
var source_location = 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)
|
||||||
|
print("target_location ", target_location)
|
||||||
|
handleMovement(target_location, true)
|
||||||
|
return
|
||||||
|
|
||||||
func exit() -> void:
|
func exit() -> void:
|
||||||
if game.boardContainer.is_connected("tile_pressed", handleMovement):
|
if game.boardContainer.is_connected("tile_pressed", handleMovement):
|
||||||
game.boardContainer.disconnect("tile_pressed", handleMovement)
|
game.boardContainer.disconnect("tile_pressed", handleMovement)
|
||||||
|
|
||||||
func handleMovement(location: String) -> void:
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Convert rank to 0-based index from bottom
|
||||||
|
var rank_num = rank - 1
|
||||||
|
|
||||||
|
# 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
|
# we need to prevent swapping of focus between peices after the double move process has started
|
||||||
# maybe once u start nmoving a peice global stat var is set
|
# maybe once u start nmoving a peice global stat var is set
|
||||||
# and any moving peice needs ot match that
|
# and any moving peice needs ot match that
|
||||||
# print("HANDLING MOVEMENT ", location, " | ", multiMoving, " | ", game.selectedNode)
|
print("HANDLING MOVEMENT ", location, " | ", multiMoving, " | ", game.selectedNode)
|
||||||
var node = game.get_node("Flow/" + location) as PieceContainer
|
var node = game.get_node("Flow/" + location) as PieceContainer
|
||||||
|
|
||||||
# Only try to get piece if we have a selected node
|
# Only try to get piece if we have a selected node
|
||||||
|
|
@ -89,17 +133,16 @@ func handleMovement(location: String) -> void:
|
||||||
if piece_id in moves_remaining:
|
if piece_id in moves_remaining:
|
||||||
moves_remaining.erase(piece_id)
|
moves_remaining.erase(piece_id)
|
||||||
# finished.emit(Constants.POST_MOVE)
|
# finished.emit(Constants.POST_MOVE)
|
||||||
|
|
||||||
if game.selectedNode == location:
|
if game.selectedNode == location:
|
||||||
if !is_multi_moving and multiMoving == "":
|
if !is_multi_moving and multiMoving == "":
|
||||||
print("CLEAR SELECTION*************")
|
print("CLEAR SELECTION*************")
|
||||||
game.clearSelection()
|
game.clearSelection()
|
||||||
elif isCastlingMove(node, location):
|
elif isCastlingMove(node, location):
|
||||||
handleCastling(node)
|
handleCastling(node, generated)
|
||||||
finished.emit(Constants.POST_MOVE)
|
finished.emit(Constants.POST_MOVE)
|
||||||
# En Passant
|
# En Passant
|
||||||
elif isEnPassantMove(node, location):
|
elif isEnPassantMove(node, location):
|
||||||
handleEnPassant(node)
|
handleEnPassant(node, generated)
|
||||||
# Reselect piece
|
# Reselect piece
|
||||||
elif isReselectMove(node):
|
elif isReselectMove(node):
|
||||||
if !is_multi_moving and multiMoving == "":
|
if !is_multi_moving and multiMoving == "":
|
||||||
|
|
@ -108,17 +151,17 @@ func handleMovement(location: String) -> void:
|
||||||
game.getMovableAreas()
|
game.getMovableAreas()
|
||||||
# Capture piece
|
# Capture piece
|
||||||
elif isCaptureMove(node):
|
elif isCaptureMove(node):
|
||||||
handleCapture(node)
|
handleCapture(node, generated)
|
||||||
finished.emit(Constants.POST_MOVE)
|
finished.emit(Constants.POST_MOVE)
|
||||||
# Regular move
|
# Regular move
|
||||||
# elif isRegularMove(node):
|
# elif isRegularMove(node):
|
||||||
# handleRegularMove(node)
|
# handleRegularMove(node)
|
||||||
# finished.emit(Constants.POST_MOVE)
|
# finished.emit(Constants.POST_MOVE)
|
||||||
else:
|
else:
|
||||||
if game.isValidMove(location):
|
if game.isValidMove(location) || generated:
|
||||||
# executeMove(location)
|
# executeMove(location)
|
||||||
|
|
||||||
handleRegularMove(node, consumeMove)
|
handleRegularMove(node, consumeMove, generated)
|
||||||
if consumeMove:
|
if consumeMove:
|
||||||
multiMoving = ""
|
multiMoving = ""
|
||||||
game.clearSelection()
|
game.clearSelection()
|
||||||
|
|
@ -167,86 +210,107 @@ func isCaptureMove(node: PieceContainer) -> bool:
|
||||||
func isRegularMove(node: PieceContainer) -> bool:
|
func isRegularMove(node: PieceContainer) -> bool:
|
||||||
return game.selectedNode != "" && node.get_piece() != null
|
return game.selectedNode != "" && node.get_piece() != null
|
||||||
|
|
||||||
func handleCastling(node: PieceContainer) -> void:
|
func handleCastling(node: PieceContainer, generated: bool = false) -> void:
|
||||||
print("handleCastling")
|
print("handleCastling")
|
||||||
|
if generated:
|
||||||
|
castleMovement(node)
|
||||||
|
return
|
||||||
for i in game.areas:
|
for i in game.areas:
|
||||||
if i == node.name:
|
if i == node.name:
|
||||||
var kingContainer = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
castleMovement(node)
|
||||||
var rookContainer = node as PieceContainer
|
func castleMovement(node: PieceContainer) -> void:
|
||||||
var kingTargetContainer = game.get_node("Flow/" + game.specialArea[1]) as PieceContainer
|
var kingContainer = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
||||||
var rookTargetContainer = game.get_node("Flow/" + game.specialArea[0]) as PieceContainer
|
var rookContainer = node as PieceContainer
|
||||||
|
var kingTargetContainer = game.get_node("Flow/" + game.specialArea[1]) as PieceContainer
|
||||||
|
var rookTargetContainer = game.get_node("Flow/" + game.specialArea[0]) as PieceContainer
|
||||||
|
|
||||||
var king = kingContainer.get_piece()
|
var king = kingContainer.get_piece()
|
||||||
var rook = rookContainer.get_piece()
|
var rook = rookContainer.get_piece()
|
||||||
|
|
||||||
# kingContainer.remove_piece(true)
|
# kingContainer.remove_piece(true)
|
||||||
# rookContainer.remove_piece(true)
|
# rookContainer.remove_piece(true)
|
||||||
# kingTargetContainer.set_piece(king)
|
# kingTargetContainer.set_piece(king)
|
||||||
# rookTargetContainer.set_piece(rook)
|
# rookTargetContainer.set_piece(rook)
|
||||||
|
|
||||||
kingTargetContainer.animate_movement(kingContainer, king);
|
kingTargetContainer.animate_movement(kingContainer, king);
|
||||||
|
|
||||||
rookTargetContainer.animate_movement(rookContainer, rook);
|
rookTargetContainer.animate_movement(rookContainer, rook);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
game.currentlyMovingPiece = king
|
game.currentlyMovingPiece = king
|
||||||
game.resolveMoveEffects()
|
game.resolveMoveEffects()
|
||||||
|
|
||||||
func handleEnPassant(node: PieceContainer) -> void:
|
func handleEnPassant(node: PieceContainer, generated: bool = false) -> void:
|
||||||
print("handleEnPassant")
|
print("handleEnPassant")
|
||||||
for i in game.specialArea:
|
if generated:
|
||||||
|
handleEnPassantMovement(node)
|
||||||
|
return
|
||||||
|
for i in game.areas:
|
||||||
if i == node.name:
|
if i == node.name:
|
||||||
var targetContainer = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
handleEnPassantMovement(node)
|
||||||
var pawn = (game.get_node("Flow/" + game.selectedNode) as PieceContainer).get_piece()
|
|
||||||
# node.remove_piece()
|
|
||||||
# targetContainer.set_piece(pawn)
|
|
||||||
game.currentlyMovingPiece = pawn
|
|
||||||
|
|
||||||
|
|
||||||
targetContainer.animate_movement(node, pawn);
|
func handleEnPassantMovement(node: PieceContainer) -> void:
|
||||||
game.resolveMoveEffects()
|
var targetContainer = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
||||||
|
var pawn = (game.get_node("Flow/" + game.selectedNode) as PieceContainer).get_piece()
|
||||||
|
# node.remove_piece()
|
||||||
|
# targetContainer.set_piece(pawn)
|
||||||
|
game.currentlyMovingPiece = pawn
|
||||||
|
|
||||||
func handleCapture(node: PieceContainer) -> void:
|
|
||||||
|
targetContainer.animate_movement(node, pawn);
|
||||||
|
game.resolveMoveEffects()
|
||||||
|
|
||||||
|
func handleCapture(node: PieceContainer, generated: bool = false) -> void:
|
||||||
print("handleCapture")
|
print("handleCapture")
|
||||||
|
if generated:
|
||||||
|
handleCaptureMovement(node)
|
||||||
|
return
|
||||||
for i in game.areas:
|
for i in game.areas:
|
||||||
if i == node.name:
|
if i == node.name:
|
||||||
|
handleCaptureMovement(node)
|
||||||
|
|
||||||
|
|
||||||
var source_container = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
func handleCaptureMovement(node: PieceContainer) -> void:
|
||||||
|
var source_container = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
||||||
|
|
||||||
|
|
||||||
var moving_piece = source_container.get_piece()
|
var moving_piece = source_container.get_piece()
|
||||||
var captured_piece = node.get_piece()
|
var captured_piece = node.get_piece()
|
||||||
|
|
||||||
if moving_piece && captured_piece:
|
if moving_piece && captured_piece:
|
||||||
await game.animatePieceCapture(captured_piece)
|
await game.animatePieceCapture(captured_piece)
|
||||||
game.updatePointsAndCapture(captured_piece, false)
|
game.updatePointsAndCapture(captured_piece, false)
|
||||||
node.animate_movement(source_container, moving_piece);
|
node.animate_movement(source_container, moving_piece);
|
||||||
var tile = game.tileManager.get_tile(node.name)
|
var tile = game.tileManager.get_tile(node.name)
|
||||||
if tile:
|
if tile:
|
||||||
tile.apply_effect(moving_piece)
|
tile.apply_effect(moving_piece)
|
||||||
|
|
||||||
|
|
||||||
game.currentlyMovingPiece = moving_piece
|
game.currentlyMovingPiece = moving_piece
|
||||||
game.resolveMoveEffects()
|
game.resolveMoveEffects()
|
||||||
|
|
||||||
func handleRegularMove(node: PieceContainer, consume: bool) -> void:
|
|
||||||
|
func handleRegularMove(node: PieceContainer, consume: bool, generated: bool = false) -> void:
|
||||||
print("handleRegularMove", node, game.selectedNode)
|
print("handleRegularMove", node, game.selectedNode)
|
||||||
|
if generated:
|
||||||
|
moveAndConsume(node, consume)
|
||||||
|
return
|
||||||
for i in game.areas:
|
for i in game.areas:
|
||||||
if i == node.name:
|
if i == node.name:
|
||||||
print(i)
|
moveAndConsume(node, consume)
|
||||||
var sourceContainer = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
|
||||||
var piece = sourceContainer.get_piece()
|
|
||||||
print("Removing Piece 1")
|
|
||||||
node.animate_movement(sourceContainer, piece);
|
|
||||||
game.currentlyMovingPiece = piece
|
|
||||||
if consume:
|
|
||||||
game.resolveMoveEffects()
|
|
||||||
else:
|
|
||||||
game.togglePieceChessEffect()
|
|
||||||
|
|
||||||
|
|
||||||
|
func moveAndConsume(node: PieceContainer, consume: bool) -> void:
|
||||||
|
var sourceContainer = game.get_node("Flow/" + game.selectedNode) as PieceContainer
|
||||||
|
var piece = sourceContainer.get_piece()
|
||||||
|
print("Removing Piece 1")
|
||||||
|
node.animate_movement(sourceContainer, piece);
|
||||||
|
game.currentlyMovingPiece = piece
|
||||||
|
if consume:
|
||||||
|
game.resolveMoveEffects()
|
||||||
|
else:
|
||||||
|
game.togglePieceChessEffect()
|
||||||
|
|
||||||
|
|
||||||
func executeMove(targetLocation: String) -> void:
|
func executeMove(targetLocation: String) -> void:
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ func enter(_previous: String, _data := {}) -> void:
|
||||||
game.updateEffectDurations()
|
game.updateEffectDurations()
|
||||||
# if (game.isPlayerTurn()):
|
# if (game.isPlayerTurn()):
|
||||||
# game.deckManager.updateCardDurations()
|
# game.deckManager.updateCardDurations()
|
||||||
finished.emit(Constants.EVALUATE_POSITION)
|
finished.emit(Constants.TILE_EFFECTS)
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@ extends "res://Systems/StateMachine/ChessGameState.gd"
|
||||||
func enter(_previous: String, _data := {}) -> void:
|
func enter(_previous: String, _data := {}) -> void:
|
||||||
print("ENTERING STATE ", Constants.PERSISTENT_EFFECTS)
|
print("ENTERING STATE ", Constants.PERSISTENT_EFFECTS)
|
||||||
|
|
||||||
finished.emit(Constants.TILE_EFFECTS)
|
finished.emit(Constants.PRE_MOVE)
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,6 @@ offset_bottom = -5.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
script = ExtResource("1_fkb2r")
|
script = ExtResource("1_fkb2r")
|
||||||
boardXSize = null
|
|
||||||
boardYSize = null
|
|
||||||
tileXSize = null
|
|
||||||
tileYSize = null
|
|
||||||
windowXSize = null
|
|
||||||
windowYSize = null
|
|
||||||
FEN = null
|
|
||||||
|
|
||||||
[node name="Flow" type="FlowContainer" parent="."]
|
[node name="Flow" type="FlowContainer" parent="."]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,4 @@ import/blender/enabled=false
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
renderer/rendering_method="gl_compatibility"
|
renderer/rendering_method="mobile"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue