pub mod kins;
pub mod properties;
pub mod redcell;
use crate::cards::kins::Kin;
use crate::cards::properties::Array;
use crate::cards::properties::Number;
use crate::cards::properties::Read;
use crate::cards::properties::Text;
use crate::clean_ascii_keep_case;
use crate::numbers::MaybeImprecise;
use std::{collections::HashMap, fmt::Display};
use redcell::RedcellString;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct Image {
pub sources: Vec<String>,
pub authors: Vec<String>,
}
#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq, Default)]
pub struct Card {
pub id: String,
pub name: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub images: Vec<Image>,
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub description: String,
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub description_raw: String,
#[serde(default)]
#[serde(skip_serializing_if = "RedcellString::is_empty")]
pub description_rich: RedcellString,
pub cost: MaybeImprecise,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub flip_cost: Option<MaybeImprecise>,
pub health: MaybeImprecise,
pub defense: MaybeImprecise,
pub power: MaybeImprecise,
pub r#type: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub keywords: Vec<Keyword>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub kin: Option<Kin>,
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub set: String,
pub chapter: String,
pub legality: HashMap<String, String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub other: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub functions: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub flavor_text: String,
}
impl Display for Card {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut name = self.name.as_str();
if name.len() > 20 {
name = &name[0..18];
}
let mut nameline = name.to_owned();
for _ in nameline.len()..24 {
nameline.push(' ');
}
nameline.push_str(&self.cost.to_string());
writeln!(f, "{nameline}")?;
writeln!(f)?;
writeln!(f, "{}", self.description)
}
}
impl Read for Card {
fn get_flavor_text(&self) -> Option<&str> {
Some(&self.flavor_text)
}
fn get_num_property(&self, property: &Number) -> Option<MaybeImprecise> {
match property {
Number::Cost => Some(self.cost.clone()),
Number::Health => {
if self.r#type.contains("command") {
None
} else {
Some(self.health.clone())
}
}
Number::Defense => {
if self.r#type.contains("command") {
None
} else {
Some(self.defense.clone())
}
}
Number::Power => {
if self.r#type.contains("command") {
None
} else {
Some(self.power.clone())
}
}
Number::FlipCost => self.flip_cost.clone(),
}
}
fn get_text_property(&self, property: &Text) -> Option<String> {
Some(match property {
Text::Id => self.id.to_string(),
Text::Name => self.name.to_string(),
Text::Type => self.r#type.to_string(),
Text::Description => self.description_raw.to_string(),
Text::FlavorText => self.flavor_text.to_string(),
Text::SetId => self.set.to_string(),
Text::Chapter => self.chapter.to_string(),
})
}
fn get_vec_property(&self, property: &Array) -> Option<&[String]> {
Some(match property {
Array::Functions => &self.functions,
})
}
fn get_keywords(&self) -> Option<&[Keyword]> {
Some(&self.keywords)
}
fn get_name(&self) -> Option<&str> {
Some(&self.name)
}
fn get_description(&self) -> Option<&str> {
Some(&self.description)
}
fn get_type(&self) -> Option<&str> {
Some(&self.r#type)
}
fn get_kin(&self) -> Option<&Kin> {
self.kin.as_ref()
}
}
impl Read for &Card {
fn get_flavor_text(&self) -> Option<&str> {
Some(&self.flavor_text)
}
fn get_num_property(&self, property: &Number) -> Option<MaybeImprecise> {
match property {
Number::Cost => Some(self.cost.clone()),
Number::Health => {
if self.r#type.contains("command") {
None
} else {
Some(self.health.clone())
}
}
Number::Defense => {
if self.r#type.contains("command") {
None
} else {
Some(self.defense.clone())
}
}
Number::Power => {
if self.r#type.contains("command") {
None
} else {
Some(self.power.clone())
}
}
Number::FlipCost => self.flip_cost.clone(),
}
}
fn get_text_property(&self, property: &Text) -> Option<String> {
Some(match property {
Text::Id => self.id.to_string(),
Text::Name => self.name.to_string(),
Text::Type => self.r#type.to_string(),
Text::Description => self.description_raw.to_string(),
Text::FlavorText => self.flavor_text.to_string(),
Text::SetId => self.set.to_string(),
Text::Chapter => self.chapter.to_string(),
})
}
fn get_vec_property(&self, property: &Array) -> Option<&[String]> {
Some(match property {
Array::Functions => &self.functions,
})
}
fn get_keywords(&self) -> Option<&[Keyword]> {
Some(&self.keywords)
}
fn get_name(&self) -> Option<&str> {
Some(&self.name)
}
fn get_description(&self) -> Option<&str> {
Some(&self.description)
}
fn get_type(&self) -> Option<&str> {
Some(&self.r#type)
}
fn get_kin(&self) -> Option<&Kin> {
self.kin.as_ref()
}
}
impl Read for CardId {
fn get_flavor_text(&self) -> Option<&str> {
None
}
fn get_num_property(&self, property: &Number) -> Option<MaybeImprecise> {
match property {
Number::Cost => self.cost.clone(),
Number::Health => {
if self.r#type.as_ref().is_some_and(|x| x.contains("command")) {
None
} else {
self.health.clone()
}
}
Number::Defense => {
if self.r#type.as_ref().is_some_and(|x| x.contains("command")) {
None
} else {
self.defense.clone()
}
}
Number::Power => {
if self.r#type.as_ref().is_some_and(|x| x.contains("command")) {
None
} else {
self.power.clone()
}
}
Number::FlipCost => self.flip_cost.clone(),
}
}
fn get_text_property(&self, property: &Text) -> Option<String> {
match property {
Text::Name => self.name.as_deref().map(ToString::to_string),
Text::Type => self.r#type.as_deref().map(ToString::to_string),
Text::Description => self.description.as_ref().map(ToString::to_string),
Text::FlavorText | Text::Id | Text::SetId | Text::Chapter => None,
}
}
fn get_vec_property(&self, property: &Array) -> Option<&[String]> {
match property {
Array::Functions => self.functions.as_deref(),
}
}
fn get_keywords(&self) -> Option<&[Keyword]> {
self.keywords.as_deref()
}
fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
fn get_description(&self) -> Option<&str> {
self.description.as_deref()
}
fn get_type(&self) -> Option<&str> {
self.r#type.as_deref()
}
fn get_kin(&self) -> Option<&Kin> {
self.kin.as_ref()
}
}
impl Read for &CardId {
fn get_flavor_text(&self) -> Option<&str> {
None
}
fn get_num_property(&self, property: &Number) -> Option<MaybeImprecise> {
match property {
Number::Cost => self.cost.clone(),
Number::Health => {
if self.r#type.as_ref().is_some_and(|x| x.contains("command")) {
None
} else {
self.health.clone()
}
}
Number::Defense => {
if self.r#type.as_ref().is_some_and(|x| x.contains("command")) {
None
} else {
self.defense.clone()
}
}
Number::Power => {
if self.r#type.as_ref().is_some_and(|x| x.contains("command")) {
None
} else {
self.power.clone()
}
}
Number::FlipCost => self.flip_cost.clone(),
}
}
fn get_text_property(&self, property: &Text) -> Option<String> {
match property {
Text::Name => self.name.as_deref().map(ToString::to_string),
Text::Type => self.r#type.as_deref().map(ToString::to_string),
Text::Description => self.description.as_ref().map(ToString::to_string),
Text::FlavorText | Text::Id | Text::SetId | Text::Chapter => None,
}
}
fn get_vec_property(&self, property: &Array) -> Option<&[String]> {
match property {
Array::Functions => self.functions.as_deref(),
}
}
fn get_keywords(&self) -> Option<&[Keyword]> {
self.keywords.as_deref()
}
fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
fn get_description(&self) -> Option<&str> {
self.description.as_deref()
}
fn get_type(&self) -> Option<&str> {
self.r#type.as_deref()
}
fn get_kin(&self) -> Option<&Kin> {
self.kin.as_ref()
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct CardId {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<MaybeImprecise>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flip_cost: Option<MaybeImprecise>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub keywords: Option<Vec<Keyword>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub kin: Option<Kin>,
#[serde(skip_serializing_if = "Option::is_none")]
pub health: Option<MaybeImprecise>,
#[serde(skip_serializing_if = "Option::is_none")]
pub defense: Option<MaybeImprecise>,
#[serde(skip_serializing_if = "Option::is_none")]
pub power: Option<MaybeImprecise>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub abilities: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub functions: Option<Vec<String>>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "type")]
pub enum KeywordData {
CardId(Box<CardId>),
String(String),
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct Keyword {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<KeywordData>,
}
impl Card {
#[must_use]
pub fn get_random_image_path(&self) -> String {
self.images
.last()
.and_then(|x| {
x.sources.get(
(getrandom::u32().expect("What do you Mean you can't obtain this") as usize)
% x.sources.len(),
)
})
.cloned()
.unwrap_or_else(|| self.get_name_image_path())
}
#[must_use]
pub fn get_image_path(&self, index: usize) -> String {
self.images
.get(index)
.and_then(|x| {
x.sources.get(
(getrandom::u32().expect("What do you Mean you can't obtain this") as usize)
% x.sources.len(),
)
})
.cloned()
.unwrap_or_else(|| self.get_name_image_path())
}
#[must_use]
pub fn get_name_image_path(&self) -> String {
clean_ascii_keep_case(&self.name.replace(' ', ""))
}
#[must_use]
pub fn get_artists(&self) -> Vec<&String> {
self.images
.iter()
.flat_map(|x| -> &[String] { x.authors.as_ref() })
.collect()
}
}