From b834fd654fe2f5665e6386ad7728c3e490e54141 Mon Sep 17 00:00:00 2001 From: Evan Slack Date: Sat, 7 Dec 2024 23:50:04 -0500 Subject: [PATCH 1/5] new joker concept --- src/core/effect.rs | 15 ----- src/core/joker.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 15 deletions(-) delete mode 100644 src/core/effect.rs create mode 100644 src/core/joker.rs diff --git a/src/core/effect.rs b/src/core/effect.rs deleted file mode 100644 index b637bbd..0000000 --- a/src/core/effect.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::core::game::Game; - -pub trait Effect { - fn apply(&self, game: &mut Game); -} - -pub struct Joker { - pub name: String, -} - -impl Effect for Joker { - fn apply(&self, game: &mut Game) { - game.mult += 4; - } -} diff --git a/src/core/joker.rs b/src/core/joker.rs new file mode 100644 index 0000000..033431a --- /dev/null +++ b/src/core/joker.rs @@ -0,0 +1,133 @@ +use crate::core::game::Game; +use strum::{EnumIter, IntoEnumIterator}; + +pub trait Effect { + fn apply(&self, game: &mut Game); +} + +pub enum Effects { + OnPlay(Box), + OnDiscard(Box), + OnScore(Box), + OnHandRank(Box), +} + +pub trait Joker: std::fmt::Debug + Clone { + fn name(&self) -> String; + fn desc(&self) -> String; + fn rarity(&self) -> Rarity; + fn categories(&self) -> Vec; + fn effects(&self, game: Game) -> Vec; +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Categories { + MultPlus, + MultMult, + Chips, + Economy, + Retrigger, + Effect, +} + +#[derive(Debug, Clone, Eq, PartialEq, EnumIter)] +pub enum Rarity { + Common, + Uncommon, + Rare, + Legendary, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, EnumIter, Eq, PartialEq, Hash)] +pub enum Jokers { + TheJoker(TheJoker), + LustyJoker(LustyJoker), +} + +impl Jokers { + pub fn by_rarity(rarirty: Rarity) -> Vec { + return Self::iter().filter(|j| j.rarity() == rarirty).collect(); + } +} + +impl Joker for Jokers { + fn name(&self) -> String { + match self { + Self::TheJoker(j) => j.name(), + Self::LustyJoker(j) => j.name(), + } + } + fn desc(&self) -> String { + match self { + Self::TheJoker(j) => j.desc(), + Self::LustyJoker(j) => j.desc(), + } + } + fn rarity(&self) -> Rarity { + match self { + Self::TheJoker(j) => j.rarity(), + Self::LustyJoker(j) => j.rarity(), + } + } + fn categories(&self) -> Vec { + match self { + Self::TheJoker(j) => j.categories(), + Self::LustyJoker(j) => j.categories(), + } + } + fn effects(&self, game: Game) -> Vec { + match self { + Self::TheJoker(j) => j.effects(game), + Self::LustyJoker(j) => j.effects(game), + } + } +} + +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +pub struct TheJoker {} + +impl Joker for TheJoker { + fn name(&self) -> String { + "Joker".to_string() + } + fn desc(&self) -> String { + "+4 Mult".to_string() + } + fn rarity(&self) -> Rarity { + Rarity::Common + } + fn categories(&self) -> Vec { + vec![Categories::MultPlus] + } + fn effects(&self, _in: Game) -> Vec { + fn apply(g: &mut Game) { + g.mult += 4; + } + vec![Effects::OnScore(Box::new(apply))] + } +} + +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +pub struct LustyJoker {} + +impl Joker for LustyJoker { + fn name(&self) -> String { + "LustyJoker".to_string() + } + fn desc(&self) -> String { + "+4 Mult for each heart".to_string() + } + fn rarity(&self) -> Rarity { + Rarity::Common + } + fn categories(&self) -> Vec { + vec![Categories::MultPlus] + } + fn effects(&self, _in: Game) -> Vec { + fn apply(g: &mut Game) { + g.mult += 4; + } + vec![Effects::OnScore(Box::new(apply))] + } +} From 3c8d56dc36da4628e3be03fe5393526adb42f9cc Mon Sep 17 00:00:00 2001 From: Evan Slack Date: Sat, 7 Dec 2024 23:50:16 -0500 Subject: [PATCH 2/5] add shop --- src/core/shop.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/core/shop.rs diff --git a/src/core/shop.rs b/src/core/shop.rs new file mode 100644 index 0000000..01bd563 --- /dev/null +++ b/src/core/shop.rs @@ -0,0 +1,72 @@ +use crate::core::action::Action; +use crate::core::error::GameError; +use crate::core::joker::{Jokers, Rarity}; +use rand::distributions::WeightedIndex; +use rand::prelude::*; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct Shop { + pub jokers: Vec, + joker_gen: JokerGenerator, +} + +impl Shop { + pub fn refresh(&mut self) { + let j1 = self.joker_gen.gen_joker(); + let j2 = self.joker_gen.gen_joker(); + self.jokers = vec![j1, j2] + } + + pub fn buy_joker(&mut self, joker: Jokers) -> Result { + let i = self + .jokers + .iter() + .position(|j| *j == joker) + .ok_or(GameError::NoJokerMatch)?; + let out = self.jokers.remove(i); + return Ok(out); + } + + pub fn new() -> Self { + return Shop { + joker_gen: JokerGenerator {}, + jokers: Vec::new(), + }; + } + + pub fn gen_moves_buy_joker(&self) -> Option> { + if self.jokers.len() == 0 { + return None; + } + let buys = self.jokers.clone().into_iter().map(|j| Action::BuyJoker(j)); + return Some(buys); + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone)] +pub struct JokerGenerator {} + +impl JokerGenerator { + // Randomly generate rarity of new joker. + // 70% chance Common, 25% chance Uncommon, 5% chance Rare. + // Legendary can only appear from Soul Spectral Card. + fn gen_rarity(&self) -> Rarity { + let choices = [Rarity::Common, Rarity::Uncommon, Rarity::Rare]; + let weights = [70, 25, 5]; + let dist = WeightedIndex::new(&weights).unwrap(); + let mut rng = thread_rng(); + return choices[dist.sample(&mut rng)].clone(); + } + + // Generate a random new joker + pub fn gen_joker(&self) -> Jokers { + let rarity = self.gen_rarity(); + let choices = Jokers::by_rarity(rarity); + let i = thread_rng().gen_range(0..choices.len()); + // TODO: don't regenerate already generated jokers. + // track with hashmap. + return choices[i].clone(); + } +} From 43933063773494beb39034adc31b68e26c866f7f Mon Sep 17 00:00:00 2001 From: Evan Slack Date: Sat, 7 Dec 2024 23:50:32 -0500 Subject: [PATCH 3/5] jokers/shop in game --- Cargo.lock | 35 +++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/core/action.rs | 6 +++++- src/core/error.rs | 4 +++- src/core/game.rs | 28 +++++++++++++++++++++++++++- src/core/mod.rs | 3 ++- 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff27378..d5bf9e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "rand", "serde", "serde_json", + "strum", "thiserror", "tracing", "uuid", @@ -57,6 +58,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "indexmap" version = "2.6.0" @@ -163,6 +170,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.18" @@ -201,6 +214,28 @@ dependencies = [ "serde", ] +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.89" diff --git a/Cargo.toml b/Cargo.toml index 80c28b5..948c7fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ tracing = { version = "~0.1.40", optional = true} uuid = {version = "~1.9.1", optional = true, features = ["v7"]} itertools = "0.13.0" indexmap = "2.6.0" +strum = { version = "0.26", features = ["derive"] } [features] default = ["serde"] diff --git a/src/core/action.rs b/src/core/action.rs index 6f41282..7f65168 100644 --- a/src/core/action.rs +++ b/src/core/action.rs @@ -1,5 +1,6 @@ use crate::core::card::Card; use crate::core::hand::SelectHand; +use crate::core::joker::Jokers; use crate::core::stage::Blind; use std::fmt; @@ -30,7 +31,7 @@ pub enum Action { Discard(SelectHand), MoveCard(MoveDirection, Card), CashOut(usize), - // BuyConsumable(Consumable) + BuyJoker(Jokers), NextRound(), SelectBlind(Blind), // SkipBlind(Blind), @@ -51,6 +52,9 @@ impl fmt::Display for Action { Self::CashOut(reward) => { write!(f, "CashOut: {:}", reward) } + Self::BuyJoker(joker) => { + write!(f, "BuyJoker: {:?}", joker) + } Self::NextRound() => { write!(f, "NextRound") } diff --git a/src/core/error.rs b/src/core/error.rs index 829047f..460a321 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -24,8 +24,10 @@ pub enum GameError { InvalidAction, #[error("No card match")] InvalidBlind, - #[error("Invalid blind")] + #[error("No card match")] NoCardMatch, + #[error("No joker match")] + NoJokerMatch, #[error("Invalid move direction")] InvalidMoveDirection, } diff --git a/src/core/game.rs b/src/core/game.rs index 37aa1cc..d9337cd 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -4,6 +4,8 @@ use crate::core::card::Card; use crate::core::deck::Deck; use crate::core::error::GameError; use crate::core::hand::{MadeHand, SelectHand}; +use crate::core::joker::Jokers; +use crate::core::shop::Shop; use crate::core::stage::{Blind, End, Stage}; use std::collections::HashSet; use std::fmt; @@ -26,9 +28,11 @@ const BASE_SCORE: usize = 0; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct Game { + pub shop: Shop, pub deck: Deck, pub available: Vec, pub discarded: Vec, + pub jokers: Vec, pub blind: Option, pub stage: Stage, pub ante: Ante, @@ -50,9 +54,11 @@ pub struct Game { impl Game { pub fn new() -> Self { Self { + shop: Shop::new(), deck: Deck::default(), available: Vec::new(), discarded: Vec::new(), + jokers: Vec::new(), blind: None, stage: Stage::PreBlind, ante: Ante::One, @@ -196,6 +202,11 @@ impl Game { return Ok(()); } + pub fn buy_joker(&mut self, joker: Jokers) -> Result<(), GameError> { + self.jokers.push(joker); + return Ok(()); + } + pub fn select_blind(&mut self, blind: Blind) -> Result<(), GameError> { // can only set blind if stage is pre blind if self.stage != Stage::PreBlind { @@ -404,6 +415,15 @@ impl Game { } } + // get buy joker moves + pub fn gen_moves_buy_joker(&self) -> Option> { + // If stage is not shop, cannot buy + if self.stage != Stage::Shop { + return None; + } + return self.shop.gen_moves_buy_joker(); + } + // get all legal moves that can be executed given current state pub fn gen_moves(&self) -> impl Iterator { let plays = self.gen_moves_play(); @@ -412,6 +432,7 @@ impl Game { let cashouts = self.gen_moves_cash_out(); let nextrounds = self.gen_moves_next_round(); let selectblinds = self.gen_moves_select_blind(); + let buy_jokers = self.gen_moves_buy_joker(); return plays .into_iter() @@ -420,7 +441,8 @@ impl Game { .chain(move_cards.into_iter().flatten()) .chain(cashouts.into_iter().flatten()) .chain(nextrounds.into_iter().flatten()) - .chain(selectblinds.into_iter().flatten()); + .chain(selectblinds.into_iter().flatten()) + .chain(buy_jokers.into_iter().flatten()); } pub fn handle_action(&mut self, action: Action) -> Result<(), GameError> { @@ -442,6 +464,10 @@ impl Game { Stage::PostBlind => self.cashout(), _ => Err(GameError::InvalidAction), }, + Action::BuyJoker(joker) => match self.stage { + Stage::Shop => self.buy_joker(joker), + _ => Err(GameError::InvalidAction), + }, Action::NextRound() => match self.stage { Stage::Shop => self.next_round(), _ => Err(GameError::InvalidAction), diff --git a/src/core/mod.rs b/src/core/mod.rs index 7bf56b3..0c64198 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -2,9 +2,10 @@ pub mod action; pub mod ante; pub mod card; pub mod deck; -pub mod effect; pub mod error; pub mod game; pub mod hand; +pub mod joker; pub mod rank; +pub mod shop; pub mod stage; From cfda69426e6200f7fd71d0ea7ecdc24457b9bd3f Mon Sep 17 00:00:00 2001 From: Evan Slack Date: Sun, 8 Dec 2024 01:21:23 -0500 Subject: [PATCH 4/5] start to use effects for scoring --- src/core/deck.rs | 11 +--------- src/core/effect.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++ src/core/game.rs | 49 ++++++++++++++++++++++++++++++++---------- src/core/joker.rs | 27 ++++++++--------------- src/core/mod.rs | 1 + 5 files changed, 102 insertions(+), 39 deletions(-) create mode 100644 src/core/effect.rs diff --git a/src/core/deck.rs b/src/core/deck.rs index 5dc7cee..ceb44fd 100644 --- a/src/core/deck.rs +++ b/src/core/deck.rs @@ -11,19 +11,13 @@ impl Deck { pub fn new() -> Self { Self { cards: Vec::new() } } - pub(crate) fn contains(&self, c: &Card) -> bool { - self.cards.contains(c) - } - pub(crate) fn remove(&mut self, c: &Card) -> bool { + pub fn remove(&mut self, c: &Card) -> bool { if let Some(pos) = self.cards.iter().position(|x| x == c) { self.cards.remove(pos); return true; } return false; } - pub(crate) fn push(&mut self, c: Card) { - self.cards.push(c) - } pub(crate) fn draw(&mut self, n: usize) -> Option> { if self.cards.len() < n { return None; @@ -33,9 +27,6 @@ impl Deck { pub(crate) fn len(&self) -> usize { self.cards.len() } - pub(crate) fn is_empty(&self) -> bool { - self.cards.is_empty() - } pub(crate) fn shuffle(&mut self) { self.cards.shuffle(&mut thread_rng()); diff --git a/src/core/effect.rs b/src/core/effect.rs new file mode 100644 index 0000000..ff1d04c --- /dev/null +++ b/src/core/effect.rs @@ -0,0 +1,53 @@ +use crate::core::game::Game; +use crate::core::joker::{Joker, Jokers}; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct EffectRegistry { + pub on_play: Vec, + pub on_discard: Vec, + pub on_score: Vec, + pub on_handrank: Vec, +} + +impl EffectRegistry { + pub fn new() -> Self { + return Self { + on_play: Vec::new(), + on_discard: Vec::new(), + on_score: Vec::new(), + on_handrank: Vec::new(), + }; + } + pub fn register_jokers(&mut self, jokers: Vec, game: &Game) { + for j in jokers.clone() { + for e in j.effects(game) { + match e { + Effects::OnPlay(_) => self.on_play.push(e), + Effects::OnDiscard(_) => self.on_discard.push(e), + Effects::OnScore(_) => self.on_score.push(e), + Effects::OnHandRank(_) => self.on_handrank.push(e), + } + } + } + } +} + +#[derive(Clone)] +pub enum Effects { + OnPlay(Arc), + OnDiscard(Arc), + OnScore(Arc), + OnHandRank(Arc), +} + +impl std::fmt::Debug for Effects { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::OnPlay(_) => write!(f, "OnPlay"), + Self::OnDiscard(_) => write!(f, "OnDiscard"), + Self::OnScore(_) => write!(f, "OnScore"), + Self::OnHandRank(_) => write!(f, "OnHandRank"), + } + } +} diff --git a/src/core/game.rs b/src/core/game.rs index d9337cd..bb52acd 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -2,6 +2,7 @@ use crate::core::action::{Action, MoveDirection}; use crate::core::ante::Ante; use crate::core::card::Card; use crate::core::deck::Deck; +use crate::core::effect::EffectRegistry; use crate::core::error::GameError; use crate::core::hand::{MadeHand, SelectHand}; use crate::core::joker::Jokers; @@ -12,6 +13,8 @@ use std::fmt; use itertools::Itertools; +use super::effect::Effects; + const DEFAULT_ROUND_START: usize = 0; const DEFAULT_PLAYS: usize = 4; const DEFAULT_DISCARDS: usize = 4; @@ -21,24 +24,26 @@ const DEFAULT_MONEY_PER_HAND: usize = 1; const DEFAULT_INTEREST_RATE: f32 = 0.2; const DEFAULT_INTEREST_MAX: usize = 5; const HAND_SIZE: usize = 8; -const BASE_MULT: usize = 1; +const BASE_MULT: usize = 0; const BASE_CHIPS: usize = 0; const BASE_SCORE: usize = 0; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct Game { pub shop: Shop, pub deck: Deck, pub available: Vec, pub discarded: Vec, - pub jokers: Vec, pub blind: Option, pub stage: Stage, pub ante: Ante, pub action_history: Vec, pub round: usize, + // jokers and their effects + pub jokers: Vec, + pub effect_registry: EffectRegistry, + // playing pub plays: usize, pub discards: usize, @@ -58,12 +63,13 @@ impl Game { deck: Deck::default(), available: Vec::new(), discarded: Vec::new(), - jokers: Vec::new(), blind: None, stage: Stage::PreBlind, ante: Ante::One, action_history: Vec::new(), round: DEFAULT_ROUND_START, + jokers: Vec::new(), + effect_registry: EffectRegistry::new(), plays: DEFAULT_PLAYS, discards: DEFAULT_DISCARDS, reward: DEFAULT_REWARD, @@ -165,11 +171,30 @@ impl Game { } } - pub fn calc_score(&self, hand: MadeHand) -> usize { - let base_mult = hand.rank.level().mult; - let base_chips = hand.rank.level().chips; - let hand_chips: usize = hand.hand.cards().iter().map(|c| c.chips()).sum(); - return (hand_chips + base_chips) * base_mult; + pub fn calc_score(&mut self, hand: MadeHand) -> usize { + // compute chips and mult from hand level + self.chips += hand.rank.level().chips; + self.mult += hand.rank.level().mult; + + // add chips for each played card + let card_chips: usize = hand.hand.cards().iter().map(|c| c.chips()).sum(); + self.chips += card_chips; + + // Apply effects that modify game.chips and game.mult + for e in self.effect_registry.on_score.clone() { + match e { + Effects::OnScore(f) => f(self), + _ => (), + } + } + + // compute score + let score = self.chips * self.mult; + + // reset chips and mult + self.mult = BASE_MULT; + self.chips = BASE_CHIPS; + return score; } pub fn required_score(&self) -> Result { @@ -204,6 +229,8 @@ impl Game { pub fn buy_joker(&mut self, joker: Jokers) -> Result<(), GameError> { self.jokers.push(joker); + self.effect_registry + .register_jokers(self.jokers.clone(), &self.clone()); return Ok(()); } @@ -507,7 +534,7 @@ mod tests { let g = Game::new(); assert_eq!(g.available.len(), 0); assert_eq!(g.deck.len(), 52); - assert_eq!(g.mult, 1); + assert_eq!(g.mult, 0); } #[test] @@ -548,7 +575,7 @@ mod tests { #[test] fn test_score() { - let g = Game::new(); + let g = &mut Game::new(); let ace = Card::new(Value::Ace, Suit::Heart); let king = Card::new(Value::King, Suit::Diamond); let jack = Card::new(Value::Jack, Suit::Club); diff --git a/src/core/joker.rs b/src/core/joker.rs index 033431a..6a8ae07 100644 --- a/src/core/joker.rs +++ b/src/core/joker.rs @@ -1,23 +1,14 @@ +use crate::core::effect::Effects; use crate::core::game::Game; +use std::sync::Arc; use strum::{EnumIter, IntoEnumIterator}; -pub trait Effect { - fn apply(&self, game: &mut Game); -} - -pub enum Effects { - OnPlay(Box), - OnDiscard(Box), - OnScore(Box), - OnHandRank(Box), -} - pub trait Joker: std::fmt::Debug + Clone { fn name(&self) -> String; fn desc(&self) -> String; fn rarity(&self) -> Rarity; fn categories(&self) -> Vec; - fn effects(&self, game: Game) -> Vec; + fn effects(&self, game: &Game) -> Vec; } #[derive(Debug, Clone, Eq, PartialEq)] @@ -30,7 +21,7 @@ pub enum Categories { Effect, } -#[derive(Debug, Clone, Eq, PartialEq, EnumIter)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum Rarity { Common, Uncommon, @@ -76,7 +67,7 @@ impl Joker for Jokers { Self::LustyJoker(j) => j.categories(), } } - fn effects(&self, game: Game) -> Vec { + fn effects(&self, game: &Game) -> Vec { match self { Self::TheJoker(j) => j.effects(game), Self::LustyJoker(j) => j.effects(game), @@ -100,11 +91,11 @@ impl Joker for TheJoker { fn categories(&self) -> Vec { vec![Categories::MultPlus] } - fn effects(&self, _in: Game) -> Vec { + fn effects(&self, _in: &Game) -> Vec { fn apply(g: &mut Game) { g.mult += 4; } - vec![Effects::OnScore(Box::new(apply))] + vec![Effects::OnScore(Arc::new(apply))] } } @@ -124,10 +115,10 @@ impl Joker for LustyJoker { fn categories(&self) -> Vec { vec![Categories::MultPlus] } - fn effects(&self, _in: Game) -> Vec { + fn effects(&self, _in: &Game) -> Vec { fn apply(g: &mut Game) { g.mult += 4; } - vec![Effects::OnScore(Box::new(apply))] + vec![Effects::OnScore(Arc::new(apply))] } } diff --git a/src/core/mod.rs b/src/core/mod.rs index 0c64198..c076604 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -2,6 +2,7 @@ pub mod action; pub mod ante; pub mod card; pub mod deck; +pub mod effect; pub mod error; pub mod game; pub mod hand; From f8513c1bec319812e4c00634187e2b2de9145eb1 Mon Sep 17 00:00:00 2001 From: Evan Slack Date: Mon, 9 Dec 2024 00:42:08 -0500 Subject: [PATCH 5/5] test the joker --- src/core/game.rs | 3 +++ src/core/joker.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/core/game.rs b/src/core/game.rs index bb52acd..2691af6 100644 --- a/src/core/game.rs +++ b/src/core/game.rs @@ -228,6 +228,9 @@ impl Game { } pub fn buy_joker(&mut self, joker: Jokers) -> Result<(), GameError> { + if self.stage != Stage::Shop { + return Err(GameError::InvalidStage); + } self.jokers.push(joker); self.effect_registry .register_jokers(self.jokers.clone(), &self.clone()); diff --git a/src/core/joker.rs b/src/core/joker.rs index 6a8ae07..246bb90 100644 --- a/src/core/joker.rs +++ b/src/core/joker.rs @@ -122,3 +122,41 @@ impl Joker for LustyJoker { vec![Effects::OnScore(Arc::new(apply))] } } + +#[cfg(test)] +mod tests { + use crate::core::card::{Card, Suit, Value}; + use crate::core::hand::SelectHand; + use crate::core::stage::{Blind, Stage}; + + use super::*; + + #[test] + fn test_the_joker() { + let mut g = Game::new(); + g.stage = Stage::Blind(Blind::Small); + let j = Jokers::TheJoker(TheJoker {}); + + let ace = Card::new(Value::Ace, Suit::Heart); + let hand = SelectHand::new(vec![ace]); + + // Score Ace high without joker + // High card (level 1) -> 5 chips, 1 mult + // Played cards (1 ace) -> 11 chips + // (5 + 11) * (1) = 16 + let score = g.calc_score(hand.best_hand().unwrap()); + assert_eq!(score, 16); + + // buy (and apply) the joker + g.stage = Stage::Shop; + g.buy_joker(j).unwrap(); + g.stage = Stage::Blind(Blind::Small); + // Score Ace high with the Joker + // High card (level 1) -> 5 chips, 1 mult + // Played cards (1 ace) -> 11 chips + // Joker (The Joker) -> 4 mult + // (5 + 11) * (1 + 4) = 80 + let score = g.calc_score(hand.best_hand().unwrap()); + assert_eq!(score, 80); + } +}