224 lines
5.4 KiB
GDScript3
224 lines
5.4 KiB
GDScript3
class_name AIController
|
|
extends Node
|
|
|
|
## AIController - Coordinates AI player turns
|
|
## Handles timing, action execution, and phase transitions
|
|
|
|
signal ai_action_started
|
|
signal ai_action_completed
|
|
signal ai_thinking(player_index: int)
|
|
|
|
var strategy: AIStrategy
|
|
var game_state: GameState
|
|
var player_index: int
|
|
var is_processing: bool = false
|
|
|
|
# Reference to GameManager for executing actions
|
|
var _game_manager: Node
|
|
|
|
|
|
func _init() -> void:
|
|
pass
|
|
|
|
|
|
func setup(p_player_index: int, difficulty: AIStrategy.Difficulty, p_game_manager: Node) -> void:
|
|
player_index = p_player_index
|
|
_game_manager = p_game_manager
|
|
|
|
# Create appropriate strategy based on difficulty
|
|
match difficulty:
|
|
AIStrategy.Difficulty.EASY:
|
|
strategy = EasyAI.new(player_index)
|
|
AIStrategy.Difficulty.NORMAL:
|
|
strategy = NormalAI.new(player_index)
|
|
AIStrategy.Difficulty.HARD:
|
|
strategy = HardAI.new(player_index)
|
|
|
|
|
|
func set_game_state(state: GameState) -> void:
|
|
game_state = state
|
|
if strategy:
|
|
strategy.set_game_state(state)
|
|
|
|
|
|
## Called when it's the AI's turn to act in the current phase
|
|
func process_turn() -> void:
|
|
if is_processing:
|
|
return
|
|
|
|
is_processing = true
|
|
ai_thinking.emit(player_index)
|
|
|
|
# Add thinking delay
|
|
var delay := strategy.get_thinking_delay()
|
|
await get_tree().create_timer(delay).timeout
|
|
|
|
var phase := game_state.turn_manager.current_phase
|
|
|
|
match phase:
|
|
Enums.TurnPhase.ACTIVE:
|
|
# Active phase is automatic - no AI decision needed
|
|
_pass_priority()
|
|
|
|
Enums.TurnPhase.DRAW:
|
|
# Draw phase is automatic
|
|
_pass_priority()
|
|
|
|
Enums.TurnPhase.MAIN_1, Enums.TurnPhase.MAIN_2:
|
|
await _process_main_phase()
|
|
|
|
Enums.TurnPhase.ATTACK:
|
|
await _process_attack_phase()
|
|
|
|
Enums.TurnPhase.END:
|
|
# End phase is automatic
|
|
_pass_priority()
|
|
|
|
is_processing = false
|
|
ai_action_completed.emit()
|
|
|
|
|
|
## Process main phase - play cards or pass
|
|
func _process_main_phase() -> void:
|
|
var max_actions := 10 # Prevent infinite loops
|
|
var actions_taken := 0
|
|
|
|
while actions_taken < max_actions:
|
|
var decision := strategy.decide_main_phase_action()
|
|
|
|
if decision.action == "pass":
|
|
_pass_priority()
|
|
break
|
|
|
|
elif decision.action == "play":
|
|
var card: CardInstance = decision.card
|
|
var success := await _try_play_card(card)
|
|
|
|
if not success:
|
|
# Couldn't play - pass
|
|
_pass_priority()
|
|
break
|
|
|
|
# Small delay between actions
|
|
await get_tree().create_timer(0.3).timeout
|
|
|
|
actions_taken += 1
|
|
|
|
|
|
## Try to play a card, handling CP generation if needed
|
|
func _try_play_card(card: CardInstance) -> bool:
|
|
var player := game_state.get_player(player_index)
|
|
var cost := card.card_data.cost
|
|
|
|
# Check if we have enough CP
|
|
var current_cp := player.cp_pool.get_total_cp()
|
|
|
|
if current_cp < cost:
|
|
# Need to generate CP
|
|
var needed := cost - current_cp
|
|
var success := await _generate_cp(needed, card.card_data.elements)
|
|
if not success:
|
|
return false
|
|
|
|
# Try to play the card
|
|
return _game_manager.try_play_card(card)
|
|
|
|
|
|
## Generate CP by dulling backups or discarding cards
|
|
func _generate_cp(needed: int, elements: Array) -> bool:
|
|
var generated := 0
|
|
var max_attempts := 20
|
|
|
|
while generated < needed and max_attempts > 0:
|
|
var decision := strategy.decide_cp_generation({ "needed": needed - generated, "elements": elements })
|
|
|
|
if decision.is_empty():
|
|
return false
|
|
|
|
if decision.action == "dull_backup":
|
|
var backup: CardInstance = decision.card
|
|
if _game_manager.dull_backup_for_cp(backup):
|
|
generated += 1
|
|
await get_tree().create_timer(0.2).timeout
|
|
|
|
elif decision.action == "discard":
|
|
var discard_card: CardInstance = decision.card
|
|
if _game_manager.discard_card_for_cp(discard_card):
|
|
generated += 2
|
|
await get_tree().create_timer(0.2).timeout
|
|
|
|
max_attempts -= 1
|
|
|
|
return generated >= needed
|
|
|
|
|
|
## Process attack phase - declare attacks
|
|
func _process_attack_phase() -> void:
|
|
var attack_step := game_state.turn_manager.attack_step
|
|
|
|
match attack_step:
|
|
Enums.AttackStep.PREPARATION, Enums.AttackStep.DECLARATION:
|
|
await _process_attack_declaration()
|
|
|
|
Enums.AttackStep.BLOCK_DECLARATION:
|
|
# This shouldn't happen - AI blocks are handled in opponent's turn
|
|
_pass_priority()
|
|
|
|
Enums.AttackStep.DAMAGE_RESOLUTION:
|
|
# Automatic
|
|
_pass_priority()
|
|
|
|
|
|
## Declare attacks with forwards
|
|
func _process_attack_declaration() -> void:
|
|
var max_attacks := 5
|
|
var attacks_made := 0
|
|
|
|
while attacks_made < max_attacks:
|
|
var decision := strategy.decide_attack_action()
|
|
|
|
if decision.action == "end_attacks":
|
|
# End attack phase
|
|
_game_manager.pass_priority()
|
|
break
|
|
|
|
elif decision.action == "attack":
|
|
var attacker: CardInstance = decision.card
|
|
var success := _game_manager.declare_attack(attacker)
|
|
|
|
if success:
|
|
attacks_made += 1
|
|
# Wait for block decision or damage resolution
|
|
await get_tree().create_timer(0.5).timeout
|
|
else:
|
|
# Couldn't attack - end attacks
|
|
_game_manager.pass_priority()
|
|
break
|
|
|
|
|
|
## Called when AI needs to decide on blocking
|
|
func process_block_decision(attacker: CardInstance) -> void:
|
|
if is_processing:
|
|
return
|
|
|
|
is_processing = true
|
|
ai_thinking.emit(player_index)
|
|
|
|
var delay := strategy.get_thinking_delay()
|
|
await get_tree().create_timer(delay).timeout
|
|
|
|
var decision := strategy.decide_block_action(attacker)
|
|
|
|
if decision.action == "block":
|
|
var blocker: CardInstance = decision.card
|
|
_game_manager.declare_block(blocker)
|
|
else:
|
|
_game_manager.skip_block()
|
|
|
|
is_processing = false
|
|
ai_action_completed.emit()
|
|
|
|
|
|
func _pass_priority() -> void:
|
|
_game_manager.pass_priority()
|