feature updates
This commit is contained in:
161
scripts/game/ai/NormalAI.gd
Normal file
161
scripts/game/ai/NormalAI.gd
Normal file
@@ -0,0 +1,161 @@
|
||||
class_name NormalAI
|
||||
extends AIStrategy
|
||||
|
||||
## Normal AI - Balanced play using cost/power heuristics
|
||||
## Makes generally good decisions but doesn't deeply analyze
|
||||
|
||||
|
||||
func _init(p_player_index: int) -> void:
|
||||
super._init(Difficulty.NORMAL, p_player_index)
|
||||
|
||||
|
||||
func decide_main_phase_action() -> Dictionary:
|
||||
var playable := get_playable_cards()
|
||||
|
||||
if playable.is_empty():
|
||||
return { "action": "pass" }
|
||||
|
||||
# Sort by value (best cards first)
|
||||
playable.sort_custom(_compare_card_value)
|
||||
|
||||
# Consider board state - prioritize forwards if we're behind
|
||||
var board_eval := evaluate_board_state()
|
||||
|
||||
for card in playable:
|
||||
var card_type := card.card_data.type
|
||||
|
||||
# If behind on board, prioritize forwards
|
||||
if board_eval < -5.0 and card_type == Enums.CardType.FORWARD:
|
||||
return { "action": "play", "card": card }
|
||||
|
||||
# If ahead, might want backups for sustainability
|
||||
if board_eval > 5.0 and card_type == Enums.CardType.BACKUP:
|
||||
if get_player().field_backups.get_card_count() < 5:
|
||||
return { "action": "play", "card": card }
|
||||
|
||||
# Default: play the highest value card we can afford
|
||||
return { "action": "play", "card": playable[0] }
|
||||
|
||||
|
||||
func decide_cp_generation(needed_cp: Dictionary) -> Dictionary:
|
||||
var player := get_player()
|
||||
|
||||
# First, dull backups (they refresh next turn)
|
||||
for backup in player.field_backups.get_cards():
|
||||
if backup.state == Enums.CardState.ACTIVE:
|
||||
return { "action": "dull_backup", "card": backup }
|
||||
|
||||
# Then, discard lowest value card from hand
|
||||
var hand_cards := player.hand.get_cards()
|
||||
if hand_cards.is_empty():
|
||||
return {}
|
||||
|
||||
# Sort by value (lowest first for discard)
|
||||
var sorted_hand := hand_cards.duplicate()
|
||||
sorted_hand.sort_custom(_compare_card_value_reverse)
|
||||
|
||||
return { "action": "discard", "card": sorted_hand[0] }
|
||||
|
||||
|
||||
func decide_attack_action() -> Dictionary:
|
||||
var attackers := get_attackable_forwards()
|
||||
var opponent := get_opponent()
|
||||
|
||||
if attackers.is_empty():
|
||||
return { "action": "end_attacks" }
|
||||
|
||||
# Get opponent's potential blockers
|
||||
var opponent_blockers := opponent.get_blockable_forwards()
|
||||
|
||||
# Evaluate each potential attacker
|
||||
var best_attacker: CardInstance = null
|
||||
var best_score := -999.0
|
||||
|
||||
for attacker in attackers:
|
||||
var score := _evaluate_attack(attacker, opponent_blockers, opponent)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_attacker = attacker
|
||||
|
||||
# Only attack if the score is positive (favorable)
|
||||
if best_score > 0 and best_attacker:
|
||||
return { "action": "attack", "card": best_attacker }
|
||||
|
||||
return { "action": "end_attacks" }
|
||||
|
||||
|
||||
func decide_block_action(attacker: CardInstance) -> Dictionary:
|
||||
var blockers := get_blockable_forwards()
|
||||
var player := get_player()
|
||||
|
||||
if blockers.is_empty():
|
||||
return { "action": "skip" }
|
||||
|
||||
var attacker_power := attacker.get_power()
|
||||
|
||||
# Check if this attack would be lethal
|
||||
var current_damage := player.get_damage_count()
|
||||
var would_be_lethal := current_damage >= 6 # 7th damage loses
|
||||
|
||||
# Find best blocker
|
||||
var best_blocker: CardInstance = null
|
||||
var best_score := -999.0
|
||||
|
||||
for blocker in blockers:
|
||||
var blocker_power := blocker.get_power()
|
||||
var score := 0.0
|
||||
|
||||
# Would we win the trade?
|
||||
if blocker_power >= attacker_power:
|
||||
score += 5.0 # We kill their forward
|
||||
if attacker_power >= blocker_power:
|
||||
score -= calculate_card_value(blocker) # We lose our blocker
|
||||
|
||||
# If lethal, blocking is very important
|
||||
if would_be_lethal:
|
||||
score += 10.0
|
||||
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_blocker = blocker
|
||||
|
||||
# Block if favorable or if lethal
|
||||
if best_score > 0 or would_be_lethal:
|
||||
if best_blocker:
|
||||
return { "action": "block", "card": best_blocker }
|
||||
|
||||
return { "action": "skip" }
|
||||
|
||||
|
||||
func _evaluate_attack(attacker: CardInstance, opponent_blockers: Array[CardInstance], opponent: Player) -> float:
|
||||
var score := 0.0
|
||||
var attacker_power := attacker.get_power()
|
||||
|
||||
# Base value: dealing damage is good
|
||||
score += 2.0
|
||||
|
||||
# Check if opponent can block profitably
|
||||
var can_be_blocked := false
|
||||
for blocker in opponent_blockers:
|
||||
if blocker.get_power() >= attacker_power:
|
||||
can_be_blocked = true
|
||||
score -= 3.0 # Likely to lose our forward
|
||||
break
|
||||
|
||||
# If unblockable damage, more valuable
|
||||
if not can_be_blocked:
|
||||
score += 3.0
|
||||
|
||||
# If this would be lethal damage (7th), very valuable
|
||||
if opponent.get_damage_count() >= 6:
|
||||
score += 10.0
|
||||
|
||||
return score
|
||||
|
||||
|
||||
func _compare_card_value(a: CardInstance, b: CardInstance) -> bool:
|
||||
return calculate_card_value(a) > calculate_card_value(b)
|
||||
|
||||
|
||||
func _compare_card_value_reverse(a: CardInstance, b: CardInstance) -> bool:
|
||||
return calculate_card_value(a) < calculate_card_value(b)
|
||||
Reference in New Issue
Block a user