Skip to main content

server/domain/plays/
play_state.rs

1use constants::*;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    display::format_vec,
6    domain::{
7        Card, GoStatus, Hand, Hands, PLAYER0, PLAYER1, PLAYERS, Play, Player, ScoreSheet, Value,
8    },
9};
10
11/// Represents the current state of play during the pegging phase.
12///
13/// Tracks which player's turn it is, pending cards, the go status,
14/// current and previous plays.
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct PlayState {
17    next_to_play: Player,
18    pending_plays: Vec<Vec<Card>>,
19    go_status: GoStatus,
20    current_plays: Vec<Play>,
21    previous_plays: Vec<Play>,
22}
23
24/// Trait for types that expose a `PlayState`.
25pub trait HasPlayState {
26    /// Returns an immutable reference to the play state.
27    fn play_state(&self) -> &PlayState;
28
29    /// Returns a mutable reference to the play state.
30    fn play_state_mut(&mut self) -> &mut PlayState;
31}
32
33impl PlayState {
34    /// Creates a new `PlayState` with the specified next player to play.
35    pub fn new(next_to_play: Player) -> Self {
36        Self {
37            next_to_play,
38            pending_plays: vec![Vec::default(), Vec::default()],
39            go_status: GoStatus::default(),
40            current_plays: Vec::default(),
41            previous_plays: Vec::default(),
42        }
43    }
44
45    /// Returns the player whose turn is next.
46    #[must_use]
47    pub const fn next_to_play(&self) -> Player {
48        self.next_to_play
49    }
50
51    /// Sets pending plays for a player and returns the updated state.
52    ///
53    /// (Note: This is really part of the constructor information)
54    #[must_use]
55    pub fn with_pending_plays(mut self, player: Player, cards: &[Card]) -> Self {
56        self.pending_plays.as_mut_slice()[player] = Vec::from(cards);
57        self
58    }
59
60    /// Returns the current running total of points in the play sequence.
61    #[must_use]
62    pub fn running_total(&self) -> Value {
63        self.current_plays.iter().map(|p| p.card().value()).sum()
64    }
65
66    /// Returns `true` if the specified player has any cards left to play regardless
67    /// whether they are currently legal plays or not.
68    #[must_use]
69    pub fn has_cards(&self, player: Player) -> bool {
70        !self.pending_plays[player].is_empty()
71    }
72
73    /// Returns the legal cards the specified player may play without exceeding the play limit.
74    #[must_use]
75    pub fn legal_plays(&self, player: Player) -> Vec<Card> {
76        let running_total = self.running_total();
77        let is_in_limit = |c: &Card| running_total + c.value() <= Value::from(PLAY_TARGET);
78
79        self.pending_plays[player]
80            .iter()
81            .filter_map(|c| is_in_limit(c).then_some(*c))
82            .collect::<Vec<_>>()
83    }
84
85    /// Returns an immutable reference to the current plays.
86    #[must_use]
87    pub fn current_plays(&self) -> &Vec<Play> {
88        &self.current_plays
89    }
90
91    /// Returns an immutable reference to the previous plays.
92    #[must_use]
93    pub fn previous_plays(&self) -> &Vec<Play> {
94        &self.previous_plays
95    }
96
97    /// Plays a card for the current player, updating the state and returning the resulting
98    /// score sheet for the played card.
99    #[must_use]
100    pub fn play(&mut self, card: Card) -> ScoreSheet {
101        let player = self.next_to_play;
102        let opponent = player.opponent();
103
104        self.pending_plays.as_mut_slice()[player].retain(|c| c != &card);
105        self.current_plays.push(Play::new(player, card));
106        let sheet = ScoreSheet::play_card(self);
107
108        let reached_target = self.running_total() == Value::from(PLAY_TARGET);
109        let player_has_cards = self.has_cards(player);
110        let opponent_has_cards = self.has_cards(opponent);
111
112        if reached_target {
113            self.start_new_play();
114        } else {
115            match self.go_status {
116                GoStatus::NotCalled if opponent_has_cards => {
117                    self.next_to_play = opponent;
118                }
119                GoStatus::NotCalled => {}
120                GoStatus::Called | GoStatus::PlayContinued => {
121                    self.go_status = GoStatus::PlayContinued;
122                    // next_to_play remains player until they run out of cards
123                    if !player_has_cards {
124                        self.start_new_play();
125                    }
126                }
127            }
128        }
129
130        sheet
131    }
132
133    /// Calls "go" for the current player, updating the state and returning the resulting score sheet.
134    #[must_use]
135    pub fn go(&mut self) -> ScoreSheet {
136        let player = self.next_to_play;
137        let opponent = player.opponent();
138
139        let opponent_has_cards = self.has_cards(opponent);
140
141        let mut sheet = ScoreSheet::default();
142
143        match self.go_status {
144            GoStatus::NotCalled if opponent_has_cards => {
145                self.go_status = GoStatus::Called;
146                self.next_to_play = opponent;
147            }
148            GoStatus::NotCalled => {
149                self.go_status = GoStatus::Called;
150                sheet = ScoreSheet::go(self);
151                self.start_new_play();
152            }
153            GoStatus::Called | GoStatus::PlayContinued => {
154                sheet = ScoreSheet::go(self);
155                self.start_new_play();
156            }
157        }
158
159        sheet
160    }
161
162    fn start_new_play(&mut self) {
163        // There will always be a valid play before a go can occur. The `or` condition
164        // in `map_or` will never occur.
165        let last_player = self
166            .current_plays
167            .last()
168            .map_or(self.next_to_play, Play::player);
169        let opponent = last_player.opponent();
170        let opponent_has_cards = self.has_cards(opponent);
171
172        self.next_to_play = if opponent_has_cards {
173            opponent
174        } else {
175            last_player
176        };
177
178        self.previous_plays.append(&mut self.current_plays);
179
180        self.go_status = GoStatus::NotCalled;
181    }
182
183    /// Returns `true` if all players have no cards left.
184    #[must_use]
185    pub fn is_finished(&self) -> bool {
186        self.pending_plays.iter().all(Vec::is_empty)
187    }
188
189    /// Finishes the current plays and returns the regathered hands.
190    #[must_use]
191    pub fn finish_plays(&mut self) -> Hands {
192        let hands = self.regather_hands();
193        self.current_plays = Vec::default();
194        self.previous_plays = Vec::default();
195        hands
196    }
197
198    fn regather_hands(&self) -> Hands {
199        let plays = self
200            .previous_plays
201            .iter()
202            .chain(self.current_plays.iter())
203            .collect::<Vec<_>>();
204
205        let hands = PLAYERS
206            .into_iter()
207            .map(|player| {
208                plays
209                    .iter()
210                    .filter_map(|p| (p.player() == player).then_some(p.card()))
211                    .collect::<Hand>()
212            })
213            .collect::<Vec<_>>();
214        let hands = hands.as_slice();
215
216        [hands[PLAYER0].clone(), hands[PLAYER1].clone()]
217    }
218
219    /// Returns the current go status.
220    #[must_use]
221    pub fn go_status(&self) -> &GoStatus {
222        &self.go_status
223    }
224
225    #[cfg(test)]
226    pub(crate) fn go_status_mut(&mut self) -> &mut GoStatus {
227        &mut self.go_status
228    }
229
230    #[cfg(test)]
231    pub(crate) fn current_plays_mut(&mut self) -> &mut Vec<Play> {
232        &mut self.current_plays
233    }
234
235    #[cfg(test)]
236    pub(crate) fn previous_plays_mut(&mut self) -> &mut Vec<Play> {
237        &mut self.previous_plays
238    }
239}
240
241impl std::fmt::Display for PlayState {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        write!(
244            f,
245            "Next({}), GoStatus({}), Pending({} -> {}, {} -> {}), Current({}), Previous({})",
246            self.next_to_play,
247            self.go_status.as_ref(),
248            PLAYER0,
249            format_vec(&self.pending_plays[PLAYER0]),
250            PLAYER1,
251            format_vec(&self.pending_plays[PLAYER1]),
252            format_vec(&self.current_plays),
253            format_vec(&self.previous_plays)
254        )
255    }
256}