tcg/tcg/match/match_manager.gd

207 lines
7.9 KiB
GDScript

extends Node
class_name MatchManager
signal match_begun
signal state_transitioned(transition: PhaseTransition)
const PLAYER_1_ID: int = 0
const PLAYER_2_ID: int = 1
var rps_wins = {
"rock": "scissors",
"scissors": "paper",
"paper": "rock"
}
var players: Dictionary
var player_1: MatchPlayer:
get:
return players[actual_p1_id] as MatchPlayer
var player_2: MatchPlayer:
get:
return players[actual_p2_id] as MatchPlayer
var phase: Match.Phase = Match.Phase.PREGAME
var actual_p1_id: int
var actual_p2_id: int
func _ready() -> void:
cleanup()
func init(decks: Dictionary, actual_p1_id: int, actual_p2_id: int):
self.actual_p1_id = actual_p1_id
self.actual_p2_id = actual_p2_id
cleanup()
for player_id in [PLAYER_1_ID, PLAYER_2_ID]:
var deck_shuffled = decks[player_id].duplicate() as Array[Card]
deck_shuffled.shuffle()
var first_monster = deck_shuffled.filter(func(card): return card is MonsterCard)[0]
var first_monster_idx = deck_shuffled.find(first_monster)
deck_shuffled.pop_at(first_monster_idx)
deck_shuffled.push_back(first_monster)
players[player_id] = MatchPlayer.new(player_id, deck_shuffled)
func cleanup():
for player_id in [PLAYER_1_ID, PLAYER_2_ID]:
var existing = players.get(player_id)
if existing:
existing.free()
phase = Match.Phase.PREGAME
func resolve(action_by_player_id: Dictionary) -> PhaseTransition:
var initial_phase = phase
var events = [] as Array[Event]
var player_1_action = action_by_player_id.get(actual_p1_id) as Action
var player_2_action = action_by_player_id.get(actual_p2_id) as Action
match phase:
Match.Phase.PREGAME:
var CARDS_TO_DRAW: int = 3
for player: MatchPlayer in [player_1, player_2]:
var n_cards_to_draw = min(player.deck.size(), CARDS_TO_DRAW)
var drawn_cards = range(n_cards_to_draw).map(func(_i): return player.deck.pop_back()) as Array[Card]
for card in drawn_cards:
player.hand.push_back(card)
var drawn_card_events = drawn_cards.map(func(card): return EventCardDrawn.new(player.id, card))
events.append_array(
drawn_card_events
)
phase = Match.Phase.SUMMON
Match.Phase.SUMMON:
for player_id in action_by_player_id.keys():
var action = action_by_player_id[player_id]
if action is ActionPlayCard:
var card = action.card
if card is MonsterCard:
players[player_id].monster = _monster_from_card(card)
players[player_id].hand = players[player_id].hand.filter(func(card_in_hand): return card.id != card_in_hand.id)
events.append(EventMonsterSummoned.new(card))
players[player_id].energy = players[player_id].energy - card.energy_cost
if players.values().all(func(player: MatchPlayer): return player.monster):
phase = Match.Phase.SUPPORT_1
Match.Phase.SUPPORT_1:
var support_cards = []
if player_1_action is ActionPlayCard:
support_cards.append({
"player": player_1,
"card": player_1_action.card
})
player_1.hand.erase(player_1_action.card)
if player_2_action is ActionPlayCard:
support_cards.append({
"player": player_2,
"card": player_2_action.card
})
player_2.hand.erase(player_2_action.card)
for played_card in support_cards:
var player: MatchPlayer = played_card.player
var card: SupportCard = played_card.card
if card is ImplementedSupportCard and card.scope != 'instant':
player.active_support_cards.append(card)
var executed_instant_effects_events = _execute_support_card_instant_effects(player, card)
events.append_array(executed_instant_effects_events)
phase = Match.Phase.RPS
Match.Phase.RPS:
var damage_pairs = []
var tie = player_1_action.move == player_2_action.move
var player_1_win = rps_wins[player_1_action.move] == player_2_action.move
var player_2_win = rps_wins[player_2_action.move] == player_1_action.move
if tie or player_1_win:
damage_pairs.append({"from": player_1, "to": player_2})
if tie or player_2_win:
damage_pairs.append({"from": player_2, "to": player_1})
for pair in damage_pairs:
var computed_damage = pair.from.monster.card.damage[action_by_player_id[pair.from.id].move]
if _player_has_active_support_card(pair.from, "all_out_attack"):
var rps = [pair.from.monster.card.rock, pair.from.monster.card.paper, pair.from.monster.card.scissors]
computed_damage = rps.reduce(func (a, b): return a + b, 0)
pair.from.monster.health_delta -= rps.min()
if _player_has_active_support_card(pair.from, "sword_mastery"):
computed_damage += 20
if _player_has_active_support_card(pair.from, "lifesteal"):
pair.from.monster.health_delta += computed_damage
pair.to.monster.health_delta -= computed_damage
phase = Match.Phase.SUPPORT_2
Match.Phase.SUPPORT_2:
var support_cards = []
if player_1_action is ActionPlayCard:
support_cards.append({
"player": player_1,
"card": player_1_action.card
})
player_1.hand.erase(player_1_action.card)
if player_2_action is ActionPlayCard:
support_cards.append({
"player": player_2,
"card": player_2_action.card
})
player_2.hand.erase(player_2_action.card)
for played_card in support_cards:
var player: MatchPlayer = played_card.player
var card: SupportCard = played_card.card
if card is ImplementedSupportCard and card.scope != 'instant':
player.active_support_cards.append(card)
events.append_array(_execute_support_card_instant_effects(player, card))
for player: MatchPlayer in players.values():
if _player_has_active_support_card(player, "absorb") and player.monster.health_delta < 0:
player.monster.health_delta = -player.monster.health_delta
var monster_final_health = player.monster.health + player.monster.health_delta
if _player_has_active_support_card(player, "invisibility"):
monster_final_health = player.monster.health
if _player_has_active_support_card(player, "insurance"):
monster_final_health = max(10, monster_final_health)
if monster_final_health <= 0:
player.monster = null
var card_expires_with_monster = func(card): return card is ImplementedSupportCard and card.scope == 'monster'
player.active_support_cards = player.active_support_cards.filter(func (card): return not card_expires_with_monster.call(card))
events.append(EventMonsterDied.new(player.id))
else:
player.monster.health = monster_final_health
player.monster.health_delta = 0
var drawn_card = player.deck.pop_back()
if drawn_card:
player.hand.append(drawn_card)
events.append(EventCardDrawn.new(player.id, drawn_card))
var card_expires_this_turn = func(card): return card is ImplementedSupportCard and card.scope == 'turn'
player.active_support_cards = player.active_support_cards.filter(func (card): return not card_expires_this_turn.call(card))
var players_without_monster = players.values().filter(func(player): return player.monster == null)
if players_without_monster.size() == 0:
phase = Match.Phase.SUPPORT_1
elif players_without_monster.all(
func(player): return player.hand.any(func(it): return it is MonsterCard and it.energy_cost <= player.energy)
):
phase = Match.Phase.SUMMON
else:
phase = Match.Phase.END
_:
pass
var transition = PhaseTransition.new(events, initial_phase, phase)
state_transitioned.emit(transition)
return transition
func _monster_from_card(card: MonsterCard) -> MatchMonster:
return MatchMonster.new(card, card.base_health, 0)
func _execute_support_card_instant_effects(player: MatchPlayer, card: SupportCard) -> Array[Event]:
var events: Array[Event]
if card is ImplementedSupportCard:
match card.id:
"potion":
player.monster.health_delta += 20
"energy_booster":
player.energy += 1
# events.append(Event)
return events
func _player_has_active_support_card(player: MatchPlayer, card_id: String) -> bool:
return player.active_support_cards.any(func (card): return card.id == card_id)
func _player_opponent(player: MatchPlayer) -> MatchPlayer:
return player_2 if player.id == PLAYER_1_ID else player_1