Skip to main content

fantasy_realms_unofficial_api/
lib.rs

1#[cfg(test)]
2mod tests;
3pub mod deck;
4pub mod card_collection;
5pub mod hand;
6
7use crate::card_collection::{CardCollection, CardCollectionContains};
8use crate::hand::{Hand, Turn};
9use crate::deck::Card;
10
11/// Represents a player with complete information.
12#[derive(Debug, PartialEq, Eq, Clone)]
13pub struct Player {
14    /// The players name.
15    pub name: String,
16    /// The players hand.
17    pub hand: Hand,
18    /// The cards in this players hand that are known by opponents.
19    pub cards_known_to_opponents: CardCollection,
20} impl Player {
21    /// Creates a new `Player` instance.
22    /// `cards_known_to_opponents` is initialized as empty,
23    /// as no cards are known to opponents at the start of the game.
24    /// # Arguments
25    /// * `name` - A `String` representing the player's name.
26    /// * `hand` - A `Hand` containing the cards initially dealt to the player.
27    /// # Returns
28    /// A new `Player` instance.
29    pub fn new(name: String, hand: Hand) -> Self {
30        Player {
31            name: name, 
32            hand: hand, 
33            cards_known_to_opponents: CardCollection::new()
34        }
35    }
36}
37
38/// Represents a game with complete information. 
39#[derive(Debug, PartialEq, Eq, Clone)]
40pub struct Game {
41    /// The current turn.
42    /// This value is zero-indexed, and ranges from `0` to `number_of_players - 1`.
43    pub current_turn: usize,
44    /// A collection of cards in the discard pile.
45    pub discard_pile: CardCollection,
46    /// A collection of cards in the deck.
47    pub deck: CardCollection,
48    /// A list of the players playing the game.
49    pub players: Vec<Player>,
50    /// Weather or not the game is over.
51    pub over: bool,
52} impl Game {
53    /// Creates a new `Game` instance.
54    /// `current_turn` is initialized to 0.
55    /// `discard_pile` is initialized as empty,
56    /// as the discard pile is empty at the start of the game.
57    /// `deck` is initialized to all cards not in the hands of players.
58    /// `over` is initialized to false.
59    /// # Arguments
60    /// * `players` - A `Vec<Player>` that contains all players in the game
61    /// # Errors
62    /// This function returns an `Err(String)` if:
63    /// * The number of `players` is not between 3 and 6 (inclusive).
64    /// * The same card is found in multiple hands.
65    /// # Returns
66    /// A `Result<Self, String>` which is:
67    /// * `Ok(Game)` if the game can be successfully initialized.
68    /// * `Err(String)` containing an error message if validation fails.
69    pub fn new(players: Vec<Player>) -> Result<Self, String> {
70        if players.len() < 3 || players.len() > 6 {
71            return Err("Number of players must be between 3 and 6.".to_string());
72        }
73        let unique_hand_cards: CardCollection = players
74            .iter()
75            .fold(CardCollection::new(), 
76                |mut acc, player| {
77                for card in &player.hand {
78                    acc += *card;
79                }
80                acc
81            });
82        if unique_hand_cards.len() != 7 * players.len() as u32 {
83            return Err("Same card found in multiple hands.".to_string());
84        }
85        Ok(Game {
86            current_turn: 0,
87            players: players,
88            discard_pile: CardCollection::new(),
89            deck: (!CardCollection::new()) - unique_hand_cards,
90            over: false,
91        })
92    }
93
94    /// Plays a single turn in the game based on the provided `Turn` action.
95    /// This function validates a draw and discard action and updates the game state accordingly.
96    /// # Arguments
97    /// * `turn` - A `Turn` represents a draw and a discard action.
98    /// # Errors
99    /// This function returns an `Err(String)` if:
100    /// * The `discard_pile` already contains 10 or more cards.
101    /// * The card the player attempts to `draw` is not available in the deck or discard pile.
102    /// * The card the current player attempts to `discard` is not found in their own hand.
103    /// # Side Effects
104    /// * **`discard_pile`**:
105    ///    * Removes draw card if drawn from discard pile
106    ///    * Adds discarded card to the
107    /// * **`deck`**:
108    ///    * Removes draw card if drawn from deck
109    /// * **`players[self.current_turn].hand`**: The current player's hand,
110    ///    * Removes discard card
111    ///    * Adds draw card
112    /// * **`players[self.current_turn].cards_known_to_opponents`**: The 
113    ///    * Adds draw card if drawn from discard pile
114    ///    * Removes discard card if known
115    /// * **`current_turn`**: 
116    ///    * Incremented to the next player's turn
117    /// * **`over`**: 
118    ///    * Set to `true` if the `discard_pile` reaches 10 cards
119    /// # Returns
120    /// A `Result<(), String>` which is:
121    /// * `Ok(())` if the turn was executed successfully
122    /// * `Err(String)` containing an error message if validation fails
123    pub fn play_turn(&mut self, turn: Turn) -> Result<(), String> {
124        if self.discard_pile.len() >= 10 {
125            return Err("Game is over.".to_string());
126        }
127        if self.players.iter().any(|player| player.hand.contains(&turn.draw)) {
128            return Err(format!("{:?} cannot be drawn.", turn.draw.name))
129        }
130        if turn.discard == turn.draw {
131            if !self.discard_pile.contains(turn.draw) {
132                self.discard_pile += turn.discard;
133                self.deck -= turn.draw;
134            }
135            if self.discard_pile.len() == 10 {
136                self.over = true;
137            } else {
138                self.current_turn = (self.current_turn + 1) % self.players.len();
139            }
140            return Ok(());
141        }
142        if !self.players[self.current_turn].hand.contains(&turn.discard) {
143            return Err(format!("{:?} not found in hand.", turn.discard.name));
144        }
145        self.discard_pile += turn.discard;
146        self.players[self.current_turn].hand.play(&turn);
147        self.players[self.current_turn].cards_known_to_opponents -= turn.discard;
148        if self.discard_pile.contains(turn.draw) {
149            self.discard_pile -= turn.draw;
150            self.players[self.current_turn].cards_known_to_opponents += turn.draw;
151        } else {
152            self.deck -= turn.draw;
153        }
154        if self.discard_pile.len() == 10 {
155            self.over = true;
156        } else {
157            self.current_turn = (self.current_turn + 1) % self.players.len();
158        }
159        Ok(())
160    }
161}
162
163/// Represents a player with incomplete information.
164#[derive(Debug, PartialEq, Eq, Clone)]
165pub struct PartialPlayer {
166    /// The name of the player.
167    pub name: String,
168    /// The cards in this players hand that are known by opponents.
169    pub cards_known_to_opponents: CardCollection,
170} impl PartialPlayer {
171    /// Creates a new `PartialPlayer` instance.
172    /// `cards_known_to_opponents` is initialized as empty,
173    /// as no cards are known to opponents at the start of the game.
174    /// # Arguments
175    /// * `name` - A `String` representing the player's name.
176    /// # Returns
177    /// A new `PartialPlayer` instance.
178    pub fn new(name: String) -> Self {
179        PartialPlayer {
180            name: name, 
181            cards_known_to_opponents: CardCollection::new()
182        }
183    }
184}
185
186/// Represents the two options for a player in a game with incomplete information.
187/// # Variants
188/// * `Human (PartialPlayer)` - A player with information unknown by the program.
189/// * `Bot (Player)` - A player with all information know by the program.
190#[derive(Debug, PartialEq, Eq, Clone)]
191pub enum PartialGamePlayer {
192    Human (PartialPlayer),
193    Bot (Player),
194}
195
196/// Represents the two options for a player of a `PartialGame`.
197/// # Variants
198/// * `Human` (PartialTurn) - A turn with information unknown by the program.
199/// * `Bot` (Turn) - A turn with all information know by the program.
200#[derive(Debug, PartialEq, Eq, Clone, Copy)]
201pub enum PartialGameTurn {
202    Human (PartialTurn),
203    Bot (Turn),
204}
205
206/// Represents the two options for a card that is drawn.
207/// # Variants
208/// * `Deck` - A card drawn from the deck.
209/// * `Discard (Card)` - A card drawn from the discard pile.
210#[derive(Debug, PartialEq, Eq, Clone, Copy)]
211pub enum DrawCard {
212    Deck,
213    Discard (Card),
214}
215
216/// Represents a turn with incomplete information
217#[derive(Debug, PartialEq, Eq, Clone, Copy)]
218pub struct PartialTurn {
219    /// The card drawn, either a specific card from the 
220    /// discard pile or an unkown card from the deck
221    pub draw: DrawCard,
222    /// The card discarded
223    pub discard: Card,
224} impl PartialTurn {
225    /// Creates a new `PartialTurn` instance.
226    /// # Arguments
227    /// * `draw` - A `DrawCard` representing the card drawn.
228    /// * `discard` - A `Card` representing the card discarded. 
229    /// # Returns 
230    /// A new `PartialTurn` instance.
231    pub fn new(draw: DrawCard, discard: Card) -> Self {
232        PartialTurn {draw: draw, discard: discard}
233    }
234}
235
236/// Represents a game with some incomplete information.
237#[derive(Debug, PartialEq, Eq, Clone)]
238pub struct PartialGame {
239    /// The current turn.
240    /// This value is zero-indexed, and ranges from `0` to `number_of_players - 1`. 
241    pub current_turn: usize,
242    /// A collection of cards in the discard pile.
243    pub discard_pile: CardCollection,
244    /// A list of the players playing the game.
245    pub players: Vec<PartialGamePlayer>,
246    /// Weather or not the game is over.
247    pub over: bool,
248} impl PartialGame {
249    /// Creates a new `PartialGame` instance.
250    /// `current_turn` is initialized to 0.
251    /// `discard_pile` is initialized as empty,
252    /// as the discard pile is empty at the start of the game.
253    /// `over` is initialized to false.
254    /// # Arguments
255    /// * `players` - A `Vec<PartialGamePlayer>` that contains all players in the game
256    /// # Errors
257    /// This function returns an `Err(String)` if:
258    /// * The number of `players` is not between 3 and 6 (inclusive).
259    /// # Returns
260    /// A `Result<Self, String>` which is:
261    /// * `Ok(Game)` if the game can be successfully initialized.
262    /// * `Err(String)` containing an error message if validation fails.
263    pub fn new(players: Vec<PartialGamePlayer>) -> Result<Self, String> {
264        if players.len() < 3 || players.len() > 6 {
265            return Err("Number of players must be between 3 and 6.".to_string());
266        }
267        Ok(PartialGame {
268            current_turn: 0,
269            players: players,
270            discard_pile: CardCollection::new(),
271            over: false,
272        })
273    }
274
275    /// Plays a single turn in the game based on the provided `PartialGameTurn` action.
276    /// This function validates a draw and discard action and updates the game state accordingly.
277    /// # Arguments
278    /// * `turn` - A `PartialGameTurn` represents a draw and a discard action.
279    /// # Errors
280    /// This function returns an `Err(String)` if:
281    /// * The `discard_pile` already contains 10 or more cards.
282    /// * The card the player attempts to `draw` is not available in the deck or discard pile.
283    /// * The card the current player attempts to `discard` is not found in their own hand.
284    /// # Side Effects
285    /// * **`discard_pile`**:
286    ///    * Removes draw card if drawn from discard pile
287    ///    * Adds discarded card to the
288    /// * **`current_turn`**: 
289    ///    * Incremented to the next player's turn
290    /// * **`over`**: 
291    ///    * Set to `true` if the `discard_pile` reaches 10 cards
292    /// ## Human Turn
293    /// * **`players[self.current_turn].cards_known_to_opponents`**: The 
294    ///    * Adds draw card if drawn from discard pile
295    ///    * Removes discard card if known
296    /// ## Bot Turn
297    /// * **`players[self.current_turn].hand`**: The current player's hand,
298    ///    * Removes discard card
299    ///    * Adds draw card
300    /// * **`players[self.current_turn].cards_known_to_opponents`**: The 
301    ///    * Adds draw card if drawn from discard pile
302    ///    * Removes discard card if known
303    /// # Returns
304    /// A `Result<(), String>` which is:
305    /// * `Ok(())` if the turn was executed successfully
306    /// * `Err(String)` containing an error message if validation fails
307    pub fn play_turn(&mut self, turn: PartialGameTurn) -> Result<(), String> {
308        if self.discard_pile.len() >= 10 {
309            return Err("Game is over.".to_string());
310        }
311        match turn {
312            PartialGameTurn::Human (turn) => self.play_human_turn(turn),
313            PartialGameTurn::Bot (turn) => self.play_bot_turn(turn),
314        }
315    }
316
317    /// Helper function of `play_turn`
318    /// Plays a single turn in the game for a human provided a `PartialTurn` action.
319    /// # Errors
320    /// This function returns an `Err(String)` if:
321    /// * the current turn is a `PartialGamePlayer (Bot)`
322    /// # Returns
323    /// A `Result<(), String>` which is:
324    /// * `Ok(())` if the turn was executed successfully
325    /// * `Err(String)` containing an error message if validation fails
326    fn play_human_turn(&mut self, turn: PartialTurn) -> Result<(), String> {
327        let player = match &mut self.players[self.current_turn] {
328            PartialGamePlayer::Human (player) => player,
329            PartialGamePlayer::Bot (_) => {
330                return Err("Received a bot turn on a human player's turn".to_string());
331            }
332        };
333        if let DrawCard::Discard (card) = turn.draw {
334            player.cards_known_to_opponents += card;
335            self.discard_pile -= card;
336        }
337        player.cards_known_to_opponents -= turn.discard;
338        self.discard_pile += turn.discard;
339        if self.discard_pile.len() == 10 {
340             self.over = true;
341        } else {
342             self.current_turn = (self.current_turn + 1) % self.players.len();
343        }
344        Ok(())
345    }
346
347    /// Helper function of `play_turn`
348    /// Plays a single turn in the game for a bot provided a `PartialTurn` action.
349    /// # Errors
350    /// This function returns an `Err(String)` if:
351    /// * the current turn is a `PartialGamePlayer (Human)`
352    /// # Returns
353    /// A `Result<(), String>` which is:
354    /// * `Ok(())` if the turn was executed successfully
355    /// * `Err(String)` containing an error message if validation fails
356    fn play_bot_turn(&mut self, turn: Turn) -> Result<(), String> {
357        let player = match &mut self.players[self.current_turn] {
358            PartialGamePlayer::Human (_) => {
359                return Err("Received a human turn on a bot player's turn".to_string());
360            }
361            PartialGamePlayer::Bot (player) => player,
362        };
363        if self.discard_pile.contains(turn.draw){
364            player.cards_known_to_opponents += turn.draw;
365        }
366        if turn.discard == turn.draw {
367            if !self.discard_pile.contains(turn.draw) {
368                self.discard_pile += turn.discard;
369            }
370            if self.discard_pile.len() == 10 {
371                 self.over = true;
372            } else {
373                 self.current_turn = (self.current_turn + 1) % self.players.len();
374            }
375            return Ok(());
376        }
377        if !player.hand.contains(&turn.discard) {
378            return Err(format!("{:?} not found in hand.", turn.discard.name));
379        }
380        player.hand.play(&turn);
381        player.cards_known_to_opponents -= turn.discard;
382        self.discard_pile += turn.discard;
383        if self.discard_pile.contains(turn.draw) {
384             self.discard_pile -= turn.draw;
385             player.cards_known_to_opponents += turn.draw;
386        }
387        if self.discard_pile.len() == 10 {
388             self.over = true;
389        } else {
390             self.current_turn = (self.current_turn + 1) % self.players.len();
391        }
392        Ok(())
393    }
394}