1#[macro_use]
46extern crate clap;
47extern crate core;
48extern crate num_cpus;
49#[macro_use]
50extern crate prettytable;
51extern crate rand;
52extern crate time;
53
54use prettytable::Table;
55use prettytable::format::consts;
56use rand::{thread_rng, Rng};
57use std::cmp;
58use std::sync::mpsc;
59use std::thread;
60use time::PreciseTime;
61
62use core::card::*;
63use core::deck::cards;
64use core::pair_iter::PairIter;
65use core::shuffle::Shuffle;
66use core::utils::*;
67
68const VERSION: &str = env!("CARGO_PKG_VERSION");
69const NUM_GAMES: u64 = 1_000_000;
70const INITIAL_DEAL: usize = 12;
71const MAX_DEAL: usize = 22;
72const SET_SIZE: usize = 3;
73
74struct Counts {
79 sets: [u64; MAX_DEAL],
81 no_sets: [u64; MAX_DEAL],
83 remainder: [u64; MAX_DEAL],
85}
86
87impl Counts {
88 fn zero() -> Counts {
89 Counts {
90 sets: [0; MAX_DEAL],
91 no_sets: [0; MAX_DEAL],
92 remainder: [0; MAX_DEAL],
93 }
94 }
95
96 fn add(&mut self, other: &Counts) {
97 for i in 0..MAX_DEAL {
98 self.sets[i] += other.sets[i];
99 self.no_sets[i] += other.no_sets[i];
100 self.remainder[i] += other.remainder[i];
101 }
102 }
103
104 fn num_simulated(&self) -> u64 {
105 self.remainder.iter().sum()
106 }
107
108 fn print_hand_stats(&self) {
109 let mut table = Table::new();
110 table.set_format(*consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
111 table.set_titles(row![r => "hand", "sets", "no sets", "total", "ratio", "% with no sets"]);
112
113 let iter = self.sets.iter().zip(self.no_sets.iter()).enumerate();
114
115 for (hand_size, (&sets, &no_sets)) in iter {
116 if hand_size == 0 || no_sets == 0 {
117 continue;
118 }
119
120 let total_hands = sets + no_sets;
121 let percentage = (no_sets as f64 / total_hands as f64) * 100.0;
123
124 let ratio = sets as f64 / no_sets as f64;
126 let ratio_string = if ratio > 1.0 {
127 format!("{}:1", ratio.round() as usize)
128 } else {
129 format!("1:{}", (1.0 / ratio).round() as usize)
130 };
131
132 table.add_row(row![r => &hand_size.to_string(),
133 &pretty_print(sets),
134 &pretty_print(no_sets),
135 &pretty_print(total_hands),
136 &ratio_string,
137 &format!("{:.5} %", percentage)]);
138 }
139
140 table.printstd();
141 }
142
143 fn print_end_game_stats(&self) {
144 let mut table = Table::new();
145 table.set_format(*consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
146 table.set_titles(row![r => "cards left", "occurrences", "% of games"]);
147
148 let num_games = self.num_simulated();
149
150 for (hand_size, &count) in self.remainder.iter().enumerate() {
151 if count == 0 {
152 continue;
153 }
154
155 let percentage = (count as f64 / num_games as f64) * 100.0;
156 table.add_row(row![r => &hand_size.to_string(),
157 &pretty_print(count),
158 &format!("{:.5} %", percentage)]);
159 }
160
161 table.printstd();
162 }
163}
164
165#[derive(Default, Clone)]
170pub struct IndexDeck {
171 stock: Vec<usize>,
172}
173
174impl IndexDeck {
178 pub fn new() -> IndexDeck {
180 let mut indices = (0..81).collect::<Vec<_>>();
181 indices.shuffle();
182 IndexDeck { stock: indices }
183 }
184
185 pub fn is_empty(&self) -> bool {
186 self.stock.is_empty()
187 }
188
189 pub fn remainder(&self) -> usize {
190 self.stock.len()
191 }
192
193 pub fn draw(&mut self, n: usize) -> Vec<usize> {
194 let r = self.remainder();
195 let x = cmp::min(n, r);
196 self.stock.split_off(r - x)
197 }
198}
199
200static mut SETS: [[usize; 81]; 81] = [[0; 81]; 81];
206
207fn build_lookup() {
208 let cards = cards();
209
210 for (&a, &b) in (0..81).collect::<Vec<_>>().pairs() {
211 let c = (cards[a], cards[b]).complete_set().index();
212 unsafe {
213 SETS[a][b] = c;
214 SETS[b][a] = c;
216 }
217 }
218}
219
220#[inline(always)]
221fn is_set(a: usize, b: usize, c: usize) -> bool {
222 unsafe { *SETS.get_unchecked(a).get_unchecked(b) == c }
223}
224
225fn find_random_set(hand: &[usize]) -> Option<(usize, usize, usize)> {
226 let mut sets = Vec::new();
227
228 for x in 2..hand.len() {
229 let a = hand[x];
230 for y in 1..x {
231 let b = hand[y];
232 for &c in hand.iter().take(y) {
233 if is_set(a, b, c) {
234 sets.push((a, b, c));
235 }
236 }
237 }
238 }
239
240 if sets.is_empty() {
241 None
242 } else {
243 let mut rng = thread_rng();
244 let random_ix = rng.gen_range(0, sets.len());
245 Some(sets[random_ix])
246 }
247}
248
249fn simulate_game(counts: &mut Counts) {
254 let mut deck = IndexDeck::new();
255 let mut hand = deck.draw(INITIAL_DEAL);
256
257 'game: loop {
258 if let Some((a, b, c)) = find_random_set(&hand) {
259 counts.sets[hand.len()] += 1;
260
261 hand.retain(|&x| x != a && x != b && x != c);
263
264 if hand.len() < INITIAL_DEAL {
265 hand.append(&mut deck.draw(SET_SIZE));
267 }
268 } else {
269 counts.no_sets[hand.len()] += 1;
270
271 if deck.is_empty() {
272 counts.remainder[hand.len()] += 1;
274 break 'game;
275 } else {
276 hand.append(&mut deck.draw(SET_SIZE));
278 }
279 }
280 }
281}
282
283fn run_simulations(num_games: u64, num_threads: u64) {
284 let start_time = PreciseTime::now();
285 let (tx, rx) = mpsc::channel();
286 let (thread_chunk, rem) = (num_games / num_threads, num_games % num_threads);
287
288 build_lookup();
290
291 for ix in 0..num_threads {
293 let tx = tx.clone();
294 let num = thread_chunk + if ix == 0 { rem } else { 0 };
295
296 thread::spawn(move || {
297 let mut counts = Counts::zero();
298 for _ in 0..num {
299 simulate_game(&mut counts)
300 }
301 tx.send(counts).unwrap();
302 });
303 }
304
305 let mut totals = Counts::zero();
307 for _ in 0..num_threads {
308 let counts = rx.recv().unwrap();
309 totals.add(&counts);
310 }
311
312 println!("{} seconds elapsed.\n", start_time.to(PreciseTime::now()));
314 totals.print_hand_stats();
315 println!();
316 totals.print_end_game_stats();
317}
318
319fn main() {
324 let games_help = &format!(
325 "Sets number of games to simulate (default: {})",
326 pretty_print(NUM_GAMES)
327 );
328
329 let matches = clap_app!(simulate =>
330 (version: VERSION)
331 (about: "Gather statistics for simulated games of SET.")
332 (@arg GAMES: -g --games +takes_value games_help)
333 (@arg THREADS: -t --threads +takes_value "Sets number of threads")
334 ).get_matches();
335
336 let num_games = value_t!(matches, "GAMES", u64).unwrap_or(NUM_GAMES);
337 let num_threads = value_t!(matches, "THREADS", u64).unwrap_or(num_cpus::get() as u64);
338
339 println!(
340 "Simulating {} games. This may take some time...",
341 pretty_print(num_games)
342 );
343 run_simulations(num_games, num_threads);
344}