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