1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
use std::{fmt::Display, ops::Deref};
use super::{PLAYERS, PlayerId, TrickTakingGame};
/// A trick is a set containing the cards played and the player who won the
/// trick, represented as `PlayerId`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Trick<G>
where
G: TrickTakingGame,
{
cards: [G::CardType; PLAYERS],
taker: PlayerId,
}
impl<G> Display for Trick<G>
where
G: TrickTakingGame,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} {} {} {}",
self.cards[0], self.cards[1], self.cards[2], self.cards[3], self.taker
)
}
}
impl<G> Trick<G>
where
G: TrickTakingGame,
{
/// Creates a new Trick from cards and the player who won.
pub fn new(cards: [G::CardType; PLAYERS], taker: PlayerId) -> Self {
Self { cards, taker }
}
/// Returns the card this trick has been won with.
///
/// # Examples
///
/// ```
/// use shuftlib::trick_taking::{Trick, PlayerId};
/// use shuftlib::core::Suit;
/// use shuftlib::core::italian::ItalianRank;
/// use shuftlib::tressette::{TressetteRules, TressetteCard};
///
/// let cards = [
/// TressetteCard::new(ItalianRank::Ace, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Two, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Three, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Four, Suit::Hearts),
/// ];
/// let trick = Trick::<TressetteRules>::new(cards, PlayerId::PLAYER_0);
/// assert_eq!(trick.taken_with(), cards[0]);
/// ```
pub fn taken_with(&self) -> G::CardType {
self.cards[self.taker.as_usize()]
}
/// Returns the `PlayerId` of the player who won the trick.
///
/// # Examples
///
/// ```
/// use shuftlib::trick_taking::{Trick, PlayerId};
/// use shuftlib::core::Suit;
/// use shuftlib::core::italian::ItalianRank;
/// use shuftlib::tressette::{TressetteRules, TressetteCard};
///
/// let cards = [
/// TressetteCard::new(ItalianRank::Ace, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Two, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Three, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Four, Suit::Hearts),
/// ];
/// let trick = Trick::<TressetteRules>::new(cards, PlayerId::PLAYER_0);
/// assert_eq!(trick.taker(), PlayerId::PLAYER_0);
/// ```
pub fn taker(&self) -> PlayerId {
self.taker
}
/// Returns the cards played during this trick.
///
/// # Examples
///
/// ```
/// use shuftlib::trick_taking::{Trick, PlayerId};
/// use shuftlib::core::Suit;
/// use shuftlib::core::italian::ItalianRank;
/// use shuftlib::tressette::{TressetteRules, TressetteCard};
///
/// let cards = [
/// TressetteCard::new(ItalianRank::Ace, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Two, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Three, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Four, Suit::Hearts),
/// ];
/// let trick = Trick::<TressetteRules>::new(cards, PlayerId::PLAYER_0);
/// assert_eq!(trick.cards(), &cards);
/// ```
pub fn cards(&self) -> &[G::CardType] {
&self.cards
}
}
/// A temporary state of a trick that's still not over: not all the players made
/// their move or a taker hasn't been determined yet.
#[derive(Clone, Copy, Debug)]
pub struct OngoingTrick<G>
where
G: TrickTakingGame,
{
cards: [Option<G::CardType>; PLAYERS],
first_to_play: PlayerId,
next_to_play: PlayerId,
play_count: usize,
}
impl<G> Deref for OngoingTrick<G>
where
G: TrickTakingGame,
{
type Target = [Option<G::CardType>; PLAYERS];
fn deref(&self) -> &Self::Target {
&self.cards
}
}
impl<G> OngoingTrick<G>
where
G: TrickTakingGame,
{
/// Adds the `Card` passed as parameter to the `OngoingTrick`.
/// Checking the validity of the card played is a responsibility of the
/// caller.
///
/// # Examples
/// ```
/// use shuftlib::trick_taking::{OngoingTrick, PlayerId, TrickTakingGame};
/// use shuftlib::core::{Card, Suit};
/// use shuftlib::core::italian::ItalianRank;
/// use shuftlib::tressette::{TressetteRules, TressetteCard};
///
/// let first_to_play = PlayerId::PLAYER_0;
/// let card = TressetteCard::new(ItalianRank::Ace, Suit::Hearts);
/// let mut trick = OngoingTrick::<TressetteRules>::new(first_to_play);
/// trick.play(card);
/// let mut second_to_play = first_to_play;
/// second_to_play.inc();
///
/// assert_eq!(trick[0], Some(card));
/// assert_eq!(trick.next_to_play(), second_to_play)
/// ```
pub fn play(&mut self, card: G::CardType) {
self.cards[self.next_to_play.as_usize()] = Some(card);
self.next_to_play.inc();
self.play_count += 1;
}
/// Tries to transform the current `OngoingTrick` into a `Trick` by
/// determining the taker of the trick. It doesn't make any assumption on
/// previously played cards during the current `OngoingHand`. It also does
/// not check if it contains duplicates since that could be valid in some games.
///
/// # Errors
///
/// Fails if any of the moves of the `OngoingTrick` this is called on is
/// None. It means that not all players made their move yet, so a taker
/// can't be determined.
///
/// # Examples
///
/// ```
/// use shuftlib::trick_taking::{OngoingTrick, PlayerId, TrickTakingGame};
/// use shuftlib::core::Suit;
/// use shuftlib::core::italian::ItalianRank;
/// use shuftlib::tressette::{TressetteRules, TressetteCard};
///
/// let cards = [
/// TressetteCard::new(ItalianRank::Ace, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Two, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Three, Suit::Hearts),
/// TressetteCard::new(ItalianRank::Four, Suit::Hearts),
/// ];
/// let first_to_play = PlayerId::PLAYER_0;
/// let mut ongoing_trick = OngoingTrick::<TressetteRules>::new(first_to_play);
/// ongoing_trick.play(cards[0]);
///
/// // After only playing a card, it's not possible to finish the OngoingTrick.
/// assert!(ongoing_trick.clone().finish().is_none());
///
/// let mut to_play = first_to_play;
/// to_play.inc();
/// cards.iter().skip(1).for_each(|&c| {
/// ongoing_trick.play(c);
/// to_play.inc();
/// });
///
/// // After every player made their play, it's possible to get the trick.
/// let trick = ongoing_trick.finish().unwrap();
/// // Finishing the trick also means determining a taker. Since in this
/// // example we are using the tressette game rules, player 2 is the taker.
/// assert_eq!(trick.taker(), PlayerId::PLAYER_2);
/// ```
pub fn finish(self) -> Option<Trick<G>> {
let mut cards: [G::CardType; PLAYERS] = [G::CardType::default(); PLAYERS];
if self
.iter()
.enumerate()
.map(|(i, &x)| {
if let Some(c) = x {
cards[i] = c;
true
} else {
false
}
})
.any(|is_some| !is_some)
{
return None;
}
let taker = G::determine_taker(&cards, self.first_to_play);
Some(Trick { cards, taker })
}
/// Returns the cards contained in this `OngoingTrick`.
///
/// # Examples
///
/// ```
/// use shuftlib::core::trick_taking::{OngoingTrick, PlayerId};
/// use shuftlib::tressette::TressetteRules;
///
/// let trick = OngoingTrick::<TressetteRules>::new(PlayerId::PLAYER_0);
/// assert!(trick.cards().iter().all(|c| c.is_none()));
/// ```
pub fn cards(&self) -> &[Option<G::CardType>] {
&self.cards
}
/// Returns the id of the person who starts the trick.
///
/// # Examples
///
/// ```
/// use shuftlib::core::trick_taking::{OngoingTrick, PlayerId};
/// use shuftlib::tressette::TressetteRules;
///
/// let trick = OngoingTrick::<TressetteRules>::new(PlayerId::PLAYER_0);
/// assert_eq!(trick.first_to_play(), PlayerId::PLAYER_0);
/// ```
pub fn first_to_play(&self) -> PlayerId {
self.first_to_play
}
/// Returns the id of the person who plays next in the trick.
///
/// # Examples
///
/// ```
/// use shuftlib::core::trick_taking::{OngoingTrick, PlayerId};
/// use shuftlib::tressette::TressetteRules;
///
/// let trick = OngoingTrick::<TressetteRules>::new(PlayerId::PLAYER_0);
/// assert_eq!(trick.next_to_play(), PlayerId::PLAYER_0);
/// ```
pub fn next_to_play(&self) -> PlayerId {
self.next_to_play
}
/// Creates a new `OngoingTrick` with all card slots empty, starting with
/// the specified player.
///
/// # Examples
///
/// ```
/// use shuftlib::core::trick_taking::{OngoingTrick, PlayerId, TrickTakingGame};
/// use shuftlib::tressette::TressetteRules;
///
/// let first_to_play = PlayerId::PLAYER_0;
/// let ongoing_trick = OngoingTrick::<TressetteRules>::new(first_to_play);
///
/// assert_eq!(ongoing_trick.first_to_play(), first_to_play);
/// ongoing_trick.cards().iter().for_each(|&c| assert!(c.is_none()));
/// ```
pub fn new(first_to_play: PlayerId) -> Self {
Self {
cards: [None; PLAYERS],
first_to_play,
next_to_play: first_to_play,
play_count: 0,
}
}
}
/// Testing utilities for trick-taking game components.
#[cfg(test)]
pub mod test_utils {
use proptest::collection::hash_set;
use proptest::prelude::*;
use crate::core::deck::Deck;
pub use crate::core::italian::test_utils::italian_card_strategy;
use crate::trick_taking::{Hand, OngoingTrick, PlayerId, TrickTakingGame};
use crate::{core::italian::ItalianCard, trick_taking::PLAYERS};
/// A minimal test implementation of TrickTakingGame for testing purposes.
///
/// This implementation always determines PLAYER_0 as the trick taker.
#[derive(Clone, Copy, Debug)]
pub struct TestGame {}
impl TrickTakingGame for TestGame {
type CardType = ItalianCard;
fn determine_taker(
_cards: &[Self::CardType; PLAYERS],
_first_to_play: PlayerId,
) -> super::super::PlayerId {
PlayerId::PLAYER_0
}
fn deck() -> Deck<Self::CardType> {
Deck::italian()
}
fn score_hand(_hand: &Hand<Self>) -> (u8, u8)
where
Self: Sized,
{
(0, 0)
}
fn is_game_over(_scores: (u8, u8)) -> bool {
true
}
}
/// Strategy to create an `OngoingTrick` filled with random cards.
pub fn ongoing_trick_strategy() -> impl Strategy<Value = OngoingTrick<TestGame>> {
hash_set(italian_card_strategy(), PLAYERS).prop_map(|hash_set| {
let mut cards = [None; PLAYERS];
hash_set
.iter()
.enumerate()
.for_each(|(i, &c)| cards[i] = Some(c));
OngoingTrick {
cards,
first_to_play: PlayerId::PLAYER_0,
next_to_play: PlayerId::PLAYER_0,
play_count: 0,
}
})
}
}
#[cfg(test)]
mod tests {
use proptest::{array, prelude::*};
use super::test_utils::{TestGame, italian_card_strategy, ongoing_trick_strategy};
use super::{OngoingTrick, PlayerId};
proptest! {
#[test]
fn play_method_works(cards in array::uniform4(italian_card_strategy())) {
let mut trick: OngoingTrick<TestGame> = OngoingTrick::new(PlayerId::PLAYER_0);
for (index, &card) in cards.iter().enumerate() {
trick.play(card);
assert_eq!(trick[index], Some(card));
}
}
#[test]
fn finish_method_works(ongoing_trick in ongoing_trick_strategy()) {
let trick = ongoing_trick.finish().ok_or_else(|| TestCaseError::fail("trick should be complete"))?;
let cards = ongoing_trick.cards();
prop_assert_eq!(trick.taker(), PlayerId::PLAYER_0);
prop_assert_eq!(trick.taken_with(), cards[0].ok_or_else(|| TestCaseError::fail("card should exist"))?);
}
}
}