use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum CoreType {
Kindred,
Artifact,
Battle,
Conspiracy,
Creature,
Dungeon,
Enchantment,
Instant,
Land,
Phenomenon,
Plane,
Planeswalker,
Scheme,
Sorcery,
Vanguard,
}
impl CoreType {
pub const ALL: [CoreType; 15] = [
CoreType::Kindred,
CoreType::Artifact,
CoreType::Battle,
CoreType::Conspiracy,
CoreType::Creature,
CoreType::Dungeon,
CoreType::Enchantment,
CoreType::Instant,
CoreType::Land,
CoreType::Phenomenon,
CoreType::Plane,
CoreType::Planeswalker,
CoreType::Scheme,
CoreType::Sorcery,
CoreType::Vanguard,
];
pub fn is_permanent(self) -> bool {
matches!(
self,
CoreType::Artifact
| CoreType::Battle
| CoreType::Creature
| CoreType::Enchantment
| CoreType::Land
| CoreType::Planeswalker
)
}
pub fn name(self) -> &'static str {
match self {
CoreType::Kindred => "Kindred",
CoreType::Artifact => "Artifact",
CoreType::Battle => "Battle",
CoreType::Conspiracy => "Conspiracy",
CoreType::Creature => "Creature",
CoreType::Dungeon => "Dungeon",
CoreType::Enchantment => "Enchantment",
CoreType::Instant => "Instant",
CoreType::Land => "Land",
CoreType::Phenomenon => "Phenomenon",
CoreType::Plane => "Plane",
CoreType::Planeswalker => "Planeswalker",
CoreType::Scheme => "Scheme",
CoreType::Sorcery => "Sorcery",
CoreType::Vanguard => "Vanguard",
}
}
pub fn from_name(s: &str) -> Option<CoreType> {
match s {
"Kindred" => Some(CoreType::Kindred),
"Artifact" => Some(CoreType::Artifact),
"Battle" => Some(CoreType::Battle),
"Conspiracy" => Some(CoreType::Conspiracy),
"Creature" => Some(CoreType::Creature),
"Dungeon" => Some(CoreType::Dungeon),
"Enchantment" => Some(CoreType::Enchantment),
"Instant" => Some(CoreType::Instant),
"Land" => Some(CoreType::Land),
"Phenomenon" => Some(CoreType::Phenomenon),
"Plane" => Some(CoreType::Plane),
"Planeswalker" => Some(CoreType::Planeswalker),
"Scheme" => Some(CoreType::Scheme),
"Sorcery" => Some(CoreType::Sorcery),
"Vanguard" => Some(CoreType::Vanguard),
_ => None,
}
}
}
impl std::fmt::Display for CoreType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Supertype {
Basic,
Elite,
Host,
Legendary,
Snow,
Ongoing,
World,
}
impl Supertype {
pub fn name(self) -> &'static str {
match self {
Supertype::Basic => "Basic",
Supertype::Elite => "Elite",
Supertype::Host => "Host",
Supertype::Legendary => "Legendary",
Supertype::Snow => "Snow",
Supertype::Ongoing => "Ongoing",
Supertype::World => "World",
}
}
pub fn from_name(s: &str) -> Option<Supertype> {
match s {
"Basic" => Some(Supertype::Basic),
"Elite" => Some(Supertype::Elite),
"Host" => Some(Supertype::Host),
"Legendary" => Some(Supertype::Legendary),
"Snow" => Some(Supertype::Snow),
"Ongoing" => Some(Supertype::Ongoing),
"World" => Some(Supertype::World),
_ => None,
}
}
}
impl std::fmt::Display for Supertype {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CardTypeLine {
pub supertypes: BTreeSet<Supertype>,
pub core_types: BTreeSet<CoreType>,
pub subtypes: Vec<String>,
}
impl CardTypeLine {
pub fn new() -> Self {
CardTypeLine {
supertypes: BTreeSet::new(),
core_types: BTreeSet::new(),
subtypes: Vec::new(),
}
}
pub fn parse(s: &str) -> Self {
let mut result = Self::new();
if s.is_empty() {
return result;
}
let (before_dash, after_dash) = if let Some(idx) = s.find(" - ") {
(&s[..idx], Some(&s[idx + 3..]))
} else if let Some(idx) = s.find(" \u{2014} ") {
(&s[..idx], Some(&s[idx + 4..]))
} else {
(s, None)
};
for word in before_dash.split_whitespace() {
if let Some(st) = Supertype::from_name(word) {
result.supertypes.insert(st);
} else if let Some(ct) = CoreType::from_name(word) {
result.core_types.insert(ct);
} else if after_dash.is_none() {
result.subtypes.push(word.to_string());
}
}
if let Some(sub_str) = after_dash {
for word in sub_str.split_whitespace() {
if !word.is_empty() {
result.subtypes.push(word.to_string());
}
}
}
result
}
pub fn is_permanent(&self) -> bool {
self.core_types.iter().any(|ct| ct.is_permanent())
}
pub fn is_creature(&self) -> bool {
self.core_types.contains(&CoreType::Creature)
}
pub fn is_land(&self) -> bool {
self.core_types.contains(&CoreType::Land)
}
pub fn is_instant(&self) -> bool {
self.core_types.contains(&CoreType::Instant)
}
pub fn is_sorcery(&self) -> bool {
self.core_types.contains(&CoreType::Sorcery)
}
pub fn is_artifact(&self) -> bool {
self.core_types.contains(&CoreType::Artifact)
}
pub fn is_enchantment(&self) -> bool {
self.core_types.contains(&CoreType::Enchantment)
}
pub fn is_planeswalker(&self) -> bool {
self.core_types.contains(&CoreType::Planeswalker)
}
pub fn is_basic(&self) -> bool {
self.supertypes.contains(&Supertype::Basic)
}
pub fn is_legendary(&self) -> bool {
self.supertypes.contains(&Supertype::Legendary)
}
pub fn is_snow(&self) -> bool {
self.supertypes.contains(&Supertype::Snow)
}
pub fn has_subtype(&self, subtype: &str) -> bool {
self.subtypes
.iter()
.any(|s| s.eq_ignore_ascii_case(subtype))
}
pub fn add_type(&mut self, t: &str) {
if let Some(st) = Supertype::from_name(t) {
self.supertypes.insert(st);
} else if let Some(ct) = CoreType::from_name(t) {
self.core_types.insert(ct);
} else if !self.subtypes.iter().any(|s| s == t) {
self.subtypes.push(t.to_string());
}
}
}
impl Default for CardTypeLine {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for CardTypeLine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut parts = Vec::new();
for st in &self.supertypes {
parts.push(st.name().to_string());
}
for ct in &self.core_types {
parts.push(ct.name().to_string());
}
write!(f, "{}", parts.join(" "))?;
if !self.subtypes.is_empty() {
write!(f, " - {}", self.subtypes.join(" "))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_creature_type() {
let tl = CardTypeLine::parse("Legendary Creature - Human Wizard");
assert!(tl.is_legendary());
assert!(tl.is_creature());
assert!(tl.has_subtype("Human"));
assert!(tl.has_subtype("Wizard"));
assert!(tl.is_permanent());
}
#[test]
fn parse_instant() {
let tl = CardTypeLine::parse("Instant");
assert!(tl.is_instant());
assert!(!tl.is_permanent());
assert!(tl.subtypes.is_empty());
}
#[test]
fn parse_basic_land() {
let tl = CardTypeLine::parse("Basic Land - Mountain");
assert!(tl.is_basic());
assert!(tl.is_land());
assert!(tl.has_subtype("Mountain"));
}
#[test]
fn core_type_from_name() {
assert_eq!(CoreType::from_name("Creature"), Some(CoreType::Creature));
assert_eq!(CoreType::from_name("Blah"), None);
}
#[test]
fn display_type_line() {
let tl = CardTypeLine::parse("Legendary Creature - Human Wizard");
assert_eq!(format!("{}", tl), "Legendary Creature - Human Wizard");
}
}