Skip to main content

deck_codes/
deck.rs

1use crate::error::DeckCodeError;
2use crate::format::Format;
3
4#[derive(PartialEq, Debug)]
5/// A representation of a Hearthstone deck
6pub struct Deck {
7    version: u8,
8    pub format: Format,
9    /// The dbfid of the heroes this deck should use
10    pub heroes: Vec<u32>,
11    /// The dbfid of the cards in the deck that have a single copy. Sorted by dbfid.
12    single_cards: Vec<u32>,
13    /// The dbfid of the cards in the deck that have a two copies. Sorted by dbfid.
14    double_cards: Vec<u32>,
15    #[allow(clippy::doc_markdown)]
16    /// The dbfid of the cards in the deck that have a more than two copies. Stored as tupes of (number_of_copies, dbfid). Sorted by dbfid.
17    multi_cards: Vec<(u8, u32)>,
18    #[allow(clippy::doc_markdown)]
19    /// The dbfid of the cards in the sideboard Stored as tupes of (dbfid, number_of_copies, owner dbfid). Sorted by dbfid.
20    sideboard_cards: Vec<(u32, u8, u32)>,
21}
22
23impl Deck {
24    /// The total number of cards in the deck
25    #[must_use]
26    pub fn total_cards(&self) -> usize {
27        let mut card_count = self.single_cards.len() + self.double_cards.len() * 2;
28        card_count += self.multi_cards.iter().fold(0, |acc, t| acc + t.0) as usize;
29        card_count
30    }
31
32    /// The number of cards in the deck. Equivalent to the height of the deck when represented in hearthstone.
33    fn total_card_slots(&self) -> usize {
34        self.single_cards.len() + self.double_cards.len() + self.multi_cards.len()
35    }
36
37    /// A representation of the all the cards in the Deck, including any sideboards.
38    /// # Panics
39    /// Panics if two cards dbfids cannot be compared. Should not occur.
40    #[must_use]
41    pub fn cards(&self) -> Vec<(u8, u32, Option<u32>)> {
42        let mut cards: Vec<(u8, u32, Option<u32>)> = Vec::new();
43
44        // Cards are sorted in the struct so no need to re-sort.
45
46        for card in &self.single_cards {
47            cards.push((1, *card, None));
48        }
49
50        for card in &self.double_cards {
51            cards.push((2, *card, None));
52        }
53
54        for (amount, card) in &self.multi_cards {
55            cards.push((*amount, *card, None));
56        }
57
58        for (card, amount, owner_dbfid) in &self.sideboard_cards {
59            cards.push((*amount, *card, Some(*owner_dbfid)));
60        }
61
62        cards
63    }
64
65    /// Create a new deck from vector of u32 bytes.
66    /// This representation is [described by Hearthsim](https://hearthsim.info/docs/deckstrings/).
67    /// # Errors
68    /// Returns an error when the bytes passed cannot produce a functional deck code.
69    #[allow(clippy::too_many_lines)]
70    pub fn new(bytes: &[u32]) -> Result<Self, DeckCodeError> {
71        let total_bytes = bytes.len();
72
73        if total_bytes < 7 {
74            return Err(DeckCodeError::InvalidDeckEncoding {
75                encoding_type: String::from("Length is too small"),
76            });
77        }
78
79        if bytes[0] != 0 {
80            return Err(DeckCodeError::InvalidDeckEncoding {
81                encoding_type: String::from("No leading 0 byte found"),
82            });
83        }
84
85        let version = std::convert::TryInto::<u8>::try_into(bytes[1]).map_err(|_| {
86            DeckCodeError::InvalidDeckEncoding {
87                encoding_type: "Could not read deck code version.".to_owned(),
88            }
89        })?;
90
91        if version != 1 {
92            return Err(DeckCodeError::UnknownVersion {
93                version: u32::from(version),
94            });
95        }
96
97        let format: Format = Format::from_u32(bytes[2])?;
98
99        // Figure out where each region lies in the bytes
100        let hero_count = bytes[3] as usize;
101        let last_hero_byte: usize = 3 + hero_count;
102        let single_card_count = bytes[last_hero_byte + 1];
103        let last_single_card_byte = next_end(last_hero_byte, single_card_count);
104        if last_single_card_byte > total_bytes {
105            return Err(DeckCodeError::InvalidDeckEncoding {
106                encoding_type: String::from(
107                    "Length of card sections does not match number of bytes",
108                ),
109            });
110        }
111
112        let double_card_count = bytes[last_single_card_byte + 1];
113        let last_double_card_byte = next_end(last_single_card_byte, double_card_count);
114        if last_double_card_byte > total_bytes {
115            return Err(DeckCodeError::InvalidDeckEncoding {
116                encoding_type: String::from(
117                    "Length of card sections does not match number of bytes",
118                ),
119            });
120        }
121
122        let multi_card_count = bytes[last_double_card_byte + 1];
123        let last_multi_card_byte = next_end(last_double_card_byte, multi_card_count * 2); // 2 * because this section stores count and card id
124        if last_multi_card_byte > total_bytes {
125            return Err(DeckCodeError::InvalidDeckEncoding {
126                encoding_type: String::from(
127                    "Length of card sections does not match number of bytes",
128                ),
129            });
130        }
131
132        // Older deck codes may not have a byte indicating if a sideboard exists. Assume no sideboard if this byte is not present.
133        // +2 because: 1 byte for byte indicating presence of sideboard and then 1 byte for number of cards in sideboard
134        let sideboard_card_count = if last_multi_card_byte + 2 >= total_bytes {
135            0
136        } else {
137            bytes[last_multi_card_byte + 2]
138        };
139
140        let last_sideboard_card_byte = next_end(last_multi_card_byte, sideboard_card_count * 2 + 1); // 2 * because this section stores card id, and sideboard_for id. +1 because the first byte indicates presence of a sideboard
141
142        if sideboard_card_count > 0 && last_sideboard_card_byte > total_bytes {
143            return Err(DeckCodeError::InvalidDeckEncoding {
144                encoding_type: String::from(
145                    "Length of card sections does not match number of bytes",
146                ),
147            });
148        }
149
150        // Iterate over heroes
151        let first_hero_byte: usize = 4;
152        let mut heroes: Vec<u32> = Vec::new();
153        for i in first_hero_byte..=last_hero_byte {
154            heroes.push(bytes[i]);
155        }
156
157        // Iterate over cards
158        let mut single_cards: Vec<u32> = Vec::with_capacity(single_card_count as usize);
159        let first_single_card_byte: usize = last_hero_byte + 2;
160        for i in first_single_card_byte..=last_single_card_byte {
161            single_cards.push(bytes[i]);
162        }
163
164        let mut double_cards: Vec<u32> = Vec::with_capacity(double_card_count as usize);
165        let first_double_card_byte: usize = last_single_card_byte + 2;
166        for i in first_double_card_byte..=last_double_card_byte {
167            double_cards.push(bytes[i]);
168        }
169
170        // Iterate over card and number pairs
171        let mut multi_cards: Vec<(u8, u32)> = Vec::with_capacity(multi_card_count as usize);
172        let mut index = last_double_card_byte + 2;
173        while index < last_multi_card_byte {
174            let card = bytes[index];
175            let number_of_card =
176                u8::try_from(bytes[index + 1]).map_err(|_| DeckCodeError::InvalidDeckEncoding {
177                    encoding_type: String::from("Amount of single card exceeded 255"),
178                })?;
179            multi_cards.push((number_of_card, card));
180            index += 2;
181        }
182
183        // Iterate over sideboard
184        let mut sideboard_cards: Vec<(u32, u8, u32)> =
185            Vec::with_capacity(sideboard_card_count as usize);
186        if sideboard_card_count > 0 {
187            let mut index = last_multi_card_byte + 3;
188            while index < last_sideboard_card_byte {
189                let card_id = bytes[index];
190                let sideboard_for_card_id = bytes[index + 1];
191                sideboard_cards.push((card_id, 1, sideboard_for_card_id));
192                index += 2;
193            }
194        }
195
196        single_cards.sort_unstable();
197        double_cards.sort_unstable();
198        multi_cards.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
199        sideboard_cards.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
200
201        Ok(Self {
202            version,
203            format,
204            heroes,
205            single_cards,
206            double_cards,
207            multi_cards,
208            sideboard_cards,
209        })
210    }
211
212    /// Encode the deck as a u32 vector
213    ///
214    /// # Panics
215    /// Panics if the Deck provided has greater than u32 unique cards
216    #[must_use]
217    pub fn to_byte_array(&self) -> Vec<u32> {
218        // Minimum amount: 0x0, version, format, hero count, single count, double count, multi-count. Total of (7 bytes) + counts
219        let mut byte_array: Vec<u32> =
220            Vec::with_capacity(7 + self.heroes.len() + self.total_card_slots());
221        let mut vec = vec![
222            0,
223            u32::from(self.version),
224            u32::from(self.format.to_u8()),
225            u32::try_from(self.heroes.len()).expect("More heroes provided than expected"),
226        ];
227        byte_array.append(&mut vec);
228        byte_array.extend(&self.heroes.clone());
229        byte_array.push(
230            u32::try_from(self.single_cards.len())
231                .expect("More single cards provided than expected"),
232        );
233        byte_array.extend(&self.single_cards.clone());
234
235        byte_array.push(
236            u32::try_from(self.double_cards.len())
237                .expect("More double cards provided than expected"),
238        );
239        byte_array.extend(&self.double_cards.clone());
240
241        byte_array.push(
242            u32::try_from(self.multi_cards.len()).expect("More multi-cards provided than expected"),
243        );
244        byte_array.extend(&flatten_multi_cards(&self.multi_cards));
245
246        if self.sideboard_cards.is_empty() {
247            byte_array.push(0);
248        } else {
249            byte_array.push(1);
250            byte_array.push(
251                u32::try_from(self.sideboard_cards.len())
252                    .expect("More sideboard cards provided than expected"),
253            );
254            byte_array.extend(&flatten_sideboard(&self.sideboard_cards));
255        }
256
257        byte_array
258    }
259}
260
261fn flatten_multi_cards(intervals: &[(u8, u32)]) -> Vec<u32> {
262    use std::iter::once;
263
264    intervals
265        .iter()
266        .flat_map(|tup| once(tup.1).chain(once(u32::from(tup.0))))
267        .collect()
268}
269
270fn flatten_sideboard(intervals: &[(u32, u8, u32)]) -> Vec<u32> {
271    use std::iter::once;
272
273    intervals
274        .iter()
275        .flat_map(|tup| once(tup.0).chain(once(tup.2)))
276        // Terminating null bytes are only for sideboards
277        .chain(once(0))
278        .chain(once(0))
279        .collect()
280}
281
282fn next_end(previous_end: usize, total_number: u32) -> usize {
283    previous_end + total_number as usize + 1
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn new_returns_err_if_there_is_a_small_number_of_bytes_than_7() {
292        let input = vec![0, 1, 2];
293        let result = Deck::new(&input);
294        assert!(result.is_err());
295    }
296
297    #[test]
298    fn new_returns_err_if_there_is_no_leading_0_byte() {
299        let input = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
300        let result = Deck::new(&input);
301        assert!(result.is_err());
302    }
303
304    #[test]
305    fn new_returns_err_if_there_is_an_unexpected_version() {
306        let input = vec![0, 2, 0, 0, 0, 0, 0, 0, 0, 0];
307        let result = Deck::new(&input);
308        assert!(result.is_err());
309    }
310
311    #[test]
312    fn new_returns_err_if_there_is_a_larger_suggested_number_of_bytes_than_total_bytes() {
313        let input = vec![
314            0, 1, 1, //
315            // Hero Section
316            1, 7, //
317            // Single card section
318            7, // A LIE: 7 Single Cards when there are none
319            // Double card section
320            0, // Paired (Id, Count) Section
321            0,
322        ];
323        let result = Deck::new(&input);
324        assert!(result.is_err());
325    }
326
327    #[test]
328    fn new_matches_simple_example() {
329        let input = vec![
330            0, // Null byte
331            1, // Version 1
332            1, // Wild
333            // Hero Section
334            1, // 1 Hero
335            7, // Hero is id 7: Garrosh
336            // Single card section
337            0, // 0 Single Cards
338            // Double card section
339            0, // 0 Double cards
340            // Paired (Id, Count) Section
341            4, // 4 sets of multi-card
342            1, 3, // id 1, 3 copies
343            2, 3, // id 2, 3 copies
344            3, 3, // id 3, 3 copies
345            4, 3, // id 4, 3 copies
346        ];
347
348        let result = Deck::new(&input);
349
350        let expected = Deck {
351            format: Format::Wild,
352            version: 1,
353            heroes: vec![7],
354            single_cards: Vec::new(),
355            double_cards: Vec::new(),
356            multi_cards: vec![(3, 1), (3, 2), (3, 3), (3, 4)],
357            sideboard_cards: Vec::new(),
358        };
359        assert_eq!(result.unwrap(), expected);
360    }
361
362    #[test]
363    fn to_byte_array_matches_simple_example() {
364        let expected = vec![
365            0, // Null byte
366            1, // Version 1
367            1, // Wild format
368            // Hero Section
369            1, // 1 Hero
370            7, // Hero is id 7: Garrosh
371            // Single card section
372            0, // 0 Single Cards
373            // Double card section
374            0, // 0 Double cards
375            // Paired (Id, Count) Section
376            4, // 4 Pairs
377            1, 3, //
378            2, 3, //
379            3, 3, //
380            4, 3, //
381            // Sideboard section
382            0, // No sideboard
383        ];
384
385        let input = Deck {
386            format: Format::Wild,
387            version: 1,
388            heroes: vec![7],
389            single_cards: Vec::new(),
390            double_cards: Vec::new(),
391            multi_cards: vec![(3, 1), (3, 2), (3, 3), (3, 4)],
392            sideboard_cards: Vec::new(),
393        };
394        let result = input.to_byte_array();
395        assert_eq!(result, expected);
396    }
397
398    #[test]
399    fn to_byte_array_matches_complex_example() {
400        let expected = vec![
401            0,   // Null byte
402            1,   // Version 1
403            2,   // Standard format
404            1,   // 1 Hero
405            637, // Jaina,
406            // Section Single Cards
407            4,     // 4 Single Cards
408            192,   // Ice Block
409            39841, // Medivh, the Guardian
410            42718, // Ghastly Conjurer
411            42790, // Bonemare
412            // Section Double Cards
413            13,    // 13 Double Cards
414            113,   // Counter Spell
415            195,   // Mirror Entity
416            315,   // Fireball
417            405,   // Mana Wyrm
418            555,   // Arcane Intellect
419            662,   // Frostbolt
420            748,   // Kirin Tor Mage,
421            39715, // Firelands Portal
422            39767, // Medivh’s Valet
423            40297, // Volcanic Potion
424            40583, // Kabal Crystal Runner
425            41153, // Arcanologist
426            41496, // Primordial Glyph,
427            // Section Multi Cards
428            0, // No 3+-copy cards
429            // Section Sideboard
430            0, // No Sideboard
431        ];
432
433        let input = Deck {
434            format: Format::Standard,
435            version: 1,
436            heroes: vec![637],
437            single_cards: vec![192, 39841, 42718, 42790],
438            double_cards: vec![
439                113, 195, 315, 405, 555, 662, 748, 39715, 39767, 40297, 40583, 41153, 41496,
440            ],
441            multi_cards: Vec::new(),
442            sideboard_cards: Vec::new(),
443        };
444        let result = input.to_byte_array();
445        assert_eq!(result, expected);
446    }
447
448    #[test]
449    fn total_cards() {
450        let input = Deck {
451            format: Format::Wild,
452            version: 1,
453            heroes: vec![7],
454            single_cards: vec![1, 2, 3, 4],                    // 4
455            double_cards: vec![1, 2, 3, 4],                    // 8
456            multi_cards: vec![(3, 1), (3, 2), (3, 3), (3, 4)], //12
457            sideboard_cards: Vec::new(),
458        };
459        assert_eq!(24, input.total_cards());
460    }
461
462    #[test]
463    fn total_card_slots() {
464        let input = Deck {
465            format: Format::Wild,
466            version: 1,
467            heroes: vec![7],
468            single_cards: vec![1, 2, 3, 4],                    // 4
469            double_cards: vec![1, 2, 3, 4],                    // 4
470            multi_cards: vec![(3, 1), (3, 2), (3, 3), (3, 4)], // 4
471            sideboard_cards: Vec::new(),
472        };
473        assert_eq!(12, input.total_card_slots());
474    }
475}