Multiplayer Turn Structure Tests
Overview
This document outlines test cases for Commander's multiplayer turn structure, focusing on turn order, extra turns, and phase management in a multiplayer environment. These tests ensure the game properly handles turn-based interactions in a format that can have 3-6 players.
Test Case: Basic Turn Order
Test: Turn Order in Multiplayer Game
#![allow(unused)] fn main() { #[test] fn test_multiplayer_turn_order() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (update_turn_order, next_turn, check_active_player)); // Create a multiplayer game with 4 players let player1 = app.world.spawn(( Player { id: 1 }, PlayerName("Player 1".to_string()), )).id(); let player2 = app.world.spawn(( Player { id: 2 }, PlayerName("Player 2".to_string()), )).id(); let player3 = app.world.spawn(( Player { id: 3 }, PlayerName("Player 3".to_string()), )).id(); let player4 = app.world.spawn(( Player { id: 4 }, PlayerName("Player 4".to_string()), )).id(); // Set up turn order app.insert_resource(TurnOrder { players: vec![player1, player2, player3, player4], current_index: 0, }); app.insert_resource(TurnManager { current_phase: Phase::Beginning(BeginningPhaseStep::Untap), active_player: player1, }); // Validate initial active player let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player1); // Execute a full turn and move to next player app.world.send_event(EndTurnEvent); app.update(); // Verify active player changed to player 2 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player2); // Execute another turn app.world.send_event(EndTurnEvent); app.update(); // Verify active player changed to player 3 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player3); // Simulate a full rotation app.world.send_event(EndTurnEvent); app.update(); app.world.send_event(EndTurnEvent); app.update(); // Should be back to player 1 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player1); } }
Test Case: Player Elimination
Test: Removing a Player from Turn Order
#![allow(unused)] fn main() { #[test] fn test_player_elimination() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (update_turn_order, handle_player_elimination, next_turn)); // Create a multiplayer game with 4 players let player1 = app.world.spawn(Player { id: 1 }).id(); let player2 = app.world.spawn(Player { id: 2 }).id(); let player3 = app.world.spawn(Player { id: 3 }).id(); let player4 = app.world.spawn(Player { id: 4 }).id(); // Set up turn order app.insert_resource(TurnOrder { players: vec![player1, player2, player3, player4], current_index: 0, }); app.insert_resource(TurnManager { current_phase: Phase::Beginning(BeginningPhaseStep::Untap), active_player: player1, }); // Player 2 is eliminated app.world.send_event(PlayerEliminatedEvent { player: player2 }); app.update(); // Verify turn order adjusted let turn_order = app.world.resource::<TurnOrder>(); assert_eq!(turn_order.players, vec![player1, player3, player4]); // Current player should still be player 1 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player1); // End turn app.world.send_event(EndTurnEvent); app.update(); // Verify active player skipped player 2 and is now player 3 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player3); } }
Test: Eliminating the Active Player
#![allow(unused)] fn main() { #[test] fn test_active_player_elimination() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (update_turn_order, handle_player_elimination, next_turn)); // Create a multiplayer game with 4 players let player1 = app.world.spawn(Player { id: 1 }).id(); let player2 = app.world.spawn(Player { id: 2 }).id(); let player3 = app.world.spawn(Player { id: 3 }).id(); let player4 = app.world.spawn(Player { id: 4 }).id(); // Set up turn order app.insert_resource(TurnOrder { players: vec![player1, player2, player3, player4], current_index: 0, }); app.insert_resource(TurnManager { current_phase: Phase::Beginning(BeginningPhaseStep::Untap), active_player: player1, }); // Active player is eliminated app.world.send_event(PlayerEliminatedEvent { player: player1 }); app.update(); // Verify turn order adjusted and active player changed let turn_order = app.world.resource::<TurnOrder>(); assert_eq!(turn_order.players, vec![player2, player3, player4]); let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player2); } }
Test Case: Extra Turns
Test: Player Takes an Extra Turn
#![allow(unused)] fn main() { #[test] fn test_extra_turn() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (update_turn_order, handle_extra_turns, next_turn)); // Create a multiplayer game with 4 players let player1 = app.world.spawn(Player { id: 1 }).id(); let player2 = app.world.spawn(Player { id: 2 }).id(); let player3 = app.world.spawn(Player { id: 3 }).id(); let player4 = app.world.spawn(Player { id: 4 }).id(); // Set up turn order app.insert_resource(TurnOrder { players: vec![player1, player2, player3, player4], current_index: 0, }); app.insert_resource(TurnManager { current_phase: Phase::Beginning(BeginningPhaseStep::Untap), active_player: player1, }); app.insert_resource(ExtraTurns::default()); // Player 1 gets an extra turn app.world.send_event(ExtraTurnEvent { player: player1, count: 1, source: EntitySource { entity: Entity::PLACEHOLDER }, }); // End current turn app.world.send_event(EndTurnEvent); app.update(); // Verify player 1 gets an extra turn instead of going to player 2 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player1); // End extra turn app.world.send_event(EndTurnEvent); app.update(); // Now it should go to player 2 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player2); } }
Test: Multiple Extra Turns Across Players
#![allow(unused)] fn main() { #[test] fn test_multiple_extra_turns() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (update_turn_order, handle_extra_turns, next_turn)); // Create a multiplayer game with 4 players let player1 = app.world.spawn(Player { id: 1 }).id(); let player2 = app.world.spawn(Player { id: 2 }).id(); let player3 = app.world.spawn(Player { id: 3 }).id(); let player4 = app.world.spawn(Player { id: 4 }).id(); // Set up turn order app.insert_resource(TurnOrder { players: vec![player1, player2, player3, player4], current_index: 0, }); app.insert_resource(TurnManager { current_phase: Phase::Beginning(BeginningPhaseStep::Untap), active_player: player1, }); app.insert_resource(ExtraTurns::default()); // Player 1 grants an extra turn to player 3 app.world.send_event(ExtraTurnEvent { player: player3, count: 1, source: EntitySource { entity: Entity::PLACEHOLDER }, }); // Player 2 grants an extra turn to themselves app.world.send_event(ExtraTurnEvent { player: player2, count: 1, source: EntitySource { entity: Entity::PLACEHOLDER }, }); // End current turn (player 1) app.world.send_event(EndTurnEvent); app.update(); // Regular turn order goes to player 2 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player2); // End turn (player 2) app.world.send_event(EndTurnEvent); app.update(); // Player 2's extra turn happens before moving to player 3 let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player2); // End extra turn (player 2) app.world.send_event(EndTurnEvent); app.update(); // Now it's player 3's turn let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player3); // End turn (player 3) app.world.send_event(EndTurnEvent); app.update(); // Player 3 gets extra turn let turn_manager = app.world.resource::<TurnManager>(); assert_eq!(turn_manager.active_player, player3); } }
Test Case: Phase Management
Test: Phase Progression Through a Turn
#![allow(unused)] fn main() { #[test] fn test_phase_progression() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (update_phase, handle_phase_transitions)); // Create player let player = app.world.spawn(Player {}).id(); // Set up turn manager app.insert_resource(TurnManager { current_phase: Phase::Beginning(BeginningPhaseStep::Untap), active_player: player, }); // Progress through beginning phase app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Beginning(BeginningPhaseStep::Untap)); app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Beginning(BeginningPhaseStep::Upkeep)); app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Beginning(BeginningPhaseStep::Draw)); // Move to main phase app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::PreCombatMain); // Move to combat app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Combat(CombatStep::Beginning)); // Cycle through combat steps app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Combat(CombatStep::DeclareAttackers)); app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Combat(CombatStep::DeclareBlockers)); app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Combat(CombatStep::CombatDamage)); app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Combat(CombatStep::End)); // Move to post-combat main app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::PostCombatMain); // Move to end step app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Ending(EndingPhaseStep::End)); app.world.send_event(NextPhaseEvent); app.update(); assert_eq!(app.world.resource::<TurnManager>().current_phase, Phase::Ending(EndingPhaseStep::Cleanup)); } }
Test: Turn-Based Actions in Each Phase
#![allow(unused)] fn main() { #[test] fn test_turn_based_actions() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (handle_untap_step, handle_draw_step, handle_phase_transitions)); // Create player with permanents let player = app.world.spawn(( Player {}, Hand::default(), )).id(); // Create some tapped permanents let permanent1 = app.world.spawn(( Card { name: "Permanent 1".to_string() }, Permanent, Status { tapped: true }, Controller { player }, )).id(); let permanent2 = app.world.spawn(( Card { name: "Permanent 2".to_string() }, Permanent, Status { tapped: true }, Controller { player }, )).id(); // Set up turn manager in untap step app.insert_resource(TurnManager { current_phase: Phase::Beginning(BeginningPhaseStep::Untap), active_player: player, }); // Process untap step app.update(); // Verify permanents were untapped let status1 = app.world.get::<Status>(permanent1).unwrap(); let status2 = app.world.get::<Status>(permanent2).unwrap(); assert!(!status1.tapped); assert!(!status2.tapped); // Move to draw step app.world.resource_mut::<TurnManager>().current_phase = Phase::Beginning(BeginningPhaseStep::Draw); // Create library with cards let card1 = app.world.spawn(( Card { name: "Card 1".to_string() }, Zone::Library, Owner { player }, )).id(); let library = app.world.spawn(( Library { owner: player }, Cards { entities: vec![card1] }, )).id(); // Process draw step app.update(); // Verify card was drawn assert_eq!(app.world.get::<Zone>(card1).unwrap(), &Zone::Hand); let hand = app.world.get::<Hand>(player).unwrap(); assert_eq!(hand.cards.len(), 1); assert!(hand.cards.contains(&card1)); } }
Test Case: Priority System
Test: Priority Passing in Turn Order
#![allow(unused)] fn main() { #[test] fn test_priority_passing() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (handle_priority, update_turn_order)); // Create a multiplayer game with 4 players let player1 = app.world.spawn(Player { id: 1 }).id(); let player2 = app.world.spawn(Player { id: 2 }).id(); let player3 = app.world.spawn(Player { id: 3 }).id(); let player4 = app.world.spawn(Player { id: 4 }).id(); // Set up turn order app.insert_resource(TurnOrder { players: vec![player1, player2, player3, player4], current_index: 0, }); app.insert_resource(TurnManager { current_phase: Phase::PreCombatMain, active_player: player1, }); app.insert_resource(PrioritySystem { has_priority: player1, stack_is_empty: true, all_players_passed: false, }); // Active player passes priority app.world.send_event(PriorityPassedEvent { player: player1 }); app.update(); // Check priority passed to next player let priority = app.world.resource::<PrioritySystem>(); assert_eq!(priority.has_priority, player2); // Second player passes app.world.send_event(PriorityPassedEvent { player: player2 }); app.update(); // Check priority passed to next player let priority = app.world.resource::<PrioritySystem>(); assert_eq!(priority.has_priority, player3); // Third player passes app.world.send_event(PriorityPassedEvent { player: player3 }); app.update(); // Check priority passed to last player let priority = app.world.resource::<PrioritySystem>(); assert_eq!(priority.has_priority, player4); // Last player passes app.world.send_event(PriorityPassedEvent { player: player4 }); app.update(); // All players passed, should move to next phase let priority = app.world.resource::<PrioritySystem>(); assert!(priority.all_players_passed); } }
Test: Priority After Casting a Spell
#![allow(unused)] fn main() { #[test] fn test_priority_after_spell() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (handle_priority, handle_spell_cast)); // Create a multiplayer game with 4 players let player1 = app.world.spawn(Player { id: 1 }).id(); let player2 = app.world.spawn(Player { id: 2 }).id(); let player3 = app.world.spawn(Player { id: 3 }).id(); let player4 = app.world.spawn(Player { id: 4 }).id(); // Set up turn order app.insert_resource(TurnOrder { players: vec![player1, player2, player3, player4], current_index: 0, }); app.insert_resource(TurnManager { current_phase: Phase::PreCombatMain, active_player: player1, }); app.insert_resource(PrioritySystem { has_priority: player1, stack_is_empty: true, all_players_passed: false, }); // Create a spell let spell = app.world.spawn(( Card { name: "Instant Spell".to_string() }, Spell, Owner { player: player1 }, )).id(); // Player 1 casts a spell app.world.send_event(SpellCastEvent { caster: player1, spell: spell, }); app.update(); // Verify spell is on the stack let stack = app.world.resource::<Stack>(); assert!(!stack.is_empty()); // Stack is not empty and priority passed to active player again let priority = app.world.resource::<PrioritySystem>(); assert_eq!(priority.has_priority, player1); assert!(!priority.stack_is_empty); // All players need to pass again for spell to resolve app.world.send_event(PriorityPassedEvent { player: player1 }); app.update(); app.world.send_event(PriorityPassedEvent { player: player2 }); app.update(); app.world.send_event(PriorityPassedEvent { player: player3 }); app.update(); app.world.send_event(PriorityPassedEvent { player: player4 }); app.update(); // Spell should resolve and priority goes back to active player let stack = app.world.resource::<Stack>(); assert!(stack.is_empty()); let priority = app.world.resource::<PrioritySystem>(); assert_eq!(priority.has_priority, player1); } }
These test cases ensure the Commander game engine properly handles multiplayer turn structure, including phase management, turn order, and priority passing in multiplayer scenarios.