1use enum_iterator::{all, Sequence};
29use rand::prelude::SliceRandom;
30use serde::{Deserialize, Serialize};
31use std::fmt::Display;
32use tracing::debug;
33use tracing::warn;
34
35#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
37pub struct Card {
38 pub suit: Suit,
39 pub rank: Rank,
40}
41
42#[derive(Debug, PartialEq, Sequence, Clone, Copy, Serialize, Deserialize)]
44pub enum Suit {
45 Clubs,
46 Diamonds,
47 Hearts,
48 Spades,
49}
50
51#[derive(
53 Debug,
54 PartialEq,
55 Eq,
56 Hash,
57 Sequence,
58 Clone,
59 Copy,
60 PartialOrd,
61 Ord,
62 Serialize,
63 Deserialize
64)]
65pub enum Rank {
66 Two,
67 Three,
68 Four,
69 Five,
70 Six,
71 Seven,
72 Eight,
73 Nine,
74 Ten,
75 Jack,
76 Queen,
77 King,
78 Ace,
79}
80
81impl Display for Rank {
82 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
83 let s = match self {
84 Rank::Two => "Two",
85 Rank::Three => "Three",
86 Rank::Four => "Four",
87 Rank::Five => "Five",
88 Rank::Six => "Six",
89 Rank::Seven => "Seven",
90 Rank::Eight => "Eight",
91 Rank::Nine => "Nine",
92 Rank::Ten => "Ten",
93 Rank::Jack => "Jack",
94 Rank::Queen => "Queen",
95 Rank::King => "King",
96 Rank::Ace => "Ace",
97 };
98 write!(f, "{}", s)
99 }
100}
101
102#[derive(Debug, Serialize, Deserialize)]
104pub struct Deck {
105 pub cards: Vec<Card>,
106}
107
108#[derive(Debug, Serialize, Deserialize, Clone)]
110pub struct IncompleteBook {
111 pub rank: Rank,
112 pub cards: Vec<Card>,
113}
114
115#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
117pub struct CompleteBook {
118 pub rank: Rank,
119 pub cards: [Card; 4],
120}
121
122#[derive(Debug, Serialize, Deserialize, Clone)]
124pub struct Hand {
125 pub books: Vec<IncompleteBook>,
126}
127
128#[derive(Debug, Serialize, Deserialize, Clone)]
130pub struct Player {
131 pub id: PlayerId,
132 pub hand: Hand,
133 pub completed_books: Vec<CompleteBook>,
134}
135
136#[derive(Debug, Serialize, Deserialize, Clone)]
139pub struct InactivePlayer {
140 pub id: PlayerId,
141 pub completed_books: Vec<CompleteBook>,
142}
143
144#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Hash)]
145pub struct PlayerId(u8);
146
147impl PlayerId {
148 pub fn new(id: u8) -> PlayerId {
149 PlayerId(id)
150 }
151}
152
153#[derive(Debug, Serialize, Deserialize)]
156pub struct Hook {
157 pub target: PlayerId,
158 pub rank: Rank,
159}
160
161#[derive(Debug)]
162pub enum TurnError {
163 TargetNotFound(PlayerId),
165 GameIsFinished
166}
167
168#[derive(Debug, Serialize, Deserialize)]
170pub struct Game {
171 pub deck: Deck,
172 pub players: Vec<Player>,
173 pub inactive_players: Vec<InactivePlayer>,
174 pub is_finished: bool,
175 player_turn: usize,
176}
177
178impl Deck {
179 pub fn new() -> Deck {
181 let cards = Self::ordered_cards();
182 Deck { cards }
183 }
184
185 pub fn shuffle(mut self) -> Self {
187 let mut rng = rand::rng();
188 self.cards.shuffle(&mut rng);
189 self
190 }
191
192 pub fn is_empty(&self) -> bool {
193 self.cards.is_empty()
194 }
195
196 pub fn len(&self) -> usize {
197 self.cards.len()
198 }
199
200 pub fn draw(&mut self) -> Option<Card> {
208 self.cards.pop()
209 }
210
211 fn ordered_cards() -> Vec<Card> {
212 let cards_from_suit = |suit| {
213 all::<Rank>()
214 .map(|rank| Card { suit, rank })
215 .collect::<Vec<_>>()
216 };
217 all::<Suit>().flat_map(cards_from_suit).collect::<Vec<_>>()
218 }
219}
220
221impl Default for Deck {
222 fn default() -> Self {
223 Self::new()
224 }
225}
226
227impl IncompleteBook {
228 pub(crate) fn combine(self, other: IncompleteBook) -> CombineBookResult {
230 if self.rank != other.rank {
231 return CombineBookResult::NotCombined(self, other);
232 }
233
234 let rank = self.rank;
235 let combined_cards = self
236 .cards
237 .into_iter()
238 .chain(other.cards)
239 .collect::<Vec<_>>();
240
241 if combined_cards.len() == 4 {
242 let cards = [
243 combined_cards[0],
244 combined_cards[1],
245 combined_cards[2],
246 combined_cards[3],
247 ];
248 let complete_book = CompleteBook { rank, cards };
249 return CombineBookResult::Completed(complete_book);
250 }
251
252 let combined_book = IncompleteBook {
253 rank,
254 cards: combined_cards,
255 };
256 CombineBookResult::Combined(combined_book)
257 }
258}
259
260impl From<Card> for IncompleteBook {
261 fn from(card: Card) -> Self {
262 let rank = card.rank;
263 let cards = vec![card];
264 IncompleteBook { rank, cards }
265 }
266}
267
268#[derive(Debug, Serialize, Deserialize, Clone)]
269pub struct GameResult {
270 pub winners: Vec<InactivePlayer>,
271 pub losers: Vec<InactivePlayer>,
272}
273
274#[derive(Debug, Serialize, Deserialize, Clone)]
275pub enum HookResult {
276 Catch(IncompleteBook),
277 GoFish,
278}
279
280#[derive(Debug, Serialize, Deserialize, Clone)]
282pub struct HookOutcome {
283 pub fisher: PlayerId,
284 pub target: PlayerId,
285 pub rank: Rank,
286 pub result: HookResult,
287}
288
289impl Hand {
290 pub fn empty() -> Hand {
292 let books = vec![];
293 Hand { books }
294 }
295
296 pub fn add_book(&mut self, book: IncompleteBook) -> Option<CompleteBook> {
298 let position = self.books.iter().position(|b| b.rank == book.rank);
299 let position = match position {
300 Some(position) => position,
301 None => {
302 self.books.push(book);
303 return None;
304 }
305 };
306
307 let existing_book = self.books.remove(position);
308 let combined_result = existing_book.combine(book);
309
310 match combined_result {
311 CombineBookResult::NotCombined(a, b) => {
312 warn!("Books {:?} and {:?} failed to combine, but we expect them to have the same rank", a, b);
313 None
314 }
315 CombineBookResult::Combined(combined_book) => {
316 self.books.push(combined_book);
317 None
318 }
319 CombineBookResult::Completed(completed_book) => Some(completed_book),
320 }
321 }
322
323 pub(crate) fn receive_hook(&mut self, rank: Rank) -> HookResult {
324 let position = self.books.iter().position(|b| b.rank == rank);
325 let position = match position {
326 None => return HookResult::GoFish,
327 Some(pos) => pos,
328 };
329
330 let catch = self.books.remove(position);
331 HookResult::Catch(catch)
332 }
333}
334
335impl Player {
336 pub(crate) fn add_book(&mut self, book: IncompleteBook) {
337 let completed_book = self.hand.add_book(book);
338
339 if let Some(completed_book) = completed_book {
340 self.completed_books.push(completed_book);
341 }
342 }
343
344 pub(crate) fn receive_hook(&mut self, rank: Rank) -> HookResult {
345 self.hand.receive_hook(rank)
346 }
347}
348
349impl Game {
350 pub fn new(deck: Deck, player_count: u8) -> Game {
352 debug!(?deck, player_count, "Creating new Game");
353 let hand_size = match player_count {
354 2 | 3 => 7,
355 _ => 5,
356 };
357
358 let mut players: Vec<Player> = vec![];
359 let mut deck = deck;
360 for n in 0..player_count {
361 let player = Self::deal_player(PlayerId(n), hand_size, &mut deck);
362 players.push(player);
363 }
364
365 Game {
366 deck,
367 players,
368 is_finished: false,
369 inactive_players: vec![],
370 player_turn: 0,
371 }
372 }
373
374 pub fn take_turn(&mut self, hook: Hook) -> Result<HookOutcome, TurnError> {
376 debug!(game.players = ?self.players,
377 game.inactive_players = ?self.inactive_players,
378 game.is_deck_empty = self.deck.is_empty(),
379 game.player_turn = self.player_turn,
380 game.is_finished = self.is_finished,
381 ?hook,
382 "Taking turn");
383 if self.is_finished {
384 warn!("Taking turn when game is finished");
385 return Err(TurnError::GameIsFinished);
386 }
387
388 let player_order = self.players.iter().map(|p| p.id).collect::<Vec<PlayerId>>();
389
390 let (mut fisher, target) =
391 Self::find_hook_players(&mut self.players, self.player_turn, hook.target);
392 let fisher_id = fisher.id;
393
394 let mut target = match target {
395 Some(target) => target,
396 None => {
397 self.players.push(fisher);
398 Self::reorder_players(&mut self.players, &player_order);
399 debug!(hook.target = hook.target.0, "Target player was not found");
400 return Err(TurnError::TargetNotFound(hook.target));
401 }
402 };
403
404 let result = target.receive_hook(hook.rank);
405 debug!(?target, ?hook, ?result, "Target received hook");
406
407 match result.clone() {
408 HookResult::Catch(catch) => {
409 fisher.add_book(catch);
410 let fisher = match fisher.hand.books.is_empty() {
411 true => Self::handle_active_player_has_empty_hand(fisher, &mut self.deck),
412 false => PlayerEmptyHandOutcome::Active(fisher),
413 };
414
415 debug!(?fisher, "Handled fisher hand state");
416 match fisher {
417 PlayerEmptyHandOutcome::Active(fisher) => {
418 self.players.push(fisher);
419 self.players.push(target);
420 Self::reorder_players(&mut self.players, &player_order);
421 }
422 PlayerEmptyHandOutcome::Inactive(fisher) => {
423 self.players.push(target);
424 Self::reorder_players(&mut self.players, &player_order);
425 self.inactive_players.push(fisher);
426 self.player_turn = match self.player_turn {
427 0 => self.players.len() - 1,
428 n => n - 1,
429 };
430
431 self.advance_player_turn()
432 }
433 }
434 }
435 HookResult::GoFish => {
436 let draw = self.deck.draw();
437 if let Some(card) = draw {
438 fisher.add_book(card.into())
439 }
440
441 self.players.push(fisher);
442 self.players.push(target);
443 Self::reorder_players(&mut self.players, &player_order);
444 self.advance_player_turn()
445 }
446 };
447
448 if self.players.is_empty() {
449 self.is_finished = true;
450 }
451
452 debug!(game.players = ?self.players,
453 game.inactive_players = ?self.inactive_players,
454 game.is_deck_empty = self.deck.is_empty(),
455 game.player_turn = self.player_turn,
456 game.is_finished = self.is_finished,
457 "Finished taking turn");
458
459 Ok(HookOutcome { fisher: fisher_id, target: hook.target, rank: hook.rank, result })
460 }
461
462 pub fn get_current_player(&self) -> Option<&Player> {
464 if self.is_finished {
465 return None;
466 }
467 if self.players.is_empty() {
468 warn!("Current game has no current player, is not finished");
469 return None;
470 }
471
472 let player = self.players.get(self.player_turn);
473 if player.is_none() {
474 warn!(player_turn = self.player_turn, players = ?self.players, "player_turn index is out of bounds");
475 }
476
477 player
478 }
479
480 pub fn get_game_result(&self) -> Option<GameResult> {
481 if !self.is_finished {
482 return None;
483 }
484
485 let max_books = self.inactive_players.iter().map(|p| p.completed_books.len()).max().unwrap();
486 let mut winners = vec![];
487 let mut losers = vec![];
488 for player in self.inactive_players.clone().into_iter() {
489 if player.completed_books.len() == max_books {
490 winners.push(player);
491 } else {
492 losers.push(player);
493 }
494 };
495 Some(GameResult { winners, losers })
496 }
497
498 fn deal_player(id: PlayerId, hand_size: usize, deck: &mut Deck) -> Player {
499 let mut hand = Hand::empty();
500 let mut completed_books = vec![];
501
502 for _ in 0..hand_size {
503 let draw = deck.draw();
504 let book = IncompleteBook::from(draw.unwrap());
505 let completed_book = hand.add_book(book);
506 if let Some(c) = completed_book {
507 completed_books.push(c);
508 }
509 }
510
511 Player {
512 id,
513 hand,
514 completed_books,
515 }
516 }
517
518 fn advance_player_turn(&mut self) {
519 if self.players.is_empty() {
520 return;
521 }
522
523 if self.players.len() == 1 {
524 let player = self.players.remove(0);
525 if !player.hand.books.is_empty() {
526 panic!("Shouldn't get here I don't think")
527 }
528 let player = InactivePlayer { id: player.id, completed_books: player.completed_books };
529 self.inactive_players.push(player);
530 return;
531 }
532
533 let mut new_turn = (self.player_turn + 1) % self.players.len();
534 let player_order = self.players.iter().map(|p| p.id).collect::<Vec<PlayerId>>();
535
536 for _ in 1..self.players.len() {
537 let current_player = self.players.remove(new_turn);
538 let result = match current_player.hand.books.is_empty() {
539 true => Self::handle_active_player_has_empty_hand(current_player, &mut self.deck),
540 false => PlayerEmptyHandOutcome::Active(current_player),
541 };
542 let found_new_player = match result {
543 PlayerEmptyHandOutcome::Active(player) => {
544 self.players.push(player);
545 Self::reorder_players(&mut self.players, &player_order);
546 true
547 }
548 PlayerEmptyHandOutcome::Inactive(player) => {
549 self.inactive_players.push(player);
550 Self::reorder_players(&mut self.players, &player_order);
551 false
552 }
553 };
554
555 if found_new_player {
556 break;
557 }
558
559 new_turn = (new_turn + 1) % self.players.len();
560 }
561
562 if self.players.len() == 1 {
563 let player = self.players.remove(0);
564 if !player.hand.books.is_empty() {
565 panic!("Shouldn't get here I don't think")
566 }
567 let player = InactivePlayer { id: player.id, completed_books: player.completed_books };
568 self.inactive_players.push(player);
569 return;
570 }
571
572 self.player_turn = new_turn;
573 }
574
575 fn handle_active_player_has_empty_hand(
576 mut player: Player,
577 deck: &mut Deck,
578 ) -> PlayerEmptyHandOutcome {
579 let draw = deck.draw();
580 match draw {
581 Some(card) => {
582 player.add_book(IncompleteBook::from(card));
583 PlayerEmptyHandOutcome::Active(player)
584 }
585 None => PlayerEmptyHandOutcome::Inactive(InactivePlayer {
586 id: player.id,
587 completed_books: player.completed_books,
588 }),
589 }
590 }
591
592 fn find_hook_players(
593 players: &mut Vec<Player>,
594 current_player_index: usize,
595 target_id: PlayerId,
596 ) -> (Player, Option<Player>) {
597 let fisher = players.remove(current_player_index);
598
599 let target_index = players.iter().position(|p| p.id == target_id);
600 let target = match target_index {
601 Some(index) => players.remove(index),
602 None => return (fisher, None),
603 };
604
605 (fisher, Some(target))
606 }
607
608 fn reorder_players(players: &mut [Player], order: &[PlayerId]) {
609 players.sort_by_key(|p| order.iter().position(|pos| &p.id == pos).unwrap());
610 }
611}
612
613#[derive(Debug)]
614pub(crate) enum CombineBookResult {
615 Combined(IncompleteBook),
616 NotCombined(IncompleteBook, IncompleteBook),
617 Completed(CompleteBook),
618}
619
620#[derive(Debug)]
621enum PlayerEmptyHandOutcome {
622 Active(Player),
623 Inactive(InactivePlayer),
624}
625
626#[cfg(feature = "bots")]
627pub mod bots;
628
629#[cfg(test)]
630mod game_tests;