Skip to main content

pokebase_core/
database.rs

1use crate::card;
2use crate::locale;
3use crate::pokemon;
4use crate::series;
5use crate::set;
6use crate::{Card, Locale, Map, Pokemon, Series, Set};
7
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Serialize};
10use std::collections::{BTreeMap, BTreeSet};
11use std::fmt;
12use std::path::Path;
13
14#[derive(Clone)]
15pub struct Database {
16    pub pokemon: Map<pokemon::Id, Pokemon>,
17    pub series: Map<series::Id, Series>,
18    pub sets: Map<set::Id, Set>,
19    pub cards: Map<card::Id, Card>,
20}
21
22impl Database {
23    pub async fn load() -> Result<Self, anywho::Error> {
24        use tokio::task;
25
26        Ok(task::spawn_blocking(|| {
27            let pokemon = load_pokemon();
28            let series: Vec<Series> = decompress(include_bytes!("../data/series.ron.gz"));
29            let sets: Vec<Set> = decompress(include_bytes!("../data/sets.ron.gz"));
30            let cards: Vec<Card> = decompress(include_bytes!("../data/cards.ron.gz"));
31
32            log::info!("Loaded database with {} cards", cards.len());
33
34            Self {
35                pokemon: Map::new(pokemon, |pokemon| pokemon.id),
36                series: Map::new(series, |series| series.id.clone()),
37                sets: Map::new(sets, |set| set.id.clone()),
38                cards: Map::new(cards, |card| card.id.clone()),
39            }
40        })
41        .await?)
42    }
43
44    pub fn generate(data: impl AsRef<Path>) -> Result<Self, anywho::Error> {
45        use std::fs::{self, File};
46        use std::io::BufReader;
47
48        let pokemon = load_pokemon();
49
50        let mut series: BTreeMap<String, Series> = BTreeMap::new();
51        let mut sets: BTreeMap<String, Set> = BTreeMap::new();
52        let mut cards: BTreeMap<String, Card> = BTreeMap::new();
53
54        let entries = fs::read_dir(&data)?;
55
56        for entry in entries {
57            let entry = entry?;
58
59            if !entry.metadata()?.is_dir() {
60                continue;
61            }
62
63            let locale = Locale(entry.file_name().to_string_lossy().to_string());
64
65            if locale == Locale("ko".to_owned()) {
66                continue;
67            }
68            dbg!(&locale);
69
70            // Series
71            #[derive(Serialize, Deserialize)]
72            #[serde(rename_all = "camelCase")]
73            struct LocalizedSeries {
74                id: String,
75                name: String,
76                release_date: String,
77            }
78
79            dbg!(entry.path().join("series.json"));
80
81            let localized_series_list: Vec<LocalizedSeries> = {
82                let file = BufReader::new(File::open(entry.path().join("series.json"))?);
83                serde_json::from_reader(file)?
84            };
85
86            for localized_series in localized_series_list {
87                let series = series
88                    .entry(localized_series.id.clone())
89                    .or_insert_with(|| Series {
90                        id: series::Id(localized_series.id),
91                        name: locale::Map::new(),
92                        release_date: localized_series.release_date,
93                    });
94
95                series.name.insert(locale.clone(), localized_series.name);
96            }
97
98            // Sets
99            #[derive(Serialize, Deserialize)]
100            #[serde(rename_all = "camelCase")]
101            struct LocalizedSet {
102                id: String,
103                name: String,
104                serie: Serie,
105                release_date: String,
106                card_count: CardCount,
107                abbreviation: Option<Abbreviation>,
108            }
109
110            #[derive(Serialize, Deserialize)]
111            struct Serie {
112                id: String,
113            }
114
115            #[derive(Serialize, Deserialize)]
116            struct CardCount {
117                total: usize,
118            }
119
120            #[derive(Serialize, Deserialize)]
121            struct Abbreviation {
122                official: String,
123            }
124
125            dbg!(entry.path().join("sets.json"));
126
127            let localized_sets: Vec<LocalizedSet> = {
128                let file = BufReader::new(File::open(entry.path().join("sets.json"))?);
129                serde_json::from_reader(file)?
130            };
131
132            for localized_set in localized_sets {
133                let set = sets.entry(localized_set.id.clone()).or_insert_with(|| Set {
134                    id: set::Id(localized_set.id),
135                    name: locale::Map::new(),
136                    series: series::Id(localized_set.serie.id),
137                    release_date: localized_set.release_date,
138                    total_cards: localized_set.card_count.total,
139                    abbreviation: None,
140                });
141
142                set.name.insert(locale.clone(), localized_set.name);
143
144                match localized_set.abbreviation {
145                    Some(abbreviation) if set.abbreviation.is_none() => {
146                        set.abbreviation = Some(abbreviation.official);
147                    }
148                    _ => {}
149                }
150            }
151
152            // Cards
153            #[derive(Serialize, Deserialize)]
154            #[serde(rename_all = "camelCase")]
155            struct LocalizedCard {
156                id: String,
157                name: String,
158                set: CardSet,
159                #[serde(default)]
160                rarity: Option<String>,
161                #[serde(default)]
162                types: Vec<String>,
163                variants: LocalizedVariants,
164                #[serde(default)]
165                illustrator: Option<String>,
166                #[serde(default)]
167                dex_id: Vec<f64>,
168            }
169
170            #[derive(Serialize, Deserialize)]
171            struct CardSet {
172                id: String,
173            }
174
175            #[derive(Serialize, Deserialize)]
176            #[serde(rename_all = "camelCase")]
177            struct LocalizedVariants {
178                first_edition: bool,
179                holo: bool,
180                normal: bool,
181                reverse: bool,
182                w_promo: bool,
183            }
184
185            dbg!(entry.path().join("cards.json"));
186
187            let localized_cards: Vec<LocalizedCard> = {
188                let file = BufReader::new(File::open(entry.path().join("cards.json"))?);
189                serde_json::from_reader(file)?
190            };
191
192            for localized_card in localized_cards {
193                let Some(set) = sets.get(&localized_card.set.id) else {
194                    continue;
195                };
196
197                if set.series.as_str() == "tcgp" {
198                    continue;
199                }
200
201                let card = cards
202                    .entry(localized_card.id.clone())
203                    .or_insert_with(|| Card {
204                        id: card::Id(localized_card.id),
205                        name: locale::Map::new(),
206                        set: set::Id(localized_card.set.id),
207                        types: BTreeSet::new(),
208                        rarity: card::Rarity::None,
209                        variants: card::Variants {
210                            first_edition: localized_card.variants.first_edition,
211                            holo: localized_card.variants.holo,
212                            normal: localized_card.variants.normal,
213                            reverse: localized_card.variants.reverse,
214                            w_promo: localized_card.variants.w_promo,
215                        },
216                        illustrator: localized_card.illustrator,
217                        pokedex: localized_card
218                            .dex_id
219                            .into_iter()
220                            .map(|id| id as usize)
221                            .map(pokemon::Id)
222                            .collect(),
223                    });
224
225                // Fill in Pokedex entries
226                if locale.0 == "en" && card.pokedex.is_empty() {
227                    for pokemon in &pokemon {
228                        let name = pokemon.name();
229
230                        if let Some(start) = localized_card.name.find(name) {
231                            let end = start + name.len();
232                            let previous = start.saturating_sub(1);
233
234                            let left = localized_card.name[previous..start].chars();
235                            let right = localized_card.name
236                                [end..(end + 1).min(localized_card.name.len())]
237                                .chars();
238
239                            if left.chain(right).all(|c| c.is_whitespace() || c == '-') {
240                                card.pokedex = vec![pokemon.id];
241                                break;
242                            }
243                        }
244                    }
245                }
246
247                card.name.insert(locale.clone(), localized_card.name);
248                card.rarity = card.rarity.max(
249                    localized_card
250                        .rarity
251                        .and_then(|rarity| parse_rarity(rarity).ok())
252                        .unwrap_or_default(),
253                );
254
255                for type_ in localized_card.types {
256                    if let Ok(type_) = parse_type(type_) {
257                        card.types.insert(type_);
258                    }
259                }
260            }
261        }
262
263        let mut cards: Vec<_> = cards.into_values().collect();
264        cards.sort_by_key(|card| {
265            sets.get(&card.set.0)
266                .map(|set| {
267                    format!(
268                        "{release_date}-{:0>5}",
269                        card.id.0.split("-").last().unwrap_or_default(),
270                        release_date = set.release_date,
271                    )
272                })
273                .unwrap_or_default()
274        });
275
276        let mut series: Vec<_> = series.into_values().collect();
277        series.sort_by(|a, b| a.release_date.cmp(&b.release_date));
278
279        let mut sets: Vec<_> = sets.into_values().collect();
280        sets.sort_by(|a, b| a.release_date.cmp(&b.release_date));
281
282        Ok(Self {
283            pokemon: Map::new(pokemon, |pokemon| pokemon.id),
284            series: Map::new(series, |series| series.id.clone()),
285            sets: Map::new(sets, |set| set.id.clone()),
286            cards: Map::new(cards, |card| card.id.clone()),
287        })
288    }
289
290    pub fn find(&self, set: &str, number: &str) -> Option<&Card> {
291        if set.len() < 2 {
292            return None;
293        }
294
295        let mut set_matches: Vec<_> = self
296            .sets
297            .values()
298            .iter()
299            .filter_map(|candidate| {
300                let abbreviation = candidate.abbreviation.as_ref()?;
301
302                if abbreviation == set {
303                    return Some((candidate, 0));
304                }
305
306                let distance = abbreviation
307                    .chars()
308                    .zip(set.chars())
309                    .map(|(a, b)| if a == b { 0 } else { 1 })
310                    .sum::<u64>();
311
312                Some((candidate, distance + 1))
313            })
314            .collect();
315
316        set_matches.sort_by_key(|(_, distance)| *distance);
317
318        let (best_set, _) = set_matches.first()?;
319        let card_id = card::Id(format!("{}-{number}", best_set.id));
320
321        self.cards.get(&card_id)
322    }
323}
324
325fn parse_type(type_: String) -> Result<card::Type, String> {
326    Ok(match type_.as_str() {
327        "Grass" => card::Type::Grass,
328        "Fire" => card::Type::Fire,
329        "Water" => card::Type::Water,
330        "Lightning" => card::Type::Lightning,
331        "Psychic" => card::Type::Psychic,
332        "Fighting" => card::Type::Fighting,
333        "Darkness" => card::Type::Darkness,
334        "Metal" => card::Type::Metal,
335        "Fairy" => card::Type::Fairy,
336        "Dragon" => card::Type::Dragon,
337        "Colorless" => card::Type::Colorless,
338        _ => Err(format!("invalid type: {type_}"))?,
339    })
340}
341
342fn parse_rarity(rarity: String) -> Result<card::Rarity, String> {
343    Ok(match rarity.as_str() {
344        "None" => card::Rarity::None,
345        "Common" | "One Diamond" => card::Rarity::Common,
346        "Uncommon" | "Two Diamond" => card::Rarity::Uncommon,
347        "Rare" | "Three Diamond" => card::Rarity::Rare,
348        "Holo Rare" | "Rare Holo" => card::Rarity::HoloRare,
349        "Rare Holo LV.X" => card::Rarity::HoloRareLvx,
350        "Holo Rare V" => card::Rarity::HoloRareV,
351        "Holo Rare VMAX" => card::Rarity::HoloRareVmax,
352        "Holo Rare VSTAR" => card::Rarity::HoloRareVstar,
353        "Shiny rare" | "One Shiny" => card::Rarity::ShinyRare,
354        "Shiny rare V" => card::Rarity::ShinyRareV,
355        "Shiny rare VMAX" => card::Rarity::ShinyRareVmax,
356        "Double rare" => card::Rarity::DoubleRare,
357        "ACE SPEC Rare" => card::Rarity::AceSpecRare,
358        "Amazing Rare" => card::Rarity::AmazingRare,
359        "Radiant Rare" => card::Rarity::RadiantRare,
360        "Rare PRIME" => card::Rarity::RarePrime,
361        "LEGEND" => card::Rarity::Legend,
362        "Classic Collection" => card::Rarity::ClassicCollection,
363        "Ultra Rare" | "Four Diamond" => card::Rarity::UltraRare,
364        "Shiny Ultra Rare" | "Two Shiny" => card::Rarity::ShinyUltraRare,
365        "Secret Rare" => card::Rarity::SecretRare,
366        "Full Art Trainer" => card::Rarity::FullArtTrainer,
367        "Illustration rare" | "One Star" => card::Rarity::IllustrationRare,
368        "Special illustration rare" | "Two Star" | "Three Star" => {
369            card::Rarity::SpecialIllustrationRare
370        }
371        "Hyper rare" | "Crown" => card::Rarity::HyperRare,
372        _ => {
373            dbg!(&rarity);
374
375            Err(format!("invalid rarity: {rarity}"))?
376        }
377    })
378}
379
380fn load_pokemon() -> Vec<Pokemon> {
381    let pokemon: Vec<String> = decompress(include_bytes!("../data/pokemon.ron.gz"));
382
383    pokemon
384        .into_iter()
385        .enumerate()
386        .map(|(i, name)| Pokemon {
387            id: pokemon::Id(i + 1),
388            name: locale::Map::from_iter([(Locale("en".to_owned()), name)]),
389        })
390        .collect()
391}
392
393fn decompress<T: DeserializeOwned>(bytes: &[u8]) -> T {
394    use flate2::read::GzDecoder;
395
396    let decoder = GzDecoder::new(bytes);
397
398    ron::de::from_reader(decoder).expect("Database is corrupt! Decompression failed.")
399}
400
401impl fmt::Debug for Database {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        f.debug_struct("Database")
404            .field("pokemon", &self.pokemon.len())
405            .field("series", &self.series.len())
406            .field("sets", &self.sets.len())
407            .field("cards", &self.cards.len())
408            .finish()
409    }
410}