Commander-Specific Rules Tests

Overview

This document outlines test cases for Commander-specific rules, including starting life totals, commander tax, color identity restrictions, singleton deck construction, and other format-specific mechanics that don't fit into other test categories.

Test Case: Starting Life Total

Test: 40 Life for Commander Format

#![allow(unused)]
fn main() {
#[test]
fn test_commander_starting_life() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, initialize_game);
       
    // Set game format
    app.insert_resource(GameFormat::Commander);
    
    // Create players
    let player1 = app.world.spawn(Player {}).id();
    let player2 = app.world.spawn(Player {}).id();
    let player3 = app.world.spawn(Player {}).id();
    let player4 = app.world.spawn(Player {}).id();
    
    // Initialize game
    app.world.send_event(InitializeGameEvent {
        players: vec![player1, player2, player3, player4],
    });
    app.update();
    
    // Verify all players have 40 life
    for player in [player1, player2, player3, player4].iter() {
        let health = app.world.get::<Health>(*player).unwrap();
        assert_eq!(health.current, 40);
        assert_eq!(health.maximum, 40);
    }
}
}

Test Case: 100-Card Singleton Deck Validation

Test: Singleton Rule (Only One Copy of Each Card)

#![allow(unused)]
fn main() {
#[test]
fn test_singleton_rule() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, validate_commander_deck);
       
    // Create player
    let player = app.world.spawn(Player {}).id();
    
    // Create a commander
    let commander = app.world.spawn((
        Card { name: "Commander".to_string() },
        Commander { owner: player, cast_count: 0 },
    )).id();
    
    // Create cards for the deck
    let card1 = app.world.spawn((
        Card { name: "Unique Card 1".to_string() },
        CardIdentity { oracle_id: "id1".to_string() },
    )).id();
    
    let card2 = app.world.spawn((
        Card { name: "Unique Card 2".to_string() },
        CardIdentity { oracle_id: "id2".to_string() },
    )).id();
    
    // Duplicate card (same oracle ID)
    let duplicate_card = app.world.spawn((
        Card { name: "Unique Card 1".to_string() },
        CardIdentity { oracle_id: "id1".to_string() },
    )).id();
    
    // Basic land (allowed multiple copies)
    let basic_land = app.world.spawn((
        Card { name: "Forest".to_string() },
        CardIdentity { oracle_id: "forest_id".to_string() },
        BasicLand,
    )).id();
    
    let basic_land2 = app.world.spawn((
        Card { name: "Forest".to_string() },
        CardIdentity { oracle_id: "forest_id".to_string() },
        BasicLand,
    )).id();
    
    // Create deck with the commander
    app.world.spawn((
        Deck { owner: player },
        CommanderDeck { commander },
        Cards { entities: vec![card1, card2, duplicate_card, basic_land, basic_land2] },
    ));
    
    // Validate deck
    app.update();
    
    // Verify validation errors for duplicate card
    let validation_errors = app.world.resource::<ValidationErrors>();
    assert!(!validation_errors.is_empty());
    
    // Should have error about duplicate card
    let duplicate_error = validation_errors.errors.iter().any(|error| {
        error.card == duplicate_card && error.error_type == ValidationErrorType::DuplicateCard
    });
    assert!(duplicate_error);
    
    // Should NOT have error about basic lands
    let basic_land_error = validation_errors.errors.iter().any(|error| {
        error.card == basic_land || error.card == basic_land2
    });
    assert!(!basic_land_error);
}
}

Test: Exactly 100 Cards Including Commander

#![allow(unused)]
fn main() {
#[test]
fn test_deck_size() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, validate_commander_deck);
       
    // Create player
    let player = app.world.spawn(Player {}).id();
    
    // Create a commander
    let commander = app.world.spawn((
        Card { name: "Commander".to_string() },
        Commander { owner: player, cast_count: 0 },
    )).id();
    
    // Create 98 cards for deck (which is invalid, should be 99 + commander)
    let mut cards = Vec::new();
    for i in 0..98 {
        let card = app.world.spawn((
            Card { name: format!("Card {}", i) },
            CardIdentity { oracle_id: format!("id{}", i) },
        )).id();
        cards.push(card);
    }
    
    // Create deck with the commander
    app.world.spawn((
        Deck { owner: player },
        CommanderDeck { commander },
        Cards { entities: cards.clone() },
    ));
    
    // Validate deck
    app.update();
    
    // Verify validation error for deck size
    let validation_errors = app.world.resource::<ValidationErrors>();
    assert!(!validation_errors.is_empty());
    
    let size_error = validation_errors.errors.iter().any(|error| {
        error.error_type == ValidationErrorType::InvalidDeckSize
    });
    assert!(size_error);
    
    // Add one more card to make it 99 + commander = 100
    let last_card = app.world.spawn((
        Card { name: "Card 99".to_string() },
        CardIdentity { oracle_id: "id99".to_string() },
    )).id();
    cards.push(last_card);
    
    // Update deck
    app.world.query_mut::<&mut Cards>().for_each_mut(|mut deck_cards| {
        deck_cards.entities = cards.clone();
    });
    
    // Clear previous errors
    app.world.resource_mut::<ValidationErrors>().errors.clear();
    
    // Re-validate deck
    app.update();
    
    // Verify no validation errors for deck size
    let validation_errors = app.world.resource::<ValidationErrors>();
    let size_error = validation_errors.errors.iter().any(|error| {
        error.error_type == ValidationErrorType::InvalidDeckSize
    });
    assert!(!size_error);
}
}

Test Case: Commander Tax Implementation

Test: Tracking Commander Tax Across Zone Changes

#![allow(unused)]
fn main() {
#[test]
fn test_commander_tax_tracking() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, (update_commander_tax, handle_zone_transitions, cast_from_command_zone));
       
    // Create player
    let player = app.world.spawn((
        Player {},
        Mana::default(),
    )).id();
    
    // Create commander
    let commander = app.world.spawn((
        Card { 
            name: "Test Commander".to_string(),
            cost: ManaCost::from_string("{3}{R}").unwrap(),
        },
        Commander { owner: player, cast_count: 0 },
        Zone::CommandZone,
    )).id();
    
    // Cast commander first time
    app.world.send_event(CastSpellEvent {
        caster: player,
        spell: commander,
    });
    app.update();
    
    // Verify commander moved to stack
    assert_eq!(app.world.get::<Zone>(commander).unwrap(), &Zone::Stack);
    
    // Verify cast count increased to 1
    assert_eq!(app.world.get::<Commander>(commander).unwrap().cast_count, 1);
    
    // Resolve spell (move to battlefield)
    app.world.send_event(ZoneChangeEvent {
        entity: commander,
        from: Zone::Stack,
        to: Zone::Battlefield,
        cause: ZoneChangeCause::SpellResolution,
    });
    app.update();
    
    // Send to command zone
    app.world.send_event(ZoneChangeEvent {
        entity: commander,
        from: Zone::Battlefield,
        to: Zone::CommandZone,
        cause: ZoneChangeCause::CommanderReplacement,
    });
    app.update();
    
    // Cast commander second time
    app.world.send_event(CastSpellEvent {
        caster: player,
        spell: commander,
    });
    app.update();
    
    // Verify cast count increased to 2
    assert_eq!(app.world.get::<Commander>(commander).unwrap().cast_count, 2);
    
    // Verify cost now includes commander tax (2 additional mana)
    let required_mana = app.world.resource::<LastCastAttempt>().required_mana.clone();
    assert_eq!(required_mana.total_cmc(), 7); // 3R + 2 tax = 7
}
}

Test Case: Partner Commanders

Test: Two Partner Commanders

#![allow(unused)]
fn main() {
#[test]
fn test_partner_commanders() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, validate_commander_setup);
       
    // Create player
    let player = app.world.spawn(Player {}).id();
    
    // Create two partner commanders
    let commander1 = app.world.spawn((
        Card { name: "Partner Commander 1".to_string() },
        Commander { owner: player, cast_count: 0 },
        PartnerAbility,
        ColorIdentity { colors: vec![Color::Red, Color::White] },
    )).id();
    
    let commander2 = app.world.spawn((
        Card { name: "Partner Commander 2".to_string() },
        Commander { owner: player, cast_count: 0 },
        PartnerAbility,
        ColorIdentity { colors: vec![Color::Blue, Color::Black] },
    )).id();
    
    // Set commanders for player
    app.world.spawn((
        CommanderList { commanders: vec![commander1, commander2] },
        Owner { player },
    ));
    
    // Validate commander setup
    app.update();
    
    // Verify no validation errors
    let validation_errors = app.world.resource::<ValidationErrors>();
    assert!(validation_errors.is_empty());
    
    // Verify combined color identity
    let color_identity = app.world.resource::<CombinedColorIdentity>();
    let player_identity = color_identity.get_colors(player);
    assert!(player_identity.contains(&Color::Red));
    assert!(player_identity.contains(&Color::White));
    assert!(player_identity.contains(&Color::Blue));
    assert!(player_identity.contains(&Color::Black));
}
}

Test: Two Non-Partner Commanders (Invalid)

#![allow(unused)]
fn main() {
#[test]
fn test_invalid_multiple_commanders() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, validate_commander_setup);
       
    // Create player
    let player = app.world.spawn(Player {}).id();
    
    // Create two regular commanders without partner
    let commander1 = app.world.spawn((
        Card { name: "Commander 1".to_string() },
        Commander { owner: player, cast_count: 0 },
        ColorIdentity { colors: vec![Color::Green] },
    )).id();
    
    let commander2 = app.world.spawn((
        Card { name: "Commander 2".to_string() },
        Commander { owner: player, cast_count: 0 },
        ColorIdentity { colors: vec![Color::Black] },
    )).id();
    
    // Set commanders for player
    app.world.spawn((
        CommanderList { commanders: vec![commander1, commander2] },
        Owner { player },
    ));
    
    // Validate commander setup
    app.update();
    
    // Verify validation errors
    let validation_errors = app.world.resource::<ValidationErrors>();
    assert!(!validation_errors.is_empty());
    
    // Should have error about too many commanders without partner
    let multi_commander_error = validation_errors.errors.iter().any(|error| {
        error.error_type == ValidationErrorType::MultipleCommandersWithoutPartner
    });
    assert!(multi_commander_error);
}
}

Test Case: Commander Color Identity

Test: Combined Color Identity with Partner Commanders

#![allow(unused)]
fn main() {
#[test]
fn test_color_identity_calculation() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, (calculate_color_identity, validate_commander_deck));
       
    // Create player
    let player = app.world.spawn(Player {}).id();
    
    // Create partner commanders with different color identities
    let commander1 = app.world.spawn((
        Card { name: "Silas Renn".to_string() },
        Commander { owner: player, cast_count: 0 },
        PartnerAbility,
        ColorIdentity { colors: vec![Color::Blue, Color::Black] },
    )).id();
    
    let commander2 = app.world.spawn((
        Card { name: "Akiri".to_string() },
        Commander { owner: player, cast_count: 0 },
        PartnerAbility,
        ColorIdentity { colors: vec![Color::Red, Color::White] },
    )).id();
    
    // Set commanders for player
    app.world.spawn((
        CommanderList { commanders: vec![commander1, commander2] },
        Owner { player },
    ));
    
    // Calculate combined color identity
    app.update();
    
    // Verify color identity includes all colors from both commanders
    let color_identity = app.world.resource::<CombinedColorIdentity>();
    let player_identity = color_identity.get_colors(player);
    
    assert!(player_identity.contains(&Color::Blue));
    assert!(player_identity.contains(&Color::Black));
    assert!(player_identity.contains(&Color::Red));
    assert!(player_identity.contains(&Color::White));
    assert!(!player_identity.contains(&Color::Green)); // Should not have green
    
    // Create cards with various color identities
    let valid_card = app.world.spawn((
        Card { name: "Esper Card".to_string() },
        ColorIdentity { colors: vec![Color::White, Color::Blue, Color::Black] },
    )).id();
    
    let invalid_card = app.world.spawn((
        Card { name: "Green Card".to_string() },
        ColorIdentity { colors: vec![Color::Green] },
    )).id();
    
    // Create deck with these cards
    app.world.spawn((
        Deck { owner: player },
        CommanderDeck { commander: commander1 }, // Just refers to one commander
        Cards { entities: vec![valid_card, invalid_card] },
    ));
    
    // Validate deck against color identity
    app.update();
    
    // Verify validation error for card outside color identity
    let validation_errors = app.world.resource::<ValidationErrors>();
    assert!(!validation_errors.is_empty());
    
    let color_identity_error = validation_errors.errors.iter().any(|error| {
        error.card == invalid_card && error.error_type == ValidationErrorType::ColorIdentityViolation
    });
    assert!(color_identity_error);
    
    // Should NOT have error for valid card
    let valid_card_error = validation_errors.errors.iter().any(|error| {
        error.card == valid_card
    });
    assert!(!valid_card_error);
}
}

Test: Color Identity Includes Mana Symbols in Rules Text

#![allow(unused)]
fn main() {
#[test]
fn test_color_identity_from_rules_text() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, calculate_comprehensive_color_identity);
       
    // Create player
    let player = app.world.spawn(Player {}).id();
    
    // Create cards with various color identity components
    
    // Card with mana cost only
    let card1 = app.world.spawn((
        Card { 
            name: "Red Creature".to_string(),
            cost: ManaCost::from_string("{1}{R}").unwrap(),
        },
        RulesText { text: "This is a red creature.".to_string() },
    )).id();
    
    // Card with color indicator
    let card2 = app.world.spawn((
        Card { 
            name: "Blue Card".to_string(),
            cost: ManaCost::from_string("{1}").unwrap(),
        },
        ColorIndicator { color: Color::Blue },
    )).id();
    
    // Card with mana symbol in rules text
    let card3 = app.world.spawn((
        Card { 
            name: "Colorless Card".to_string(),
            cost: ManaCost::from_string("{1}").unwrap(),
        },
        RulesText { text: "{G}: This card gets +1/+1 until end of turn.".to_string() },
    )).id();
    
    // Card with hybrid mana in cost
    let card4 = app.world.spawn((
        Card { 
            name: "Hybrid Card".to_string(),
            cost: ManaCost::from_string("{1}{W/B}").unwrap(),
        },
    )).id();
    
    // Calculate comprehensive color identity
    app.update();
    
    // Verify correct color identities
    let c1 = app.world.get::<ColorIdentity>(card1).unwrap();
    assert_eq!(c1.colors.len(), 1);
    assert!(c1.colors.contains(&Color::Red));
    
    let c2 = app.world.get::<ColorIdentity>(card2).unwrap();
    assert_eq!(c2.colors.len(), 1);
    assert!(c2.colors.contains(&Color::Blue));
    
    let c3 = app.world.get::<ColorIdentity>(card3).unwrap();
    assert_eq!(c3.colors.len(), 1);
    assert!(c3.colors.contains(&Color::Green));
    
    let c4 = app.world.get::<ColorIdentity>(card4).unwrap();
    assert_eq!(c4.colors.len(), 2);
    assert!(c4.colors.contains(&Color::White));
    assert!(c4.colors.contains(&Color::Black));
}
}

Test Case: Commander Damage

Test: Commander Damage Win Condition

#![allow(unused)]
fn main() {
#[test]
fn test_commander_damage_win_condition() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, (apply_combat_damage, check_commander_damage_win_condition));
       
    // Create players
    let player1 = app.world.spawn((
        Player { id: 1 },
        Health { current: 40, maximum: 40 },
        CommanderDamage::default(),
    )).id();
    
    let player2 = app.world.spawn((
        Player { id: 2 },
        Health { current: 40, maximum: 40 },
    )).id();
    
    // Create commander for player 2
    let commander = app.world.spawn((
        Card { name: "Commander".to_string() },
        Commander { owner: player2, cast_count: 0 },
        Creature { power: 7, toughness: 7 },
        Zone::Battlefield,
    )).id();
    
    // Apply commander damage 3 times (7 * 3 = 21 damage)
    for _ in 0..3 {
        app.world.send_event(CommanderDamageEvent {
            commander,
            target: player1,
            amount: 7,
        });
        app.update();
    }
    
    // Verify player received 21 commander damage
    let commander_damage = app.world.get::<CommanderDamage>(player1).unwrap();
    assert_eq!(commander_damage.get_damage(commander), 21);
    
    // Verify player was eliminated due to commander damage
    let player_status = app.world.get::<PlayerStatus>(player1).unwrap();
    assert_eq!(player_status.state, PlayerState::Eliminated);
    
    // Verify elimination reason is commander damage
    assert_eq!(player_status.elimination_reason, Some(EliminationReason::CommanderDamage));
}
}

Test: Tracking Commander Damage Separately

#![allow(unused)]
fn main() {
#[test]
fn test_multiple_commander_damage_tracking() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, apply_combat_damage);
       
    // Create player and opponents
    let player = app.world.spawn((
        Player {},
        Health { current: 40, maximum: 40 },
        CommanderDamage::default(),
    )).id();
    
    let opponent1 = app.world.spawn(Player { id: 1 }).id();
    let opponent2 = app.world.spawn(Player { id: 2 }).id();
    let opponent3 = app.world.spawn(Player { id: 3 }).id();
    
    // Create commanders for each opponent
    let commander1 = app.world.spawn((
        Card { name: "Commander 1".to_string() },
        Commander { owner: opponent1, cast_count: 0 },
        Creature { power: 3, toughness: 3 },
    )).id();
    
    let commander2 = app.world.spawn((
        Card { name: "Commander 2".to_string() },
        Commander { owner: opponent2, cast_count: 0 },
        Creature { power: 5, toughness: 5 },
    )).id();
    
    let commander3 = app.world.spawn((
        Card { name: "Commander 3".to_string() },
        Commander { owner: opponent3, cast_count: 0 },
        Creature { power: 2, toughness: 2 },
    )).id();
    
    // Apply damage from each commander
    app.world.send_event(CommanderDamageEvent {
        commander: commander1,
        target: player,
        amount: 3,
    });
    
    app.world.send_event(CommanderDamageEvent {
        commander: commander2,
        target: player,
        amount: 5,
    });
    
    app.world.send_event(CommanderDamageEvent {
        commander: commander3,
        target: player,
        amount: 2,
    });
    app.update();
    
    // Verify damage was tracked separately for each commander
    let commander_damage = app.world.get::<CommanderDamage>(player).unwrap();
    assert_eq!(commander_damage.get_damage(commander1), 3);
    assert_eq!(commander_damage.get_damage(commander2), 5);
    assert_eq!(commander_damage.get_damage(commander3), 2);
    
    // Verify total life loss is the sum of all commander damage
    let health = app.world.get::<Health>(player).unwrap();
    assert_eq!(health.current, 30); // 40 - (3+5+2)
    
    // Apply more damage from commander2 to reach lethal from a single commander
    app.world.send_event(CommanderDamageEvent {
        commander: commander2,
        target: player,
        amount: 16,
    });
    app.update();
    
    // Verify commander2 damage is now 21
    let commander_damage = app.world.get::<CommanderDamage>(player).unwrap();
    assert_eq!(commander_damage.get_damage(commander2), 21);
    
    // Verify player was eliminated
    let player_status = app.world.get::<PlayerStatus>(player).unwrap();
    assert_eq!(player_status.state, PlayerState::Eliminated);
}
}

Test Case: Command Zone Mechanics

Test: Commander Replacement Effects for Different Zones

#![allow(unused)]
fn main() {
#[test]
fn test_commander_replacement_effects() {
    // Test setup
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_systems(Update, (handle_zone_transitions, commander_replacement_effects));
       
    // Create player with commander zone choice preferences
    let player = app.world.spawn((
        Player {},
        CommanderZoneChoice::default(), // Defaults to sending commanders to command zone
    )).id();
    
    // Create commander
    let commander = app.world.spawn((
        Card { name: "Commander".to_string() },
        Commander { owner: player, cast_count: 0 },
        Zone::Battlefield,
    )).id();
    
    // Test commander dying (would go to graveyard)
    app.world.send_event(ZoneChangeEvent {
        entity: commander,
        from: Zone::Battlefield,
        to: Zone::Graveyard,
        cause: ZoneChangeCause::Death,
    });
    app.update();
    
    // Verify commander went to command zone instead
    assert_eq!(app.world.get::<Zone>(commander).unwrap(), &Zone::CommandZone);
    
    // Move commander back to battlefield for next test
    app.world.get_mut::<Zone>(commander).unwrap().0 = Zone::Battlefield.0;
    
    // Test commander being exiled
    app.world.send_event(ZoneChangeEvent {
        entity: commander,
        from: Zone::Battlefield,
        to: Zone::Exile,
        cause: ZoneChangeCause::Exile,
    });
    app.update();
    
    // Verify commander went to command zone instead
    assert_eq!(app.world.get::<Zone>(commander).unwrap(), &Zone::CommandZone);
    
    // Test with player choosing to let commander go to graveyard
    app.world.get_mut::<CommanderZoneChoice>(player).unwrap().use_command_zone = false;
    
    // Move commander back to battlefield
    app.world.get_mut::<Zone>(commander).unwrap().0 = Zone::Battlefield.0;
    
    // Test commander dying with choice to go to graveyard
    app.world.send_event(ZoneChangeEvent {
        entity: commander,
        from: Zone::Battlefield,
        to: Zone::Graveyard,
        cause: ZoneChangeCause::Death,
    });
    app.update();
    
    // Verify commander went to graveyard as chosen
    assert_eq!(app.world.get::<Zone>(commander).unwrap(), &Zone::Graveyard);
}
}

These test cases provide comprehensive coverage for Commander-specific rules and mechanics that form the foundation of the format.