feature updates

This commit is contained in:
2026-02-02 16:28:53 -05:00
parent bf9aa3fa23
commit 44c06530ac
83 changed files with 282641 additions and 11251 deletions

161
scripts/game/ai/NormalAI.gd Normal file
View 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)