manabrew-engine 0.2.3

Magic: The Gathering rules engine — a Rust port of Forge
Documentation
use crate::card::valid_filter::MatchContext;
use crate::card::Card;

pub(crate) fn matches_selector_domain_predicate(
    raw: &str,
    card: &Card,
    context: MatchContext<'_>,
) -> Option<bool> {
    let lower = raw.trim().to_ascii_lowercase();
    if let Some(comparison) = lower.strip_prefix("manaspent ") {
        return matches_comparison(
            context.source_card.paying_mana_to_cast.len() as i32,
            comparison,
        );
    }
    if let Some(comparison) = lower.strip_prefix("numtargets ") {
        return matches_comparison(
            (context.targeted_cards.len() + context.targeted_players.len()) as i32,
            comparison,
        );
    }
    if lower.starts_with("kicked ") {
        return Some(false);
    }
    match lower.as_str() {
        "cmceven" => Some(card.mana_cost.cmc() % 2 == 0),
        "cmcodd" => Some(card.mana_cost.cmc() % 2 == 1),
        "powereven" => Some(card.power() % 2 == 0),
        "powerodd" => Some(card.power() % 2 != 0),
        "powernotbasepower" => card.base_power.map(|base_power| card.power() != base_power),
        _ => None,
    }
}

fn matches_comparison(actual: i32, comparison: &str) -> Option<bool> {
    let comparison = comparison.trim();
    let operator = comparison.get(..2)?;
    let expected = comparison.get(2..)?.trim().parse::<i32>().ok()?;
    match operator {
        "eq" => Some(actual == expected),
        "ne" => Some(actual != expected),
        "lt" => Some(actual < expected),
        "le" => Some(actual <= expected),
        "gt" => Some(actual > expected),
        "ge" => Some(actual >= expected),
        _ => None,
    }
}