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}