tf2_enum/
spell_set.rs

1//! Set for holding up to 2 spells.
2
3use std::fmt;
4use std::hash::{Hash, Hasher};
5use std::ops::{BitAnd, Sub};
6use crate::Spell;
7
8const SPELL_COUNT: usize = 2;
9
10/// Contains up to 2 spells. Although the underlying data structure is an array, this structure
11/// behaves like a set. Most methods mimic those of [`HashSet`](std::collections::HashSet).
12/// 
13/// This struct solves the following problems:
14/// - An item can only hold up to 2 spells.
15/// - An item cannot have duplicate spells or multiple spells of the same type.
16/// - Comparing spells for equality is order-agnostic.
17/// - Hashing is order-agnostic.
18/// - The type is `Copy`, allowing for cheap and easy duplication.
19/// 
20/// # Examples
21/// ```
22/// use tf2_enum::{SpellSet, Spell};
23/// 
24/// // Create a set for spells with one spell.
25/// let mut spells = SpellSet::single(Spell::HeadlessHorseshoes);
26/// 
27/// // Check that spells contains Headless Horseshoes.
28/// assert!(spells.contains(&Spell::HeadlessHorseshoes));
29/// assert_eq!(spells.len(), 1);
30/// 
31/// // Add a spell.
32/// spells.insert(Spell::VoicesFromBelow);
33/// assert_eq!(spells.len(), 2);
34/// 
35/// // If a spell is added when spells are full, the insert will fail.
36/// assert!(!spells.insert(Spell::PumpkinBombs));
37/// assert!(!spells.contains(&Spell::PumpkinBombs));
38/// 
39/// // Iterate over spells.
40/// for spell in spells {
41///     println!("{}", spell);
42/// }
43/// ```
44#[derive(Debug, Default, Clone, Copy, Eq)]
45pub struct SpellSet {
46    inner: [Option<Spell>; SPELL_COUNT]
47}
48
49impl SpellSet {
50    /// Creates a set for spells.
51    /// 
52    /// # Examples
53    /// ```
54    /// use tf2_enum::SpellSet;
55    /// 
56    /// let spells = SpellSet::new();
57    /// ```
58    pub fn new() -> Self {
59        Self::default()
60    }
61    
62    /// Creates a set for spells with one spell.
63    /// 
64    /// # Examples
65    /// ```
66    /// use tf2_enum::{SpellSet, Spell};
67    /// 
68    /// let spells = SpellSet::single(Spell::HeadlessHorseshoes);
69    /// 
70    /// assert_eq!(spells.len(), 1);
71    /// ```
72    pub fn single(spell: Spell) -> Self {
73        Self {
74            inner: [
75                Some(spell),
76                None,
77            ],
78        }
79    }
80    
81    /// Creates a set for spells with two spells.
82    /// 
83    /// If the same spell is added multiple times, only one will be kept. This is also true for
84    /// spells of the same type. In cases of multiple spells of the same type, the first occuring
85    /// spell will be prioritized.
86    /// 
87    /// # Examples
88    /// ```
89    /// use tf2_enum::{SpellSet, Spell};
90    /// 
91    /// let spells = SpellSet::double(Spell::HeadlessHorseshoes, Spell::VoicesFromBelow);
92    /// 
93    /// assert_eq!(spells.len(), 2);
94    /// 
95    /// let spells = SpellSet::double(Spell::HeadlessHorseshoes, Spell::TeamSpiritFootprints);
96    /// 
97    /// assert_eq!(spells.len(), 1);
98    /// assert_eq!(SpellSet::single(Spell::HeadlessHorseshoes), spells);
99    /// ```
100    pub fn double(spell1: Spell, spell2: Spell) -> Self {
101        Self::from([
102            Some(spell1),
103            Some(spell2),
104        ])
105    }
106    
107    /// Clears the set, removing all spells.
108    /// 
109    /// # Examples
110    /// ```
111    /// use tf2_enum::{SpellSet, Spell};
112    /// 
113    /// let mut spells = SpellSet::single(Spell::HeadlessHorseshoes);
114    /// 
115    /// spells.clear();
116    /// 
117    /// assert_eq!(spells.len(), 0);
118    /// ```
119    pub fn clear(&mut self) {
120        self.inner = [None, None];
121    }
122    
123    /// Adds a spell to the first available slot.
124    /// 
125    /// Returns `false` if:
126    /// - The spell is already in the set.
127    /// - The set is full.
128    /// 
129    /// # Examples
130    /// ```
131    /// use tf2_enum::{SpellSet, Spell};
132    /// 
133    /// let mut spells = SpellSet::single(Spell::HeadlessHorseshoes);
134    /// 
135    /// assert_eq!(spells.len(), 1);
136    /// 
137    /// spells.insert(Spell::VoicesFromBelow);
138    /// 
139    /// assert_eq!(spells.len(), 2);
140    /// 
141    /// // Spells are full.
142    /// assert!(!spells.insert(Spell::PumpkinBombs));
143    /// ```
144    pub fn insert(&mut self, spell: Spell) -> bool {
145        let attribute_defindex = spell.attribute_defindex();
146        
147        if self.inner.iter().flatten().any(|s| s.attribute_defindex() == attribute_defindex) {
148            return false;
149        }
150        
151        if let Some(slot) = self.inner.iter_mut().find(|slot| slot.is_none()) {
152            *slot = Some(spell);
153            return true;
154        }
155        
156        // full set, insertion failed
157        false
158    }
159    
160    /// Removes a spell from the set. Returns whether the value was present in the set.
161    /// 
162    /// # Examples
163    /// ```
164    /// use tf2_enum::{SpellSet, Spell};
165    /// 
166    /// let mut spells = SpellSet::single(Spell::HeadlessHorseshoes);
167    /// 
168    /// assert!(spells.remove(&Spell::HeadlessHorseshoes));
169    /// assert!(!spells.contains(&Spell::HeadlessHorseshoes));
170    /// ```
171    pub fn remove(&mut self, spell: &Spell) -> bool {
172        if self.inner[0] == Some(*spell) {
173            self.inner[0] = None;
174            true
175        } else if self.inner[1] == Some(*spell) {
176            self.inner[1] = None;
177            true
178        } else {
179            false
180        }
181    }
182    
183    /// Removes and returns the spell in the set, if any, that is equal to the given one.
184    pub fn take(&mut self, spell: &Spell) -> Option<Spell> {
185        if self.inner[0] == Some(*spell) {
186            self.inner[0] = None;
187            return Some(*spell);
188        } else if self.inner[1] == Some(*spell) {
189            self.inner[1] = None;
190            return Some(*spell);
191        }
192        
193        None
194    }
195    
196    /// Returns `true` if the set contains no spells.
197    pub fn is_empty(&self) -> bool {
198        self.inner
199            .iter()
200            .all(|s| s.is_none())
201    }
202    
203    /// Returns `true` if the set contains a spell.
204    /// 
205    /// # Examples
206    /// ```
207    /// use tf2_enum::{SpellSet, Spell};
208    /// 
209    /// let mut spells = SpellSet::single(Spell::HeadlessHorseshoes);
210    /// 
211    /// assert!(spells.contains(&Spell::HeadlessHorseshoes));
212    /// ```
213    pub fn contains(&self, spell: &Spell) -> bool {
214        self.inner.contains(&Some(*spell))
215    }
216    
217    /// Returns the number of spells in the set.
218    /// 
219    /// # Examples
220    /// ```
221    /// use tf2_enum::{SpellSet, Spell};
222    /// 
223    /// let spells = SpellSet::single(Spell::HeadlessHorseshoes);
224    /// 
225    /// assert_eq!(spells.len(), 1);
226    /// ```
227    pub fn len(&self) -> usize {
228        self.inner
229            .iter()
230            .filter(|s| s.is_some())
231            .count()
232    }
233    
234    /// Returns the spells that are in `self` but not in `other`.
235    /// 
236    /// # Examples
237    /// ```
238    /// use tf2_enum::{SpellSet, Spell};
239    /// 
240    /// let spells1 = SpellSet::double(Spell::HalloweenFire, Spell::Exorcism);
241    /// let spells2 = SpellSet::double(Spell::HalloweenFire, Spell::VoicesFromBelow);
242    /// let difference = spells1.difference(&spells2);
243    /// 
244    /// assert_eq!(difference, SpellSet::single(Spell::Exorcism));
245    /// 
246    /// let difference = spells2.difference(&spells1);
247    /// 
248    /// assert_eq!(difference, SpellSet::single(Spell::VoicesFromBelow));
249    /// ```
250    pub fn difference(&self, other: &Self) -> Self {
251        let mut inner = [None, None];
252        
253        for (i, s_option) in inner.iter_mut().enumerate() {
254            if let Some(s) = self.inner[i] {
255                if !other.contains(&s) {
256                    *s_option = Some(s);
257                }
258            }
259        }
260        
261        Self {
262            inner,
263        }
264    }
265    
266    /// Returns the spells that are both in `self` and `other`.
267    /// 
268    /// # Examples
269    /// ```
270    /// use tf2_enum::{SpellSet, Spell};
271    /// 
272    /// let spells1 = SpellSet::double(Spell::HalloweenFire, Spell::Exorcism);
273    /// let spells2 = SpellSet::double(Spell::HalloweenFire, Spell::VoicesFromBelow);
274    /// let intersection = spells1.intersection(&spells2);
275    /// 
276    /// assert_eq!(intersection, SpellSet::single(Spell::HalloweenFire));
277    /// ```
278    pub fn intersection(&self, other: &Self) -> Self {
279        let mut inner = [None, None];
280        
281        for (i, s_option) in inner.iter_mut().enumerate() {
282            if let Some(s) = self.inner[i] {
283                if other.contains(&s) {
284                    *s_option = Some(s);
285                }
286            }
287        }
288        
289        Self {
290            inner,
291        }
292    }
293    
294    /// Returns `true` if `self` has no spells in common with `other`. This is equivalent to 
295    /// checking for an empty intersection.
296    /// 
297    /// # Examples
298    /// ```
299    /// use tf2_enum::{SpellSet, Spell};
300    /// 
301    /// let spells1 = SpellSet::double(Spell::HalloweenFire, Spell::Exorcism);
302    /// let spells2 = SpellSet::double(Spell::HalloweenFire, Spell::VoicesFromBelow);
303    /// 
304    /// assert!(!spells1.is_disjoint(&spells2));
305    /// ```
306    pub fn is_disjoint(&self, other: &Self) -> bool {
307        self.intersection(other).is_empty()
308    }
309    
310    /// Returns true if the set is a subset of another, i.e., other contains at least all the values in self.
311    /// 
312    /// # Examples
313    /// ```
314    /// use tf2_enum::{SpellSet, Spell};
315    ///
316    /// let sup = SpellSet::double(Spell::HalloweenFire, Spell::Exorcism);
317    /// let mut spells = SpellSet::single(Spell::HalloweenFire);
318    ///
319    /// assert!(spells.is_subset(&sup));
320    /// 
321    /// spells.insert(Spell::HeadlessHorseshoes);
322    /// 
323    /// assert!(!spells.is_subset(&sup));
324    /// ```
325    pub fn is_subset(&self, other: &Self) -> bool {
326        if self.len() > other.len() {
327            return false;
328        }
329        
330        self.iter().all(|spell| other.contains(spell))
331    }
332    
333    /// Returns true if the set is a superset of another, i.e., self contains at least all the values in other.
334    /// 
335    /// # Examples
336    /// ```
337    /// use tf2_enum::{SpellSet, Spell};
338    ///
339    /// let sub = SpellSet::double(Spell::HalloweenFire, Spell::Exorcism);
340    /// let mut spells = SpellSet::new();
341    ///
342    /// assert!(!spells.is_superset(&sub));
343    /// 
344    /// spells.insert(Spell::HalloweenFire);
345    /// 
346    /// assert!(!spells.is_superset(&sub));
347    /// 
348    /// spells.insert(Spell::Exorcism);
349    /// 
350    /// assert!(spells.is_superset(&sub));
351    /// ```
352    pub fn is_superset(&self, other: &Self) -> bool {
353        other.is_subset(self)
354    }
355    
356    /// Returns an iterator over the spells in the set.
357    pub fn iter(&self) -> impl Iterator<Item = &Spell> {
358        self.inner.iter().filter_map(|opt| opt.as_ref())
359    }
360}
361
362impl From<[Option<Spell>; SPELL_COUNT]> for SpellSet {
363    fn from(mut inner: [Option<Spell>; SPELL_COUNT]) -> Self {
364        // remove duplicates
365        // since this only contains 2 spells it's not really necessary to do this using loops but
366        // the implementation is consistent with StrangePartSet
367        for i in 0..SPELL_COUNT {
368            if let Some(val_i) = inner[i] {
369                for j in 0..i {
370                    if let Some(val_j) = inner[j] {
371                        if val_i.attribute_defindex() == val_j.attribute_defindex() {
372                            inner[i] = None;
373                            break;
374                        } 
375                    }
376                }
377            }
378        }
379        
380        Self {
381            inner,
382        }
383    }
384}
385
386// Only Sub is implemented because Add wouldn't make much sense with spells being limited to 2.
387impl Sub for SpellSet {
388    type Output = Self;
389    
390    fn sub(self, other: Self) -> Self::Output {
391        self.difference(&other)
392    }
393}
394
395impl Sub for &SpellSet {
396    type Output = SpellSet;
397    
398    fn sub(self, other: &SpellSet) -> Self::Output {
399        self.difference(other)
400    }
401}
402
403impl BitAnd for SpellSet {
404    type Output = Self;
405    
406    fn bitand(self, other: Self) -> Self::Output {
407        self.intersection(&other)
408    }
409}
410
411impl BitAnd for &SpellSet {
412    type Output = SpellSet;
413    
414    fn bitand(self, other: &SpellSet) -> Self::Output {
415        self.intersection(other)
416    }
417}
418
419impl PartialEq<Self> for SpellSet {
420    fn eq(&self, other: &Self) -> bool {
421        (self.inner[0] == other.inner[0] && self.inner[1] == other.inner[1]) || 
422        (self.inner[0] == other.inner[1] && self.inner[1] == other.inner[0])
423    }
424}
425
426impl Hash for SpellSet {
427    fn hash<H: Hasher>(&self, state: &mut H) {
428        if self.inner[0] <= self.inner[1] {
429            self.inner[0].hash(state);
430            self.inner[1].hash(state);
431        } else {
432            self.inner[1].hash(state);
433            self.inner[0].hash(state);
434        }
435    }
436}
437
438impl FromIterator<Spell> for SpellSet {
439    fn from_iter<I: IntoIterator<Item = Spell>>(iter: I) -> Self {
440        let mut spell_set = Self::new();
441        
442        for spell in iter {
443            spell_set.insert(spell);
444        }
445        
446        spell_set
447    }
448}
449
450impl IntoIterator for SpellSet {
451    type Item = Spell;
452    type IntoIter = SpellSetIterator;
453    
454    fn into_iter(self) -> Self::IntoIter {
455        SpellSetIterator {
456            inner: self.inner.into_iter(),
457        }
458    }
459}
460
461impl IntoIterator for &SpellSet {
462    type Item = Spell;
463    type IntoIter = SpellSetIterator;
464    
465    fn into_iter(self) -> Self::IntoIter {
466        SpellSetIterator {
467            inner: self.inner.into_iter(),
468        }
469    }
470}
471
472/// Iterator for spells.
473#[derive(Debug, Clone)]
474pub struct SpellSetIterator {
475    inner: std::array::IntoIter<Option<Spell>, SPELL_COUNT>,
476}
477
478impl Iterator for SpellSetIterator {
479    type Item = Spell;
480    
481    fn next(&mut self) -> Option<Self::Item> {
482        let iter = self.inner.by_ref();
483        
484        for opt in iter {
485            if opt.is_some() {
486                return opt;
487            }
488        }
489        
490        None
491    }
492}
493
494impl fmt::Display for SpellSet {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        let mut iter = self.into_iter();
497        
498        if let Some(first) = iter.next() {
499            write!(f, "{first}")?;
500            
501            for s in iter {
502                write!(f, ", {s}")?;
503            }
504        }
505        
506        Ok(())
507    }
508}
509    
510#[cfg(test)]
511mod tests {
512    use super::*;
513    use std::collections::HashSet;
514    
515    #[test]
516    fn spell_set_equals() {
517        assert_eq!(SpellSet::from([
518            Some(Spell::Exorcism),
519            Some(Spell::HalloweenFire),
520        ]), SpellSet::from([
521            Some(Spell::HalloweenFire),
522            Some(Spell::Exorcism),
523        ]));
524    }
525    
526    #[test]
527    fn spell_set_hashes() {
528        let mut set = HashSet::new();
529        
530        set.insert(SpellSet::from([
531            Some(Spell::Exorcism),
532            Some(Spell::HalloweenFire),
533        ]));
534        
535        assert!(set.contains(&SpellSet::from([
536            Some(Spell::HalloweenFire),
537            Some(Spell::Exorcism),
538        ])));
539    }
540    
541    #[test]
542    fn spell_set_no_duplicates() {
543        assert_eq!(SpellSet::from([
544            Some(Spell::Exorcism),
545            Some(Spell::Exorcism),
546        ]), SpellSet::from([
547            Some(Spell::Exorcism),
548            None,
549        ]));
550        
551        assert_eq!(SpellSet::from([
552            Some(Spell::TeamSpiritFootprints),
553            Some(Spell::HeadlessHorseshoes),
554        ]), SpellSet::from([
555            Some(Spell::TeamSpiritFootprints),
556            None,
557        ]));
558    }
559    
560    #[test]
561    fn iterates_spells() {
562        let spells = SpellSet::from([
563            Some(Spell::Exorcism),
564            Some(Spell::HalloweenFire),
565        ]);
566        let mut count = 0;
567        
568        for _spell in spells {
569            count += 1;
570        }
571        
572        assert_eq!(count, 2);
573        
574        let spells = spells.into_iter().collect::<Vec<_>>();
575        
576        assert_eq!(spells, vec![Spell::Exorcism, Spell::HalloweenFire]);
577        
578        let spells = SpellSet::from_iter(spells);
579        
580        assert_eq!(spells, SpellSet::from([
581            Some(Spell::Exorcism),
582            Some(Spell::HalloweenFire),
583        ]));
584    }
585    
586    #[test]
587    fn sub() {
588        let spells1 = SpellSet::from([
589            Some(Spell::HalloweenFire),
590            Some(Spell::Exorcism),
591        ]);
592        let spells2 = SpellSet::from([
593            Some(Spell::HalloweenFire),
594            Some(Spell::VoicesFromBelow),
595        ]);
596        
597        let difference = spells1 - spells2;
598        
599        assert_eq!(difference, SpellSet::from([
600            Some(Spell::Exorcism),
601            None,
602        ]));
603    }
604    
605    #[test]
606    fn stringify() {
607        let spells = SpellSet::from([
608            Some(Spell::Exorcism),
609            Some(Spell::HalloweenFire),
610        ]);
611        
612        assert_eq!(spells.to_string(), "Exorcism, Halloween Fire");
613    }
614}