use crate::preludes::funky::{BuffoonCard, BuffoonPile};
pub struct Joker {}
impl Joker {
pub const COMMON_JOKERS_SIZE: usize = 56;
pub const COMMON_JOKERS: [BuffoonCard; Self::COMMON_JOKERS_SIZE] = [
card::SUPERPOSITION,
card::RIFF_RAFF,
card::FORTUNE_TELLER,
card::JOKER,
card::GREEDY_JOKER,
card::LUSTY_JOKER,
card::WRATHFUL_JOKER,
card::GLUTTONOUS_JOKER,
card::JOLLY_JOKER,
card::ZANY_JOKER,
card::MAD_JOKER,
card::CRAZY_JOKER,
card::DROLL_JOKER,
card::SLY_JOKER,
card::WILY_JOKER,
card::CLEVER_JOKER,
card::DEVIOUS_JOKER,
card::CRAFTY_JOKER,
card::HALF_JOKER,
card::CREDIT_CARD,
card::BANNER,
card::EIGHT_BALL,
card::MISPRINT,
card::RAISED_FIST,
card::CHAOS_THE_CLOWN,
card::HANGING_CHAD,
card::SCARY_FACE,
card::ABSTRACT_JOKER,
card::DELAYED_GRATIFICATION,
card::GROS_MICHEL,
card::EVEN_STEVEN,
card::ODD_TODD,
card::SCHOLAR,
card::BUSINESS_CARD,
card::SUPERNOVA,
card::RIDE_THE_BUS,
card::EGG,
card::RUNNER,
card::ICE_CREAM,
card::SPLASH,
card::BLUE_JOKER,
card::FACELESS_JOKER,
card::GREEN_JOKER,
card::TO_DO_LIST,
card::CAVENDISH,
card::RED_CARD,
card::SQUARE_JOKER,
card::RESERVED_PARKING,
card::MAIL_IN_REBATE,
card::HALLUCINATION,
card::JUGGLER,
card::DRUNKARD,
card::GOLDEN_JOKER,
card::POPCORN,
card::WALKIE_TALKIE,
card::MYSTIC_SUMMIT,
];
#[must_use]
pub fn pile_common() -> BuffoonPile {
BuffoonPile::from(&Self::COMMON_JOKERS[..])
}
pub const UNCOMMON_JOKERS_SIZE: usize = 41;
pub const UNCOMMON_JOKERS: [BuffoonCard; Self::UNCOMMON_JOKERS_SIZE] = [
card::JOKER_STENCIL,
card::FOUR_FINGERS,
card::MIME,
card::CEREMONIAL_DAGGER,
card::MARBLE_JOKER,
card::LOYALTY_CARD,
card::DUSK,
card::FIBONACCI,
card::STEEL_JOKER,
card::HACK,
card::PAREIDOLIA,
card::SOCK_AND_BUSKIN,
card::SMEARED_JOKER,
card::OOPS_ALL_6S,
card::EROSION,
card::STONE_JOKER,
card::SELTZER,
card::HOLOGRAM,
card::CONSTELLATION,
card::VAMPIRE,
card::LUCHADOR,
card::MATADOR,
card::MADNESS,
card::CARD_SHARP,
card::ROCKET,
card::SPACE_JOKER,
card::BURGLAR,
card::BLACKBOARD,
card::SIXTH_SENSE,
card::HIKER,
card::SHORTCUT,
card::CLOUD_9,
card::TO_THE_MOON,
card::LUCKY_CAT,
card::BULL,
card::DIET_COLA,
card::TRADING_CARD,
card::FLASH_CARD,
card::SPARE_TROUSERS,
card::RAMEN,
card::SEANCE,
];
#[must_use]
pub fn pile_uncommon() -> BuffoonPile {
BuffoonPile::from(&Self::UNCOMMON_JOKERS[..])
}
pub const RARE_JOKERS_SIZE: usize = 10;
pub const RARE_JOKERS: [BuffoonCard; Self::RARE_JOKERS_SIZE] = [
card::VAGABOND,
card::ANCIENT_JOKER,
card::THE_DUO,
card::THE_TRIO,
card::THE_FAMILY,
card::THE_ORDER,
card::THE_TRIBE,
card::BARON,
card::DNA,
card::BASEBALL_CARD,
];
#[must_use]
pub fn pile_rare() -> BuffoonPile {
BuffoonPile::from(&Self::RARE_JOKERS[..])
}
pub const LEGENDARY_JOKERS_SIZE: usize = 5;
pub const LEGENDARY_JOKERS: [BuffoonCard; Self::LEGENDARY_JOKERS_SIZE] = [
card::CANIO,
card::TRIBOULET,
card::YORICK,
card::CHICOT,
card::PERKEO,
];
#[must_use]
pub fn pile_legendary() -> BuffoonPile {
BuffoonPile::from(&Self::LEGENDARY_JOKERS[..])
}
}
pub mod card {
use crate::funky::types::buffoon_card::{BCardType, BuffoonCard};
use crate::funky::types::edition::Edition;
use crate::funky::types::mpip::MPip;
use crate::prelude::{FrenchSuit, Pip, PipType};
pub const JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 1_000,
pip_type: PipType::Joker,
index: '⚫',
symbol: '⚫',
value: 2,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlus(4),
resell_value: 1,
edition: Edition::None,
debuffed: false,
};
pub const GREEDY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 995,
pip_type: PipType::Joker,
index: '♦',
symbol: '♦',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MULT_PLUS3_ON_DIAMONDS,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const LUSTY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 990,
pip_type: PipType::Joker,
index: '♥',
symbol: '♥',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MULT_PLUS3_ON_HEARTS,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const WRATHFUL_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 985,
pip_type: PipType::Joker,
index: '♠',
symbol: '♠',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MULT_PLUS3_ON_SPADES,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const GLUTTONOUS_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 980,
pip_type: PipType::Joker,
index: '♣',
symbol: '♣',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MULT_PLUS3_ON_CLUBS,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const JOLLY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 975,
pip_type: PipType::Joker,
index: '☺',
symbol: '☺',
value: 3,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnPair(8),
resell_value: 1,
edition: Edition::None,
debuffed: false,
};
pub const ZANY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 970,
pip_type: PipType::Joker,
index: '🤪',
symbol: '🤪',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnTrips(12),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const MAD_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 965,
pip_type: PipType::Joker,
index: '☹',
symbol: '☹',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOn2Pair(10),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const CRAZY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 960,
pip_type: PipType::Joker,
index: '▦',
symbol: '▦',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnStraight(12),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const DROLL_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 955,
pip_type: PipType::Joker,
index: '▤',
symbol: '▤',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnFlush(10),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SLY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 950,
pip_type: PipType::Joker,
index: '⛄',
symbol: '⛄',
value: 3,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsOnPair(50),
resell_value: 1,
edition: Edition::None,
debuffed: false,
};
pub const WILY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 945,
pip_type: PipType::Joker,
index: '⛕',
symbol: '⛕',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsOnTrips(100),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const CLEVER_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 940,
pip_type: PipType::Joker,
index: '∑',
symbol: '∑',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsOn2Pair(80),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const DEVIOUS_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 935,
pip_type: PipType::Joker,
index: '∫',
symbol: '∫',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsOnStraight(100),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const CRAFTY_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 930,
pip_type: PipType::Joker,
index: '∞',
symbol: '∞',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsOnFlush(80),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const HALF_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 925,
pip_type: PipType::Joker,
index: '½',
symbol: '½',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnUpToXCards(20, 3),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const JOKER_STENCIL: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 920,
pip_type: PipType::Joker,
index: '∛',
symbol: '∛',
value: 8,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultTimesOnEmptyJokerSlots(1),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const FOUR_FINGERS: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 915,
pip_type: PipType::Joker,
index: '∜',
symbol: '∜',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::FourFlushAndStraight,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const MIME: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 914,
pip_type: PipType::Joker,
index: '∝',
symbol: '∝',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::RetriggerCardsInHand(1),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const CREDIT_CARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 913,
pip_type: PipType::Joker,
index: '💳',
symbol: '💳',
value: 1,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::Credit(20),
resell_value: 1,
edition: Edition::None,
debuffed: false,
};
pub const CEREMONIAL_DAGGER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 912,
pip_type: PipType::Joker,
index: '🗡',
symbol: '🗡',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultPlusDoubleValueDestroyJokerOnRight(0),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const BANNER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 911,
pip_type: PipType::Joker,
index: '🚩',
symbol: '🚩',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsPerRemainingDiscard(30),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const MYSTIC_SUMMIT: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 910,
pip_type: PipType::Joker,
index: '🏔',
symbol: '🏔',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnZeroDiscards(15),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const MARBLE_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 909,
pip_type: PipType::Joker,
index: '🔮',
symbol: '🔮',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::AddCardTypeWhenBlindSelected(BCardType::Stone),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const LOYALTY_CARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 908,
pip_type: PipType::Joker,
index: '🛍',
symbol: '🛍',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultTimesEveryXHands(4, 6),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const EIGHT_BALL: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 907,
pip_type: PipType::Joker,
index: '🎱',
symbol: '🎱',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::CreateCardOnRankPlay(4, '8', BCardType::Tarot),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const MISPRINT: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 906,
pip_type: PipType::Joker,
index: '🃏',
symbol: '🃏',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusRandomTo(24),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const DUSK: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 905,
pip_type: PipType::Joker,
index: '🌆',
symbol: '🌆',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::RetriggerPlayedCardsInFinalRound,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const RAISED_FIST: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 904,
pip_type: PipType::Joker,
index: '✊',
symbol: '✊',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusXOnLowestRankInHand(2),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const CHAOS_THE_CLOWN: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 903,
pip_type: PipType::Joker,
index: '🤡',
symbol: '🤡',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::FreeReroll(1),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const FIBONACCI: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 902,
pip_type: PipType::Joker,
index: '🔢',
symbol: '🔢',
value: 8,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultPlusOn5Ranks(8, ['A', '2', '3', '5', '8']),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const STEEL_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 901,
pip_type: PipType::Joker,
index: '🔩',
symbol: '🔩',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultTimesPlusPerFullDeckSteel(2),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const SCARY_FACE: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 900,
pip_type: PipType::Joker,
index: '👻',
symbol: '👻',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsPlusPerScoredFace(30),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const ABSTRACT_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 895,
pip_type: PipType::Joker,
index: '🎨',
symbol: '🎨',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusPerJoker(3),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const DELAYED_GRATIFICATION: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 890,
pip_type: PipType::Joker,
index: '🕰',
symbol: '🕰',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::CashPerDiscardIfNoneUsed(2),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const HACK: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 885,
pip_type: PipType::Joker,
index: '💻',
symbol: '💻',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::RetriggerPlayedRanks(1, ['2', '3', '4', '5']),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const PAREIDOLIA: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 880,
pip_type: PipType::Joker,
index: '👁',
symbol: '👁',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::AllCardsAreFaces,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const GROS_MICHEL: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 875,
pip_type: PipType::Joker,
index: '🍌',
symbol: '🍌',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusChanceDestroyed(15, 1, 6),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const EVEN_STEVEN: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 870,
pip_type: PipType::Joker,
index: '▤',
symbol: '▤',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOn5Ranks(4, ['T', '8', '6', '4', '2']),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const ODD_TODD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 865,
pip_type: PipType::Joker,
index: '▲',
symbol: '▲',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsPlusOn5Ranks(31, ['A', '9', '7', '5', '3']),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SCHOLAR: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 860,
pip_type: PipType::Joker,
index: '🎓',
symbol: '🎓',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusChipsOnRank(4, 20, 'A'),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const BUSINESS_CARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 855,
pip_type: PipType::Joker,
index: '🪪',
symbol: '🪪',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::Odds1inCashOn3Ranks(2, 2, ['K', 'Q', 'J']),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SUPERNOVA: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 850,
pip_type: PipType::Joker,
index: '🌌',
symbol: '🌌',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnHandPlays,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const RIDE_THE_BUS: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 916,
pip_type: PipType::Joker,
index: '🚌',
symbol: '🚌',
value: 6,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusOnConsecutiveHandsNo3Ranks(0, 1, ['K', 'Q', 'J']),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const SPACE_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 917,
pip_type: PipType::Joker,
index: '∝',
symbol: '∝',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::Odds1inUpgradeHand(4),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const EGG: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 918,
pip_type: PipType::Joker,
index: '∞',
symbol: '∞',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::SellValueIncrement(3),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const BURGLAR: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 919,
pip_type: PipType::Joker,
index: '∟',
symbol: '∟',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GainHandsLoseDiscardsWhenBlindSelected(3),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const BLACKBOARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 896,
pip_type: PipType::Joker,
index: '∠',
symbol: '∠',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultTimesIfHeldAllSuits(3, ['S', 'C']),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const RUNNER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 891,
pip_type: PipType::Joker,
index: '∡',
symbol: '∡',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::GainChipsPerStraightHand(15),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const ICE_CREAM: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 886,
pip_type: PipType::Joker,
index: '∢',
symbol: '∢',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::LoseChipsPerHand(100, 5),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const DNA: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 881,
pip_type: PipType::Joker,
index: '∣',
symbol: '∣',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::Blank,
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const SPLASH: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 876,
pip_type: PipType::Joker,
index: '∤',
symbol: '∤',
value: 3,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::AllPlayedCardsScore,
resell_value: 1,
edition: Edition::None,
debuffed: false,
};
pub const BLUE_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 871,
pip_type: PipType::Joker,
index: '∥',
symbol: '∥',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsPerDeckCard(2),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SIXTH_SENSE: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 866,
pip_type: PipType::Joker,
index: '∦',
symbol: '∦',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::CreateSpectralOnFirstSingleSix,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const CONSTELLATION: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 861,
pip_type: PipType::Joker,
index: '∧',
symbol: '∧',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GainMultTimesPerPlanetUsed(10),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const HIKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 856,
pip_type: PipType::Joker,
index: '∨',
symbol: '∨',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GainChipsOnScored(4),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const FACELESS_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 851,
pip_type: PipType::Joker,
index: '∩',
symbol: '∩',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::CashOnFacesDiscarded(5, 3),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const GREEN_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 845,
pip_type: PipType::Joker,
index: '∪',
symbol: '∪',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::GainMultPerHandLessDiscard(1),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SUPERPOSITION: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 840,
pip_type: PipType::Joker,
index: '∫',
symbol: '∫',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::CreateTarotOnAceStraight,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const TO_DO_LIST: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 835,
pip_type: PipType::Joker,
index: '∬',
symbol: '∬',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::Blank,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const CAVENDISH: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 830,
pip_type: PipType::Joker,
index: '∭',
symbol: '∭',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultTimesChanceDestroyed(3, 1, 1000),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const CARD_SHARP: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 825,
pip_type: PipType::Joker,
index: '∮',
symbol: '∮',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultTimesOnRepeatedHandThisRound(3),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const RED_CARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 820,
pip_type: PipType::Joker,
index: '∯',
symbol: '∯',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusPerPackSkipped(3),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const MADNESS: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 815,
pip_type: PipType::Joker,
index: '∰',
symbol: '∰',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GainMultTimesOnNonBossBlindDestroyingJoker(50),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const SQUARE_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 810,
pip_type: PipType::Joker,
index: '∱',
symbol: '∱',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::GainChipsPerCardCountHand(4, 4),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SEANCE: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 805,
pip_type: PipType::Joker,
index: '∲',
symbol: '∲',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::CreateSpectralOnStraightFlush,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const RIFF_RAFF: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 800,
pip_type: PipType::Joker,
index: '∳',
symbol: '∳',
value: 6,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::CreateJokersWhenBlindSelected(2, BCardType::CommonJoker),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const VAMPIRE: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 795,
pip_type: PipType::Joker,
index: '∴',
symbol: '∴',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GainMultTimesPerEnhancedPlayed(10),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const SHORTCUT: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 790,
pip_type: PipType::Joker,
index: '∵',
symbol: '∵',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GappedStraight,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const HOLOGRAM: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 785,
pip_type: PipType::Joker,
index: '∶',
symbol: '∶',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GainMultTimesPerCardAdded(25),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const VAGABOND: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 780,
pip_type: PipType::Joker,
index: '∷',
symbol: '∷',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::CreateTarotOnLowMoney(4),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const BARON: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 775,
pip_type: PipType::Joker,
index: '∸',
symbol: '∸',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesPerHeldRank(15, 'K'),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const CLOUD_9: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 770,
pip_type: PipType::Joker,
index: '∹',
symbol: '∹',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::CashPerFullDeckRank(1, '9'),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const ROCKET: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 765,
pip_type: PipType::Joker,
index: '∺',
symbol: '∺',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::CashOnRoundEndGrowingOnBossDefeat(1, 2),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const EROSION: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 760,
pip_type: PipType::Joker,
index: '∻',
symbol: '∻',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultPlusPerMissingDeckCard(4),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const RESERVED_PARKING: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 755,
pip_type: PipType::Joker,
index: '∼',
symbol: '∼',
value: 6,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::Blank,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const MAIL_IN_REBATE: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 750,
pip_type: PipType::Joker,
index: '∽',
symbol: '∽',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::Blank,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const TO_THE_MOON: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 745,
pip_type: PipType::Joker,
index: '∾',
symbol: '∾',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::ExtraInterest(1),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const HALLUCINATION: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 740,
pip_type: PipType::Joker,
index: '∿',
symbol: '∿',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::CreateTarotOnPackOpen(1, 2),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const FORTUNE_TELLER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 735,
pip_type: PipType::Joker,
index: '≀',
symbol: '≀',
value: 6,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::MultPlusPerTarotUsedThisRun(1),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const JUGGLER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 730,
pip_type: PipType::Joker,
index: '≁',
symbol: '≁',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::HandSizeIncrement(1),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const DRUNKARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 725,
pip_type: PipType::Joker,
index: '≂',
symbol: '≂',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::DiscardIncrement(1),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const STONE_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 720,
pip_type: PipType::Joker,
index: '≃',
symbol: '≃',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::ChipsPerFullDeckStone(25),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const GOLDEN_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 715,
pip_type: PipType::Joker,
index: '≄',
symbol: '≄',
value: 6,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::CashOnRoundEnd(4),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const LUCKY_CAT: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 710,
pip_type: PipType::Joker,
index: '≅',
symbol: '≅',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::Blank,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const BASEBALL_CARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 705,
pip_type: PipType::Joker,
index: '≆',
symbol: '≆',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesPerUncommonJoker(15),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const BULL: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 700,
pip_type: PipType::Joker,
index: '≇',
symbol: '≇',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::ChipsPerDollar(2),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const DIET_COLA: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 695,
pip_type: PipType::Joker,
index: '≈',
symbol: '≈',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::Blank,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const TRADING_CARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 690,
pip_type: PipType::Joker,
index: '≉',
symbol: '≉',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::Blank,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const FLASH_CARD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 685,
pip_type: PipType::Joker,
index: '≊',
symbol: '≊',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::MultPlusPerReroll(2),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const POPCORN: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 680,
pip_type: PipType::Joker,
index: '≋',
symbol: '≋',
value: 5,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::LoseMultPerRound(20, 4),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SPARE_TROUSERS: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 675,
pip_type: PipType::Joker,
index: '≌',
symbol: '≌',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::GainMultPerTwoPairHand(2),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const ANCIENT_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 670,
pip_type: PipType::Joker,
index: '≍',
symbol: '≍',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesPerScoredAncientSuit(15),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const RAMEN: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 801,
pip_type: PipType::Joker,
index: '🍜',
symbol: '🍜',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::LoseMultTimesPerDiscard(200, 1),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const WALKIE_TALKIE: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 802,
pip_type: PipType::Joker,
index: '📻',
symbol: '📻',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::ChipsMultPlusPerScoredRanks(10, 4, ['T', '4']),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const SOCK_AND_BUSKIN: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 803,
pip_type: PipType::Joker,
index: '🧦',
symbol: '🧦',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::RetriggerPlayedFaces(1),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const LUCHADOR: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 809,
pip_type: PipType::Joker,
index: '🤼',
symbol: '🤼',
value: 5,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::DisableBossBlindOnSell,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const MATADOR: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 811,
pip_type: PipType::Joker,
index: '🐂',
symbol: '🐂',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::Blank,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const SELTZER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 808,
pip_type: PipType::Joker,
index: '🥤',
symbol: '🥤',
value: 6,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::RetriggerAllPlayedForHands(1, 10),
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const SMEARED_JOKER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 806,
pip_type: PipType::Joker,
index: '🖍',
symbol: '🖍',
value: 7,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::SmearedSuits,
resell_value: 3,
edition: Edition::None,
debuffed: false,
};
pub const OOPS_ALL_6S: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 807,
pip_type: PipType::Joker,
index: '🎲',
symbol: '🎲',
value: 4,
},
card_type: BCardType::UncommonJoker,
enhancement: MPip::DoubleOdds,
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const HANGING_CHAD: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 804,
pip_type: PipType::Joker,
index: '🗳',
symbol: '🗳',
value: 4,
},
card_type: BCardType::CommonJoker,
enhancement: MPip::RetriggerFirstPlayed(2),
resell_value: 2,
edition: Edition::None,
debuffed: false,
};
pub const THE_DUO: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 665,
pip_type: PipType::Joker,
index: '👯',
symbol: '👯',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesOnPair(2),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const THE_TRIO: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 660,
pip_type: PipType::Joker,
index: '🔱',
symbol: '🔱',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesOnTrips(3),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const THE_FAMILY: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 655,
pip_type: PipType::Joker,
index: '👪',
symbol: '👪',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesOn4OfAKind(4),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const THE_ORDER: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 650,
pip_type: PipType::Joker,
index: '📏',
symbol: '📏',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesOnStraight(3),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const THE_TRIBE: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 645,
pip_type: PipType::Joker,
index: '🪶',
symbol: '🪶',
value: 8,
},
card_type: BCardType::RareJoker,
enhancement: MPip::MultTimesOnFlush(2),
resell_value: 4,
edition: Edition::None,
debuffed: false,
};
pub const CANIO: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 640,
pip_type: PipType::Joker,
index: '🎭',
symbol: '🎭',
value: 0,
},
card_type: BCardType::LegendaryJoker,
enhancement: MPip::GainMultTimesPerFaceDestroyed(100),
resell_value: 0,
edition: Edition::None,
debuffed: false,
};
pub const TRIBOULET: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 635,
pip_type: PipType::Joker,
index: '👑',
symbol: '👑',
value: 0,
},
card_type: BCardType::LegendaryJoker,
enhancement: MPip::MultTimesPerScoredRank(2, ['K', 'Q']),
resell_value: 0,
edition: Edition::None,
debuffed: false,
};
pub const YORICK: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 630,
pip_type: PipType::Joker,
index: '💀',
symbol: '💀',
value: 0,
},
card_type: BCardType::LegendaryJoker,
enhancement: MPip::GainMultTimesPerDiscardedCards(100, 23),
resell_value: 0,
edition: Edition::None,
debuffed: false,
};
pub const CHICOT: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 625,
pip_type: PipType::Joker,
index: '🎪',
symbol: '🎪',
value: 0,
},
card_type: BCardType::LegendaryJoker,
enhancement: MPip::DisablesAllBossBlinds,
resell_value: 0,
edition: Edition::None,
debuffed: false,
};
pub const PERKEO: BuffoonCard = BuffoonCard {
suit: FrenchSuit::JOKER,
rank: Pip {
weight: 620,
pip_type: PipType::Joker,
index: '🧪',
symbol: '🧪',
value: 0,
},
card_type: BCardType::LegendaryJoker,
enhancement: MPip::CreateNegativeConsumableCopy,
resell_value: 0,
edition: Edition::None,
debuffed: false,
};
}
#[cfg(test)]
#[allow(non_snake_case)]
mod funky__decks__joker_tests {
use super::*;
use crate::bcards;
use crate::funky::decks::basic::card as basic;
use crate::funky::decks::planet::card as planet_card;
use crate::funky::decks::tarot::MajorArcana;
use crate::funky::decks::tarot::card as tarot_card;
use crate::funky::types::board::BuffoonBoard;
use crate::funky::types::draws::Draws;
use crate::funky::types::mpip::MPip;
use crate::preludes::funky::{BCardType, Blind, BuffoonPile, Deck, Score};
use std::collections::{HashMap, HashSet};
const ALL_JOKERS: [BuffoonCard; 112] = [
card::JOKER,
card::GREEDY_JOKER,
card::LUSTY_JOKER,
card::WRATHFUL_JOKER,
card::GLUTTONOUS_JOKER,
card::JOLLY_JOKER,
card::ZANY_JOKER,
card::MAD_JOKER,
card::CRAZY_JOKER,
card::DROLL_JOKER,
card::SLY_JOKER,
card::WILY_JOKER,
card::CLEVER_JOKER,
card::DEVIOUS_JOKER,
card::CRAFTY_JOKER,
card::HALF_JOKER,
card::JOKER_STENCIL,
card::FOUR_FINGERS,
card::MIME,
card::CREDIT_CARD,
card::CEREMONIAL_DAGGER,
card::BANNER,
card::MYSTIC_SUMMIT,
card::MARBLE_JOKER,
card::LOYALTY_CARD,
card::EIGHT_BALL,
card::MISPRINT,
card::DUSK,
card::RAISED_FIST,
card::CHAOS_THE_CLOWN,
card::FIBONACCI,
card::STEEL_JOKER,
card::SCARY_FACE,
card::ABSTRACT_JOKER,
card::DELAYED_GRATIFICATION,
card::HACK,
card::PAREIDOLIA,
card::GROS_MICHEL,
card::EVEN_STEVEN,
card::ODD_TODD,
card::SCHOLAR,
card::BUSINESS_CARD,
card::SUPERNOVA,
card::RIDE_THE_BUS,
card::SPACE_JOKER,
card::EGG,
card::BURGLAR,
card::BLACKBOARD,
card::RUNNER,
card::ICE_CREAM,
card::DNA,
card::SPLASH,
card::BLUE_JOKER,
card::SIXTH_SENSE,
card::CONSTELLATION,
card::HIKER,
card::FACELESS_JOKER,
card::GREEN_JOKER,
card::SUPERPOSITION,
card::TO_DO_LIST,
card::CAVENDISH,
card::CARD_SHARP,
card::RED_CARD,
card::MADNESS,
card::SQUARE_JOKER,
card::SEANCE,
card::RIFF_RAFF,
card::VAMPIRE,
card::SHORTCUT,
card::HOLOGRAM,
card::VAGABOND,
card::BARON,
card::CLOUD_9,
card::ROCKET,
card::EROSION,
card::RESERVED_PARKING,
card::MAIL_IN_REBATE,
card::TO_THE_MOON,
card::HALLUCINATION,
card::FORTUNE_TELLER,
card::JUGGLER,
card::DRUNKARD,
card::STONE_JOKER,
card::GOLDEN_JOKER,
card::LUCKY_CAT,
card::BASEBALL_CARD,
card::BULL,
card::DIET_COLA,
card::TRADING_CARD,
card::FLASH_CARD,
card::POPCORN,
card::SPARE_TROUSERS,
card::ANCIENT_JOKER,
card::RAMEN,
card::WALKIE_TALKIE,
card::SOCK_AND_BUSKIN,
card::HANGING_CHAD,
card::SMEARED_JOKER,
card::OOPS_ALL_6S,
card::LUCHADOR,
card::MATADOR,
card::SELTZER,
card::THE_DUO,
card::THE_TRIO,
card::THE_FAMILY,
card::THE_ORDER,
card::THE_TRIBE,
card::CANIO,
card::TRIBOULET,
card::YORICK,
card::CHICOT,
card::PERKEO,
];
#[test]
fn all_jokers__is_superset_of_every_pile() {
let registry: HashSet<_> = ALL_JOKERS.iter().collect();
for pile in [
&Joker::COMMON_JOKERS[..],
&Joker::UNCOMMON_JOKERS[..],
&Joker::RARE_JOKERS[..],
&Joker::LEGENDARY_JOKERS[..],
] {
for card in pile {
assert!(
registry.contains(card),
"{card} is in a rarity pile but missing from ALL_JOKERS"
);
}
}
}
#[test]
fn all_jokers__every_joker_is_piled_by_its_rarity() {
let mut adrift: Vec<String> = Vec::new();
for card in ALL_JOKERS {
let pile: &[BuffoonCard] = match card.card_type {
BCardType::CommonJoker => &Joker::COMMON_JOKERS,
BCardType::UncommonJoker => &Joker::UNCOMMON_JOKERS,
BCardType::RareJoker => &Joker::RARE_JOKERS,
BCardType::LegendaryJoker => &Joker::LEGENDARY_JOKERS,
other => {
adrift.push(format!("{card} has non-joker card_type {other:?}"));
continue;
}
};
if !pile.contains(&card) {
adrift.push(format!("{card}"));
}
}
assert!(
adrift.is_empty(),
"{} jokers missing from their rarity's pile:\n{}",
adrift.len(),
adrift.join("\n")
);
}
#[test]
fn all_jokers__resell_value_is_half_cost_floored_at_one() {
let mut wrong: Vec<String> = Vec::new();
for card in ALL_JOKERS {
if card.card_type == BCardType::LegendaryJoker {
continue;
}
let expected = (card.rank.value / 2).max(1);
if card.resell_value != expected {
wrong.push(format!(
"{card}: cost ${} should sell ${expected}, has ${}",
card.rank.value, card.resell_value
));
}
}
assert!(
wrong.is_empty(),
"{} jokers with a wrong resell_value:\n{}",
wrong.len(),
wrong.join("\n")
);
}
#[test]
fn all_jokers__weights_are_unique() {
let mut seen = HashSet::new();
let mut collisions: Vec<(usize, String)> = Vec::new();
for card in ALL_JOKERS {
let weight = card.rank.weight;
if !seen.insert(weight) {
collisions.push((weight, format!("{card}")));
}
}
collisions.sort();
assert!(
collisions.is_empty(),
"{} jokers share a weight with an earlier joker: {collisions:?}",
collisions.len()
);
}
#[test]
fn baron__is_rare_dollar_eight_and_piled() {
assert_eq!(
card::BARON.card_type,
BCardType::RareJoker,
"Baron is a Rare joker in Balatro"
);
assert_eq!(card::BARON.rank.value, 8, "Baron costs $8 in Balatro");
assert!(
Joker::RARE_JOKERS.contains(&card::BARON),
"Baron belongs in RARE_JOKERS"
);
}
#[test]
fn erosion_and_stone_joker__are_uncommon_dollar_six_and_piled() {
for (joker, name) in [
(card::EROSION, "Erosion"),
(card::STONE_JOKER, "Stone Joker"),
] {
assert_eq!(
joker.card_type,
BCardType::UncommonJoker,
"{name} is an Uncommon joker in Balatro"
);
assert_eq!(joker.rank.value, 6, "{name} costs $6 in Balatro");
assert!(
Joker::UNCOMMON_JOKERS.contains(&joker),
"{name} belongs in UNCOMMON_JOKERS"
);
}
}
#[test]
fn hologram_and_vampire__are_uncommon_dollar_seven_and_piled() {
for (joker, name) in [(card::HOLOGRAM, "Hologram"), (card::VAMPIRE, "Vampire")] {
assert_eq!(
joker.card_type,
BCardType::UncommonJoker,
"{name} is an Uncommon joker in Balatro"
);
assert_eq!(joker.rank.value, 7, "{name} costs $7 in Balatro");
assert_eq!(joker.resell_value, 3, "{name} sells for $3 in Balatro");
assert!(
Joker::UNCOMMON_JOKERS.contains(&joker),
"{name} belongs in UNCOMMON_JOKERS"
);
}
}
#[test]
fn phase_8_jokers__have_their_balatro_rarity_and_cost() {
for (joker, name, cost) in [
(card::MADNESS, "Madness", 7),
(card::ROCKET, "Rocket", 6),
(card::LUCHADOR, "Luchador", 5),
(card::MATADOR, "Matador", 7),
] {
assert_eq!(
joker.card_type,
BCardType::UncommonJoker,
"{name} is an Uncommon joker in Balatro"
);
assert_eq!(joker.rank.value, cost, "{name} costs ${cost} in Balatro");
assert!(
Joker::UNCOMMON_JOKERS.contains(&joker),
"{name} belongs in UNCOMMON_JOKERS"
);
}
}
#[test]
fn matador__stays_blank_because_no_boss_ability_fires_on_a_played_hand() {
assert_eq!(card::MATADOR.enhancement, MPip::Blank);
assert!(
!scores_hand(card::MATADOR.enhancement),
"so the reachability guard rightly ignores it"
);
}
#[test]
fn seltzer__is_uncommon_dollar_six_and_piled() {
assert_eq!(card::SELTZER.card_type, BCardType::UncommonJoker);
assert_eq!(card::SELTZER.rank.value, 6, "Seltzer costs $6 in Balatro");
assert_eq!(card::SELTZER.resell_value, 3);
assert!(Joker::UNCOMMON_JOKERS.contains(&card::SELTZER));
}
#[allow(clippy::too_many_lines)]
fn scores_hand(mpip: MPip) -> bool {
match mpip {
MPip::AddBaseChips(_)
| MPip::Chips(_)
| MPip::ChipsMultPlus(_, _)
| MPip::ChipsMultPlusOnHand(_, _, _)
| MPip::ChipsMultPlusPerScoredRanks(_, _, _)
| MPip::ChipsOnFlush(_)
| MPip::ChipsOnPair(_)
| MPip::ChipsOn2Pair(_)
| MPip::ChipsOnStraight(_)
| MPip::ChipsOnTrips(_)
| MPip::ChipsPerDeckCard(_)
| MPip::ChipsPerFullDeckStone(_)
| MPip::ChipsPerDollar(_)
| MPip::ChipsPerRemainingDiscard(_)
| MPip::ChipsPlusOn5Ranks(_, _)
| MPip::ChipsPlusPerScoredFace(_)
| MPip::MultPlus(_)
| MPip::MultPlusChanceDestroyed(_, _, _)
| MPip::MultTimesChanceDestroyed(_, _, _)
| MPip::MultPlusChipsOnRank(_, _, _)
| MPip::MultPlusOn5Ranks(_, _)
| MPip::MultPlusOnFlush(_)
| MPip::MultPlusOnPair(_)
| MPip::MultPlusOn2Pair(_)
| MPip::MultPlusOnStraight(_)
| MPip::MultPlusOnTrips(_)
| MPip::MultPlusOnSuit(_, _)
| MPip::MultPlusOnUpToXCards(_, _)
| MPip::MultPlusOnZeroDiscards(_)
| MPip::MultPlusZeroDiscards(_)
| MPip::MultPlusPerJoker(_)
| MPip::MultPlusPerMissingDeckCard(_)
| MPip::MultPlusXOnLowestRankInHand(_)
| MPip::MultTimes(_)
| MPip::MultTimes1Dot(_)
| MPip::MultTimesOnEmptyJokerSlots(_)
| MPip::MultTimesOnPair(_)
| MPip::MultTimesOnTrips(_)
| MPip::MultTimesOn4OfAKind(_)
| MPip::MultTimesOnStraight(_)
| MPip::MultTimesOnFlush(_)
| MPip::MultTimesPerScoredRank(_, _)
| MPip::MultTimesPerHeldRank(_, _)
| MPip::MultTimesOnRepeatedHandThisRound(_)
| MPip::MultTimesPerScoredAncientSuit(_)
| MPip::MultTimesPerUncommonJoker(_)
| MPip::MultTimesPlusPerFullDeckSteel(_)
| MPip::MultTimesIfHeldAllSuits(_, _)
| MPip::GainMultPerHandLessDiscard(_)
| MPip::LoseMultTimesPerDiscard(_, _)
| MPip::LoseChipsPerHand(_, _)
| MPip::GainChipsPerCardCountHand(_, _)
| MPip::GainMultPerTwoPairHand(_)
| MPip::GainChipsPerStraightHand(_)
| MPip::LoseMultPerRound(_, _)
| MPip::GainMultTimesPerDiscardedCards(_, _)
| MPip::GainMultTimesPerCardAdded(_)
| MPip::GainMultTimesPerFaceDestroyed(_)
| MPip::GainMultTimesPerEnhancedPlayed(_)
| MPip::GainMultTimesPerPlanetUsed(_)
| MPip::MultPlusPerTarotUsedThisRun(_)
| MPip::MultPlusPerReroll(_)
| MPip::MultPlusPerPackSkipped(_)
| MPip::GainMultTimesOnNonBossBlindDestroyingJoker(_)
| MPip::GainChipsOnScored(_)
| MPip::RetriggerPlayedRanks(_, _)
| MPip::RetriggerPlayedFaces(_)
| MPip::RetriggerFirstPlayed(_)
| MPip::RetriggerCardsInHand(_)
| MPip::RetriggerPlayedCardsInFinalRound
| MPip::RetriggerAllPlayedForHands(_, _)
| MPip::FourFlushAndStraight
| MPip::GappedStraight
| MPip::SmearedSuits
| MPip::Glass(_, _)
| MPip::Stone(_) => true,
MPip::Blank
| MPip::AddCardTypeWhenBlindSelected(_)
| MPip::CreateJokersWhenBlindSelected(_, _)
| MPip::CreateTarotOnAceStraight
| MPip::CreateTarotOnLowMoney(_)
| MPip::CreateSpectralOnStraightFlush
| MPip::CreateSpectralOnFirstSingleSix
| MPip::SpectralLevelAllHands
| MPip::SpectralCreateLegendaryJoker
| MPip::SpectralCreateRareJokerZeroMoney
| MPip::SpectralNegativeRandomJokerMinusHandSize
| MPip::SpectralPolychromeRandomJokerDestroyOthers
| MPip::SpectralCopyRandomJokerDestroyOthers
| MPip::SpectralEditionRandomHandCard
| MPip::SpectralHandToRandomSuit
| MPip::SpectralHandToRandomRankMinusHandSize
| MPip::SpectralDestroyRandomHandGainMoney(_, _)
| MPip::SpectralDestroyOneAddEnhancedFaces(_)
| MPip::SpectralDestroyOneAddEnhancedAces(_)
| MPip::SpectralDestroyOneAddEnhancedNumbered(_)
| MPip::SpectralCopySelectedHandCard(_)
| MPip::DisableBossBlindOnSell
| MPip::DisablesAllBossBlinds
| MPip::CashOnRoundEndGrowingOnBossDefeat(_, _)
| MPip::ChanceDestroyed(_, _)
| MPip::CashOnRoundEnd(_)
| MPip::CashPerDiscardIfNoneUsed(_)
| MPip::CashPerFullDeckRank(_, _)
| MPip::ExtraInterest(_)
| MPip::CashOnFacesDiscarded(_, _)
| MPip::HandSizeIncrement(_)
| MPip::DiscardIncrement(_)
| MPip::GainHandsLoseDiscardsWhenBlindSelected(_)
| MPip::CreateCardOnRankPlay(_, _, _)
| MPip::Credit(_)
| MPip::Death(_)
| MPip::DoubleMoney(_)
| MPip::AllCardsAreFaces
| MPip::AllPlayedCardsScore
| MPip::DoubleOdds
| MPip::FreeReroll(_)
| MPip::Gold(_)
| MPip::Hanged(_)
| MPip::JokersValue(_)
| MPip::Lucky(_, _)
| MPip::MultPlusDoubleValueDestroyJokerOnRight(_)
| MPip::MultPlusOnConsecutiveHandsNo3Ranks(_, _, _)
| MPip::MultPlusOnHandPlays
| MPip::MultPlusRandomTo(_)
| MPip::MultTimesEveryXHands(_, _)
| MPip::Planet(_)
| MPip::RandomJoker(_)
| MPip::RandomTarot(_)
| MPip::CreateTarotOnPackOpen(_, _)
| MPip::CreateNegativeConsumableCopy
| MPip::SellValueIncrement(_)
| MPip::Strength
| MPip::Odds1in(_)
| MPip::Odds1inCashOn3Ranks(_, _, _)
| MPip::Odds1inUpgradeHand(_)
| MPip::Wild(_)
| MPip::Diamonds(_)
| MPip::Clubs(_)
| MPip::Hearts(_)
| MPip::Spades(_)
| MPip::Custom(_) => false,
}
}
fn probe_boards() -> Vec<BuffoonBoard> {
let mk = |played: &str, held: &str, money: isize, discards: usize| {
let mut b = BuffoonBoard::new(Draws::new(4, discards), Deck::basic_buffoon_pile());
b.played = bcards!(played);
b.in_hand = bcards!(held);
b.money = money;
b.jokers.push(card::FIBONACCI);
b
};
let mut steel_held = mk("AH KH QH JH TH", "KS KC", 0, 3);
let steel_king = BuffoonCard {
enhancement: MPip::STEEL,
..bcards!("KS").iter().next().copied().unwrap()
};
steel_held.in_hand.push(steel_king);
let mut enhanced_played = mk("AH KH QH JH TH", "KS KC", 0, 3);
for index in 0..2 {
let card = enhanced_played.played.remove(index);
enhanced_played.played.insert(
index,
BuffoonCard {
enhancement: MPip::BONUS,
..card
},
);
}
let mut worn_deck = mk("AH KH QH JH TH", "KS KC", 0, 3);
let mut wear = |enhancement: MPip, n: usize| {
for _ in 0..n {
let card = worn_deck.full_deck.remove(0);
worn_deck.full_deck.push(BuffoonCard {
enhancement,
..card
});
}
};
wear(MPip::STEEL, 2);
wear(MPip::TOWER, 2);
for _ in 0..3 {
worn_deck.full_deck.remove(0);
}
vec![
mk("AH KH QH JH TH", "KS KC", 100, 3),
mk("AH KH QH JH TH", "KS KC", 0, 0),
mk("KH KS QD QC 4S", "KS KC", 0, 3),
mk("5H 5S 5D", "KS KC", 0, 3),
mk("8H 8S 8D 8C", "KS KC", 0, 3),
steel_held,
mk("9H TH JH QH 2S", "KS KC", 0, 3),
mk("2C 4D 6H 8S TC", "KS KC", 0, 3),
mk("AH KH 9H QD JD", "KS KC", 0, 3),
enhanced_played,
worn_deck,
]
}
fn drive_events(board: &mut BuffoonBoard, joker: Option<BuffoonCard>) -> Score {
let growth_hands = [
bcards!("2C 3C 4C 5C"),
bcards!("KH KS QD QC 4S"),
bcards!("AH KH QH JH TH"),
];
let big_discard =
bcards!("2C 3C 4C 5C 6C 7C 8C 9C TC JC QC KC AC 2D 3D 4D 5D 6D 7D 8D 9D TD JD");
if let Some(joker) = joker {
board.push_joker(joker);
}
board.blind = Blind::Small;
board.on_blind_selected();
board.ancient_suit = Some('H');
board.on_round_end();
board.add_card_to_deck(bcards!("7D").iter().next().copied().unwrap());
if let Some(king) = board.full_deck_index_of(bcards!("KS").iter().next().copied().unwrap())
{
board.destroy_deck_card(king);
}
board.on_discard(&big_discard);
{
use rand::{SeedableRng, rngs::StdRng};
let mut rng = StdRng::seed_from_u64(0);
board.money = 20;
board.open_shop_with_rng(&mut rng);
board.reroll_with_rng(&mut rng);
board.skip_pack(0);
}
board.create_consumable(planet_card::PLUTO);
board.use_consumable(0, &[]);
board.create_consumable(tarot_card::JUSTICE);
board.use_consumable(0, &[]);
for hand in &growth_hands {
board.on_hand_played(hand);
}
board.on_scored();
board.score()
}
fn is_reachable(joker: BuffoonCard) -> bool {
probe_boards().into_iter().any(|mut board| {
let baseline = drive_events(&mut board.clone(), None);
let probed = drive_events(&mut board, Some(joker));
probed != baseline
})
}
const KNOWN_UNWIRED: [BuffoonCard; 0] = [];
const BLANK_WITH_REASON: [(BuffoonCard, &str); 8] = [
(
card::DNA,
"needs a draw step (it copies a card into hand); its other half, \
'first hand of round', is expressible now via `hands_played`",
),
(
card::TO_DO_LIST,
"needs a per-round random target hand type",
),
(card::MAIL_IN_REBATE, "needs a per-round random target rank"),
(card::TRADING_CARD, "needs card destruction on discard"),
(
card::RESERVED_PARKING,
"a probabilistic held-card payout — deferred rather than blocked; it \
could ride the existing seeded-RNG path today",
),
(
card::LUCKY_CAT,
"needs scoring to be mutating: it gains x0.25 per Lucky *proc*, which \
happens inside the pure `&self` fold, so no `&mut` hook can see it \
without re-rolling the RNG. The same gap `on_scored` already \
characterizes for Hiker",
),
(
card::MATADOR,
"pays when a played hand triggers the Boss Blind ability; every boss \
modelled here applies its ability once at blind select, so there is \
no per-hand trigger to read",
),
(
card::DIET_COLA,
"sell it to create a free Double Tag: needs Tags, which do not exist \
— there is no object to create, and Double Tag's own effect ('copy \
the next selected Tag') needs the skip-blind tag-selection flow too. \
Its `selling_self` trigger also wants the shop. Tags first",
),
];
#[test]
fn all_jokers__every_blank_joker_has_a_stated_reason() {
let listed: HashMap<_, _> = BLANK_WITH_REASON.iter().copied().collect();
let mut unexplained = Vec::new();
let mut wired_but_listed = Vec::new();
for joker in ALL_JOKERS {
let is_blank = joker.enhancement == MPip::Blank;
let has_reason = listed.contains_key(&joker);
if is_blank && !has_reason {
unexplained.push(format!("{joker}"));
}
if !is_blank && has_reason {
wired_but_listed.push(format!("{joker}"));
}
}
assert!(
wired_but_listed.is_empty(),
"these are wired now — remove them from BLANK_WITH_REASON: {wired_but_listed:?}"
);
assert!(
unexplained.is_empty(),
"these jokers are Blank with no stated reason. Wire them, or add them \
to BLANK_WITH_REASON saying what they are waiting on — `Blank` by \
omission is indistinguishable from `Blank` by decision, and no other \
guard can tell them apart: {unexplained:?}"
);
for (joker, reason) in BLANK_WITH_REASON {
assert!(
reason.len() > 20,
"{joker} needs a real reason, not `{reason}`"
);
}
}
#[test]
fn blank_jokers__every_reason_names_a_blocker() {
const BLOCKER_WORDS: [&str; 8] = [
"needs",
"not exist",
"deferred",
"no ",
"cannot",
"wants",
"blocked",
"first hand",
];
for (joker, reason) in BLANK_WITH_REASON {
let lower = reason.to_lowercase();
assert!(
BLOCKER_WORDS.iter().any(|word| lower.contains(word)),
"{joker}'s reason does not name what is missing: `{reason}`"
);
assert!(
!lower.contains("untriaged") && !lower.contains("todo"),
"{joker} is not triaged — decide what it needs, then wire it or say why not: `{reason}`"
);
}
}
#[test]
fn all_jokers__intended_hand_scorers_are_reachable() {
let known: HashSet<_> = KNOWN_UNWIRED.iter().collect();
let mut new_silent_zero = Vec::new();
let mut now_wired = Vec::new();
let mut misclassified = Vec::new();
for joker in ALL_JOKERS {
let intends = scores_hand(joker.enhancement);
let reachable = is_reachable(joker);
let listed = known.contains(&joker);
if intends && !reachable && !listed {
new_silent_zero.push(format!("{joker}"));
}
if listed && reachable {
now_wired.push(format!("{joker}"));
}
if !intends && reachable {
misclassified.push(format!("{joker}"));
}
}
assert!(
misclassified.is_empty(),
"these scored but are classified non-scoring — reclassify in `scores_hand`: {misclassified:?}"
);
assert!(
now_wired.is_empty(),
"these are wired now — remove them from KNOWN_UNWIRED: {now_wired:?}"
);
assert!(
new_silent_zero.is_empty(),
"these jokers intend to score but silently add nothing (wire them or, if intentional, adjust the data): {new_silent_zero:?}"
);
}
fn all_card_enhancements() -> Vec<MPip> {
let plain = basic::KING_HEARTS;
let mut found: Vec<MPip> = MajorArcana::DECK
.iter()
.map(|tarot| plain.enhance(*tarot).enhancement)
.filter(|enhancement| *enhancement != MPip::Blank)
.collect::<HashSet<_>>()
.into_iter()
.collect();
found.sort_unstable();
found
}
fn is_card_enhancement_reachable(enhancement: MPip) -> bool {
let plain = basic::KING_HEARTS;
let enhanced = BuffoonCard {
enhancement,
..plain
};
let probe = |plain_card: BuffoonCard, enhanced_card: BuffoonCard, held: bool| {
let mut board = BuffoonBoard::new(Draws::new(4, 3), Deck::basic_buffoon_pile());
board.played = bcards!("2S 5D 8C TS");
let mut with = |card: BuffoonCard| {
if held {
board.in_hand = BuffoonPile::from(vec![card]);
} else {
board.played = bcards!("2S 5D 8C TS");
board.played.push(card);
}
board.score()
};
with(plain_card) != with(enhanced_card)
};
probe(plain, enhanced, false) || probe(plain, enhanced, true)
}
const KNOWN_UNWIRED_CARD_ENHANCEMENTS: [MPip; 0] = [];
#[test]
fn all_card_enhancements__intended_hand_scorers_are_reachable() {
let known: HashSet<_> = KNOWN_UNWIRED_CARD_ENHANCEMENTS.iter().collect();
let mut new_silent_zero = Vec::new();
let mut now_wired = Vec::new();
let mut misclassified = Vec::new();
for enhancement in all_card_enhancements() {
let intends = scores_hand(enhancement);
let reachable = is_card_enhancement_reachable(enhancement);
let listed = known.contains(&enhancement);
if intends && !reachable && !listed {
new_silent_zero.push(format!("{enhancement}"));
}
if listed && reachable {
now_wired.push(format!("{enhancement}"));
}
if !intends && reachable {
misclassified.push(format!("{enhancement}"));
}
}
assert!(
misclassified.is_empty(),
"these card enhancements scored but are classified non-scoring — reclassify in `scores_hand`: {misclassified:?}"
);
assert!(
now_wired.is_empty(),
"these are wired now — remove them from KNOWN_UNWIRED_CARD_ENHANCEMENTS: {now_wired:?}"
);
assert!(
new_silent_zero.is_empty(),
"these card enhancements intend to score but silently add nothing: {new_silent_zero:?}"
);
}
fn assert_rarity_pile(cards: &[BuffoonCard], size: usize, rarity: BCardType) {
assert_eq!(cards.len(), size, "declared size does not match the array");
for card in cards {
assert!(card.is_joker(), "{card} is not a joker");
assert_eq!(card.card_type, rarity, "{card} is not tagged {rarity:?}");
}
let distinct: HashSet<_> = cards.iter().collect();
assert_eq!(distinct.len(), size, "the pile contains duplicate cards");
}
#[test]
fn common_jokers__data_invariants() {
assert_rarity_pile(
&Joker::COMMON_JOKERS,
Joker::COMMON_JOKERS_SIZE,
BCardType::CommonJoker,
);
assert_eq!(Joker::pile_common().len(), Joker::COMMON_JOKERS_SIZE);
}
#[test]
fn uncommon_jokers__data_invariants() {
assert_rarity_pile(
&Joker::UNCOMMON_JOKERS,
Joker::UNCOMMON_JOKERS_SIZE,
BCardType::UncommonJoker,
);
assert_eq!(Joker::pile_uncommon().len(), Joker::UNCOMMON_JOKERS_SIZE);
}
#[test]
fn rare_jokers__data_invariants() {
assert_rarity_pile(
&Joker::RARE_JOKERS,
Joker::RARE_JOKERS_SIZE,
BCardType::RareJoker,
);
assert_eq!(Joker::pile_rare().len(), Joker::RARE_JOKERS_SIZE);
}
#[test]
fn legendary_jokers__data_invariants() {
assert_rarity_pile(
&Joker::LEGENDARY_JOKERS,
Joker::LEGENDARY_JOKERS_SIZE,
BCardType::LegendaryJoker,
);
assert_eq!(Joker::pile_legendary().len(), Joker::LEGENDARY_JOKERS_SIZE);
}
#[test]
fn rarity_piles__no_card_appears_in_two_piles() {
let mut all: Vec<BuffoonCard> = Vec::new();
all.extend(Joker::COMMON_JOKERS);
all.extend(Joker::UNCOMMON_JOKERS);
all.extend(Joker::RARE_JOKERS);
all.extend(Joker::LEGENDARY_JOKERS);
let distinct: HashSet<_> = all.iter().collect();
assert_eq!(
distinct.len(),
all.len(),
"a joker appears in more than one rarity pile"
);
}
}