Skip to main content

forge_foundation/
zone.rs

1use serde::{Deserialize, Serialize};
2
3/// Game zones. Mirrors Java `ZoneType`.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5pub enum ZoneType {
6    Hand,
7    Library,
8    Graveyard,
9    Battlefield,
10    Exile,
11    Flashback,
12    Command,
13    Stack,
14    Sideboard,
15    Ante,
16    Merged,
17    SchemeDeck,
18    PlanarDeck,
19    AttractionDeck,
20    Junkyard,
21    ContraptionDeck,
22    Subgame,
23    ExtraHand,
24    None,
25}
26
27impl ZoneType {
28    /// Whether this zone holds hidden information (cards not visible to opponents).
29    pub fn is_hidden(self) -> bool {
30        matches!(
31            self,
32            ZoneType::Hand
33                | ZoneType::Library
34                | ZoneType::Sideboard
35                | ZoneType::SchemeDeck
36                | ZoneType::PlanarDeck
37                | ZoneType::AttractionDeck
38                | ZoneType::ContraptionDeck
39                | ZoneType::Subgame
40                | ZoneType::ExtraHand
41                | ZoneType::None
42        )
43    }
44
45    pub fn is_known(self) -> bool {
46        !self.is_hidden()
47    }
48
49    pub fn is_deck(self) -> bool {
50        matches!(
51            self,
52            ZoneType::Library
53                | ZoneType::SchemeDeck
54                | ZoneType::PlanarDeck
55                | ZoneType::AttractionDeck
56                | ZoneType::ContraptionDeck
57        )
58    }
59
60    pub fn is_part_of_command_zone(self) -> bool {
61        matches!(
62            self,
63            ZoneType::Command
64                | ZoneType::SchemeDeck
65                | ZoneType::PlanarDeck
66                | ZoneType::AttractionDeck
67                | ZoneType::ContraptionDeck
68                | ZoneType::Junkyard
69        )
70    }
71
72    /// Zones that can host static abilities in Forge runtime checks.
73    /// Mirrors Java's `ZoneType.STATIC_ABILITIES_SOURCE_ZONES` usage.
74    pub fn is_static_ability_source(self) -> bool {
75        matches!(self, ZoneType::Battlefield | ZoneType::Command)
76    }
77
78    pub fn from_str_compat(s: &str) -> Option<Self> {
79        let s = s.trim();
80        if s.eq_ignore_ascii_case("All") {
81            return None;
82        }
83        match s {
84            "Hand" => Some(ZoneType::Hand),
85            "Library" => Some(ZoneType::Library),
86            "Graveyard" => Some(ZoneType::Graveyard),
87            "Battlefield" => Some(ZoneType::Battlefield),
88            "Exile" => Some(ZoneType::Exile),
89            "Flashback" => Some(ZoneType::Flashback),
90            "Command" => Some(ZoneType::Command),
91            "Stack" => Some(ZoneType::Stack),
92            "Sideboard" => Some(ZoneType::Sideboard),
93            "Ante" => Some(ZoneType::Ante),
94            "Merged" => Some(ZoneType::Merged),
95            "SchemeDeck" => Some(ZoneType::SchemeDeck),
96            "PlanarDeck" => Some(ZoneType::PlanarDeck),
97            "AttractionDeck" => Some(ZoneType::AttractionDeck),
98            "Junkyard" => Some(ZoneType::Junkyard),
99            "ContraptionDeck" => Some(ZoneType::ContraptionDeck),
100            "Subgame" => Some(ZoneType::Subgame),
101            "ExtraHand" => Some(ZoneType::ExtraHand),
102            "None" => Some(ZoneType::None),
103            _ => {
104                // Case-insensitive fallback
105                for zt in Self::ALL.iter() {
106                    if format!("{zt:?}").eq_ignore_ascii_case(s) {
107                        return Some(*zt);
108                    }
109                }
110                None
111            }
112        }
113    }
114
115    pub const ALL: [ZoneType; 19] = [
116        ZoneType::Hand,
117        ZoneType::Library,
118        ZoneType::Graveyard,
119        ZoneType::Battlefield,
120        ZoneType::Exile,
121        ZoneType::Flashback,
122        ZoneType::Command,
123        ZoneType::Stack,
124        ZoneType::Sideboard,
125        ZoneType::Ante,
126        ZoneType::Merged,
127        ZoneType::SchemeDeck,
128        ZoneType::PlanarDeck,
129        ZoneType::AttractionDeck,
130        ZoneType::Junkyard,
131        ZoneType::ContraptionDeck,
132        ZoneType::Subgame,
133        ZoneType::ExtraHand,
134        ZoneType::None,
135    ];
136}
137
138impl std::fmt::Display for ZoneType {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        write!(f, "{self:?}")
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn zone_hidden() {
150        assert!(ZoneType::Hand.is_hidden());
151        assert!(ZoneType::Library.is_hidden());
152        assert!(!ZoneType::Graveyard.is_hidden());
153        assert!(!ZoneType::Battlefield.is_hidden());
154    }
155
156    #[test]
157    fn zone_from_str() {
158        assert_eq!(
159            ZoneType::from_str_compat("Battlefield"),
160            Some(ZoneType::Battlefield)
161        );
162        assert_eq!(ZoneType::from_str_compat("All"), None);
163    }
164}