Skip to main content

manabrew_engine/mana/
mana_conversion_matrix.rs

1use forge_foundation::mana::ManaAtom;
2
3/// Ordered mana type array matching Java's ManaAtom.MANATYPES.
4const MANA_TYPES: [u16; 6] = [
5    ManaAtom::WHITE,
6    ManaAtom::BLUE,
7    ManaAtom::BLACK,
8    ManaAtom::RED,
9    ManaAtom::GREEN,
10    ManaAtom::COLORLESS,
11];
12
13/// Identity matrix: each color maps to itself.
14const IDENTITY: [u8; 6] = [
15    ManaAtom::WHITE as u8,
16    ManaAtom::BLUE as u8,
17    ManaAtom::BLACK as u8,
18    ManaAtom::RED as u8,
19    ManaAtom::GREEN as u8,
20    ManaAtom::COLORLESS as u8,
21];
22
23/// Mana conversion/restriction matrix controlling what colors can pay for what.
24///
25/// Mirrors Java's `forge.game.mana.ManaConversionMatrix`.
26///
27/// The conversion matrix ORs byte values to make mana more payable (additive).
28/// The restriction matrix ANDs byte values to make mana less payable (restrictive).
29#[derive(Debug, Clone)]
30pub struct ManaConversionMatrix {
31    /// Conversion matrix: OR-ed values broaden what each color can pay for.
32    color_conversion_matrix: [u8; 6],
33    /// Restriction matrix: AND-ed values narrow what each color can pay for.
34    color_restriction_matrix: [u8; 6],
35    /// Whether snow mana can substitute for colored mana.
36    snow_for_color: bool,
37}
38
39impl Default for ManaConversionMatrix {
40    fn default() -> Self {
41        let mut m = Self {
42            color_conversion_matrix: [0; 6],
43            color_restriction_matrix: [0xFF; 6],
44            snow_for_color: false,
45        };
46        m.restore_color_replacements();
47        m
48    }
49}
50
51impl ManaConversionMatrix {
52    /// Create a new matrix with identity conversion and no restrictions.
53    /// Mirrors Java's constructor which calls `restoreColorReplacements()`.
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    /// Get the effective color uses for a given color after applying conversion and restriction.
59    /// Mirrors Java's `ManaConversionMatrix.getPossibleColorUses()`.
60    pub fn get_possible_color_uses(&self, color: u8) -> u8 {
61        let idx = get_index_of_first_mana_type(color);
62        let matrix_idx = if idx < 0 { 5 } else { idx as usize };
63        self.color_conversion_matrix[matrix_idx] & self.color_restriction_matrix[matrix_idx]
64    }
65
66    /// Adjust color replacement: additive ORs into conversion, restrictive ANDs into restriction.
67    /// Mirrors Java's `ManaConversionMatrix.adjustColorReplacement()`.
68    pub fn adjust_color_replacement(&mut self, original: u8, replacement: u8, additive: bool) {
69        let row_idx = get_index_of_first_mana_type(original);
70        let idx = if row_idx < 0 { 5 } else { row_idx as usize };
71        if additive {
72            self.color_conversion_matrix[idx] |= replacement;
73        } else {
74            self.color_restriction_matrix[idx] &= replacement;
75        }
76    }
77
78    /// Merge another matrix into this one: OR conversion, AND restriction.
79    /// Mirrors Java's `ManaConversionMatrix.applyCardMatrix()`.
80    pub fn apply_card_matrix(&mut self, other: &ManaConversionMatrix) {
81        for i in 0..6 {
82            self.color_conversion_matrix[i] |= other.color_conversion_matrix[i];
83        }
84        for i in 0..6 {
85            self.color_restriction_matrix[i] &= other.color_restriction_matrix[i];
86        }
87        self.snow_for_color = other.snow_for_color;
88    }
89
90    /// Reset to identity: each color pays only itself, no restrictions.
91    /// Mirrors Java's `ManaConversionMatrix.restoreColorReplacements()`.
92    pub fn restore_color_replacements(&mut self) {
93        self.color_conversion_matrix = IDENTITY;
94        self.color_restriction_matrix = [ManaAtom::ALL_MANA_TYPES as u8; 6];
95        self.snow_for_color = false;
96    }
97
98    /// Whether snow mana can substitute for colored mana.
99    /// Mirrors Java's `ManaConversionMatrix.isSnowForColor()`.
100    pub fn is_snow_for_color(&self) -> bool {
101        self.snow_for_color
102    }
103
104    /// Set whether snow mana can substitute for colored mana.
105    /// Mirrors Java's `ManaConversionMatrix.setSnowForColor()`.
106    pub fn set_snow_for_color(&mut self, value: bool) {
107        self.snow_for_color = value;
108    }
109}
110
111/// Find the index into MANA_TYPES for the given color bitmask.
112/// Returns -1 if not found (mirrors Java's `ManaAtom.getIndexOfFirstManaType()`).
113fn get_index_of_first_mana_type(color: u8) -> i32 {
114    for (i, &mt) in MANA_TYPES.iter().enumerate() {
115        if (color as u16 & mt) != 0 {
116            return i as i32;
117        }
118    }
119    -1
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn identity_matrix_maps_each_color_to_itself() {
128        let m = ManaConversionMatrix::new();
129        assert_eq!(
130            m.get_possible_color_uses(ManaAtom::WHITE as u8),
131            ManaAtom::WHITE as u8
132        );
133        assert_eq!(
134            m.get_possible_color_uses(ManaAtom::BLUE as u8),
135            ManaAtom::BLUE as u8
136        );
137        assert_eq!(
138            m.get_possible_color_uses(ManaAtom::COLORLESS as u8),
139            ManaAtom::COLORLESS as u8
140        );
141    }
142
143    #[test]
144    fn additive_replacement_broadens_uses() {
145        let mut m = ManaConversionMatrix::new();
146        m.adjust_color_replacement(ManaAtom::WHITE as u8, ManaAtom::BLUE as u8, true);
147        let uses = m.get_possible_color_uses(ManaAtom::WHITE as u8);
148        assert_ne!(uses & ManaAtom::WHITE as u8, 0);
149        assert_ne!(uses & ManaAtom::BLUE as u8, 0);
150    }
151
152    #[test]
153    fn restrictive_replacement_narrows_uses() {
154        let mut m = ManaConversionMatrix::new();
155        // Restrict WHITE to only pay for WHITE (no other bits set after AND)
156        m.adjust_color_replacement(ManaAtom::WHITE as u8, ManaAtom::WHITE as u8, false);
157        let uses = m.get_possible_color_uses(ManaAtom::WHITE as u8);
158        assert_eq!(uses, ManaAtom::WHITE as u8);
159    }
160
161    #[test]
162    fn restore_resets_matrix() {
163        let mut m = ManaConversionMatrix::new();
164        m.adjust_color_replacement(ManaAtom::RED as u8, ManaAtom::GREEN as u8, true);
165        m.restore_color_replacements();
166        let uses = m.get_possible_color_uses(ManaAtom::RED as u8);
167        assert_eq!(uses, ManaAtom::RED as u8);
168    }
169}