Skip to main content

manabrew_engine/phase/
phase_type.rs

1//! PhaseType — turn phases/steps.
2//!
3//! Mirrors Java's `PhaseType.java`.
4//! The core enum lives in `forge_foundation::PhaseType`; this module
5//! re-exports it and adds phase-module-specific helper functions.
6
7pub use forge_foundation::PhaseType;
8
9use std::collections::HashSet;
10
11/// Phase group index — maps each phase to its parent group.
12/// Mirrors Java's `PHASE_INDEX` map.
13///
14/// Groups: 0=Beginning (Untap/Upkeep/Draw), 1=Main1, 2=Combat,
15///         3=Main2, 4=EndOfTurn, 5=Cleanup
16pub fn phase_group_index(phase: PhaseType) -> usize {
17    match phase {
18        PhaseType::Untap | PhaseType::Upkeep | PhaseType::Draw => 0,
19        PhaseType::Main1 => 1,
20        PhaseType::CombatBegin
21        | PhaseType::CombatDeclareAttackers
22        | PhaseType::CombatDeclareBlockers
23        | PhaseType::CombatFirstStrikeDamage
24        | PhaseType::CombatDamage
25        | PhaseType::CombatEnd => 2,
26        PhaseType::Main2 => 3,
27        PhaseType::EndOfTurn => 4,
28        PhaseType::Cleanup => 5,
29    }
30}
31
32/// Parse a range of phases from a comma-separated string.
33/// Supports "Phase1->Phase2" range syntax and "Main" alias for Main1+Main2.
34/// Mirrors Java's `PhaseType.parseRange()`.
35pub fn parse_range(values: &str) -> HashSet<PhaseType> {
36    let mut result = HashSet::new();
37    for s in values.split(',') {
38        let s = s.trim();
39        if let Some(idx) = s.find("->") {
40            let from_str = &s[..idx];
41            let to_str = &s[idx + 2..];
42            let from = smart_value_of(from_str);
43            let to = if to_str.trim().is_empty() {
44                Some(PhaseType::Cleanup)
45            } else {
46                smart_value_of(to_str)
47            };
48            if let (Some(from), Some(to)) = (from, to) {
49                let from_idx = from.index();
50                let to_idx = to.index();
51                for &phase in &PhaseType::TURN_ORDER[from_idx..=to_idx] {
52                    result.insert(phase);
53                }
54            }
55        } else if s.eq_ignore_ascii_case("Main") {
56            result.insert(PhaseType::Main1);
57            result.insert(PhaseType::Main2);
58        } else if let Some(phase) = smart_value_of(s) {
59            result.insert(phase);
60        }
61    }
62    result
63}
64
65/// Parse a phase type from a string, matching by script name or enum name.
66/// Mirrors Java's `PhaseType.smartValueOf()`.
67pub fn smart_value_of(value: &str) -> Option<PhaseType> {
68    PhaseType::from_script_name(value)
69}
70
71/// Returns true if this is the last phase in the turn.
72/// Mirrors Java's `PhaseType.isLast()`.
73pub fn is_last(phase: PhaseType) -> bool {
74    phase == PhaseType::Cleanup
75}
76
77/// Get the next phase, optionally with reversed phase order (Topsy Turvy).
78/// Mirrors Java's `PhaseType.getNext(current, isTopsy)`.
79pub fn get_next(phase: PhaseType, is_topsy: bool) -> PhaseType {
80    if is_topsy {
81        let idx = phase.index();
82        if idx == 0 {
83            PhaseType::TURN_ORDER[PhaseType::TURN_ORDER.len() - 1]
84        } else {
85            PhaseType::TURN_ORDER[idx - 1]
86        }
87    } else {
88        phase.next()
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn parse_range_simple() {
98        let result = parse_range("Upkeep");
99        assert!(result.contains(&PhaseType::Upkeep));
100        assert_eq!(result.len(), 1);
101    }
102
103    #[test]
104    fn parse_range_main_alias() {
105        let result = parse_range("Main");
106        assert!(result.contains(&PhaseType::Main1));
107        assert!(result.contains(&PhaseType::Main2));
108        assert_eq!(result.len(), 2);
109    }
110
111    #[test]
112    fn parse_range_arrow() {
113        let result = parse_range("Upkeep->Main1");
114        assert!(result.contains(&PhaseType::Upkeep));
115        assert!(result.contains(&PhaseType::Draw));
116        assert!(result.contains(&PhaseType::Main1));
117        assert_eq!(result.len(), 3);
118    }
119
120    #[test]
121    fn smart_value_of_test() {
122        assert_eq!(smart_value_of("BeginCombat"), Some(PhaseType::CombatBegin));
123        assert_eq!(smart_value_of("End of Turn"), Some(PhaseType::EndOfTurn));
124    }
125
126    #[test]
127    fn is_last_test() {
128        assert!(is_last(PhaseType::Cleanup));
129        assert!(!is_last(PhaseType::EndOfTurn));
130    }
131
132    #[test]
133    fn get_next_normal() {
134        assert_eq!(get_next(PhaseType::Untap, false), PhaseType::Upkeep);
135        assert_eq!(get_next(PhaseType::Cleanup, false), PhaseType::Untap);
136    }
137
138    #[test]
139    fn get_next_topsy() {
140        assert_eq!(get_next(PhaseType::Upkeep, true), PhaseType::Untap);
141        assert_eq!(get_next(PhaseType::Untap, true), PhaseType::Cleanup);
142    }
143}