feature updates
This commit is contained in:
223
scripts/game/ai/AIController.gd
Normal file
223
scripts/game/ai/AIController.gd
Normal file
@@ -0,0 +1,223 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user