Add card sets and allow to filter by them

This commit is contained in:
kageru 2023-02-01 18:47:27 +01:00
parent 1d3a79857c
commit eee93f0d6e
5 changed files with 88 additions and 10 deletions

View File

@ -1,5 +1,5 @@
use serde::Deserialize;
use std::fmt::{self, Display};
use std::fmt::{self, Display, Write};
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct CardInfo {
@ -26,6 +26,26 @@ pub struct Card {
pub link_rating: Option<i32>,
#[serde(rename = "linkmarkers")]
pub link_arrows: Option<Vec<String>>,
#[serde(default)]
pub card_sets: Vec<CardSet>,
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct CardSet {
pub set_name: String,
pub set_code: String,
pub set_rarity: String,
}
impl Card {
pub fn extended_info(&self) -> Result<String, fmt::Error> {
let mut s = String::with_capacity(1000);
s.push_str("<h3>Printings:</h3>");
for printing in &self.card_sets {
write!(s, "{}: {} ({})<br/>", printing.set_name, printing.set_code, printing.set_rarity)?;
}
Ok(s)
}
}
impl Display for Card {
@ -70,7 +90,23 @@ pub mod tests {
"name": "The Cheerful Coffin",
"type": "Spell Card",
"desc": "Discard up to 3 Monster Cards from your hand to the Graveyard.",
"race": "Normal"
"race": "Normal",
"card_sets": [
{
"set_name": "Dark Beginning 1",
"set_code": "DB1-EN167",
"set_rarity": "Common",
"set_rarity_code": "(C)",
"set_price": "1.41"
},
{
"set_name": "Metal Raiders",
"set_code": "MRD-059",
"set_rarity": "Common",
"set_rarity_code": "(C)",
"set_price": "1.55"
}
]
}"#;
pub const RAW_MONSTER: &str = r#"
@ -83,7 +119,23 @@ pub mod tests {
"def": 600,
"level": 3,
"race": "Zombie",
"attribute": "EARTH"
"attribute": "EARTH",
"card_sets": [
{
"set_name": "Astral Pack Three",
"set_code": "AP03-EN018",
"set_rarity": "Common",
"set_rarity_code": "(C)",
"set_price": "1.24"
},
{
"set_name": "Gold Series",
"set_code": "GLD1-EN010",
"set_rarity": "Common",
"set_rarity_code": "(C)",
"set_price": "2.07"
}
]
}"#;
#[test]
@ -97,6 +149,14 @@ pub mod tests {
name: "The Cheerful Coffin".to_owned(),
text: "Discard up to 3 Monster Cards from your hand to the Graveyard.".to_owned(),
r#type: "Normal".to_owned(),
card_sets: vec![
CardSet {
set_name: "Dark Beginning 1".to_owned(),
set_code: "DB1-EN167".to_owned(),
set_rarity: "Common".to_owned(),
},
CardSet { set_name: "Metal Raiders".to_owned(), set_code: "MRD-059".to_owned(), set_rarity: "Common".to_owned() }
],
..Default::default()
}
)
@ -119,6 +179,14 @@ pub mod tests {
level: Some(3),
r#type: "Zombie".to_owned(),
attribute: Some("EARTH".to_owned()),
card_sets: vec![
CardSet {
set_name: "Astral Pack Three".to_owned(),
set_code: "AP03-EN018".to_owned(),
set_rarity: "Common".to_owned(),
},
CardSet { set_name: "Gold Series".to_owned(), set_code: "GLD1-EN010".to_owned(), set_rarity: "Common".to_owned() }
],
..Default::default()
},
)

View File

@ -18,6 +18,7 @@ pub struct SearchCard {
level: Option<i32>,
link_rating: Option<i32>,
link_arrows: Option<Vec<String>>,
sets: Vec<String>,
}
impl From<&Card> for SearchCard {
@ -34,6 +35,7 @@ impl From<&Card> for SearchCard {
level: card.level,
link_rating: card.link_rating,
link_arrows: card.link_arrows.as_ref().map(|arrows| arrows.iter().map(|a| a.to_lowercase()).collect()),
sets: card.card_sets.iter().filter_map(|s| s.set_code.split('-').next().map(str::to_lowercase)).collect(),
}
}
}
@ -72,6 +74,7 @@ pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> {
RawCardFilter(Field::Text, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.text.contains(&s)),
RawCardFilter(Field::Name, Operator::Equal, Value::String(s)) => Box::new(move |card| card.name.contains(&s)),
RawCardFilter(Field::Name, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.name.contains(&s)),
RawCardFilter(Field::Set, Operator::Equal, Value::String(s)) => Box::new(move |card| card.sets.contains(&s)),
q => Err(format!("unknown query: {q:?}"))?,
})
}
@ -85,8 +88,8 @@ mod tests {
fn level_filter_test() {
let lacooda = SearchCard::from(&serde_json::from_str::<Card>(RAW_MONSTER).unwrap());
let filter_level_3 = parse_filters("l=3").unwrap();
assert!(filter_level_3[0].1(&lacooda));
assert!(filter_level_3.1[0](&lacooda));
let filter_level_5 = parse_filters("l=5").unwrap();
assert!(!filter_level_5[0].1(&lacooda));
assert!(!filter_level_5.1[0](&lacooda));
}
}

View File

@ -83,9 +83,11 @@ async fn card_info(card_id: web::Path<usize>) -> Result<HttpResponse, Box<dyn st
<div>
<img class="fullimage" src="{}/static/full/{}.jpg"/>
{card}
{}
</div>"#,
IMG_HOST.as_str(),
card.id,
card.extended_info().unwrap_or_else(|_| String::new()),
)?;
}
None => res.push_str("Card not found"),

View File

@ -66,11 +66,12 @@ pub enum Field {
Def = 2,
Level = 3,
LinkRating = 4,
Type = 5,
Attribute = 6,
Class = 7,
Name = 8,
Text = 9,
Set = 5,
Type = 6,
Attribute = 7,
Class = 8,
Name = 9,
Text = 10,
}
impl Display for Field {
@ -85,6 +86,7 @@ impl Display for Field {
Self::Atk => "ATK",
Self::Def => "DEF",
Self::LinkRating => "link rating",
Self::Set => "set",
})
}
}
@ -102,6 +104,7 @@ impl FromStr for Field {
"o" | "eff" | "text" | "effect" | "e" => Self::Text,
"lr" | "linkrating" => Self::LinkRating,
"name" => Self::Name,
"set" | "s" => Self::Set,
_ => Err(s.to_string())?,
})
}

View File

@ -12,6 +12,7 @@ Currently supported search fields are:
<li>The <code>type</code> (or <code>t</code>) of a card (this is “Warrior”, “Pyro”, “Insect”, etc. for monsters, but also “quick-play”, “counter”, or “normal” for Spells/Traps).</li>
<li>The <code>attribute</code> (or <code>attr</code> or <code>a</code>) of a card. This is “Light”, “Dark”, “Earth”, etc.</li>
<li>The <code>text</code> (or <code>effect</code>, <code>eff</code>, <code>e</code>, or <code>o</code>) of a card. This is either the effect or flavor text (for normal monsters). For pendulum cards, this searches in both pendulum and monster effects. The <code>o</code> alias is to help my muscle memory coming from Scryfall.</li>
<li>The <code>set</code> (or <code>s</code>) a card was printed in. This considers all printings, not just the original.</li>
</ul>
Anything not associated with a search field is interpreted as a search in the card name, so <a href="/?q=l%3A4+utopia"><code>l:4 utopia</code></a> will show all level/rank 4 monsters with “Utopia” in their name.<br/>
If your search contains spaces (e.g. searching for an effect that says “destroy that target”), the text must be quoted like <code>effect:"destroy that target"</code>.
@ -32,4 +33,5 @@ The following search operators are supported:
<li>All “Blue-eyes” fusion monsters except the ones that are level 12: <a href="/?q=c%3Afusion+l%21%3D12+blue-eyes"><code>c:fusion l!=12 blue-eyes</code></a></li>
<li>All Synchro monsters that are Dark attribute, level 5 or higher, and have exactly 2200 ATK: <a href="/?q=c%3Asynchro+a%3Adark+l%3E%3D5+atk%3A2200"><code>c:synchro a:dark l>=5 atk:2200</code></a></li>
<li>All counter traps that can negate summons: <a href="/?q=c%3Atrap+t%3Acounter+e%3A%22negate+the+summon%22"><code>c:trap t:counter e:"negate the summon"</code></a></li>
<li>All effect monsters printed in Legend of Blue-Eyes: <a href="/?q=set%3Alob+c%3Aeffect"><code>set:lob c:effect</code></a></li>
</ul>