Skip to main content

forge_foundation/
card_split.rs

1use serde::{Deserialize, Serialize};
2
3/// How a split/transform/modal card selects which face to show.
4/// Mirrors Java `CardSplitType.FaceSelectionMethod`.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
6pub enum FaceSelectionMethod {
7    UseActiveFace,
8    UsePrimaryFace,
9    Combine,
10}
11
12/// The type of split/transform/modal card.
13/// Mirrors Java `CardSplitType`.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum CardSplitType {
16    None,
17    Transform,
18    Meld,
19    Split,
20    Flip,
21    Adventure,
22    Omen,
23    Modal,
24    Specialize,
25}
26
27impl CardSplitType {
28    pub fn aggregation_method(self) -> FaceSelectionMethod {
29        match self {
30            Self::None => FaceSelectionMethod::UsePrimaryFace,
31            Self::Transform => FaceSelectionMethod::UseActiveFace,
32            Self::Meld => FaceSelectionMethod::UseActiveFace,
33            Self::Split => FaceSelectionMethod::Combine,
34            Self::Flip => FaceSelectionMethod::UsePrimaryFace,
35            Self::Adventure => FaceSelectionMethod::UsePrimaryFace,
36            Self::Omen => FaceSelectionMethod::UsePrimaryFace,
37            Self::Modal => FaceSelectionMethod::UseActiveFace,
38            Self::Specialize => FaceSelectionMethod::UseActiveFace,
39        }
40    }
41
42    pub fn changed_state_name(self) -> Option<CardStateName> {
43        match self {
44            Self::None => None,
45            Self::Transform => Some(CardStateName::Backside),
46            Self::Meld => Some(CardStateName::Meld),
47            Self::Split => Some(CardStateName::RightSplit),
48            Self::Flip => Some(CardStateName::Flipped),
49            Self::Adventure => Some(CardStateName::Secondary),
50            Self::Omen => Some(CardStateName::Secondary),
51            Self::Modal => Some(CardStateName::Backside),
52            Self::Specialize => None,
53        }
54    }
55
56    pub fn is_dual_faced(self) -> bool {
57        matches!(self, Self::Transform | Self::Meld | Self::Modal)
58    }
59
60    pub fn from_str_compat(s: &str) -> Option<Self> {
61        match s {
62            "None" => Some(Self::None),
63            "Transform" | "DoubleFaced" => Some(Self::Transform),
64            "Meld" => Some(Self::Meld),
65            "Split" => Some(Self::Split),
66            "Flip" => Some(Self::Flip),
67            "Adventure" => Some(Self::Adventure),
68            "Omen" => Some(Self::Omen),
69            "Modal" => Some(Self::Modal),
70            "Specialize" => Some(Self::Specialize),
71            _ => None,
72        }
73    }
74}
75
76impl Default for CardSplitType {
77    fn default() -> Self {
78        Self::None
79    }
80}
81
82/// Which face/state a card is currently showing.
83/// Mirrors Java `CardStateName`.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub enum CardStateName {
86    Original,
87    FaceDown,
88    Flipped,
89    Backside,
90    Meld,
91    LeftSplit,
92    RightSplit,
93    Secondary,
94    EmptyRoom,
95    SpecializeW,
96    SpecializeU,
97    SpecializeB,
98    SpecializeR,
99    SpecializeG,
100}
101
102impl CardStateName {
103    pub fn from_str_compat(s: &str) -> Option<Self> {
104        let s = s.trim();
105        if s.eq_ignore_ascii_case("All") {
106            return None;
107        }
108        match s {
109            "Original" => Some(Self::Original),
110            "FaceDown" => Some(Self::FaceDown),
111            "Flipped" | "Flip" => Some(Self::Flipped),
112            "Backside" | "DoubleFaced" => Some(Self::Backside),
113            "Meld" => Some(Self::Meld),
114            "LeftSplit" => Some(Self::LeftSplit),
115            "RightSplit" => Some(Self::RightSplit),
116            "Secondary" => Some(Self::Secondary),
117            "EmptyRoom" => Some(Self::EmptyRoom),
118            "SpecializeW" => Some(Self::SpecializeW),
119            "SpecializeU" => Some(Self::SpecializeU),
120            "SpecializeB" => Some(Self::SpecializeB),
121            "SpecializeR" => Some(Self::SpecializeR),
122            "SpecializeG" => Some(Self::SpecializeG),
123            _ => {
124                // Case-insensitive fallback
125                let lower = s.to_ascii_lowercase();
126                match lower.as_str() {
127                    "original" => Some(Self::Original),
128                    "facedown" => Some(Self::FaceDown),
129                    "flipped" | "flip" => Some(Self::Flipped),
130                    "backside" | "doublefaced" => Some(Self::Backside),
131                    "meld" => Some(Self::Meld),
132                    "leftsplit" => Some(Self::LeftSplit),
133                    "rightsplit" => Some(Self::RightSplit),
134                    "secondary" => Some(Self::Secondary),
135                    "emptyroom" => Some(Self::EmptyRoom),
136                    "specializew" => Some(Self::SpecializeW),
137                    "specializeu" => Some(Self::SpecializeU),
138                    "specializeb" => Some(Self::SpecializeB),
139                    "specializer" => Some(Self::SpecializeR),
140                    "specializeg" => Some(Self::SpecializeG),
141                    _ => None,
142                }
143            }
144        }
145    }
146}
147
148impl Default for CardStateName {
149    fn default() -> Self {
150        Self::Original
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn split_type_compat() {
160        assert_eq!(
161            CardSplitType::from_str_compat("DoubleFaced"),
162            Some(CardSplitType::Transform)
163        );
164        assert_eq!(
165            CardSplitType::from_str_compat("Transform"),
166            Some(CardSplitType::Transform)
167        );
168    }
169
170    #[test]
171    fn state_name_compat() {
172        assert_eq!(
173            CardStateName::from_str_compat("Flip"),
174            Some(CardStateName::Flipped)
175        );
176        assert_eq!(
177            CardStateName::from_str_compat("DoubleFaced"),
178            Some(CardStateName::Backside)
179        );
180        assert_eq!(CardStateName::from_str_compat("All"), None);
181    }
182
183    #[test]
184    fn dual_faced() {
185        assert!(CardSplitType::Transform.is_dual_faced());
186        assert!(CardSplitType::Modal.is_dual_faced());
187        assert!(!CardSplitType::Split.is_dual_faced());
188        assert!(!CardSplitType::None.is_dual_faced());
189    }
190}