Skip to content

Commit

Permalink
Merge pull request #1 from evanofslack/joker
Browse files Browse the repository at this point in the history
Start of joker concepts
  • Loading branch information
evanofslack authored Dec 10, 2024
2 parents 8417c16 + f8513c1 commit ee53f23
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 29 deletions.
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
6 changes: 5 additions & 1 deletion src/core/action.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -30,7 +31,7 @@ pub enum Action {
Discard(SelectHand),
MoveCard(MoveDirection, Card),
CashOut(usize),
// BuyConsumable(Consumable)
BuyJoker(Jokers),
NextRound(),
SelectBlind(Blind),
// SkipBlind(Blind),
Expand All @@ -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")
}
Expand Down
11 changes: 1 addition & 10 deletions src/core/deck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<Card>> {
if self.cards.len() < n {
return None;
Expand All @@ -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());
Expand Down
52 changes: 45 additions & 7 deletions src/core/effect.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,53 @@
use crate::core::game::Game;
use crate::core::joker::{Joker, Jokers};
use std::sync::Arc;

pub trait Effect {
fn apply(&self, game: &mut Game);
#[derive(Debug, Clone)]
pub struct EffectRegistry {
pub on_play: Vec<Effects>,
pub on_discard: Vec<Effects>,
pub on_score: Vec<Effects>,
pub on_handrank: Vec<Effects>,
}

pub struct Joker {
pub name: String,
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<Jokers>, 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<dyn Fn(&mut Game)>),
OnDiscard(Arc<dyn Fn(&mut Game)>),
OnScore(Arc<dyn Fn(&mut Game)>),
OnHandRank(Arc<dyn Fn(&mut Game)>),
}

impl Effect for Joker {
fn apply(&self, game: &mut Game) {
game.mult += 4;
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"),
}
}
}
4 changes: 3 additions & 1 deletion src/core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
76 changes: 66 additions & 10 deletions src/core/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ 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;
use crate::core::shop::Shop;
use crate::core::stage::{Blind, End, Stage};
use std::collections::HashSet;
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;
Expand All @@ -19,13 +24,13 @@ 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<Card>,
pub discarded: Vec<Card>,
Expand All @@ -35,6 +40,10 @@ pub struct Game {
pub action_history: Vec<Action>,
pub round: usize,

// jokers and their effects
pub jokers: Vec<Jokers>,
pub effect_registry: EffectRegistry,

// playing
pub plays: usize,
pub discards: usize,
Expand All @@ -50,6 +59,7 @@ pub struct Game {
impl Game {
pub fn new() -> Self {
Self {
shop: Shop::new(),
deck: Deck::default(),
available: Vec::new(),
discarded: Vec::new(),
Expand All @@ -58,6 +68,8 @@ impl Game {
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,
Expand Down Expand Up @@ -159,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<usize, GameError> {
Expand Down Expand Up @@ -196,6 +227,16 @@ impl Game {
return Ok(());
}

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());
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 {
Expand Down Expand Up @@ -404,6 +445,15 @@ impl Game {
}
}

// get buy joker moves
pub fn gen_moves_buy_joker(&self) -> Option<impl Iterator<Item = Action>> {
// 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<Item = Action> {
let plays = self.gen_moves_play();
Expand All @@ -412,6 +462,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()
Expand All @@ -420,7 +471,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> {
Expand All @@ -442,6 +494,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),
Expand Down Expand Up @@ -481,7 +537,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]
Expand Down Expand Up @@ -522,7 +578,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);
Expand Down
Loading

0 comments on commit ee53f23

Please sign in to comment.