core/
card.rs

1// Copyright (C) 2017 Steve Sprang
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16//! Card, Set, and SuperSet implementation.
17//!
18//! A `Card` has four features, each of which has three possible
19//! values. As such, it can be represented by a four-element vector
20//! where each element is a ternary digit (trit): 0, 1, or 2. The
21//! implementation here packs this vector into the four bytes of a
22//! `u32`.
23//!
24
25#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub struct Card(u32);
27
28impl Card {
29    pub fn new(mut index: usize) -> Card {
30        // Convert the index to ternary and pack each of the resulting
31        // four trits into the bytes of a u32. The least significant
32        // trit ends up in the leftmost byte.
33        let mut value = index % 3;
34
35        for _ in 0..3 {
36            value <<= 8;
37            index /= 3;
38            value |= index % 3;
39        }
40
41        Card(value as u32)
42    }
43
44    /// Maps the card value back to the index from which it was derived.
45    pub fn index(self) -> usize {
46        let mut value = self.0;
47        let mut result = value & 0xff;
48
49        for _ in 0..3 {
50            value >>= 8;
51            result *= 3;
52            result += value & 0xff;
53        }
54
55        result as usize
56    }
57}
58
59////////////////////////////////////////////////////////////////////////////////
60// Card: Feature Extraction
61////////////////////////////////////////////////////////////////////////////////
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64enum Feature { Count, Shape, Color, Shading }
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum Shape { Oval, Squiggle, Diamond }
68
69/// Unlike the other features, Color doesn't have a fixed interpretation.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum Color { A, B, C }
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum Shading { Solid, Striped, Outlined }
75
76impl Card {
77    /// Extracts the byte corresponding to the given `Feature`. Since
78    /// the bytes represent ternary digits, the returned value will
79    /// always be in the interval [0,2].
80    fn feature(self, feature: Feature) -> u8 {
81        (self.0 >> (feature as u32 * 8) & 0xff) as u8
82    }
83
84    /// Returns a shape count in the interval [1,3]
85    pub fn count(self) -> u8 {
86        self.feature(Feature::Count) + 1
87    }
88
89    pub fn shape(self) -> Shape {
90        match self.feature(Feature::Shape) {
91            0 => Shape::Oval,
92            1 => Shape::Squiggle,
93            _ => Shape::Diamond,
94        }
95    }
96
97    pub fn color(self) -> Color {
98        match self.feature(Feature::Color) {
99            0 => Color::A,
100            1 => Color::B,
101            _ => Color::C,
102        }
103    }
104
105    pub fn shading(self) -> Shading {
106        match self.feature(Feature::Shading) {
107            0 => Shading::Solid,
108            1 => Shading::Striped,
109            _ => Shading::Outlined,
110        }
111    }
112}
113
114////////////////////////////////////////////////////////////////////////////////
115// Card: Debug
116////////////////////////////////////////////////////////////////////////////////
117
118use std::fmt;
119
120impl fmt::Debug for Card {
121    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122        write!(f, "Card({})", self.index())
123    }
124}
125
126////////////////////////////////////////////////////////////////////////////////
127// Set
128////////////////////////////////////////////////////////////////////////////////
129
130type Triple = (Card, Card, Card);
131
132/// Validated Set
133pub struct Set { cards: Triple }
134
135impl Set {
136    pub fn cards(&self) -> Triple {
137        self.cards
138    }
139}
140
141pub trait ToSet {
142    fn to_set(self) -> Option<Set>;
143}
144
145impl ToSet for Triple {
146    /// Three cards form a `Set` if their sum is (0,0,0,0) modulo 3.
147    fn to_set(self) -> Option<Set> {
148        let (a, b, c) = self;
149        let mut sum = a.0 + b.0 + c.0;
150        let mut result = true;
151
152        while sum != 0 {
153            // see if each byte is divisible by 3
154            result &= (sum & 0xff) % 3 == 0;
155            sum >>= 8;
156        }
157
158        if result {
159            let set = Set { cards: self };
160            Some(set)
161        } else {
162            None
163        }
164    }
165}
166
167////////////////////////////////////////////////////////////////////////////////
168// SuperSet
169////////////////////////////////////////////////////////////////////////////////
170
171type Pair = (Card, Card);
172
173/// Validated SuperSet
174pub struct SuperSet { left: Pair, right: Pair }
175
176impl SuperSet {
177    pub fn left(&self) -> Pair {
178        self.left
179    }
180
181    pub fn right(&self) -> Pair {
182        self.right
183    }
184}
185
186pub trait ToSuperSet {
187    fn to_superset(self) -> Option<SuperSet>;
188}
189
190impl ToSuperSet for (Card, Card, Card, Card) {
191    fn to_superset(self) -> Option<SuperSet> {
192        let (a, b, c, d) = self;
193        let pair_combos = &[((a, b), (c, d)),
194                            ((a, c), (b, d)),
195                            ((a, d), (b, c))];
196
197        for &(left, right) in pair_combos {
198            // Two pairs of cards form a SuperSet if and only if the
199            // Set-completing card for each pair is the same
200            if left.complete_set() == right.complete_set() {
201                let superset = SuperSet { left, right };
202                return Some(superset);
203            }
204        }
205
206        None
207    }
208}
209
210////////////////////////////////////////////////////////////////////////////////
211// CompleteSet
212////////////////////////////////////////////////////////////////////////////////
213
214pub trait CompleteSet {
215    fn complete_set(self) -> Card;
216}
217
218impl CompleteSet for Pair {
219    /// Given a pair of cards, returns the third card needed to
220    /// complete the `Set`.
221    ///
222    /// This method works by taking the trit-wise sum of the cards in
223    /// the pair. Each unique sum maps to a matching trit which is the
224    /// appropriate component of the missing card:
225    ///
226    ///   A | B | A+B | Match
227    ///  ---+---+-----+-------
228    ///   0 | 0 |   0 |     0
229    ///   0 | 1 |   1 |     2
230    ///   0 | 2 |   2 |     1
231    ///   1 | 1 |   2 |     1
232    ///   1 | 2 |   3 |     0
233    ///   2 | 2 |   4 |     2
234    ///
235    fn complete_set(self) -> Card {
236        const MATCHES: [u32; 5] = [0, 2, 1, 0, 2];
237
238        let (a, b) = self;
239        let mut sum = (a.0 + b.0) as usize;
240
241        let mut value = MATCHES[sum & 0xff];
242        for _ in 0..3 {
243            value <<= 8;
244            sum >>= 8;
245            value |= MATCHES[sum & 0xff];
246        }
247
248        let reversed = value.swap_bytes();
249        Card(reversed)
250    }
251}
252
253impl<'a> CompleteSet for (&'a Card, &'a Card) {
254    fn complete_set(self) -> Card {
255        let (&a, &b) = self;
256        (a, b).complete_set()
257    }
258}
259
260////////////////////////////////////////////////////////////////////////////////
261// Tests
262////////////////////////////////////////////////////////////////////////////////
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267    use crate::deck::cards;
268    use crate::pair_iter::PairIter;
269
270    #[test]
271    fn check_card_conversions() {
272        for i in 0..81 {
273            let card = Card::new(i);
274            assert_eq!(i, card.index());
275        }
276    }
277
278    #[test]
279    fn check_set_completion() {
280        let cards = cards();
281        let mut set_count = 0;
282
283        for (&a, &b) in cards.pairs() {
284            let c = (a, b).complete_set();
285            let triple = (a, b, c);
286            assert!(triple.to_set().is_some());
287            set_count += 1;
288        }
289        // each set is encountered thrice
290        assert_eq!(set_count, 1080 * 3)
291    }
292}