2023-02-02 11:34:58 +01:00
use time ::Date ;
2023-01-26 23:07:16 +01:00
use crate ::{
2023-02-03 16:19:26 +01:00
data ::{ BanlistStatus , Card } ,
2023-01-27 15:48:07 +01:00
parser ::{ Field , Operator , RawCardFilter , Value , OPERATOR_CHARS } ,
2023-02-02 11:34:58 +01:00
SETS_BY_NAME ,
2023-01-26 23:07:16 +01:00
} ;
/// A struct derived from `Card` that has all fields lowercased for easier search
#[ derive(Debug, PartialEq, Eq, Clone) ]
pub struct SearchCard {
2023-02-02 11:34:58 +01:00
pub id : usize ,
card_type : String ,
name : String ,
text : String ,
atk : Option < i32 > ,
def : Option < i32 > ,
attribute : Option < String > ,
r#type : String ,
2023-01-26 23:07:16 +01:00
// also includes rank
2023-02-02 11:34:58 +01:00
level : Option < i32 > ,
link_rating : Option < i32 > ,
link_arrows : Option < Vec < String > > ,
sets : Vec < String > ,
original_year : Option < i32 > ,
2023-02-03 16:19:26 +01:00
legal_copies : i32 ,
2023-01-26 23:07:16 +01:00
}
impl From < & Card > for SearchCard {
fn from ( card : & Card ) -> Self {
Self {
2023-02-02 11:34:58 +01:00
id : card . id ,
card_type : card . card_type . to_lowercase ( ) ,
name : card . name . to_lowercase ( ) ,
text : card . text . to_lowercase ( ) ,
atk : card . atk ,
def : card . def ,
attribute : card . attribute . as_ref ( ) . map ( | s | s . to_lowercase ( ) ) ,
r#type : card . r#type . to_lowercase ( ) ,
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 ( ) ,
original_year : card
. card_sets
. iter ( )
. filter_map ( | s | {
SETS_BY_NAME . get ( & s . set_name . to_lowercase ( ) ) . unwrap_or_else ( | | panic! ( " Set {} not found " , s . set_name ) ) . tcg_date
} )
. map ( Date ::year )
. min ( ) ,
2023-02-03 16:19:26 +01:00
legal_copies : card . banlist_info . map ( | bi | bi . ban_tcg ) . unwrap_or ( BanlistStatus ::Unlimited ) as i32 ,
2023-01-26 23:07:16 +01:00
}
}
}
pub type CardFilter = Box < dyn Fn ( & SearchCard ) -> bool > ;
pub fn fallback_filter ( query : & str ) -> Result < RawCardFilter , String > {
2023-01-26 23:10:13 +01:00
if query . contains ( OPERATOR_CHARS ) {
2023-01-26 23:07:16 +01:00
return Err ( format! ( " Invalid query: {query} " ) ) ;
}
let q = query . to_lowercase ( ) ;
2023-01-27 15:48:07 +01:00
Ok ( RawCardFilter ( Field ::Name , Operator ::Equal , Value ::String ( q ) ) )
2023-01-26 23:07:16 +01:00
}
pub fn build_filter ( query : RawCardFilter ) -> Result < CardFilter , String > {
Ok ( match query {
2023-01-27 15:48:07 +01:00
RawCardFilter ( Field ::Atk , op , Value ::Numerical ( n ) ) = > Box ::new ( move | card | op . filter_number ( card . atk , n ) ) ,
RawCardFilter ( Field ::Def , op , Value ::Numerical ( n ) ) = > Box ::new ( move | card | op . filter_number ( card . def , n ) ) ,
2023-01-26 23:07:16 +01:00
// ? ATK/DEF is modeled as None in the source json. At least for some monsters.
// Let’s at least find those.
2023-01-27 15:48:07 +01:00
RawCardFilter ( Field ::Atk , _ , Value ::String ( s ) ) if s = = " ? " = > {
Box ::new ( move | card | card . atk . is_none ( ) & & card . card_type . contains ( " monster " ) )
}
RawCardFilter ( Field ::Def , _ , Value ::String ( s ) ) if s = = " ? " = > {
2023-01-26 23:07:16 +01:00
Box ::new ( move | card | card . def . is_none ( ) & & card . link_rating . is_none ( ) & & card . card_type . contains ( " monster " ) )
}
2023-01-27 15:48:07 +01:00
RawCardFilter ( Field ::Level , op , Value ::Numerical ( n ) ) = > Box ::new ( move | card | op . filter_number ( card . level , n ) ) ,
2023-01-30 11:51:36 +01:00
RawCardFilter ( Field ::LinkRating , op , Value ::Numerical ( n ) ) = > Box ::new ( move | card | op . filter_number ( card . link_rating , n ) ) ,
2023-02-02 11:34:58 +01:00
RawCardFilter ( Field ::Year , op , Value ::Numerical ( n ) ) = > Box ::new ( move | card | op . filter_number ( card . original_year , n ) ) ,
2023-02-03 16:19:26 +01:00
RawCardFilter ( Field ::Legal , op , Value ::Numerical ( n ) ) = > Box ::new ( move | card | op . filter_number ( Some ( card . legal_copies ) , n ) ) ,
2023-01-27 15:48:07 +01:00
RawCardFilter ( Field ::Type , Operator ::Equal , Value ::String ( s ) ) = > Box ::new ( move | card | card . r#type = = s ) ,
RawCardFilter ( Field ::Type , Operator ::NotEqual , Value ::String ( s ) ) = > Box ::new ( move | card | card . r#type ! = s ) ,
RawCardFilter ( Field ::Attribute , Operator ::Equal , Value ::String ( s ) ) = > Box ::new ( move | card | card . attribute . contains ( & s ) ) ,
RawCardFilter ( Field ::Attribute , Operator ::NotEqual , Value ::String ( s ) ) = > Box ::new ( move | card | ! card . attribute . contains ( & s ) ) ,
RawCardFilter ( Field ::Class , Operator ::Equal , Value ::String ( s ) ) = > Box ::new ( move | card | card . card_type . contains ( & s ) ) ,
RawCardFilter ( Field ::Class , Operator ::NotEqual , Value ::String ( s ) ) = > Box ::new ( move | card | ! card . card_type . contains ( & s ) ) ,
RawCardFilter ( Field ::Text , Operator ::Equal , Value ::String ( s ) ) = > Box ::new ( move | card | card . text . contains ( & s ) ) ,
2023-01-30 17:40:19 +01:00
RawCardFilter ( Field ::Text , Operator ::NotEqual , Value ::String ( s ) ) = > Box ::new ( move | card | ! card . text . contains ( & s ) ) ,
2023-01-27 15:48:07 +01:00
RawCardFilter ( Field ::Name , Operator ::Equal , Value ::String ( s ) ) = > Box ::new ( move | card | card . name . contains ( & s ) ) ,
2023-01-30 17:40:19 +01:00
RawCardFilter ( Field ::Name , Operator ::NotEqual , Value ::String ( s ) ) = > Box ::new ( move | card | ! card . name . contains ( & s ) ) ,
2023-02-01 18:47:27 +01:00
RawCardFilter ( Field ::Set , Operator ::Equal , Value ::String ( s ) ) = > Box ::new ( move | card | card . sets . contains ( & s ) ) ,
2023-01-26 23:07:16 +01:00
q = > Err ( format! ( " unknown query: {q:?} " ) ) ? ,
} )
}
#[ cfg(test) ]
mod tests {
use super ::* ;
use crate ::{ data ::tests ::RAW_MONSTER , parser ::parse_filters } ;
#[ test ]
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 ( ) ;
2023-02-01 18:47:27 +01:00
assert! ( filter_level_3 . 1 [ 0 ] ( & lacooda ) ) ;
2023-01-26 23:07:16 +01:00
let filter_level_5 = parse_filters ( " l=5 " ) . unwrap ( ) ;
2023-02-01 18:47:27 +01:00
assert! ( ! filter_level_5 . 1 [ 0 ] ( & lacooda ) ) ;
2023-01-26 23:07:16 +01:00
}
}