feature updates
This commit is contained in:
190
scripts/game/ai/AIStrategy.gd
Normal file
190
scripts/game/ai/AIStrategy.gd
Normal file
@@ -0,0 +1,190 @@
|
||||
class_name AIStrategy
|
||||
extends RefCounted
|
||||
|
||||
## Base class for AI decision-making strategies
|
||||
## Subclasses implement different difficulty levels
|
||||
|
||||
enum Difficulty { EASY, NORMAL, HARD }
|
||||
|
||||
var difficulty: Difficulty
|
||||
var player_index: int
|
||||
var game_state: GameState
|
||||
|
||||
|
||||
func _init(p_difficulty: Difficulty, p_player_index: int) -> void:
|
||||
difficulty = p_difficulty
|
||||
player_index = p_player_index
|
||||
|
||||
|
||||
func set_game_state(state: GameState) -> void:
|
||||
game_state = state
|
||||
|
||||
|
||||
## Returns the player this AI controls
|
||||
func get_player() -> Player:
|
||||
return game_state.get_player(player_index)
|
||||
|
||||
|
||||
## Returns the opponent player
|
||||
func get_opponent() -> Player:
|
||||
return game_state.get_player(1 - player_index)
|
||||
|
||||
|
||||
## Called during Main Phase - decide what card to play or pass
|
||||
## Returns: { "action": "play", "card": CardInstance } or { "action": "pass" }
|
||||
func decide_main_phase_action() -> Dictionary:
|
||||
push_error("AIStrategy.decide_main_phase_action() must be overridden")
|
||||
return { "action": "pass" }
|
||||
|
||||
|
||||
## Called when CP is needed - decide how to generate CP
|
||||
## Returns: { "action": "discard", "card": CardInstance } or { "action": "dull_backup", "card": CardInstance }
|
||||
func decide_cp_generation(needed_cp: Dictionary) -> Dictionary:
|
||||
push_error("AIStrategy.decide_cp_generation() must be overridden")
|
||||
return {}
|
||||
|
||||
|
||||
## Called during Attack Phase - decide which forward to attack with
|
||||
## Returns: { "action": "attack", "card": CardInstance } or { "action": "end_attacks" }
|
||||
func decide_attack_action() -> Dictionary:
|
||||
push_error("AIStrategy.decide_attack_action() must be overridden")
|
||||
return { "action": "end_attacks" }
|
||||
|
||||
|
||||
## Called during Block Declaration - decide how to block
|
||||
## Returns: { "action": "block", "card": CardInstance } or { "action": "skip" }
|
||||
func decide_block_action(attacker: CardInstance) -> Dictionary:
|
||||
push_error("AIStrategy.decide_block_action() must be overridden")
|
||||
return { "action": "skip" }
|
||||
|
||||
|
||||
## Get thinking delay range in seconds based on difficulty
|
||||
func get_thinking_delay() -> float:
|
||||
match difficulty:
|
||||
Difficulty.EASY:
|
||||
return randf_range(1.5, 2.5)
|
||||
Difficulty.NORMAL:
|
||||
return randf_range(1.0, 1.5)
|
||||
Difficulty.HARD:
|
||||
return randf_range(0.5, 1.0)
|
||||
return 1.0
|
||||
|
||||
|
||||
# ============ HELPER METHODS FOR SUBCLASSES ============
|
||||
|
||||
## Get all cards in hand that can be played (have enough CP or can generate CP)
|
||||
func get_playable_cards() -> Array[CardInstance]:
|
||||
var player := get_player()
|
||||
var playable: Array[CardInstance] = []
|
||||
|
||||
for card in player.hand.get_cards():
|
||||
if _can_afford_card(card):
|
||||
playable.append(card)
|
||||
|
||||
return playable
|
||||
|
||||
|
||||
## Check if a card can be afforded (either have CP or can generate it)
|
||||
func _can_afford_card(card: CardInstance) -> bool:
|
||||
var player := get_player()
|
||||
var cost := card.card_data.cost
|
||||
var elements := card.card_data.elements
|
||||
|
||||
# Check if we already have enough CP
|
||||
var current_cp := player.cp_pool.get_total_cp()
|
||||
if current_cp >= cost:
|
||||
# Check element requirements
|
||||
for element in elements:
|
||||
if player.cp_pool.get_cp(element) > 0 or player.cp_pool.get_cp(Enums.Element.NONE) > 0:
|
||||
return true
|
||||
# If no specific element needed (Light/Dark cards), any CP works
|
||||
if elements.is_empty():
|
||||
return true
|
||||
|
||||
# Check if we can generate enough CP
|
||||
var potential_cp := _calculate_potential_cp()
|
||||
return potential_cp >= cost
|
||||
|
||||
|
||||
## Calculate total CP we could generate (hand discards + backup dulls)
|
||||
func _calculate_potential_cp() -> int:
|
||||
var player := get_player()
|
||||
var total := player.cp_pool.get_total_cp()
|
||||
|
||||
# Each card in hand can be discarded for 2 CP
|
||||
total += player.hand.get_card_count() * 2
|
||||
|
||||
# Each active backup can be dulled for 1 CP
|
||||
for backup in player.field_backups.get_cards():
|
||||
if backup.state == Enums.CardState.ACTIVE:
|
||||
total += 1
|
||||
|
||||
return total
|
||||
|
||||
|
||||
## Get forwards that can attack
|
||||
func get_attackable_forwards() -> Array[CardInstance]:
|
||||
return get_player().get_attackable_forwards()
|
||||
|
||||
|
||||
## Get forwards that can block
|
||||
func get_blockable_forwards() -> Array[CardInstance]:
|
||||
return get_player().get_blockable_forwards()
|
||||
|
||||
|
||||
## Calculate a simple card value score
|
||||
func calculate_card_value(card: CardInstance) -> float:
|
||||
var data := card.card_data
|
||||
var value := 0.0
|
||||
|
||||
match data.type:
|
||||
Enums.CardType.FORWARD:
|
||||
# Forwards valued by power/cost ratio + abilities
|
||||
value = float(data.power) / float(max(data.cost, 1))
|
||||
if data.has_ability("Brave"):
|
||||
value *= 1.3
|
||||
if data.has_ability("First Strike"):
|
||||
value *= 1.2
|
||||
if data.has_ability("Haste"):
|
||||
value *= 1.4
|
||||
Enums.CardType.BACKUP:
|
||||
# Backups valued by utility (cost efficiency)
|
||||
value = 3.0 / float(max(data.cost, 1))
|
||||
Enums.CardType.SUMMON:
|
||||
# Summons valued by effect strength (approximated by cost)
|
||||
value = float(data.cost) * 0.8
|
||||
Enums.CardType.MONSTER:
|
||||
# Monsters similar to forwards
|
||||
value = float(data.power) / float(max(data.cost, 1))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
## Evaluate board advantage (positive = we're ahead)
|
||||
func evaluate_board_state() -> float:
|
||||
var player := get_player()
|
||||
var opponent := get_opponent()
|
||||
|
||||
var score := 0.0
|
||||
|
||||
# Forward power advantage
|
||||
var our_power := 0
|
||||
for forward in player.field_forwards.get_cards():
|
||||
our_power += forward.get_power()
|
||||
|
||||
var their_power := 0
|
||||
for forward in opponent.field_forwards.get_cards():
|
||||
their_power += forward.get_power()
|
||||
|
||||
score += (our_power - their_power) / 1000.0
|
||||
|
||||
# Backup count advantage
|
||||
score += (player.field_backups.get_card_count() - opponent.field_backups.get_card_count()) * 2.0
|
||||
|
||||
# Hand size advantage
|
||||
score += (player.hand.get_card_count() - opponent.hand.get_card_count()) * 0.5
|
||||
|
||||
# Damage disadvantage (more damage = worse)
|
||||
score -= (player.get_damage_count() - opponent.get_damage_count()) * 3.0
|
||||
|
||||
return score
|
||||
Reference in New Issue
Block a user