Files
FFCardGame/scripts/game/Player.gd
2026-01-26 16:14:57 -05:00

288 lines
7.0 KiB
GDScript

class_name Player
extends RefCounted
## Player - Represents a player's game state
var player_index: int = 0
var player_name: String = "Player"
# Zones
var deck: Zone
var hand: Zone
var field_forwards: Zone
var field_backups: Zone
var damage_zone: Zone
var break_zone: Zone
# Resources
var cp_pool: CPPool
# Game state
var is_first_player: bool = false
var has_mulliganed: bool = false
# Constants
const MAX_HAND_SIZE: int = 5
const MAX_BACKUPS: int = 5
const DAMAGE_TO_LOSE: int = 7
func _init(index: int, name: String = "") -> void:
player_index = index
player_name = name if name != "" else "Player %d" % (index + 1)
# Initialize zones
deck = Zone.new(Enums.ZoneType.DECK, index)
hand = Zone.new(Enums.ZoneType.HAND, index)
field_forwards = Zone.new(Enums.ZoneType.FIELD_FORWARDS, index)
field_backups = Zone.new(Enums.ZoneType.FIELD_BACKUPS, index)
damage_zone = Zone.new(Enums.ZoneType.DAMAGE, index)
break_zone = Zone.new(Enums.ZoneType.BREAK, index)
# Initialize CP pool
cp_pool = CPPool.new()
## Setup the player's deck from a list of card IDs
func setup_deck(card_ids: Array[String]) -> void:
for card_id in card_ids:
var card_data = CardDatabase.get_card(card_id)
if card_data:
var card = CardInstance.new(card_data, player_index)
deck.add_card(card)
# Shuffle the deck
deck.shuffle()
## Draw cards from deck to hand
func draw_cards(count: int) -> Array[CardInstance]:
var drawn: Array[CardInstance] = []
for i in range(count):
if deck.is_empty():
break # Can't draw from empty deck
var card = deck.pop_top_card()
if card:
hand.add_card(card)
drawn.append(card)
return drawn
## Check if player can draw (deck not empty)
func can_draw() -> bool:
return not deck.is_empty()
## Get current damage count
func get_damage_count() -> int:
return damage_zone.get_count()
## Check if player has lost (7+ damage)
func has_lost() -> bool:
return get_damage_count() >= DAMAGE_TO_LOSE
## Take damage (move cards from deck to damage zone)
func take_damage(amount: int) -> Array[CardInstance]:
var damage_cards: Array[CardInstance] = []
for i in range(amount):
if deck.is_empty():
break
var card = deck.pop_top_card()
if card:
damage_zone.add_card(card)
damage_cards.append(card)
return damage_cards
## Discard a card from hand to generate CP
func discard_for_cp(card: CardInstance) -> bool:
if not hand.has_card(card):
return false
# Light/Dark cards cannot be discarded for CP
if card.is_light_or_dark():
return false
hand.remove_card(card)
break_zone.add_card(card)
# Generate 2 CP of the card's element
var element = card.get_element()
cp_pool.add_cp(element, 2)
return true
## Dull a backup to generate CP
func dull_backup_for_cp(card: CardInstance) -> bool:
if not field_backups.has_card(card):
return false
if not card.is_backup():
return false
if card.is_dull():
return false
card.dull()
# Generate 1 CP of the backup's element
var element = card.get_element()
cp_pool.add_cp(element, 1)
return true
## Play a card from hand to field
## Returns dictionary of CP spent, or empty dict on failure
func play_card(card: CardInstance) -> Dictionary:
if not hand.has_card(card):
return {}
# Check if we can afford it
if not cp_pool.can_afford_card(card.card_data):
return {}
# Check field restrictions
if card.is_backup():
if field_backups.get_count() >= MAX_BACKUPS:
return {}
# Check unique name restriction (non-generic cards can't share names across entire field)
if not card.card_data.is_generic:
if field_forwards.has_card_with_name(card.card_data.name):
return {}
if field_backups.has_card_with_name(card.card_data.name):
return {}
# Check Light/Dark restriction
if card.is_light_or_dark():
if field_forwards.has_light_or_dark() or field_backups.has_light_or_dark():
return {}
# Pay the cost (returns dict of spent CP, empty if failed)
var spent = cp_pool.spend_for_card(card.card_data)
if spent.is_empty():
return {}
# Move to field
hand.remove_card(card)
if card.is_forward():
field_forwards.add_card(card)
card.state = Enums.CardState.ACTIVE
elif card.is_backup():
field_backups.add_card(card)
card.state = Enums.CardState.DULL # Backups enter dull
card.entered_field()
return spent
## Cast a summon from hand (pays cost, removes from hand, returns card for effect resolution)
func cast_summon(card: CardInstance) -> CardInstance:
if not hand.has_card(card):
return null
if not card.is_summon():
return null
# Check if we can afford it
if not cp_pool.can_afford_card(card.card_data):
return null
# Pay the cost (returns dict of spent CP, empty if failed)
var spent = cp_pool.spend_for_card(card.card_data)
if spent.is_empty():
return null
# Remove from hand - card will be moved to break zone after effect resolves
hand.remove_card(card)
return card
## Break a card (move from field to break zone)
func break_card(card: CardInstance) -> bool:
var removed = false
if field_forwards.has_card(card):
field_forwards.remove_card(card)
removed = true
elif field_backups.has_card(card):
field_backups.remove_card(card)
removed = true
if removed:
break_zone.add_card(card)
return true
return false
## Activate all dull cards (Active Phase)
func activate_all() -> void:
for card in field_forwards.get_dull_cards():
card.activate()
for card in field_backups.get_dull_cards():
card.activate()
## Discard down to hand size (End Phase)
func discard_to_hand_limit() -> Array[CardInstance]:
var discarded: Array[CardInstance] = []
while hand.get_count() > MAX_HAND_SIZE:
# For now, just discard the last card
# In actual game, player would choose
var card = hand.pop_top_card()
if card:
break_zone.add_card(card)
discarded.append(card)
return discarded
## End of turn cleanup
func end_turn_cleanup() -> void:
# Clear CP pool
cp_pool.clear()
# Reset temporary effects on field cards
for card in field_forwards.get_cards():
card.end_turn_cleanup()
for card in field_backups.get_cards():
card.end_turn_cleanup()
## Start of turn setup
func start_turn() -> void:
# Increment turn counter for field cards
for card in field_forwards.get_cards():
card.start_turn()
for card in field_backups.get_cards():
card.start_turn()
## Get all cards on field
func get_all_field_cards() -> Array[CardInstance]:
var cards: Array[CardInstance] = []
cards.append_array(field_forwards.get_cards())
cards.append_array(field_backups.get_cards())
return cards
## Get forwards that can attack
func get_attackable_forwards() -> Array[CardInstance]:
var attackers: Array[CardInstance] = []
for card in field_forwards.get_cards():
if card.can_attack():
attackers.append(card)
return attackers
## Get forwards that can block
func get_blockable_forwards() -> Array[CardInstance]:
var blockers: Array[CardInstance] = []
for card in field_forwards.get_cards():
if card.can_block():
blockers.append(card)
return blockers
func _to_string() -> String:
return "[Player: %s, Hand: %d, Forwards: %d, Backups: %d, Damage: %d]" % [
player_name,
hand.get_count(),
field_forwards.get_count(),
field_backups.get_count(),
damage_zone.get_count()
]