1use std::collections::HashMap;
6use std::convert::Infallible;
7use std::fmt::Display;
8use std::str::FromStr;
9
10use binrw::BinRead;
11use binrw::BinWrite;
12use binrw::binrw;
13use modular_bitfield::Specifier;
14use modular_bitfield::bitfield;
15use num_enum::IntoPrimitive;
16use num_enum::TryFromPrimitive;
17
18use crate::constants::OT;
19use crate::constants::Rule;
20use crate::constants::Type;
21use crate::data::Card;
22use crate::data::LFList;
23
24const DECK_MIN: usize = 40;
25const DECK_MAX: usize = 60;
26const EXTRA_MAX: usize = 15;
27const SIDE_MAX: usize = 15;
28
29#[binrw]
31#[derive(Debug, Clone, Default)]
32pub struct Deck {
33 #[bw(calc = main.len() as u32 + extra.len() as u32)]
34 main_size: u32,
35 #[bw(calc = side.len() as u32)]
36 side_size: u32,
37 #[br(count = main_size)]
38 pub main: Vec<u32>,
39 #[br(count = side_size)]
40 pub side: Vec<u32>,
41 #[br(ignore)]
42 pub extra: Vec<u32>,
43}
44
45impl Deck {
46 pub fn new() -> Self { Self::default() }
48
49 pub fn load_from_codes(codes: &[u32], mainc: usize, sidec: usize) -> Self {
51 let mut d = Self::new();
52 let mc = mainc.min(codes.len());
53 d.main.extend_from_slice(&codes[..mc]);
54 let sc = sidec.min(codes.len().saturating_sub(mc));
55 d.side.extend_from_slice(&codes[mc..mc + sc]);
56 d
57 }
58
59 pub fn get_hash(&self) -> HashMap<u32, usize> {
61 let mut counts: HashMap<u32, usize> = HashMap::new();
62 for &code in self.main.iter().chain(self.extra.iter()).chain(self.side.iter()) {
63 *counts.entry(code).or_insert(0) += 1;
64 }
65 counts
66 }
67
68 pub fn load<'a>(&mut self, resolve_card: impl Fn(u32) -> Option<&'a Card>) -> Option<DeckError> {
70 let response = remove_unknown_cards(&mut self.main, |c| resolve_card(c).map(|c| c.card_type))
71 .or(remove_unknown_cards(&mut self.side, |c| resolve_card(c).map(|c| c.card_type)));
72 self.separate(|c| resolve_card(c).map(|c| c.card_type).unwrap_or(Type::empty()));
73 response
74 }
75
76 pub fn prepare<'a>(&mut self, lflist: &LFList, rule: Rule, resolve_card: impl Fn(u32) -> Option<&'a Card>) -> Result<(), DeckError> {
78 self.check(lflist, rule,
79 |c| resolve_card(c).map(|c| c.ot).unwrap_or(OT::empty()),
80 |c| resolve_card(c).map(|c| c.card_type).unwrap_or(Type::empty()),
81 |c| resolve_card(c).map(|c| c.duel_code()).unwrap_or(0))
82 }
83
84 pub fn check_after_replacing_side<'a>(&self, deck: &mut Deck, resolve_card: impl Fn(u32) -> Option<&'a Card>) -> Result<(), DeckError> {
86 deck.separate(|c| resolve_card(c).map(|c| c.card_type).unwrap_or(Type::empty()));
87 if self == deck {
88 Ok(())
89 } else {
90 Err(DeckError::new().with_error_type(DeckErrorType::SideCount))
91 }
92 }
93
94 pub fn separate(&mut self, resolve_type: impl Fn(u32) -> Type) {
96 separate_main_and_extra(&mut self.main, &mut self.extra, resolve_type);
97 }
98
99 pub fn check(&self, lflist: &LFList, rule: Rule, get_rule: impl Fn(u32) -> OT, get_type: impl Fn(u32) -> Type, resolve_code: impl Fn(u32) -> u32) -> Result<(), DeckError> {
101 check_deck_length(&self.main, &self.extra, &self.side)?;
102 check_illegal_cards(&self.main, &self.side, &self.extra, get_type)?;
103 let iter = self.main.iter().chain(self.extra.iter()).chain(self.side.iter());
104 check_rule(iter.clone(), rule, get_rule)?;
105 check_deck_lflists(iter, lflist, resolve_code)
106 }
107}
108
109impl ToString for Deck {
110 fn to_string(&self) -> String {
111 let mut text = String::from("#ygopro-rs deck generated\n#main\n");
112 for code in &self.main {
113 text.push_str(&code.to_string());
114 text.push('\n');
115 }
116 if self.extra.len() > 0 {
117 text.push_str("#extra\n");
118 for code in &self.extra {
119 text.push_str(&code.to_string());
120 text.push('\n');
121 }
122 }
123 text.push_str("!side\n");
124 for code in &self.side {
125 text.push_str(&code.to_string());
126 text.push('\n');
127 }
128 text
129 }
130}
131
132impl FromStr for Deck {
133 type Err = Infallible;
134
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
136 let mut deck = Self::new();
137 let mut section = &mut deck.main;
138 for line in s.lines() {
139 let line = line.trim();
140 if line.is_empty() { continue; }
141 match line.as_bytes()[0] {
142 b'!' => section = &mut deck.side,
143 b'#' => match line {
144 "#main" => section = &mut deck.main,
145 "#extra" => section = &mut deck.extra,
146 _ => {}
147 },
148 b'0'..=b'9' => {
149 let code_end = line.find(|c: char| !c.is_ascii_digit()).unwrap_or(line.len());
150 if let Ok(code) = line[..code_end].parse::<u32>() {
151 section.push(code);
152 }
153 }
154 _ => {}
155 }
156 }
157 Ok(deck)
158 }
159}
160
161impl PartialEq for Deck {
162 fn eq(&self, other: &Self) -> bool {
163 if self.main.len() != other.main.len()
164 || self.side.len() != other.side.len()
165 || self.extra.len() != other.extra.len() {
166 return false;
167 }
168 self.get_hash() == other.get_hash()
169 }
170}
171
172impl Eq for Deck {}
173
174#[derive(Specifier, Clone, Copy, Debug, IntoPrimitive, TryFromPrimitive, PartialEq, Eq)]
176#[bits = 4]
177#[repr(u8)]
178pub enum DeckErrorType {
179 Lflist = 0x1,
181 OcgOnly = 0x2,
183 TcgOnly = 0x3,
185 UnknownCard = 0x4,
187 CardCount = 0x5,
189 MainCount = 0x6,
191 ExtraCount = 0x7,
193 SideCount = 0x8,
195 NotAvailable = 0x9,
197}
198
199#[bitfield]
201#[derive(BinRead, BinWrite, Debug, Clone, Copy, PartialEq, Eq)]
202#[br(map = Self::from_bytes)]
203#[bw(map = |&x| Self::into_bytes(x))]
204#[repr(u32)]
205pub struct DeckError {
206 pub code: modular_bitfield::specifiers::B28,
208 pub error_type: DeckErrorType,
210}
211
212impl Display for DeckError {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 write!(f, "DeckError({:?}, code: {})", self.error_type(), self.code())
215 }
216}
217
218impl std::error::Error for DeckError {}
219
220const EXTRA_TYPE: Type = Type::from_bits_retain(0x4802040);
221
222pub fn separate_main_and_extra(main: &mut Vec<u32>, ex: &mut Vec<u32>, resolve_type: impl Fn(u32) -> Type) {
224 main.retain(|&code| {
225 if resolve_type(code).intersects(EXTRA_TYPE) {
226 if ex.len() < EXTRA_MAX { ex.push(code); }
227 false
228 } else {
229 true
230 }
231 });
232}
233
234pub fn check_deck_length(main: &[u32], extra: &[u32], side: &[u32]) -> Result<(),DeckError> {
236 if main.len() < DECK_MIN || main.len() > DECK_MAX { return Err(DeckError::new().with_error_type(DeckErrorType::MainCount).with_code(main.len() as u32)); }
237 if extra.len() > EXTRA_MAX { return Err(DeckError::new().with_error_type(DeckErrorType::ExtraCount).with_code(extra.len() as u32)); }
238 if side.len() > SIDE_MAX { return Err(DeckError::new().with_error_type(DeckErrorType::SideCount).with_code(side.len() as u32)); }
239 Ok(())
240}
241
242pub fn remove_unknown_cards(main: &mut Vec<u32>, get_type: impl Fn(u32) -> Option<Type>) -> Option<DeckError> {
244 let mut last_removed_code = None;
245 main.retain(|code| {
246 let _type = get_type(*code);
247 if match _type {
248 Some(_type) => _type.contains(Type::Token),
249 None => true
250 } {
251 last_removed_code = Some(*code);
252 false
253 } else { true }
254 });
255 last_removed_code.map(|code| DeckError::new().with_error_type(DeckErrorType::UnknownCard).with_code(code))
256}
257
258pub fn check_illegal_cards(main: &Vec<u32>, side: &Vec<u32>, ex: &Vec<u32>, get_type: impl Fn(u32) -> Type) -> Result<(), DeckError> {
260 for code in main {
261 let card_type = get_type(*code);
262 if card_type.contains(Type::Token) || card_type.intersects(EXTRA_TYPE) {
263 return Err(DeckError::new().with_error_type(DeckErrorType::MainCount).with_code(0));
264 }
265 }
266 for code in side {
267 if get_type(*code).contains(Type::Token) {
268 return Err(DeckError::new().with_error_type(DeckErrorType::SideCount).with_code(0));
269 }
270 }
271 for code in ex {
272 let card_type = get_type(*code);
273 if card_type.contains(Type::Token) || !card_type.intersects(EXTRA_TYPE) {
274 return Err(DeckError::new().with_error_type(DeckErrorType::ExtraCount).with_code(0));
275 }
276 }
277 Ok(())
278}
279
280pub fn check_rule<'a>(codes: impl Iterator<Item = &'a u32>, rule: Rule, get_rule: impl Fn(u32) -> OT) -> Result<(), DeckError> {
282 for &code in codes {
283 let ot = get_rule(code);
284 if let Some(error_type) = rule.check_ot(ot) {
285 return Err(DeckError::new().with_error_type(error_type).with_code(code));
286 }
287 }
288 Ok(())
289}
290
291pub fn check_deck_lflists<'a>(codes: impl Iterator<Item = &'a u32>, lflist: &LFList, resolve_code: impl Fn(u32) -> u32) -> Result<(), DeckError> {
293 let mut counts: HashMap<u32, u32> = HashMap::new();
294 for &code in codes {
295 let resolved = resolve_code(code);
296 *counts.entry(resolved).or_insert(0) += 1;
297 }
298
299 let mut current = 0;
300 for (&code, &count) in &counts {
301 if count > 3 {
302 return Err(DeckError::new().with_error_type(DeckErrorType::CardCount).with_code(code));
303 }
304 if lflist.genesys > 0 && let Some(&limit) = lflist.glist.get(&code) {
305 current += limit * count;
306 if current > lflist.genesys {
307 return Err(DeckError::new().with_error_type(DeckErrorType::Lflist).with_code(code));
308 }
309 }
310 if let Some(&limit) = lflist.content.get(&code)
311 && count as u8 > limit {
312 return Err(DeckError::new().with_error_type(DeckErrorType::Lflist).with_code(code));
313 }
314 }
315 Ok(())
316}
317
318#[cfg(test)]
319mod tests {
320 use crate::data::Deck;
321
322 #[test]
323 fn splits_main_and_side_at_bang_marker() {
324 let deck: Deck = "#created by test\n#main\n123\n456\n#extra\n789\n!side\n111\n222\n"
325 .parse()
326 .unwrap();
327 assert_eq!(deck.main, vec![123, 456, 789]);
328 assert!(deck.extra.is_empty());
329 assert_eq!(deck.side, vec![111, 222]);
330 }
331
332 #[test]
333 fn drops_comment_blank_and_invalid_lines() {
334 let deck: Deck = " \n#comment\n123abc\nnot-a-number\n!side\n\nxyz\n".parse().unwrap();
335 assert_eq!(deck.main, vec![123]);
336 assert!(deck.side.is_empty());
337 }
338
339 #[test]
340 fn round_trips_through_to_string() {
341 let deck: Deck = "#main\n1\n2\n3\n!side\n4\n".parse().unwrap();
342 let text = deck.to_string();
343 let reparsed: Deck = text.parse().unwrap();
344 assert_eq!(reparsed.main, deck.main);
345 assert_eq!(reparsed.extra, deck.extra);
346 assert_eq!(reparsed.side, deck.side);
347 }
348}