use forge_foundation::{CardTypeLine, ColorSet, ManaCost, ZoneType};
use manabrew_engine::agent::{PlayOption, PlayerAgent, TargetChoice};
use manabrew_engine::card::CardInstance;
use manabrew_engine::combat::DefenderId;
use manabrew_engine::game::GameState;
use manabrew_engine::game_loop::GameLoop;
use manabrew_engine::ids::{CardId, PlayerId};
use manabrew_engine::parsing::Params;
use manabrew_engine::player::actions::PlayerAction;
use manabrew_engine::spellability::{SpellAbility, StackEntry};
struct PassAgent;
impl PlayerAgent for PassAgent {
fn mulligan_decision(
&mut self,
_player: PlayerId,
_hand: &[CardId],
_mulligan_count: 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_target_spell(
&mut self,
_player: PlayerId,
valid: &[u32],
_source: Option<CardId>,
) -> Option<u32> {
valid.first().copied()
}
fn choose_land_or_spell(&mut self, _player: PlayerId) -> Option<bool> {
None
}
fn choose_attackers(
&mut self,
_player: PlayerId,
_available: &[CardId],
_possible_defenders: &[DefenderId],
) -> Vec<(CardId, DefenderId)> {
Vec::new()
}
fn choose_blockers(
&mut self,
_player: PlayerId,
_attackers: &[CardId],
_available_blockers: &[CardId],
_max_blockers: Option<usize>,
) -> Vec<(CardId, CardId)> {
Vec::new()
}
fn choose_target_player(
&mut self,
_player: PlayerId,
valid: &[PlayerId],
_sa: Option<&manabrew_engine::spellability::SpellAbility>,
) -> Option<PlayerId> {
valid.first().copied()
}
fn choose_target_card(
&mut self,
_player: PlayerId,
valid: &[CardId],
_sa: Option<&manabrew_engine::spellability::SpellAbility>,
) -> Option<CardId> {
valid.first().copied()
}
fn choose_target_any(
&mut self,
_player: PlayerId,
valid_players: &[PlayerId],
valid_cards: &[CardId],
_sa: Option<&manabrew_engine::spellability::SpellAbility>,
) -> TargetChoice {
if let Some(&pid) = valid_players.last() {
TargetChoice::Player(pid)
} else if let Some(&cid) = valid_cards.first() {
TargetChoice::Card(cid)
} else {
TargetChoice::None
}
}
fn choose_targets_for(
&mut self,
sa: &mut manabrew_engine::spellability::SpellAbility,
game: &manabrew_engine::game::GameState,
mana_pools: &[manabrew_engine::mana::ManaPool],
) -> bool {
manabrew_engine::spellability::choose_targets_by_kind(self, sa, game, mana_pools)
}
}
fn make_control_magic(owner: PlayerId) -> CardInstance {
let mut card = CardInstance::new(
CardId(0),
"Control Magic".to_string(),
owner,
CardTypeLine::parse("Enchantment - Aura"),
ManaCost::parse("2 U U"),
ColorSet::BLUE,
None,
None,
vec![],
vec![],
);
let sa = manabrew_engine::staticability::parse_static_ability(
"S$ Mode$ Continuous | Affected$ Card.EnchantedBy | GainControl$ You",
)
.expect("static ability should parse");
card.static_abilities.push(sa);
card
}
fn make_grizzly_bears(owner: PlayerId) -> CardInstance {
CardInstance::new(
CardId(1),
"Grizzly Bears".to_string(),
owner,
CardTypeLine::parse("Creature - Bear"),
ManaCost::parse("1 G"),
ColorSet::GREEN,
Some(2),
Some(2),
vec![],
vec![],
)
}
fn make_island(owner: PlayerId) -> CardInstance {
CardInstance::new(
CardId(2),
"Island".to_string(),
owner,
CardTypeLine::parse("Basic Land - Island"),
ManaCost::no_cost(),
ColorSet::COLORLESS,
None,
None,
vec![],
vec![],
)
}
#[test]
fn test_control_magic_grants_control() {
let mut game = GameState::new(&["Alice", "Bob"], 20);
let p0 = PlayerId(0); let p1 = PlayerId(1);
for _ in 0..4 {
let island = game.create_card(make_island(p0));
game.move_card(island, ZoneType::Battlefield, p0);
}
let bears = game.create_card(make_grizzly_bears(p1));
game.move_card(bears, ZoneType::Battlefield, p1);
let bears_initial = game.card(bears);
assert_eq!(
bears_initial.controller, p1,
"Bears should initially be controlled by Bob"
);
let control_magic = game.create_card(make_control_magic(p0));
game.move_card(control_magic, ZoneType::Battlefield, p0);
game.attach_to(control_magic, bears);
let aura = game.card(control_magic);
assert_eq!(
aura.attached_to,
Some(bears),
"Control Magic should be attached to Bears"
);
assert_eq!(
aura.controller, p0,
"Control Magic should be controlled by Alice"
);
let bears_card = game.card(bears);
assert!(
bears_card.attachments.contains(&control_magic),
"Bears should have Control Magic attached"
);
println!("Before applying continuous effects:");
println!(" Bears controller: {:?}", game.card(bears).controller);
println!(
" Aura controller: {:?}",
game.card(control_magic).controller
);
println!(
" Aura attached to: {:?}",
game.card(control_magic).attached_to
);
manabrew_engine::staticability::layer::apply_continuous_effects(&mut game);
println!("After applying continuous effects:");
println!(" Bears controller: {:?}", game.card(bears).controller);
let bears_after = game.card(bears);
if bears_after.controller != p0 {
println!("BUG CONFIRMED: Control Magic didn't grant control!");
println!(
"Expected controller: {:?}, Actual controller: {:?}",
p0, bears_after.controller
);
println!("\nReason: Layer 2 (control-changing) effects are not implemented in the static ability system.");
}
assert_eq!(
bears_after.controller, p0,
"BUG: Control Magic should grant control but doesn't - Layer 2 not implemented"
);
}
#[test]
fn test_control_magic_attachment_tracking() {
let mut game = GameState::new(&["Alice", "Bob"], 20);
let p0 = PlayerId(0);
let p1 = PlayerId(1);
let bears = game.create_card(make_grizzly_bears(p1));
game.move_card(bears, ZoneType::Battlefield, p1);
let control_magic = game.create_card(make_control_magic(p0));
game.move_card(control_magic, ZoneType::Battlefield, p0);
game.attach_to(control_magic, bears);
let aura_after = game.card(control_magic);
assert_eq!(aura_after.attached_to, Some(bears));
let bears_after = game.card(bears);
assert!(bears_after.attachments.contains(&control_magic));
println!("✓ Attachment mechanics work correctly");
}