1 use rand::seq::SliceRandom;
4 pub const NUM_RANKS: usize = 13;
5 pub const NUM_SUITS: usize = 4;
6 pub const NUM_JOKERS: usize = 2;
7 pub const NUM_CARDS: usize = NUM_RANKS * NUM_SUITS + NUM_JOKERS;
9 pub const STARTING_CARDS: u8 = 3;
10 pub const STARTING_MAD_SCIENCE_TOKENS: i8 = 15;
11 pub const STARTING_PROGRESS: i8 = -10;
13 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
17 pub fn value(&self) -> u8 {
21 pub fn is_face(&self) -> bool {
25 pub fn random() -> Self {
28 .gen_range(0..NUM_RANKS)
30 .expect("Too many ranks?"),
35 #[derive(Clone, Copy, Eq, PartialEq)]
38 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
42 pub fn is_joker(&self) -> bool {
43 usize::from(self.0) >= NUM_RANKS * NUM_SUITS
46 pub fn rank(&self) -> Option<Rank> {
47 (!self.is_joker()).then_some(Rank(self.0 >> 2))
50 pub fn suit(&self) -> Option<Suit> {
51 (!self.is_joker()).then_some(Suit(self.0 & 3))
55 #[derive(Clone, Copy)]
56 pub enum WithOrWithoutJokers {
62 pub fn deck(j: WithOrWithoutJokers) -> Vec<Card> {
63 let limit = u8::try_from(match j {
64 WithOrWithoutJokers::WithJokers => NUM_CARDS,
65 WithOrWithoutJokers::WithoutJokers => NUM_SUITS * NUM_RANKS,
67 .expect("Too many cards?");
68 (0..limit).map(Card).collect()
71 fn shuffle(cards: &mut Vec<Card>) {
72 cards.shuffle(&mut rand::thread_rng());
75 fn shuffled(mut cards: Vec<Card>) -> Vec<Card> {
80 #[derive(Clone, Copy, Debug)]
81 pub struct PathLength(Rank);
84 pub fn random() -> Self {
89 #[derive(Clone, Copy, Default)]
90 pub struct PathLengthInfo(u16);
93 pub fn is_showing(&self, i: Rank) -> bool {
94 (self.0 >> i.0) & 1 == 1
96 fn reveal(&mut self, i: Rank) {
99 pub fn reveal_random(&mut self, true_length: PathLength) -> Option<Rank> {
100 let showing = usize::try_from(self.0.count_ones()).expect("There aren't that many bits");
101 let not_showing = NUM_RANKS - showing;
102 if not_showing <= 1 {
106 let mut show = rand::thread_rng().gen_range(0..not_showing - 1);
107 for i in 0..NUM_RANKS {
108 let r = Rank(u8::try_from(i).expect("Too many cards?"));
109 if !self.is_showing(r) && r != true_length.0 {
126 pub fn discard(&mut self, card: Card) {
127 self.cards.push(card);
130 pub fn top(&self) -> Option<&Card> {
133 fn len(&self) -> usize {
143 pub fn new(cards: Vec<Card>) -> Self {
146 pub fn draw(&mut self, discard: &mut Discard) -> Option<Card> {
147 if self.cards.is_empty() {
148 if let Some(top_discard) = discard.cards.pop() {
149 std::mem::swap(&mut self.cards, &mut discard.cards);
150 discard.discard(top_discard);
151 shuffle(&mut self.cards);
156 fn len(&self) -> usize {
161 #[derive(Debug, Default)]
166 fn add(&mut self, card: Card) {
167 self.cards.push(card);
169 fn remove(&mut self, card: Card) -> Result<(), &'static str> {
173 .position(|&e| e == card)
174 .ok_or("That card is not in your hand")?;
175 self.cards.swap_remove(i);
178 fn len(&self) -> usize {
181 fn random(&self) -> Option<&Card> {
182 self.cards.choose(&mut rand::thread_rng())
186 #[derive(Copy, Clone)]
187 pub struct PlayerIndex(usize);
189 fn next(self, num_players: usize) -> Self {
190 Self((self.0 + 1) % num_players)
194 #[derive(Copy, Clone, Debug)]
200 #[derive(Eq, PartialEq)]
207 pub enum GameOutcome {
212 pub enum PlayOutcome {
218 mad_science_tokens: i8,
219 progress: [i8; NUM_SUITS],
220 path_lengths: [PathLength; NUM_SUITS],
221 path_length_info: [PathLengthInfo; NUM_SUITS],
229 pub fn add_player(&mut self) {
230 self.hands.push(Hand::default());
231 for _ in 0..STARTING_CARDS {
232 self.draw_for_player(PlayerIndex(self.hands.len() - 1));
237 /// Will return `Err` on invalid plays, like trying to draw during Play phase,
238 /// or trying to play a card that's not in your hand.
239 pub fn play(&mut self, play: Play) -> Result<PlayOutcome, &'static str> {
241 Play::Play(card) => self.play_card(card),
242 Play::Draw => self.draw_for_momentum(),
247 pub fn current_player_hand(&self) -> &Hand {
248 &self.hands[self.turn.0]
250 fn player_hand_mut(&mut self, pi: PlayerIndex) -> &mut Hand {
251 &mut self.hands[pi.0]
253 fn current_player_hand_mut(&mut self) -> &mut Hand {
254 self.player_hand_mut(self.turn)
257 fn play_card(&mut self, card: Card) -> Result<PlayOutcome, &'static str> {
258 let momentum = self.apply_card(card)?;
259 if self.phase == Phase::Play && momentum {
260 self.phase = Phase::Momentum;
261 Ok(PlayOutcome::Continue)
263 Ok(self.end_of_turn())
266 fn draw_for_momentum(&mut self) -> Result<PlayOutcome, &'static str> {
267 if self.phase != Phase::Momentum {
268 return Err("You don't have momentum");
270 self.draw_for_player(self.turn);
271 Ok(self.end_of_turn())
274 fn draw_for_player(&mut self, pi: PlayerIndex) {
276 if let Some(card) = self.library.draw(&mut self.discard) {
278 self.remove_mad_science_token();
279 self.discard.discard(card);
281 self.player_hand_mut(pi).add(card);
285 println!("Library ran out of cards");
289 fn remove_mad_science_token(&mut self) {
291 self.mad_science_tokens -= 1;
292 if self.mad_science_tokens != 0 {
297 fn make_progress(&mut self, card: Card) {
298 let rank = card.rank().expect("Can't play jokers").0;
300 let roll = rand::thread_rng().gen_range(1..=6);
302 self.remove_mad_science_token();
305 self.progress[usize::from(card.suit().expect("Can't play jokers").0)] += 1;
307 fn forecast(&mut self, card: Card) {
308 let suit = usize::from(card.suit().expect("Can't play jokers").0);
309 self.path_length_info[suit].reveal_random(self.path_lengths[suit]);
311 // Returns whether or not this play grants momentum
312 fn apply_card(&mut self, card: Card) -> Result<bool, &'static str> {
313 self.current_player_hand_mut().remove(card)?;
314 if card.rank().expect("Can't play jokers").is_face() {
317 self.make_progress(card);
319 let suits_match = self
322 .map_or(false, |dis| dis.suit() == card.suit());
323 self.discard.discard(card);
326 fn valid(&self) -> bool {
327 108 == (self.library.len()
329 + self.hands.iter().map(Hand::len).sum::<usize>())
331 fn roll_mad_science(&mut self) -> PlayOutcome {
332 let mut tokens = std::iter::from_fn(|| Some(rand::thread_rng().gen_bool(0.5)))
333 .take(usize::try_from(self.mad_science_tokens.abs()).expect("wat?"));
334 let keep_going = if self.mad_science_tokens > 0 {
340 PlayOutcome::Continue
342 PlayOutcome::End(self.final_score())
345 fn final_score(&self) -> GameOutcome {
349 .zip(self.path_lengths.iter())
350 .any(|(&prog, len)| prog >= len.0.value().try_into().expect("wat?"))
357 fn end_of_turn(&mut self) -> PlayOutcome {
358 assert!(self.valid());
359 self.phase = Phase::Play;
360 self.turn = self.turn.next(self.hands.len());
361 if self.turn.0 == 0 {
362 if let PlayOutcome::End(game_outcome) = self.roll_mad_science() {
363 return PlayOutcome::End(game_outcome);
366 self.draw_for_player(self.turn);
367 assert!(self.valid());
368 PlayOutcome::Continue
371 impl Default for Game {
372 fn default() -> Self {
374 mad_science_tokens: STARTING_MAD_SCIENCE_TOKENS,
375 progress: [STARTING_PROGRESS; NUM_SUITS],
376 path_lengths: std::iter::from_fn(|| Some(PathLength::random()))
381 path_length_info: [PathLengthInfo::default(); NUM_SUITS],
382 library: Library::new(shuffled(
384 deck(WithOrWithoutJokers::WithJokers),
385 deck(WithOrWithoutJokers::WithJokers),
389 discard: Discard::default(),
391 turn: PlayerIndex(0),
397 pub struct Player(Box<dyn FnMut(&Game) -> Play>);
400 pub fn new<T>(f: T) -> Self
402 T: FnMut(&Game) -> Play + 'static,
409 pub fn random_player(game: &Game) -> Play {
411 Phase::Play => Play::Play(
413 .current_player_hand()
415 .expect("I always have a card to play because I just drew one"),
418 if rand::thread_rng().gen_bool(0.5) {
421 match game.current_player_hand().random() {
422 Some(card) => Play::Play(*card),
432 /// Will return `Err` on invalid plays, like trying to draw during Play phase,
433 /// or trying to play a card that's not in your hand.
434 pub fn play(mut game: Game, mut players: Vec<Player>) -> Result<GameOutcome, &'static str> {
435 game.draw_for_player(game.turn);
437 if let PlayOutcome::End(game_outcome) = game.play(players[game.turn.0].0(&game))? {
438 return Ok(game_outcome);
448 fn path_length_info_random_reveal() {
449 let length = PathLength(Rank(7));
450 let mut pli = PathLengthInfo::default();
452 let old_pli = PathLengthInfo::clone(&pli);
453 match pli.reveal_random(length) {
454 None => panic!("Nothing revealed?"),
456 assert!(!old_pli.is_showing(r));
457 assert!(pli.is_showing(r));
460 assert_eq!(pli.0.count_ones(), 1 + old_pli.0.count_ones());
462 assert!(pli.reveal_random(length).is_none());
467 use WithOrWithoutJokers::*;
468 let d = deck(WithoutJokers);
469 let rank_sum: u32 = d
473 .map(|r| u32::from(r.value()))
475 assert_eq!(rank_sum, 364);
476 let _dj = deck(WithJokers);
481 let mut lib = Library::new(vec![Card(7)]);
482 let mut dis = Discard::default();
483 dis.discard(Card(8));
484 dis.discard(Card(9));
485 assert_eq!(lib.draw(&mut dis), Some(Card(7)));
486 assert_eq!(lib.draw(&mut dis), Some(Card(8)));
487 assert_eq!(lib.draw(&mut dis), None);
492 let mut h = Hand::default();
493 assert!(h.remove(Card(4)).is_err());
495 assert!(h.remove(Card(3)).is_err());
496 assert!(h.remove(Card(4)).is_ok());
497 assert!(h.remove(Card(4)).is_err());
502 for num_players in 1..10 {
503 let players: Vec<_> = std::iter::from_fn(|| Some(Player::new(random_player)))
506 let mut game = Game::default();
507 for _ in 0..num_players {
510 assert!(play(game, players).is_ok());