mirror of
https://github.com/Steffo99/patched-porobot.git
synced 2024-12-23 01:54:22 +00:00
parent
c0ed6189e5
commit
e91e89cab1
12 changed files with 399 additions and 89 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1039,7 +1039,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "patched_porobot"
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"glob",
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
@ -654,4 +841,63 @@ mod tests {
|
|||
]);
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
if card.supertype == CardSupertype::Champion {
|
||||
format!("<b>{}×</b> <u>{}</u>", &quantity, &card.name)
|
||||
} else {
|
||||
format!("<b>{}×</b> {}", &quantity, &card.name)
|
||||
.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),
|
||||
}
|
||||
};
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue