use crate::Card;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Shutout {
Double,
Flat(u16),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum OklahomaAce {
One,
GinOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct Rules {
pub knock_limit: u8,
#[cfg_attr(feature = "serde", serde(default))]
pub oklahoma: Option<OklahomaAce>,
pub gin_bonus: u16,
pub big_gin_bonus: Option<u16>,
pub undercut_bonus: u16,
pub undercut_on_tie: bool,
pub box_bonus: u16,
pub immediate_boxes: bool,
pub game_bonus: u16,
pub game_target: u16,
pub shutout: Shutout,
}
impl Rules {
#[must_use]
#[inline]
pub const fn new() -> Self {
Self {
knock_limit: 10,
oklahoma: None,
gin_bonus: 25,
big_gin_bonus: Some(31),
undercut_bonus: 25,
undercut_on_tie: true,
box_bonus: 25,
immediate_boxes: false,
game_bonus: 100,
game_target: 100,
shutout: Shutout::Double,
}
}
#[must_use]
#[inline]
pub const fn classic() -> Self {
Self {
gin_bonus: 20,
big_gin_bonus: None,
undercut_bonus: 10,
box_bonus: 20,
shutout: Shutout::Flat(100),
..Self::new()
}
}
#[must_use]
#[inline]
pub const fn palace() -> Self {
Self {
big_gin_bonus: None,
undercut_bonus: 20,
box_bonus: 10,
immediate_boxes: true,
game_bonus: 0,
shutout: Shutout::Flat(0),
..Self::new()
}
}
#[must_use]
#[inline]
pub const fn knock_limit_for(&self, initial_upcard: Card) -> u8 {
let value = match self.oklahoma {
None => return self.knock_limit,
Some(OklahomaAce::GinOnly) if initial_upcard.rank.get() == 1 => 0,
Some(_) => initial_upcard.rank.deadwood(),
};
if value < self.knock_limit {
value
} else {
self.knock_limit
}
}
}
impl Default for Rules {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presets() {
let modern = Rules::default();
assert_eq!(modern, Rules::new());
assert_eq!(modern.knock_limit, 10);
assert_eq!(modern.gin_bonus, 25);
assert_eq!(modern.big_gin_bonus, Some(31));
assert_eq!(modern.undercut_bonus, 25);
assert_eq!(modern.box_bonus, 25);
assert!(!modern.immediate_boxes);
assert_eq!(modern.game_bonus, 100);
assert_eq!(modern.game_target, 100);
assert_eq!(modern.shutout, Shutout::Double);
let classic = Rules::classic();
assert_eq!(classic.gin_bonus, 20);
assert_eq!(classic.big_gin_bonus, None);
assert_eq!(classic.undercut_bonus, 10);
assert_eq!(classic.box_bonus, 20);
assert!(!classic.immediate_boxes);
assert_eq!(classic.shutout, Shutout::Flat(100));
let palace = Rules::palace();
assert_eq!(palace.gin_bonus, 25);
assert_eq!(palace.big_gin_bonus, None);
assert_eq!(palace.undercut_bonus, 20);
assert_eq!(palace.box_bonus, 10);
assert!(palace.immediate_boxes);
assert_eq!(palace.game_bonus, 0);
assert_eq!(palace.shutout, Shutout::Flat(0));
for rules in [modern, classic, palace] {
assert_eq!(rules.knock_limit, 10);
assert_eq!(rules.oklahoma, None);
assert!(rules.undercut_on_tie);
assert_eq!(rules.game_target, 100);
}
}
#[test]
fn oklahoma_knock_limits() {
let card = |s: &str| s.parse::<Card>().unwrap();
let mut rules = Rules::default();
assert_eq!(rules.knock_limit_for(card("7♦")), 10);
rules.oklahoma = Some(OklahomaAce::One);
assert_eq!(rules.knock_limit_for(card("7♦")), 7);
assert_eq!(rules.knock_limit_for(card("Q♥")), 10);
assert_eq!(rules.knock_limit_for(card("A♣")), 1);
rules.oklahoma = Some(OklahomaAce::GinOnly);
assert_eq!(rules.knock_limit_for(card("7♦")), 7);
assert_eq!(rules.knock_limit_for(card("A♣")), 0);
rules.knock_limit = 3;
assert_eq!(rules.knock_limit_for(card("7♦")), 3);
}
}