1
Fork 0
mirror of https://github.com/Steffo99/patched-porobot.git synced 2024-12-22 17:44:22 +00:00

Batch of changes for v0.8.0 (#2)

See changelog for more details.
This commit is contained in:
Steffo 2023-01-30 18:33:50 +01:00 committed by GitHub
parent c0ed6189e5
commit e91e89cab1
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 399 additions and 89 deletions

2
Cargo.lock generated
View file

@ -1039,7 +1039,7 @@ dependencies = [
[[package]]
name = "patched_porobot"
version = "0.7.1"
version = "0.8.0"
dependencies = [
"data-encoding",
"glob",

View file

@ -1,6 +1,6 @@
[package]
name = "patched_porobot"
version = "0.7.1"
version = "0.8.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
edition = "2021"
description = "Legends of Runeterra card database utilities and bots"
@ -23,9 +23,9 @@ regex = { version = "1.6.0" }
lazy_static = { version = "1.4.0" }
data-encoding = { version = "2.3.2" }
varint-rs = { version = "2.2.0" }
glob = { version = "0.3.0" }
# exec
pretty_env_logger = { version = "0.4.0", optional = true }
glob = { version = "0.3.0", optional = true }
# data
serde = { version = "1.0.140", features = ["derive"] }
serde_json = { version = "1.0.82" }
@ -43,7 +43,7 @@ rand = { version = "0.8.5", optional = true }
[features]
# data = [] # Always included
exec = ["pretty_env_logger", "glob"]
exec = ["pretty_env_logger"]
search = ["tantivy"]
telegram = ["exec", "search", "teloxide", "reqwest", "tokio", "md5", "rand"]
discord = ["exec", "search"]

View file

@ -46,7 +46,7 @@ impl CoreBundle {
let locale = metadata.locale().ok_or(LoadingError::GettingLocale)?;
let globals_path = &bundle_path
.join(&locale)
.join(locale)
.join("data")
.join(format!("globals-{}.json", &locale));
@ -59,3 +59,19 @@ impl CoreBundle {
})
}
}
/// Create [`globals::LocalizedGlobalsIndexes`] from the core bundle in the current working directory.
///
/// This function tries to load data from the first directory matching the [glob] `./data/core-*`.
pub fn create_globalindexes_from_wd() -> globals::LocalizedGlobalsIndexes {
let path = glob::glob("./data/core-*")
.expect("glob to be a valid glob")
.filter_map(Some)
.find_map(Result::ok)
.expect("a valid core bundle to exist");
let core = CoreBundle::load(&path)
.expect("to be able to load `core-en_us` bundle");
globals::LocalizedGlobalsIndexes::from(core.globals)
}

View file

@ -2,13 +2,15 @@
use super::format::DeckCodeFormat;
use crate::data::deckcode::version::{DeckCodeVersion, DeckCodeVersioned};
use crate::data::setbundle::card::{Card, CardIndex};
use crate::data::setbundle::code::CardCode;
use crate::data::setbundle::region::CardRegion;
use crate::data::setbundle::set::CardSet;
use itertools::Itertools;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::io::{Cursor, Read, Write};
use varint_rs::{VarintReader, VarintWriter};
use crate::data::setbundle::supertype::CardSupertype;
/// A unshuffled Legends of Runeterra card deck.
#[derive(Clone, Debug, PartialEq, Eq)]
@ -408,6 +410,170 @@ impl Deck {
Ok(Self::encode_code(&cursor.into_inner()))
}
/// Get an [`Iterator`] of all Champion [`Card`]s in the deck.
pub fn champions<'a>(&'a self, cards: &'a CardIndex) -> impl Iterator<Item = &'a Card> {
self.contents.keys()
.filter_map(|cc| cc.to_card(cards))
.filter(|c| c.supertype == CardSupertype::Champion)
}
/// Count the number of copies of the given card present in this deck.
///
/// # Example
///
/// ```rust
/// use patched_porobot::deck;
/// use patched_porobot::data::deckcode::deck::Deck;
/// use patched_porobot::data::setbundle::code::CardCode;
///
/// let d: Deck = deck![
/// "01DE002": 40,
/// ];
///
/// let copies = d.copies_of(&CardCode::from("01DE002".to_string()));
/// assert_eq!(copies, 40);
/// ```
pub fn copies_of(&self, code: &CardCode) -> u32 {
self.contents.get(code).map_or(0, ToOwned::to_owned)
}
/// Get the number of cards in the deck.
///
/// In the *Standard* and *Singleton* formats, this is never more than 40.
///
/// # Example
///
/// ```rust
/// use patched_porobot::deck;
/// use patched_porobot::data::deckcode::deck::Deck;
/// use patched_porobot::data::setbundle::card::CardIndex;
/// use patched_porobot::data::setbundle::create_cardindex_from_wd;
///
/// let index: CardIndex = create_cardindex_from_wd();
/// let deck: Deck = deck!("CECQCAQCA4AQIAYKAIAQGLRWAQAQECAPEUXAIAQDAEBQOCIBAIAQEMJYAA");
/// assert_eq!(d.card_count(&index), 40);
///
pub fn card_count(&self) -> u32 {
self.contents.values().sum()
}
/// Get the number of champion cards in the deck.
///
/// In the *Standard* and *Singleton* format, this is never more than 6.
///
/// # Example
///
/// ```rust
/// use patched_porobot::deck;
/// use patched_porobot::data::deckcode::deck::Deck;
/// use patched_porobot::data::setbundle::card::CardIndex;
/// use patched_porobot::data::setbundle::create_cardindex_from_wd;
///
/// let index: CardIndex = create_cardindex_from_wd();
/// let deck: Deck = deck!("CECQCAQCA4AQIAYKAIAQGLRWAQAQECAPEUXAIAQDAEBQOCIBAIAQEMJYAA");
/// assert_eq!(d.champions_count(&index), 6);
/// ```
pub fn champions_count(&self, cards: &CardIndex) -> u32 {
self.champions(cards)
.map(|c| &c.code)
.map(|cc| self.copies_of(cc))
.sum()
}
/// Find the first possible set of regions that the [`Deck`] fits in.
///
/// Lower amounts of regions are preferred: the limit will be increased from 1 up to `limit` until a valid solution is found.
///
/// # Warning
///
/// This method traverses the tree of possible region selections until one is found.
///
/// The time required to run this function may grow exponentially with the amount of cards in the deck!
pub fn regions(&self, card_index: &CardIndex, limit: usize) -> Option<HashSet<CardRegion>> {
let cards: Vec<&Card> = self.contents.keys()
.flat_map(|cc| cc.to_card(card_index))
.collect();
for n in 1..=limit {
let result = Self::regions_recursive_first_limit(cards.as_slice(), HashSet::new(), n);
if result.is_some() {
return result;
}
}
None
}
/// Find the first possible set of regions that covers all given [`Card`]s.
///
/// This function is *recursive*: the `cards` parameter holds the [`Card`]s left to process, while the `regions` parameter holds the regions found so far, and the `limit` parameter holds the size of the `regions` set to stop the recursion at.
///
/// # Warning
///
/// This method traverses the tree of possible region selections until one is found.
///
/// The time required to run this function may grow exponentially with the size of `cards`!
fn regions_recursive_first_limit(cards: &[&Card], regions: HashSet<CardRegion>, limit: usize) -> Option<HashSet<CardRegion>> {
match cards.get(0) {
None => Some(regions),
Some(card) => {
card.regions.iter()
.map(|region| {
match region {
CardRegion::Unsupported => {
None
}
CardRegion::Runeterra => {
Self::regions_recursive_first_limit(&cards[1..], regions.clone(), limit)
}
_ => {
let mut regions = regions.clone();
let inserted = regions.insert(*region);
match inserted && regions.len() > limit {
true => None,
false => Self::regions_recursive_first_limit(&cards[1..], regions, limit)
}
}
}
})
.find(Option::is_some)
.unwrap_or(None)
},
}
}
/// Check if the [`Deck`] is legal for play in the *Standard* format.
///
/// # Returns
///
/// - `None` if the deck is not legal for *Standard* play.
/// - `Some(regions)` if the deck is legal for *Standard* play considering the specified region set.
pub fn standard(&self, cards: &CardIndex) -> Option<HashSet<CardRegion>> {
let copies_limit = self.contents.values().all(|n| n <= &3);
let cards_limit = self.card_count() == 40;
let champions_limit = self.champions_count(cards) <= 6;
let regions = self.regions(cards, 2);
match copies_limit && cards_limit && champions_limit {
false => None,
true => regions,
}
}
/// Check if the [`Deck`] is legal for play in the *Singleton* format.
pub fn singleton(&self, cards: &CardIndex) -> Option<HashSet<CardRegion>> {
let copies_limit = self.contents.values().all(|n| n <= &1);
let cards_limit = self.card_count() == 40;
let champions_limit = self.champions_count(cards) <= 6;
let regions = self.regions(cards, 3);
match copies_limit && cards_limit && champions_limit {
false => None,
true => regions,
}
}
}
/// An error occoured while decoding a [`Deck`] from a code.
@ -448,9 +614,26 @@ pub type DeckDecodingResult<T> = Result<T, DeckDecodingError>;
/// The [`Result`] of a [`Deck`] **encoding** operation, for example [`Deck::to_code`].
pub type DeckEncodingResult<T> = Result<T, DeckEncodingError>;
/// Macro to build a deck from card code strings and quantities.
/// Macro to build a deck.
///
/// # Example
/// Useful to quickly build [`Deck`]s from trusted input, such as when creating tests.
///
/// It can build a deck:
///
/// - from a deck code;
/// - from card code strings and quantities.
///
/// # Panics
///
/// If the deck code is not valid.
///
/// # Examples
///
/// ```rust
/// use patched_porobot::deck;
///
/// let _my_deck = deck!("CECQCAQCA4AQIAYKAIAQGLRWAQAQECAPEUXAIAQDAEBQOCIBAIAQEMJYAA");
/// ```
///
/// ```rust
/// use patched_porobot::deck;
@ -480,13 +663,17 @@ pub type DeckEncodingResult<T> = Result<T, DeckEncodingError>;
/// ```
#[macro_export]
macro_rules! deck {
[$($cd:literal: $qty:literal),* $(,)?] => {
[ $($cd:literal: $qty:expr),* $(,)? ] => {
$crate::data::deckcode::deck::Deck {
contents: std::collections::HashMap::from([
$(($crate::data::setbundle::code::CardCode { full: $cd.to_string() }, $qty),)*
])
}
}
};
( $code:expr ) => {
$crate::data::deckcode::deck::Deck::from_code($code).expect("deck code created with deck!() to be valid")
};
}
#[rustfmt::skip::macros(test_de_ser, test_ser_de)]
@ -653,5 +840,64 @@ mod tests {
"02BW012": 69,
]);
//test_ser_de!(test_ser_de_, deck![]);
// test_ser_de!(test_ser_de_, deck![]);
macro_rules! test_legality {
( $id:ident, $deck:expr, $check:path, $assert:expr ) => {
#[test]
fn $id() {
let index = $crate::data::setbundle::create_cardindex_from_wd();
let deck: Deck = $deck;
let result = $check(&deck, &index).is_some();
assert_eq!(result, $assert);
}
}
}
test_legality!(
test_legality_standard_lonelyporo1,
deck!("CEAAAAIBAEAQQ"),
Deck::standard, false
);
test_legality!(
test_legality_standard_twistedshrimp,
deck!("CICACBAFAEBAGBQICABQCBJLF4YQOAQGAQEQYEQUDITAAAIBAMCQO"),
Deck::standard, true
);
test_legality!(
test_legality_standard_poros,
deck!("CQDQCAQBAMAQGAICAECACDYCAECBIFYCAMCBEEYCAUFIYANAAEBQCAIICA2QCAQBAEVTSAA"),
Deck::standard, true
);
test_legality!(
test_legality_standard_sand,
deck!("CMBAGBAHANTXEBQBAUCAOFJGFIYQEAIBAUOQIBAHGM5HM6ICAECAOOYCAECRSGY"),
Deck::standard, true
);
test_legality!(
test_legality_singleton_lonelyporo1,
deck!("CEAAAAIBAEAQQ"),
Deck::singleton, false
);
test_legality!(
test_legality_singleton_twistedshrimp,
deck!("CICACBAFAEBAGBQICABQCBJLF4YQOAQGAQEQYEQUDITAAAIBAMCQO"),
Deck::singleton, false
);
test_legality!(
test_legality_singleton_poros,
deck!("CQDQCAQBAMAQGAICAECACDYCAECBIFYCAMCBEEYCAUFIYANAAEBQCAIICA2QCAQBAEVTSAA"),
Deck::singleton, false
);
test_legality!(
test_legality_singleton_sand,
deck!("CMBAGBAHANTXEBQBAUCAOFJGFIYQEAIBAUOQIBAHGM5HM6ICAECAOOYCAECRSGY"),
Deck::singleton, false
);
test_legality!(
test_legality_singleton_paltri,
deck!("CQAAADABAICACAIFBLAACAIFAEHQCBQBEQBAGBADAQBAIAIKBUBAKBAWDUBQIBACA4GAMAIBAMCAYHJBGADAMBAOCQKRMKBLA4AQIAQ3D4QSIKZYBACAODJ3JRIW3AABQIAYUAI"),
Deck::singleton, true
);
}

View file

@ -77,9 +77,6 @@ impl DeckCodeVersioned for CardRegion {
CardRegion::BandleCity => Some(DeckCodeVersion::V4),
CardRegion::Runeterra => Some(DeckCodeVersion::V5),
CardRegion::Jhin => Some(DeckCodeVersion::V5),
CardRegion::Evelynn => Some(DeckCodeVersion::V5),
CardRegion::Bard => Some(DeckCodeVersion::V5),
_ => None,
}

View file

@ -2,7 +2,7 @@
use crate::data::setbundle::card::{Card, CardIndex};
/// The internal code of a [Card](super::card::Card).
/// The internal code of a [`Card`].
///
/// It is a ASCII string composed of the following segments:
/// - `0..2`: set;
@ -98,3 +98,10 @@ impl From<String> for CardCode {
CardCode { full }
}
}
/// Extract the card code from a [`Card`].
impl From<Card> for CardCode {
fn from(c: Card) -> Self {
c.code
}
}

View file

@ -6,7 +6,7 @@
use super::anybundle::metadata::BundleMetadata;
use crate::data::anybundle::outcomes::{LoadingError, LoadingResult};
use std::fs::File;
use std::path::Path;
use std::path::{Path, PathBuf};
pub mod art;
pub mod card;
@ -50,7 +50,7 @@ impl SetBundle {
let mut json_filename = name.to_os_string();
json_filename.push(".json");
&bundle_path.join(&locale).join("data").join(&json_filename)
&bundle_path.join(locale).join("data").join(&json_filename)
};
let name = name
@ -70,3 +70,37 @@ impl SetBundle {
})
}
}
/// Create a [`card::CardIndex`] from set bundles in the given paths.
///
/// # Panics
///
/// If any of the required files cannot be loaded (see [`SetBundle::load`]).
pub fn create_cardindex_from_paths(paths: impl Iterator<Item = PathBuf>) -> card::CardIndex {
let mut index = card::CardIndex::new();
for path in paths {
let set = SetBundle::load(&path).expect("to be able to load SetBundle");
for card in set.cards {
index.insert(card.code.clone(), card);
}
};
index
}
/// Create a [`card::CardIndex`] from set bundles in the current working directory.
///
/// This function tries to load data from any directory matching the [glob] `./data/set*-*`.
///
/// # Panics
///
/// See [`create_cardindex_from_paths`].
pub fn create_cardindex_from_wd() -> card::CardIndex {
let paths = glob::glob("./data/set*-*")
.expect("glob to be a valid glob")
.filter_map(Some)
.filter_map(Result::ok);
create_cardindex_from_paths(paths)
}

View file

@ -32,22 +32,15 @@ pub enum CardRegion {
/// Runeterra.
Runeterra,
/// Origin: The Virtuoso.
Jhin,
/// Origin: Agony's Embrace.
Evelynn,
/// Origin: The Wandering Caretaker.
Bard,
/// Unsupported region.
#[serde(other)]
Unsupported,
}
impl CardRegion {
/// Get the [LocalizedCardRegion] associated with this [CardRegion].
/// Get the [`LocalizedCardRegion`] associated with this [`CardRegion`].
///
/// Returns [Option::None] if no matching [LocalizedCardRegion] was found, for example for [CardRegion::Unsupported] regions.
/// Returns [`None`] if no matching [`LocalizedCardRegion`] was found, for example for [`CardRegion::Unsupported`] regions.
///
/// Equivalent to calling [LocalizedCardRegionIndex::get].
pub fn localized<'hm>(
@ -79,7 +72,7 @@ impl CardRegion {
/// Get the short code of this [`CardRegion`].
///
/// If the region has no short code, it will return [`Option::None`].
/// If the region has no short code, it will return [`None`].
pub fn to_code(&self) -> Option<String> {
match self {
Self::Demacia => Some("DE".to_string()),
@ -122,7 +115,7 @@ impl From<u32> for CardRegion {
/// Get the internal id of this [`CardRegion`].
///
/// If the region has no internal id, it will return [`Result::Err`].
/// If the region has no internal id, it will return [`Err`].
impl TryFrom<CardRegion> for u32 {
type Error = ();
@ -169,8 +162,5 @@ mod tests {
test_deserialization!(deserialize_piltoverzaun, r#""PiltoverZaun""#, CardRegion::PiltoverZaun);
test_deserialization!(deserialize_bandlecity, r#""BandleCity""#, CardRegion::BandleCity);
test_deserialization!(deserialize_runeterra, r#""Runeterra""#, CardRegion::Runeterra);
test_deserialization!(deserialize_jhin, r#""Jhin""#, CardRegion::Jhin);
test_deserialization!(deserialize_evelynn, r#""Evelynn""#, CardRegion::Evelynn);
test_deserialization!(deserialize_bard, r#""Bard""#, CardRegion::Bard);
test_deserialization!(deserialize_fallback, r#""Xyzzy""#, CardRegion::Unsupported);
}

View file

@ -2,7 +2,7 @@
use crate::data::corebundle::speed::{LocalizedSpellSpeed, LocalizedSpellSpeedIndex};
/// A possible [Spell](super::type::CardType::Spell) speed.
/// A possible [`Spell`](super::r#type::CardType::Spell) speed.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum SpellSpeed {
/// Non-spell cards have this speed.
@ -17,11 +17,11 @@ pub enum SpellSpeed {
}
impl SpellSpeed {
/// Get the [LocalizedSpellSpeed] associated with this [SpellSpeed].
/// Get the [`LocalizedSpellSpeed`] associated with this [`SpellSpeed`].
///
/// Returns [Option::None] if no matching [LocalizedSpellSpeed] was found, for example spell speeds missing from the index.
/// Returns [`None`] if no matching [`LocalizedSpellSpeed`] was found, for example spell speeds missing from the index.
///
/// Equivalent to calling [LocalizedSpellSpeedIndex::get].
/// Equivalent to calling [`LocalizedSpellSpeedIndex::get`].
pub fn localized<'hm>(
&self,
hm: &'hm LocalizedSpellSpeedIndex,

View file

@ -2,6 +2,8 @@
//!
//! [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
use std::cmp::Ordering::{Equal, Greater, Less};
use std::collections::HashSet;
use crate::data::corebundle::globals::LocalizedGlobalsIndexes;
use crate::data::corebundle::keyword::LocalizedCardKeywordIndex;
use crate::data::corebundle::region::LocalizedCardRegionIndex;
@ -161,33 +163,79 @@ fn display_levelup(levelup: &String) -> String {
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
pub fn display_deck(index: &CardIndex, deck: &Deck, code: &str, name: &Option<&str>) -> String {
// TODO: optimize this
let cards = deck
.contents
.keys()
.sorted_by(|a, b| {
let card_a = index.get(a).expect("card to exist in the index");
let card_b = index.get(b).expect("card to exist in the index");
.map(|k| (
index.get(k),
deck.contents.get(k).expect("CardCode from Deck to have a quantity"),
))
.sorted_by(|(opt_a, _), (opt_b, _)| {
if opt_a.is_none() && opt_b.is_none() {
return Equal;
}
if opt_b.is_none() {
return Greater;
}
else if opt_a.is_none() {
return Less;
}
let card_a = opt_a.expect("opt_a to be Some");
let card_b = opt_b.expect("opt_b to be Some");
card_a
.cost
.cmp(&card_b.cost)
.then(card_a.name.cmp(&card_b.name))
})
.map(|k| {
let card = index.get(k).expect("card to exist in the index");
let quantity = deck.contents.get(k).unwrap();
.map(|(card, quantity)| {
let name = match card {
None => "<i>Unknown Card</i>".to_string(),
Some(card) => match card.supertype {
CardSupertype::Champion => format!("<u>{}</u>", escape(&card.name)),
_ => escape(&card.name),
}
};
if card.supertype == CardSupertype::Champion {
format!("<b>{}×</b> <u>{}</u>", &quantity, &card.name)
} else {
format!("<b>{}×</b> {}", &quantity, &card.name)
}
format!("<b>{}×</b> {}", &quantity, &name)
})
.join("\n");
let mut tags: Vec<&'static str> = vec![];
let regions = if let Some(regions) = deck.standard(&index) {
tags.push("#Standard");
regions
} else if let Some(regions) = deck.singleton(&index) {
tags.push("#Singleton");
regions
} else {
HashSet::new()
};
for region in regions {
tags.push(match region {
CardRegion::Noxus => "#Noxus",
CardRegion::Demacia => "#Demacia",
CardRegion::Freljord => "#Freljord",
CardRegion::ShadowIsles => "#ShadowIsles",
CardRegion::Targon => "#Targon",
CardRegion::Ionia => "#Ionia",
CardRegion::Bilgewater => "#Bilgewater",
CardRegion::Shurima => "#Shurima",
CardRegion::PiltoverZaun => "#PiltoverZaun",
CardRegion::BandleCity => "#BandleCity",
CardRegion::Runeterra => "#Runeterra",
CardRegion::Unsupported => "<i>Unknown</i>",
})
}
let tags = tags.join(", ");
let tags = if tags.len() > 0 { format!("{}\n", &tags) } else { "".to_string() };
match name {
Some(name) => format!("<b><u>{}</u></b>\n<code>{}</code>\n\n{}", &name, &code, &cards),
None => format!("<code>{}</code>\n\n{}", &code, &cards),
Some(name) => format!("<b><u>{}</u></b>\n<code>{}</code>\n{}\n{}", &name, &code, &tags, &cards),
None => format!("<code>{}</code>\n{}\n{}", &code, &tags, &cards),
}
}

View file

@ -55,6 +55,8 @@ pub fn deck_to_inlinequeryresult(
.to_code(DeckCodeFormat::F1)
.expect("serialized deck to deserialize properly");
let message_text = display_deck(index, deck, &code, &name);
InlineQueryResult::Article(InlineQueryResultArticle {
id: format!("{}:{:x}", &crystal, md5::compute(&code)),
title: match &name {

View file

@ -1,14 +1,10 @@
//! This module defines the [`main`] function for `patched_porobot_telegram`.
use crate::data::corebundle::globals::LocalizedGlobalsIndexes;
use crate::data::corebundle::CoreBundle;
use crate::data::setbundle::card::{Card, CardIndex};
use crate::data::setbundle::SetBundle;
use crate::data::corebundle::create_globalindexes_from_wd;
use crate::data::setbundle::create_cardindex_from_wd;
use crate::search::cardsearch::CardSearchEngine;
use crate::telegram::handler::{inline_query_handler, message_handler};
use glob::glob;
use log::*;
use std::path::PathBuf;
use rand::Rng;
use teloxide::prelude::*;
@ -17,43 +13,17 @@ pub async fn main() {
pretty_env_logger::init();
debug!("Logger initialized successfully!");
debug!("Loading core bundle...");
let core = CoreBundle::load(&*PathBuf::from("./data/core-en_us"))
.expect("to be able to load `core-en_us` bundle");
debug!("Loaded core bundle successfully!");
debug!("Creating LocalizedGlobalIndexes...");
let globals = create_globalindexes_from_wd();
debug!("Created LocalizedGlobalIndexes!");
debug!("Loading set bundles...");
let setpaths = glob("./data/set*-*")
.expect("setglob to be a valid glob")
.into_iter()
.filter(|sp| sp.is_ok())
.map(|sp| sp.unwrap());
let mut cards: Vec<Card> = vec![];
for setpath in setpaths {
debug!("Loading {:?}...", &setpath);
let set = SetBundle::load(&setpath).expect(&*format!(
"to be able to load {:?} as a set bundle",
&setpath
));
let mut setcards = set.cards;
cards.append(&mut setcards);
}
debug!("Loaded {} cards!", &cards.len());
debug!("Creating CardIndex...");
let cards = create_cardindex_from_wd();
debug!("Created CardIndex!");
debug!("Indexing globals...");
let globals = LocalizedGlobalsIndexes::from(core.globals);
debug!("Indexed globals!");
let mut index = CardIndex::new();
for card in cards {
index.insert(card.code.clone(), card);
}
let cards = index;
debug!("Indexed cards!");
debug!("Creating search engine...");
debug!("Creating CardSearchEngine...");
let engine = CardSearchEngine::new(globals, cards);
debug!("Created search engine!");
debug!("Created CardSearchEngine!");
debug!("Creating Telegram bot with parameters from the environment...");
let bot = Bot::from_env();