use std::collections::HashMap;
use crate::game_view_dto::GameViewDto;
use crate::prompt::*;
fn parse_mana_tokens(mana_cost: &str) -> Vec<String> {
mana_cost
.match_indices('{')
.filter_map(|(start, _)| {
mana_cost[start + 1..]
.find('}')
.map(|end| mana_cost[start + 1..start + 1 + end].to_string())
})
.collect()
}
fn color_letter(color: ManaColor) -> &'static str {
match color {
ManaColor::White => "W",
ManaColor::Blue => "U",
ManaColor::Black => "B",
ManaColor::Red => "R",
ManaColor::Green => "G",
ManaColor::Colorless => "C",
}
}
fn mana_matches_color(mana: &Mana, letter: &str) -> bool {
color_letter(mana.color) == letter
}
fn action_produces_color(action: &PaymentAction, letter: &str) -> bool {
match &action.kind {
PaymentActionKind::ActivateManaAbility(info) => info
.produced_mana
.as_ref()
.map(|mana| mana.iter().any(|m| mana_matches_color(m, letter)))
.unwrap_or(false),
_ => false,
}
}
fn can_pay_mana_cost(pool: &HashMap<String, i32>, mana_cost: &str, player_life: i32) -> bool {
let mut available = pool.clone();
let mut generic = 0i32;
let mut hybrids: Vec<(String, String)> = Vec::new();
let mut phyrexian_life_needed = 0i32;
for token in parse_mana_tokens(mana_cost) {
if let Ok(n) = token.parse::<i32>() {
generic += n;
continue;
}
if token == "X" {
continue;
}
if token.contains('/') {
let mut parts = token.split('/');
if let (Some(a), Some(b)) = (parts.next(), parts.next()) {
if b == "P" {
let count = available.entry(a.to_string()).or_insert(0);
if *count > 0 {
*count -= 1;
} else {
phyrexian_life_needed += 2;
}
continue;
}
hybrids.push((a.to_string(), b.to_string()));
continue;
}
}
let count = available.entry(token.clone()).or_insert(0);
if *count <= 0 {
return false;
}
*count -= 1;
}
for (a, b) in hybrids {
let a_count = *available.get(&a).unwrap_or(&0);
let b_count = *available.get(&b).unwrap_or(&0);
if a_count > 0 {
if let Some(count) = available.get_mut(&a) {
*count -= 1;
}
} else if b_count > 0 {
if let Some(count) = available.get_mut(&b) {
*count -= 1;
}
} else {
return false;
}
}
if phyrexian_life_needed > player_life {
return false;
}
let remaining_total: i32 = available.values().copied().sum();
remaining_total >= generic
}
pub fn choose_pay_mana_cost_action(
game_view: &GameViewDto,
mana_cost: &str,
actions: &[PaymentAction],
) -> Option<PromptOutput> {
let player = game_view
.players
.iter()
.find(|p| p.id == game_view.priority_player_id)
.cloned();
let player_life = player.as_ref().map(|p| p.life).unwrap_or_default();
let player_pool: HashMap<String, i32> = player
.map(|p| p.mana_pool)
.unwrap_or_default()
.into_iter()
.map(|(color, amount)| (color_letter(color).to_string(), amount as i32))
.collect();
let mut needed_colors: Vec<String> = parse_mana_tokens(mana_cost)
.into_iter()
.filter_map(|token| {
if token.len() == 1 && token != "X" {
return Some(token);
}
if let Some(color) = token.strip_suffix("/P") {
return Some(color.to_string());
}
None
})
.collect();
for (color, amount) in &player_pool {
for _ in 0..(*amount).max(0) {
if let Some(pos) = needed_colors.iter().position(|needed| needed == color) {
needed_colors.remove(pos);
}
}
}
for needed in &needed_colors {
if let Some(action) = actions
.iter()
.find(|action| action_produces_color(action, needed))
{
return Some(PromptOutput::PayManaCost(PayManaCostOutput::Act {
action_id: action.id.clone(),
}));
}
}
if can_pay_mana_cost(&player_pool, mana_cost, player_life) {
return Some(PromptOutput::PayManaCost(PayManaCostOutput::Pay {
auto: true,
}));
}
actions
.iter()
.find(|action| matches!(action.kind, PaymentActionKind::ActivateManaAbility(_)))
.map(|action| {
PromptOutput::PayManaCost(PayManaCostOutput::Act {
action_id: action.id.clone(),
})
})
}