lib_hearts/
lib.rs

1use std::{cmp::Ordering, error::Error, fmt::Display, usize};
2
3use rand::prelude::*;
4use serde_derive::{Deserialize, Serialize};
5use uuid::Uuid;
6
7pub const DECK_SIZE: usize = 52;
8pub const PLAYER_CARD_SIZE: usize = 13;
9pub const NUMBER_REPLACEABLE_CARDS: usize = 3;
10pub const PLAYER_NUMBER: usize = 4;
11pub const CARD_TO_START: Card = Card::Number(2, TypeCard::Club, "🃒");
12pub const QUEEN_OF_SPADE: Card = Card::Queen(TypeCard::Spade, "🂭");
13pub const ACE_OF_HEARTS: Card = Card::Ace(TypeCard::Heart, "🂱");
14
15pub const MAX_SCORE: usize = 26;
16
17const GREATER: Option<Ordering> = Some(Ordering::Greater);
18const LESS: Option<Ordering> = Some(Ordering::Less);
19const EQUAL: Option<Ordering> = Some(Ordering::Equal);
20
21pub type PositionInDeck = usize;
22
23pub fn get_card_by_idx(idx: usize) -> &'static Card {
24    &DECK.0[idx]
25}
26
27#[derive(Debug)]
28struct StackState {
29    first_card_played_pos: usize,
30    current_losing_player_pos: usize,
31    current_losing_card_pos: usize,
32    score: usize,
33}
34
35#[derive(Clone, Copy, Serialize, Debug, Deserialize, PartialEq, Eq)]
36pub struct PlayerState {
37    pub player_id: Uuid,
38    pub score: usize,
39}
40
41#[derive(Debug, Serialize, Copy, Clone, Deserialize, PartialEq)]
42#[serde(rename_all = "UPPERCASE")]
43pub enum Card {
44    Queen(TypeCard, &'static str),
45    King(TypeCard, &'static str),
46    Jack(TypeCard, &'static str),
47    Ace(TypeCard, &'static str),
48    Number(u8, TypeCard, &'static str),
49}
50
51impl Display for Card {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        let emoji = self.get_emoji();
54        write!(f, "{emoji}")
55    }
56}
57
58impl Display for Player {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        let card_emojis = self
61            .get_cards()
62            .map(|c| if let Some(c) = c { c.get_emoji() } else { "-" })
63            .join(" ");
64        write!(f, "Player {}: {}", self.id, card_emojis)
65    }
66}
67
68impl Card {
69    pub const fn get_emoji(&self) -> &'static str {
70        match self {
71            Card::King(_, emoji)
72            | Card::Jack(_, emoji)
73            | Card::Queen(_, emoji)
74            | Card::Ace(_, emoji)
75            | Card::Number(_, _, emoji) => emoji,
76        }
77    }
78    pub const fn get_value(&self) -> usize {
79        match self {
80            Card::Queen(TypeCard::Spade, _) => 13,
81            Card::Ace(TypeCard::Heart, _)
82            | Card::King(TypeCard::Heart, _)
83            | Card::Queen(TypeCard::Heart, _)
84            | Card::Jack(TypeCard::Heart, _)
85            | Card::Number(_, TypeCard::Heart, _) => 1,
86            _ => 0,
87        }
88    }
89
90    pub const fn get_type(&self) -> &TypeCard {
91        match self {
92            Card::Queen(TypeCard::Heart, _)
93            | Card::King(TypeCard::Heart, _)
94            | Card::Ace(TypeCard::Heart, _)
95            | Card::Number(_, TypeCard::Heart, _)
96            | Card::Jack(TypeCard::Heart, _) => &TypeCard::Heart,
97            Card::King(TypeCard::Diamond, _)
98            | Card::Queen(TypeCard::Diamond, _)
99            | Card::Ace(TypeCard::Diamond, _)
100            | Card::Number(_, TypeCard::Diamond, _)
101            | Card::Jack(TypeCard::Diamond, _) => &TypeCard::Diamond,
102            Card::King(TypeCard::Spade, _)
103            | Card::Queen(TypeCard::Spade, _)
104            | Card::Ace(TypeCard::Spade, _)
105            | Card::Number(_, TypeCard::Spade, _)
106            | Card::Jack(TypeCard::Spade, _) => &TypeCard::Spade,
107            Card::King(TypeCard::Club, _)
108            | Card::Queen(TypeCard::Club, _)
109            | Card::Ace(TypeCard::Club, _)
110            | Card::Number(_, TypeCard::Club, _)
111            | Card::Jack(TypeCard::Club, _) => &TypeCard::Club,
112        }
113    }
114}
115impl PartialOrd for Card {
116    // FIXME this is shit
117    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
118        match (self.get_type(), other.get_type()) {
119            (TypeCard::Heart, TypeCard::Spade)
120            | (TypeCard::Heart, TypeCard::Diamond)
121            | (TypeCard::Heart, TypeCard::Club) => return GREATER,
122            (TypeCard::Spade, TypeCard::Heart)
123            | (TypeCard::Diamond, TypeCard::Heart)
124            | (TypeCard::Club, TypeCard::Heart) => return LESS,
125            _ => {}
126        }
127        match (self, other) {
128            (Card::Queen(_, _), Card::Queen(_, _)) => EQUAL,
129            (Card::Queen(_, _), Card::King(_, _)) => LESS,
130            (Card::Queen(_, _), Card::Jack(_, _)) => GREATER,
131            (Card::Queen(_, _), Card::Ace(_, _)) => LESS,
132            (Card::Queen(_, _), Card::Number(_, _, _)) => GREATER,
133            (Card::King(_, _), Card::Queen(_, _)) => GREATER,
134            (Card::King(_, _), Card::King(_, _)) => EQUAL,
135            (Card::King(_, _), Card::Jack(_, _)) => GREATER,
136            (Card::King(_, _), Card::Ace(_, _)) => LESS,
137            (Card::King(_, _), Card::Number(_, _, _)) => GREATER,
138            (Card::Jack(_, _), Card::Queen(_, _)) => LESS,
139            (Card::Jack(_, _), Card::King(_, _)) => LESS,
140            (Card::Jack(_, _), Card::Jack(_, _)) => EQUAL,
141            (Card::Jack(_, _), Card::Ace(_, _)) => LESS,
142            (Card::Jack(_, _), Card::Number(_, _, _)) => GREATER,
143            (Card::Ace(_, _), Card::Queen(_, _)) => GREATER,
144            (Card::Ace(_, _), Card::King(_, _)) => GREATER,
145            (Card::Ace(_, _), Card::Jack(_, _)) => GREATER,
146            (Card::Ace(_, _), Card::Ace(_, _)) => EQUAL,
147            (Card::Ace(_, _), Card::Number(_, _, _)) => GREATER,
148            (Card::Number(_, _, _), Card::Queen(_, _)) => LESS,
149            (Card::Number(_, _, _), Card::King(_, _)) => LESS,
150            (Card::Number(_, _, _), Card::Jack(_, _)) => LESS,
151            (Card::Number(_, _, _), Card::Ace(_, _)) => LESS,
152            (Card::Number(n1, _, _), Card::Number(n2, _, _)) => n1.partial_cmp(n2),
153        }
154    }
155}
156#[derive(Debug, Serialize, Copy, Clone, Deserialize, PartialEq)]
157#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
158pub enum TypeCard {
159    Heart,   // coeur
160    Spade,   // pique
161    Diamond, // careau
162    Club,    // treifle
163}
164
165#[derive(Debug)]
166pub struct Deck([Card; DECK_SIZE]);
167
168impl TryFrom<&str> for &Card {
169    type Error = GameError;
170
171    fn try_from(value: &str) -> Result<Self, Self::Error> {
172        if let Some(card) = DECK.0.iter().find(|c| c.get_emoji() == value) {
173            Ok(card)
174        } else {
175            Err(GameError::UnknownCard)
176        }
177    }
178}
179
180impl Deck {
181    pub const fn new() -> Self {
182        Deck([
183            CARD_TO_START,
184            Card::Number(3, TypeCard::Club, "🃓"),
185            Card::Number(4, TypeCard::Club, "🃔"),
186            Card::Number(5, TypeCard::Club, "🃕"),
187            Card::Number(6, TypeCard::Club, "🃖"),
188            Card::Number(7, TypeCard::Club, "🃗"),
189            Card::Number(8, TypeCard::Club, "🃘"),
190            Card::Number(9, TypeCard::Club, "🃙"),
191            Card::Number(10, TypeCard::Club, "🃚"),
192            Card::Jack(TypeCard::Club, "🃛"),
193            Card::Queen(TypeCard::Club, "🃝"),
194            Card::King(TypeCard::Club, "🃞"),
195            Card::Ace(TypeCard::Club, "🃑"),
196            Card::Number(2, TypeCard::Diamond, "🃂"),
197            Card::Number(3, TypeCard::Diamond, "🃃"),
198            Card::Number(4, TypeCard::Diamond, "🃄"),
199            Card::Number(5, TypeCard::Diamond, "🃅"),
200            Card::Number(6, TypeCard::Diamond, "🃆"),
201            Card::Number(7, TypeCard::Diamond, "🃇"),
202            Card::Number(8, TypeCard::Diamond, "🃈"),
203            Card::Number(9, TypeCard::Diamond, "🃉"),
204            Card::Number(10, TypeCard::Diamond, "🃊"),
205            Card::Jack(TypeCard::Diamond, "🃋"),
206            Card::Queen(TypeCard::Diamond, "🃍"),
207            Card::King(TypeCard::Diamond, "🃎"),
208            Card::Ace(TypeCard::Diamond, "🃁"),
209            Card::Number(2, TypeCard::Spade, "🂢"),
210            Card::Number(3, TypeCard::Spade, "🂣"),
211            Card::Number(4, TypeCard::Spade, "🂤"),
212            Card::Number(5, TypeCard::Spade, "🂥"),
213            Card::Number(6, TypeCard::Spade, "🂦"),
214            Card::Number(7, TypeCard::Spade, "🂧"),
215            Card::Number(8, TypeCard::Spade, "🂨"),
216            Card::Number(9, TypeCard::Spade, "🂩"),
217            Card::Number(10, TypeCard::Spade, "🂪"),
218            Card::Jack(TypeCard::Spade, "🂫"),
219            QUEEN_OF_SPADE,
220            Card::King(TypeCard::Spade, "🂮"),
221            Card::Ace(TypeCard::Spade, "🂡"),
222            Card::Number(2, TypeCard::Heart, "🂲"),
223            Card::Number(3, TypeCard::Heart, "🂳"),
224            Card::Number(4, TypeCard::Heart, "🂴"),
225            Card::Number(5, TypeCard::Heart, "🂵"),
226            Card::Number(6, TypeCard::Heart, "🂶"),
227            Card::Number(7, TypeCard::Heart, "🂷"),
228            Card::Number(8, TypeCard::Heart, "🂸"),
229            Card::Number(9, TypeCard::Heart, "🂹"),
230            Card::Number(10, TypeCard::Heart, "🂺"),
231            Card::Jack(TypeCard::Heart, "🂻"),
232            Card::Queen(TypeCard::Heart, "🂽"),
233            Card::King(TypeCard::Heart, "🂾"),
234            ACE_OF_HEARTS,
235        ])
236    }
237}
238
239const DECK: Deck = Deck::new();
240
241#[derive(Copy, Clone, Debug)]
242pub struct Player {
243    id: Uuid,
244    score: usize,
245    cards: [Option<usize>; PLAYER_CARD_SIZE], // card position in the deck
246    is_bot: bool,
247}
248
249impl Player {
250    pub fn new(id: Uuid, is_bot: bool, cards: [Option<usize>; PLAYER_CARD_SIZE]) -> Self {
251        Self {
252            id,
253            score: 0,
254            cards,
255            is_bot,
256        }
257    }
258    fn replace_cards(&mut self, new_cards: [usize; NUMBER_REPLACEABLE_CARDS]) {
259        let empty_slots: Vec<&mut Option<usize>> =
260            self.cards.iter_mut().filter(|c| c.is_none()).collect();
261
262        if empty_slots.len() != NUMBER_REPLACEABLE_CARDS {
263            panic!("{NUMBER_REPLACEABLE_CARDS} slots must be empty to replace cards!");
264        }
265        for (i, slot) in empty_slots.into_iter().enumerate() {
266            *slot = Some(new_cards[i]);
267        }
268    }
269    fn remove_card(&mut self, card_p: usize) -> Option<usize> {
270        let pos = self.cards.iter_mut().find(|p| p == &&Some(card_p));
271        if let Some(pos) = pos {
272            pos.take()
273        } else {
274            None
275        }
276    }
277
278    pub fn get_cards(&self) -> [Option<&Card>; 13] {
279        self.cards.map(|c| c.map(get_card_by_idx))
280    }
281    pub fn get_cards_and_pos_in_deck(&self) -> [Option<(PositionInDeck, &Card)>; 13] {
282        let mut cards = self.cards.map(|c| c.map(|c| (c, get_card_by_idx(c))));
283        cards.sort_by(|c1, c2| c1.map(|c11| c11.0).cmp(&c2.map(|c22| c22.0))); // sort by their
284                                                                               // position in deck
285        cards
286    }
287
288    pub fn has_card(&self, card_p: usize) -> bool {
289        self.cards.iter().any(|c| c == &Some(card_p))
290    }
291    pub fn get_score(&self) -> usize {
292        self.score
293    }
294    pub fn get_id(&self) -> Uuid {
295        self.id
296    }
297    pub fn is_bot(&self) -> bool {
298        self.is_bot
299    }
300}
301
302#[derive(Debug, PartialEq, Copy, Clone)]
303pub enum GameState {
304    ExchangeCards {
305        commands: [Option<(usize, [usize; NUMBER_REPLACEABLE_CARDS])>; PLAYER_NUMBER],
306    },
307    PlayingHand {
308        stack: [Option<(usize, usize)>; PLAYER_NUMBER],
309        current_scores: [usize; PLAYER_NUMBER],
310    },
311    ComputeScore {
312        stack: [Option<(usize, usize)>; PLAYER_NUMBER],
313        current_scores: [usize; PLAYER_NUMBER],
314    },
315    EndHand,
316
317    End,
318}
319impl From<&GameState> for &str {
320    fn from(value: &GameState) -> Self {
321        match value {
322            GameState::ExchangeCards { commands: _ } => "ExchangeCards",
323            GameState::PlayingHand {
324                stack: _,
325                current_scores: _,
326            } => "PlayingHand",
327            GameState::EndHand => "EndHand",
328            GameState::ComputeScore {
329                stack: _,
330                current_scores: _,
331            } => "ComputeScore",
332            GameState::End => "End",
333        }
334    }
335}
336
337#[derive(Debug, Copy, Clone)]
338pub struct Game {
339    pub players: [Player; PLAYER_NUMBER],
340    pub current_player_pos: usize,
341    pub current_hand: u8,
342    back_in_deck: [Option<usize>; DECK_SIZE],
343    pub state: GameState,
344    pub hands: u8,
345}
346
347impl Game {
348    pub fn new(player_builders: [(Uuid, bool); PLAYER_NUMBER], hands: u8) -> Self {
349        let players = player_builders.map(|(player_id, is_bot)| {
350            let player_cards: [Option<usize>; PLAYER_CARD_SIZE] = [None; PLAYER_CARD_SIZE];
351            Player::new(player_id, is_bot, player_cards)
352        });
353
354        let mut game = Self {
355            hands,
356            current_hand: 1,
357            players,
358            state: GameState::ExchangeCards {
359                commands: [None; PLAYER_NUMBER],
360            },
361            back_in_deck: [None; DECK_SIZE],
362            current_player_pos: 0,
363        };
364        game.deal_cards().expect("should be unreachable");
365        game
366    }
367
368    pub fn exchange_cards(
369        &mut self,
370        cards: [usize; NUMBER_REPLACEABLE_CARDS],
371    ) -> Result<(), GameError> {
372        match &mut self.state {
373            GameState::ExchangeCards { commands } if commands.iter().any(|c| c.is_none()) => {
374                let next = commands
375                    .iter_mut()
376                    .find(|c| c.is_none())
377                    .ok_or(GameError::StateError)?;
378                let player = self
379                    .players
380                    .get_mut(self.current_player_pos)
381                    .ok_or(GameError::StateError)?;
382                for card in &cards {
383                    if !player.has_card(*card) {
384                        return Err(GameError::ForbiddenMove);
385                    }
386                    player.remove_card(*card);
387                }
388                *next = Some((self.current_player_pos, cards));
389                if self.current_player_pos == PLAYER_NUMBER - 1 {
390                    self.current_player_pos = 0;
391                } else {
392                    self.current_player_pos += 1;
393                }
394                if commands.iter().all(|c| c.is_some()) {
395                    for command in commands {
396                        let (player_pos, command) = command.take().unwrap();
397                        let next_player_pos = if player_pos == PLAYER_NUMBER - 1 {
398                            0
399                        } else {
400                            player_pos + 1
401                        };
402                        let next_player = self.players.get_mut(next_player_pos).unwrap();
403                        next_player.replace_cards(command);
404                    }
405
406                    self.current_player_pos = self
407                        .players
408                        .iter()
409                        .enumerate()
410                        .find(|(_, p)| p.get_cards().contains(&Some(&CARD_TO_START)))
411                        .map(|(idx, _)| idx)
412                        .ok_or(GameError::StateError)?;
413                    self.state = GameState::PlayingHand {
414                        stack: [None; PLAYER_NUMBER],
415                        current_scores: [0; PLAYER_NUMBER],
416                    }
417                }
418
419                Ok(())
420            }
421            _ => Err(GameError::StateError),
422        }
423    }
424
425    pub fn get_player_cards(
426        &self,
427        player_id: Uuid,
428    ) -> [Option<(PositionInDeck, &Card)>; PLAYER_CARD_SIZE] {
429        if let Some(player) = self.players.iter().find(|p| p.id == player_id) {
430            player.get_cards_and_pos_in_deck()
431        } else {
432            [None; PLAYER_CARD_SIZE]
433        }
434    }
435    fn is_deal_valid(&self) -> bool {
436        !self.players.iter().any(|p| {
437            p.get_cards()
438                .iter()
439                .filter_map(|c| c.as_ref().map(|c| c.get_type()))
440                .filter(|t| !matches!(t, TypeCard::Heart))
441                .count()
442                == 0
443        })
444    }
445    fn deal(&mut self, rng: &mut ThreadRng) {
446        let mut deck_shuffled_positions = [0usize; DECK_SIZE];
447        for (n, item) in deck_shuffled_positions
448            .iter_mut()
449            .enumerate()
450            .take(DECK_SIZE)
451        {
452            *item = n;
453        }
454        deck_shuffled_positions.shuffle(rng);
455        for (pos_player, player) in self.players.iter_mut().enumerate() {
456            let start_pos = pos_player * PLAYER_CARD_SIZE;
457            for (i, random_pos_in_deck) in deck_shuffled_positions
458                .into_iter()
459                .skip(start_pos)
460                .take(PLAYER_CARD_SIZE)
461                .enumerate()
462            {
463                player.cards[i] = Some(random_pos_in_deck);
464                if DECK.0[random_pos_in_deck] == CARD_TO_START {
465                    self.current_player_pos = pos_player;
466                }
467            }
468        }
469    }
470
471    pub fn deal_cards(&mut self) -> Result<(), GameError> {
472        let mut rng = thread_rng();
473        match &self.state {
474            GameState::ExchangeCards { commands: _ } => {
475                self.players.shuffle(&mut rng);
476            }
477            GameState::EndHand if self.current_hand <= self.hands => {
478                self.state = GameState::ExchangeCards {
479                    commands: [None; PLAYER_NUMBER],
480                };
481            }
482            _ => return Err(GameError::StateError),
483        }
484        while !self.is_deal_valid() {
485            self.deal(&mut rng);
486        }
487
488        for player in &mut self.players {
489            player.cards.sort_by(|c1, c2| match (c1, c2) {
490                (Some(c1), Some(c2)) => c1.cmp(c2),
491                _ => unreachable!(),
492            });
493        }
494        Ok(())
495    }
496    pub fn play_bot(&mut self) -> Result<(), GameError> {
497        if let GameState::PlayingHand {
498            stack: _,
499            current_scores: _,
500        } = &self.state
501        {
502            fn filter_not_empty_slot<'a>(
503                o: &'a Option<(usize, &'a Card)>,
504            ) -> Option<(usize, &'a Card)> {
505                o.as_ref().copied()
506            }
507
508            let player = self.players.get(self.current_player_pos).unwrap();
509
510            let mut cards = player.get_cards_and_pos_in_deck();
511            cards.sort_by(|c1, c2| match (c1, c2) {
512                (Some((_, c1)), Some((_, c2))) => {
513                    if c1 == &&ACE_OF_HEARTS || c2 == &&QUEEN_OF_SPADE {
514                        Ordering::Less
515                    } else if c1 == &&QUEEN_OF_SPADE {
516                        Ordering::Greater
517                    } else {
518                        let Some(ordering) =c1.partial_cmp(c2) else {unreachable!()};
519                        ordering
520                    }
521                }
522                (None, None) => Ordering::Equal,
523                (Some(_), None) => Ordering::Greater,
524                (None, Some(_)) => Ordering::Less,
525            });
526            let current_stack_state = self.get_current_stack_state();
527
528            let mut min_card: Option<(usize, &Card)> = None;
529            // TODO probably can be simplified, not liking it
530            for (idx, card) in cards.iter().filter_map(filter_not_empty_slot) {
531                if let Ok(idx) = self.validate_play(idx) {
532                    if let Some((min_idx, min_card)) = &mut min_card {
533                        if let Some(current_stack_state) = &current_stack_state {
534                            let first_card =
535                                get_card_by_idx(current_stack_state.first_card_played_pos);
536                            let current_losing_card =
537                                get_card_by_idx(current_stack_state.current_losing_card_pos);
538
539                            let first_card_type = first_card.get_type();
540                            let min_card_type = min_card.get_type();
541
542                            if min_card_type == first_card_type {
543                                if (current_losing_card > card
544                                    && card > min_card
545                                    && min_card != &&QUEEN_OF_SPADE)
546                                    || (current_losing_card < min_card
547                                        && (card < min_card || min_card == &&QUEEN_OF_SPADE))
548                                {
549                                    (*min_idx, *min_card) = (idx, card);
550                                }
551                            } else if (card > min_card && min_card != &&QUEEN_OF_SPADE)
552                                || card == &QUEEN_OF_SPADE
553                            {
554                                // play the max card
555                                (*min_idx, *min_card) = (idx, card);
556                            }
557                        } else if (card < min_card && card != &QUEEN_OF_SPADE)
558                            || min_card == &&QUEEN_OF_SPADE
559                        {
560                            (*min_idx, *min_card) = (idx, card); // in this case we want
561                                                                 // the lowest
562                        }
563                    } else {
564                        min_card = Some((idx, card));
565                    }
566                }
567            }
568
569            let Some((min_idx, _)) = min_card else {
570                unreachable!("should still not happen bro, bcos trust me. if it happens,
571                              then probably deck not well shuffled")
572            };
573            self.play(min_idx)
574        } else if let GameState::ExchangeCards { commands: _ } = &self.state {
575            let Some(player) = self.players.get(self.current_player_pos) else {unreachable!()};
576            let mut exchange = [0; 3];
577            let player_cards = player.get_cards_and_pos_in_deck();
578
579            for (i, (c, _)) in player_cards.iter().rev().take(3).flatten().enumerate() {
580                exchange[i] = *c;
581            }
582            self.exchange_cards(exchange)
583        } else {
584            Err(GameError::StateError)
585        }
586    }
587
588    pub fn validate_play(&self, card_to_play_idx: usize) -> Result<usize, GameError> {
589        match &self.state {
590            GameState::PlayingHand {
591                stack,
592                current_scores: _,
593            } => {
594                let player = self.players.get(self.current_player_pos).unwrap();
595                if stack.iter().all(|s| s.is_some()) {
596                    return Err(GameError::ForbiddenMove);
597                }
598                if !player.cards.contains(&Some(card_to_play_idx)) {
599                    return Err(GameError::PlayerDoesntHaveCard);
600                }
601                if player.cards.iter().any(|c| {
602                    if let Some(c) = c {
603                        if c == &card_to_play_idx {
604                            return false;
605                        }
606                        DECK.0[*c] == CARD_TO_START
607                    } else {
608                        false
609                    }
610                }) {
611                    return Err(GameError::MustUseStartCard);
612                }
613                let card_to_play = get_card_by_idx(card_to_play_idx);
614                let card_to_play_type = card_to_play.get_type();
615                // not the first to play
616                if let Some(Some((_, card_idx))) = stack.get(0) {
617                    let firs_played_card = get_card_by_idx(*card_idx);
618                    let first_played_type_card = firs_played_card.get_type();
619
620                    if card_to_play_type != first_played_type_card {
621                        // can play same kind
622                        if player.get_cards().iter().any(|c| {
623                            if let Some(c) = c {
624                                return c.get_type() == first_played_type_card;
625                            }
626                            false
627                        }) {
628                            return Err(GameError::MustPlaySameKind);
629                        }
630
631                        // cannot play Q club or heart if it's the first hand
632
633                        if firs_played_card == &CARD_TO_START
634                            && (card_to_play_type == &TypeCard::Heart
635                                || card_to_play == &QUEEN_OF_SPADE)
636                        {
637                            return Err(GameError::CannotStartWithQueenOrHeart);
638                        }
639                    }
640                } else {
641                    // first to play
642                    // unless player has no other choice
643                    // check if heart and there's no heart used in deck
644                    if card_to_play_type == &TypeCard::Heart
645                        && !self.back_in_deck.iter().any(|c| {
646                            if let Some(c) = c {
647                                DECK.0[*c].get_type() == &TypeCard::Heart
648                            } else {
649                                false
650                            }
651                        })
652                        && player
653                            .get_cards()
654                            .iter()
655                            .filter_map(|c| c.as_ref().map(|c| c.get_type()))
656                            .any(|c| c != &TypeCard::Heart)
657                    {
658                        return Err(GameError::HeartNeverPlayedBefore);
659                    }
660                }
661                Ok(card_to_play_idx)
662            }
663            _ => Err(GameError::StateError),
664        }
665    }
666
667    pub fn play(&mut self, card_to_play_idx: usize) -> Result<(), GameError> {
668        let card_to_play_idx = self.validate_play(card_to_play_idx)?;
669        let GameState::PlayingHand { stack, current_scores } = &mut self.state else {
670            unreachable!("already validated")};
671        let player = self.players.get_mut(self.current_player_pos).unwrap();
672
673        // we're done with the checks
674        let Some(empty_slot) = stack.iter_mut().find(|s| s.is_none()) else {
675            return Err(GameError::ForbiddenMove)};
676        *empty_slot = Some((self.current_player_pos, card_to_play_idx));
677        // update current player position
678        if self.current_player_pos == PLAYER_NUMBER - 1 {
679            self.current_player_pos = 0;
680        } else {
681            self.current_player_pos += 1;
682        }
683        // remove card from player
684        if let Some(card) = player
685            .cards
686            .iter_mut()
687            .find(|c| c == &&Some(card_to_play_idx))
688        {
689            card.take();
690        }
691        // check if stack full
692        if stack.iter().all(|s| s.is_some()) {
693            self.state = GameState::ComputeScore {
694                stack: *stack,
695                current_scores: *current_scores,
696            };
697        }
698
699        Ok(())
700    }
701    pub fn compute_score(&mut self) -> Result<(), GameError> {
702        let Some(current_stack_state) = self.get_current_stack_state() else {unreachable!()};
703        self.current_player_pos = current_stack_state.current_losing_player_pos;
704        let GameState::ComputeScore { stack, current_scores } = &mut self.state else {
705            return Err(GameError::StateError)};
706        if current_scores[self.current_player_pos] + current_stack_state.score == MAX_SCORE {
707            for (idx, score) in current_scores.iter_mut().enumerate() {
708                if idx == self.current_player_pos {
709                    *score = 0;
710                } else {
711                    *score = MAX_SCORE;
712                }
713            }
714        } else {
715            current_scores[self.current_player_pos] += current_stack_state.score;
716        }
717
718        if self.back_in_deck.iter().filter(|s| s.is_some()).count() == DECK_SIZE - PLAYER_NUMBER {
719            for s in *stack {
720                let Some((pos_player, _)) = s else {unreachable!()};
721                self.players[pos_player].score += current_scores[pos_player];
722            }
723
724            self.current_hand += 1;
725            for card_in_deck in self.back_in_deck.iter_mut() {
726                *card_in_deck = None;
727            }
728            if self.current_hand <= self.hands {
729                self.state = GameState::EndHand;
730            } else {
731                self.state = GameState::End;
732            }
733        } else {
734            for s in stack.iter_mut() {
735                let Some(empty_slot) = self.back_in_deck.iter_mut()
736                    .find(|s| s.is_none()) else {unreachable!()};
737                empty_slot.replace(s.take().map(|(_, c)| c).unwrap());
738            }
739            self.state = GameState::PlayingHand {
740                stack: *stack,
741                current_scores: *current_scores,
742            };
743        }
744        Ok(())
745    }
746
747    fn get_current_stack_state(&self) -> Option<StackState> {
748        let (stack, _) = match self.state {
749            GameState::PlayingHand {
750                stack,
751                current_scores,
752            }
753            | GameState::ComputeScore {
754                stack,
755                current_scores,
756            } => Some((stack, current_scores)),
757            _ => None,
758        }?;
759        let (first_player_pos, first_card_idx) = &stack[0]?;
760        let first_played_card = get_card_by_idx(*first_card_idx);
761        let first_played_type_card = first_played_card.get_type();
762        let mut max_card = (first_player_pos, first_played_card, *first_card_idx);
763        let mut score = first_played_card.get_value();
764        for c in stack.iter().skip(1).filter(|c| c.is_some()) {
765            let Some((next_player_pos, next_card_idx)) = c else {return None};
766
767            let next_played_card = get_card_by_idx(*next_card_idx);
768            let next_played_type_card = next_played_card.get_type();
769
770            if next_played_type_card == first_played_type_card && max_card.1 < next_played_card {
771                max_card = (next_player_pos, next_played_card, *next_card_idx);
772            }
773            score += next_played_card.get_value();
774        }
775        Some(StackState {
776            first_card_played_pos: *first_card_idx,
777            current_losing_player_pos: *max_card.0,
778            current_losing_card_pos: max_card.2,
779            score,
780        })
781    }
782
783    pub fn current_player_id(&self) -> Option<Uuid> {
784        self.players.get(self.current_player_pos).map(|p| p.id)
785    }
786    pub fn current_player_is_bot(&self) -> bool {
787        if let Some(current_player) = self.players.get(self.current_player_pos) {
788            current_player.is_bot()
789        } else {
790            false
791        }
792    }
793
794    pub fn player_ids_in_order(&self) -> [Uuid; PLAYER_NUMBER] {
795        self.players.map(|player| player.id)
796    }
797
798    pub fn player_score_by_id(&self) -> [PlayerState; PLAYER_NUMBER] {
799        self.players.map(|p| PlayerState {
800            player_id: p.id,
801            score: p.score,
802        })
803    }
804    pub fn current_score_by_id(&self) -> [PlayerState; PLAYER_NUMBER] {
805        let mut player_scores = self.player_score_by_id();
806        match self.state {
807            GameState::ExchangeCards { commands: _ } | GameState::EndHand | GameState::End => {}
808            GameState::PlayingHand {
809                stack: _,
810                current_scores,
811            }
812            | GameState::ComputeScore {
813                stack: _,
814                current_scores,
815            } => {
816                for (idx, score) in current_scores.iter().enumerate() {
817                    player_scores[idx].score = *score;
818                }
819            }
820        }
821        player_scores
822    }
823
824    pub fn print_state(&self) {
825        println!("************* current state **************");
826        println!(
827            "Current player: {}\nState: {}",
828            self.players[self.current_player_pos].id,
829            Into::<&str>::into(&self.state)
830        );
831        match &self.state {
832            GameState::ComputeScore {
833                stack,
834                current_scores,
835            } => {
836                // avoid allocating
837                println!("Player order:");
838                for (pl_idx, _) in stack.iter().flatten() {
839                    println!("\t{pl_idx}: {}", self.players[*pl_idx].id);
840                }
841                println!();
842
843                println!(
844                    "Current stack: {}",
845                    stack
846                        .map(|c| if let Some((_, c)) = c {
847                            get_card_by_idx(c).get_emoji()
848                        } else {
849                            "-"
850                        })
851                        .join(" ")
852                );
853                // avoid allocating
854                print!("Current Score: ");
855                for (pl_idx, _) in stack.iter().flatten() {
856                    print!("{} ", current_scores[*pl_idx]);
857                }
858                println!();
859                for player in self.players.iter() {
860                    println!("{player}");
861                }
862            }
863            GameState::ExchangeCards { commands: _ } => {
864                for player in self.players.iter() {
865                    println!("{player}");
866                }
867            }
868            GameState::EndHand | GameState::End => {
869                // avoid allocating
870                print!("Hand Score:  ");
871                for p in &self.players {
872                    print!("{} ", p.score);
873                }
874                println!();
875            }
876            _ => {}
877        }
878    }
879}
880
881#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
882#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
883pub enum GameError {
884    UnknownCard,
885    ForbiddenMove,
886    PlayerDoesntHaveCard,
887    HeartNeverPlayedBefore,
888    MustUseStartCard,
889    MustPlaySameKind,
890    CannotStartWithQueenOrHeart,
891    StateError,
892}
893impl Display for GameError {
894    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
895        write!(f, "{:?}", self)
896    }
897}
898impl Error for GameError {}
899
900#[cfg(test)]
901mod test {
902    use uuid::Uuid;
903
904    use crate::{Card, TypeCard, CARD_TO_START, PLAYER_CARD_SIZE, QUEEN_OF_SPADE};
905
906    use super::{Game, GameState};
907
908    #[test]
909    pub fn new_game_test() {
910        let game = Game::new(
911            [
912                (Uuid::new_v4(), true),
913                (Uuid::new_v4(), true),
914                (Uuid::new_v4(), true),
915                (Uuid::new_v4(), true),
916            ],
917            1,
918        );
919        let first_player = game.players.get(game.current_player_pos).unwrap();
920        assert!(
921            first_player.get_cards().contains(&Some(&CARD_TO_START)),
922            "player {first_player:?} is not supposed to start!"
923        );
924
925        let all_positions: Vec<Option<usize>> = game
926            .players
927            .into_iter()
928            .flat_map(|p| p.cards.into_iter())
929            .collect();
930
931        let mut copy: Vec<&Option<usize>> = all_positions.iter().collect();
932        copy.sort();
933        copy.dedup();
934        assert_eq!(copy.len(), all_positions.len());
935    }
936
937    #[test]
938    pub fn test_compare() {
939        assert!(Card::Queen(TypeCard::Spade, "_") > Card::Jack(TypeCard::Spade, "_"));
940    }
941
942    #[test]
943    pub fn exchange_cards_test() {
944        let mut game = Game::new(
945            [
946                (Uuid::new_v4(), true),
947                (Uuid::new_v4(), true),
948                (Uuid::new_v4(), true),
949                (Uuid::new_v4(), true),
950            ],
951            1,
952        );
953
954        let clone: Vec<[Option<usize>; PLAYER_CARD_SIZE]> =
955            game.players.iter().map(|p| p.cards).collect();
956        while matches!(&game.state, &GameState::ExchangeCards { commands: _ }) {
957            let player = game.players.get(game.current_player_pos).unwrap();
958            let mut exchange = [0; 3];
959            for (i, c) in player.cards.iter().take(3).enumerate() {
960                exchange[i] = c.unwrap();
961            }
962            game.exchange_cards(exchange).unwrap();
963        }
964
965        assert_eq!(clone[0][0..3], game.players[1].cards[0..3]);
966        assert_eq!(clone[1][0..3], game.players[2].cards[0..3]);
967        assert_eq!(clone[2][0..3], game.players[3].cards[0..3]);
968        assert_eq!(clone[3][0..3], game.players[0].cards[0..3]);
969    }
970    #[test]
971    pub fn play() {
972        let mut game = Game::new(
973            [
974                (Uuid::new_v4(), true),
975                (Uuid::new_v4(), true),
976                (Uuid::new_v4(), true),
977                (Uuid::new_v4(), true),
978            ],
979            1,
980        );
981        assert!(matches!(
982            game.state,
983            GameState::ExchangeCards { commands: _ }
984        ));
985
986        loop {
987            match game.state {
988                GameState::ExchangeCards { commands: _ } => {
989                    game.play_bot().unwrap();
990                }
991                GameState::PlayingHand {
992                    stack: _,
993                    current_scores: _,
994                } => game.play_bot().unwrap(),
995                GameState::ComputeScore {
996                    stack: _,
997                    current_scores: _,
998                } => {
999                    game.print_state();
1000                    game.compute_score().unwrap();
1001                }
1002                GameState::EndHand => {
1003                    game.print_state();
1004                    game.deal_cards().unwrap();
1005                }
1006                GameState::End => {
1007                    game.print_state();
1008                    break;
1009                }
1010            }
1011        }
1012    }
1013    #[test]
1014    fn test_serialize() {
1015        println!("{}", serde_json::to_string_pretty(&QUEEN_OF_SPADE).unwrap());
1016    }
1017}