Turn Phases

This document details the implementation of turn phases and steps in Rummage, explaining how the game progresses through the structured sequence of a Magic: The Gathering turn.

Phase and Step Structure

A turn in Magic: The Gathering consists of five phases, some of which are divided into steps:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Phase {
    Beginning,
    PreCombatMain,
    Combat,
    PostCombatMain,
    Ending,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Step {
    // Beginning Phase
    Untap,
    Upkeep,
    Draw,
    
    // Main Phase (no steps)
    Main,
    
    // Combat Phase
    BeginningOfCombat,
    DeclareAttackers,
    DeclareBlockers,
    CombatDamage,
    FirstStrikeDamage, // Only used when first/double strike is involved
    EndOfCombat,
    
    // Ending Phase
    End,
    Cleanup,
}
}

Phase Transitions

The game progresses through phases and steps in a specific order. This is managed by a phase transition system:

#![allow(unused)]
fn main() {
pub fn phase_transition_system(
    mut commands: Commands,
    mut game_state: ResMut<GameState>,
    mut phase_events: EventWriter<PhaseChangeEvent>,
    mut step_events: EventWriter<StepChangeEvent>,
    stack: Res<Stack>,
    priority: Res<PrioritySystem>,
) {
    // Only transition if priority is not active and stack is empty
    if priority.active || !stack.items.is_empty() {
        return;
    }
    
    // Current phase and step
    let current_phase = game_state.current_phase;
    let current_step = game_state.current_step;
    
    // Determine next phase and step
    let (next_phase, next_step) = get_next_phase_step(current_phase, current_step);
    
    // Update game state
    game_state.current_phase = next_phase;
    game_state.current_step = next_step;
    
    // Send events
    if current_phase != next_phase {
        phase_events.send(PhaseChangeEvent {
            previous: current_phase,
            current: next_phase,
        });
    }
    
    step_events.send(StepChangeEvent {
        previous: current_step,
        current: next_step,
    });
    
    // Execute phase/step entry actions
    execute_phase_entry_actions(next_phase, next_step, &mut commands, &game_state);
}
}

Phase Progression

The progression from one phase/step to the next follows this pattern:

#![allow(unused)]
fn main() {
fn get_next_phase_step(current_phase: Phase, current_step: Step) -> (Phase, Step) {
    match (current_phase, current_step) {
        // Beginning Phase progression
        (Phase::Beginning, Step::Untap) => (Phase::Beginning, Step::Upkeep),
        (Phase::Beginning, Step::Upkeep) => (Phase::Beginning, Step::Draw),
        (Phase::Beginning, Step::Draw) => (Phase::PreCombatMain, Step::Main),
        
        // Pre-Combat Main Phase progression
        (Phase::PreCombatMain, Step::Main) => (Phase::Combat, Step::BeginningOfCombat),
        
        // Combat Phase progression
        (Phase::Combat, Step::BeginningOfCombat) => (Phase::Combat, Step::DeclareAttackers),
        (Phase::Combat, Step::DeclareAttackers) => (Phase::Combat, Step::DeclareBlockers),
        (Phase::Combat, Step::DeclareBlockers) => {
            // Check if first strike is needed
            if combat_has_first_strike() {
                (Phase::Combat, Step::FirstStrikeDamage)
            } else {
                (Phase::Combat, Step::CombatDamage)
            }
        },
        (Phase::Combat, Step::FirstStrikeDamage) => (Phase::Combat, Step::CombatDamage),
        (Phase::Combat, Step::CombatDamage) => (Phase::Combat, Step::EndOfCombat),
        (Phase::Combat, Step::EndOfCombat) => (Phase::PostCombatMain, Step::Main),
        
        // Post-Combat Main Phase progression
        (Phase::PostCombatMain, Step::Main) => (Phase::Ending, Step::End),
        
        // Ending Phase progression
        (Phase::Ending, Step::End) => (Phase::Ending, Step::Cleanup),
        (Phase::Ending, Step::Cleanup) => {
            // Move to next turn
            (Phase::Beginning, Step::Untap)
        },
        
        // Default case (should never happen)
        _ => (current_phase, current_step),
    }
}
}

Phase Entry Actions

Each phase and step has specific actions that occur when entering:

#![allow(unused)]
fn main() {
fn execute_phase_entry_actions(
    phase: Phase,
    step: Step,
    commands: &mut Commands,
    game_state: &GameState,
) {
    match (phase, step) {
        // Beginning Phase actions
        (Phase::Beginning, Step::Untap) => {
            // Untap all permanents controlled by active player
            commands.add(untap_permanents_command(game_state.active_player));
            
            // Handle "at beginning of untap step" triggers
            commands.add(check_beginning_of_step_triggers_command(phase, step));
        },
        (Phase::Beginning, Step::Upkeep) => {
            // Handle "at beginning of upkeep" triggers
            commands.add(check_beginning_of_step_triggers_command(phase, step));
            
            // Grant priority to active player
            commands.add(grant_priority_command(game_state.active_player));
        },
        (Phase::Beginning, Step::Draw) => {
            // Active player draws a card (except on first turn)
            if game_state.turn_number > 1 {
                commands.add(draw_card_command(game_state.active_player, 1));
            }
            
            // Handle "at beginning of draw step" triggers
            commands.add(check_beginning_of_step_triggers_command(phase, step));
            
            // Grant priority to active player
            commands.add(grant_priority_command(game_state.active_player));
        },
        
        // Main Phase actions
        (Phase::PreCombatMain, Step::Main) | (Phase::PostCombatMain, Step::Main) => {
            // Reset land plays for turn if entering first main phase
            if phase == Phase::PreCombatMain && game_state.current_phase != Phase::PreCombatMain {
                commands.add(reset_land_plays_command());
            }
            
            // Grant priority to active player
            commands.add(grant_priority_command(game_state.active_player));
        },
        
        // Combat Phase actions
        (Phase::Combat, _) => {
            // Handle specific combat step actions
            match step {
                Step::BeginningOfCombat => {
                    // Handle "at beginning of combat" triggers
                    commands.add(check_beginning_of_step_triggers_command(phase, step));
                },
                Step::DeclareAttackers => {
                    // Active player declares attackers
                    commands.add(declare_attackers_command());
                },
                Step::DeclareBlockers => {
                    // Defending players declare blockers
                    commands.add(declare_blockers_command());
                },
                Step::FirstStrikeDamage => {
                    // Assign and deal first strike damage
                    commands.add(assign_first_strike_damage_command());
                    commands.add(deal_combat_damage_command(true));
                },
                Step::CombatDamage => {
                    // Assign and deal regular combat damage
                    commands.add(assign_combat_damage_command());
                    commands.add(deal_combat_damage_command(false));
                },
                _ => {}
            }
            
            // Grant priority to active player (for all combat steps)
            commands.add(grant_priority_command(game_state.active_player));
        },
        
        // Ending Phase actions
        (Phase::Ending, Step::End) => {
            // Handle "at beginning of end step" triggers
            commands.add(check_beginning_of_step_triggers_command(phase, step));
            
            // Grant priority to active player
            commands.add(grant_priority_command(game_state.active_player));
        },
        (Phase::Ending, Step::Cleanup) => {
            // Discard to hand size
            commands.add(discard_to_hand_size_command(game_state.active_player));
            
            // Remove "until end of turn" effects
            commands.add(remove_until_end_of_turn_effects_command());
            
            // Clear damage from permanents
            commands.add(clear_damage_command());
            
            // No priority is granted in cleanup step unless a trigger occurs
        },
    }
}
}

Turn Advancement

When a turn ends, the game advances to the next player's turn:

#![allow(unused)]
fn main() {
fn advance_turn_system(
    mut commands: Commands,
    mut game_state: ResMut<GameState>,
    mut turn_events: EventWriter<TurnChangeEvent>,
) {
    // Only advance turn when transitioning from cleanup to untap
    if game_state.current_phase == Phase::Ending && 
       game_state.current_step == Step::Cleanup &&
       !game_state.transitioning_to_next_turn {
        
        // Mark that we're transitioning to next turn
        game_state.transitioning_to_next_turn = true;
        
        // Get next player
        let next_player = get_next_player(game_state.active_player, &game_state);
        
        // Send turn change event
        turn_events.send(TurnChangeEvent {
            previous_player: game_state.active_player,
            next_player,
            turn_number: game_state.turn_number + 1,
        });
        
        // Update game state
        game_state.active_player = next_player;
        game_state.turn_number += 1;
        
        // Reset turn-based tracking
        commands.add(reset_turn_tracking_command());
    }
}
}

Extra Turns

The system also supports extra turns, which are handled by modifying the turn order:

#![allow(unused)]
fn main() {
pub fn add_extra_turn(
    player: Entity,
    game_state: &mut GameState,
    extra_turn_events: &mut EventWriter<ExtraTurnEvent>,
) {
    // Add extra turn to the queue
    game_state.extra_turns.push(player);
    
    // Send event
    extra_turn_events.send(ExtraTurnEvent {
        player,
        source_turn: game_state.turn_number,
    });
}

fn determine_next_turn_player(game_state: &GameState) -> Entity {
    // Check if there are extra turns queued
    if !game_state.extra_turns.is_empty() {
        // Take the next extra turn
        return game_state.extra_turns[0];
    }
    
    // Otherwise, proceed to next player in turn order
    get_next_player(game_state.active_player, game_state)
}
}

Implementation Status

The turn structure implementation currently:

  • ✅ Handles all standard phases and steps
  • ✅ Implements proper phase transitions
  • ✅ Supports phase-specific actions
  • ✅ Manages turn advancement
  • ✅ Supports extra turns
  • 🔄 Implementing special turn modifications (e.g., additional combat phases)
  • 🔄 Supporting time counters and suspended cards

Next: Combat System