manabrew-engine 0.6.0

Magic: The Gathering rules engine — a Rust port of Forge
Documentation
use std::cell::RefCell;
use std::rc::Rc;

use forge_foundation::ZoneType;
use manabrew_engine::agent::{PlayOption, PlayerAgent, TargetChoice};
use manabrew_engine::combat::DefenderId;
use manabrew_engine::game::GameState;
use manabrew_engine::ids::{CardId, PlayerId};
use manabrew_engine::mana::ManaPool;
use manabrew_engine::player::actions::PlayerAction;
use manabrew_engine::spellability::SpellAbility;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CallbackEvent {
    Reveal(Vec<String>),
    PayCostToPreventEffect(String),
    Reorder(Vec<String>),
    ChooseSingle(Vec<String>),
}

#[derive(Default)]
pub struct RecordingState {
    pub events: Vec<CallbackEvent>,
    pub pay_answers: Vec<bool>,
}

pub struct RecordingAgent {
    state: Rc<RefCell<RecordingState>>,
}

impl RecordingAgent {
    pub fn new(state: Rc<RefCell<RecordingState>>) -> Self {
        Self { state }
    }

    fn card_names(game: &GameState, cards: &[CardId]) -> Vec<String> {
        cards
            .iter()
            .map(|&cid| game.card(cid).card_name.clone())
            .collect()
    }
}

impl PlayerAgent for RecordingAgent {
    fn mulligan_decision(&mut self, _: PlayerId, _: &[CardId], _: u32) -> bool {
        true
    }

    fn choose_action(
        &mut self,
        player: PlayerId,
        action_space: Option<&manabrew_engine::agent::PriorityActionSpace>,
        request_action_space: &mut dyn FnMut() -> manabrew_engine::agent::PriorityActionSpace,
    ) -> PlayerAction {
        PlayerAction::PassPriority
    }

    fn choose_attackers(
        &mut self,
        _: PlayerId,
        _: &[CardId],
        _: &[DefenderId],
    ) -> Vec<(CardId, DefenderId)> {
        vec![]
    }

    fn choose_blockers(
        &mut self,
        _: PlayerId,
        _: &[CardId],
        _: &[CardId],
        _: Option<usize>,
    ) -> Vec<(CardId, CardId)> {
        vec![]
    }

    fn choose_targets_for(
        &mut self,
        sa: &mut SpellAbility,
        game: &GameState,
        mana_pools: &[ManaPool],
    ) -> bool {
        manabrew_engine::spellability::choose_targets_by_kind(self, sa, game, mana_pools)
    }

    fn choose_target_player(
        &mut self,
        _: PlayerId,
        valid: &[PlayerId],
        _: Option<&SpellAbility>,
    ) -> Option<PlayerId> {
        valid.first().copied()
    }

    fn choose_target_card(
        &mut self,
        _: PlayerId,
        valid: &[CardId],
        _: Option<&SpellAbility>,
    ) -> Option<CardId> {
        valid.first().copied()
    }

    fn choose_target_any(
        &mut self,
        _: PlayerId,
        valid_players: &[PlayerId],
        valid_cards: &[CardId],
        _: Option<&SpellAbility>,
    ) -> TargetChoice {
        if let Some(&pid) = valid_players.first() {
            TargetChoice::Player(pid)
        } else if let Some(&cid) = valid_cards.first() {
            TargetChoice::Card(cid)
        } else {
            TargetChoice::None
        }
    }

    fn reveal_cards(
        &mut self,
        game: &GameState,
        _: PlayerId,
        cards: &[CardId],
        _: ZoneType,
        _: PlayerId,
        _: Option<&str>,
    ) {
        self.state
            .borrow_mut()
            .events
            .push(CallbackEvent::Reveal(Self::card_names(game, cards)));
    }

    fn pay_cost_to_prevent_effect(
        &mut self,
        _: PlayerId,
        cost_kind: &str,
        _: &str,
        _: Option<CardId>,
        _: Option<manabrew_engine::ability::api_type::ApiType>,
        _can_pay: bool,
        _: &[manabrew_engine::agent::GameEntity],
        _: &str,
    ) -> bool {
        let mut state = self.state.borrow_mut();
        state
            .events
            .push(CallbackEvent::PayCostToPreventEffect(cost_kind.to_string()));
        state.pay_answers.remove(0)
    }

    fn choose_reorder_library(
        &mut self,
        _: &GameState,
        _: PlayerId,
        cards: &[CardId],
    ) -> Vec<CardId> {
        self.state.borrow_mut().events.push(CallbackEvent::Reorder(
            cards.iter().map(|c| format!("{}", c.0)).collect(),
        ));
        cards.to_vec()
    }

    fn choose_single_card_for_zone_change(
        &mut self,
        _: &GameState,
        _: PlayerId,
        valid: &[CardId],
        _: &str,
        _: bool,
    ) -> Option<CardId> {
        self.state
            .borrow_mut()
            .events
            .push(CallbackEvent::ChooseSingle(
                valid.iter().map(|c| format!("{}", c.0)).collect(),
            ));
        valid.first().copied()
    }

    fn choose_land_or_spell(&mut self, _: PlayerId) -> Option<bool> {
        None
    }
}