Subgames and Game Restarting Implementation
This document outlines the technical implementation of subgames (like those created by Shahrazad) and game restarting mechanics (like Karn Liberated's ultimate ability) within our engine.
Component Design
The implementation uses several key components and resources to manage subgames and game restarting:
#![allow(unused)] fn main() { // Tracks whether we're currently in a subgame #[derive(Resource)] pub struct SubgameState { /// Stack of game states, with the main game at the bottom pub game_stack: Vec<GameSnapshot>, /// Current subgame depth (0 = main game) pub depth: usize, } // Marks cards that were exiled by Karn Liberated #[derive(Component)] pub struct ExiledWithKarn; // Component that can restart the game #[derive(Component)] pub struct GameRestarter { pub condition: GameRestarterCondition, } // Enum defining what triggers game restart pub enum GameRestarterCondition { KarnUltimate, Custom(Box<dyn Fn(&World) -> bool + Send + Sync + 'static>), } }
Subgame Implementation
Subgames are implemented using a stack-based approach that leverages our snapshot system:
Starting a Subgame
When a card like Shahrazad resolves:
- The current game state is captured via the snapshot system
- The snapshot is pushed onto the
game_stack
inSubgameState
- The depth counter is incremented
- A new game state is initialized with the appropriate starting conditions
- Players' libraries from the main game become their decks in the subgame
#![allow(unused)] fn main() { fn start_subgame( mut commands: Commands, mut subgame_state: ResMut<SubgameState>, snapshot_system: Res<SnapshotSystem>, // Other dependencies... ) { // Take snapshot of current game let current_game = snapshot_system.capture_game_state(); // Push to stack and increment depth subgame_state.game_stack.push(current_game); subgame_state.depth += 1; // Initialize new game state for subgame... // Transfer libraries from parent game to new subgame... } }
Ending a Subgame
When a subgame concludes:
- The result of the subgame is determined (winner/loser)
- The most recent snapshot is popped from the
game_stack
- The depth counter is decremented
- The main game state is restored from the snapshot
- Any effects from the subgame are applied to the main game (loser loses half life)
#![allow(unused)] fn main() { fn end_subgame( mut commands: Commands, mut subgame_state: ResMut<SubgameState>, snapshot_system: Res<SnapshotSystem>, game_result: Res<SubgameResult>, // Other dependencies... ) { // Pop game state and decrement depth let parent_game = subgame_state.game_stack.pop().unwrap(); subgame_state.depth -= 1; // Restore parent game state snapshot_system.restore_game_state(parent_game); // Apply subgame results to parent game // (loser loses half their life, rounded up)... } }
Game Restarting Implementation
Game restarting is a complete reinitialization of the game state with some information carried over:
Tracking Exiled Cards
Cards exiled with abilities like Karn Liberated's are marked with special components:
#![allow(unused)] fn main() { fn track_karn_exile( mut commands: Commands, karn_query: Query<Entity, With<KarnLiberated>>, exile_events: EventReader<CardExiledEvent>, ) { for event in exile_events.read() { if event.source_ability == AbilityType::KarnExile { commands.entity(event.card_entity).insert(ExiledWithKarn); } } } }
Restarting the Game
When a game restart ability triggers:
- Cards exiled with Karn are identified and stored in a temporary resource
- All existing game resources are cleaned up
- A new game is initialized with standard starting conditions
- The exiled cards are placed in their owners' hands in the new game
#![allow(unused)] fn main() { fn restart_game( mut commands: Commands, restart_events: EventReader<GameRestartEvent>, exiled_cards: Query<(Entity, &Owner), With<ExiledWithKarn>>, // Other dependencies... ) { if restart_events.is_empty() { return; } // Store information about exiled cards let cards_to_return = exiled_cards .iter() .map(|(entity, owner)| (entity, owner.0)) .collect::<Vec<_>>(); // Clean up existing game state... // Initialize new game... // Return exiled cards to their owners' hands for (card_entity, owner_id) in cards_to_return { // Move card to owner's hand in the new game // ... } } }
Interacting with Game Systems
Both subgames and game restarting interact with several core systems:
Snapshot System Integration
The Snapshot System is crucial for both features:
- Subgames use it to preserve and restore game states
- Game restarting uses it to track information that needs to persist through restarts
Turn System Integration
The turn system needs special handling for subgames:
- Subgames have their own turn structure independent of the main game
- When returning from a subgame, the turn structure of the main game must be properly restored
Zone Management
Both features require special zone handling:
- Subgames need to track cards that leave the subgame
- Game restarting needs to move cards to their proper starting zones
Error Handling and Edge Cases
The implementation includes handling for various edge cases:
- Nested subgames (subgames within subgames)
- Game restarts during a subgame
- Subgame creation during a game restart
- Corrupted state recovery
- Performance considerations for deep subgame nesting
See the MTG Rules documentation for more information on the rules governing these mechanics.