blackjack/src/hand.rs

179 lines
4.5 KiB
Rust

use crate::card::Card;
use itertools::Itertools;
use std::fmt;
use std::sync::{Arc, RwLock};
pub struct Hand {
cards: Vec<Card>,
discard: Option<Arc<RwLock<Vec<Card>>>>,
}
impl Hand {
pub fn new() -> Self {
Self {
cards: Vec::new(),
discard: None,
}
}
pub fn with_discard(mut self, discard: Arc<RwLock<Vec<Card>>>) -> Self {
self.discard = Some(discard);
self
}
/// Returns the value of the hand. If there are any aces, this is the highest value without
/// busting.
pub fn value(&self) -> u8 {
// TODO: Return the highest value without busting? And maybe let the player function
// determine if they have an ace and want to hit instead.
// Basic Strategy actually says what to do in the case of an ace
let mut num_aces = 0;
let mut sum = 0;
// count up the value of everything that's not an ace
for card in &self.cards {
if card.value == "A" {
num_aces += 1;
} else {
sum += u8::from(card);
}
}
if num_aces == 0 {
return sum;
}
// Figure out the possible ace sums based on how many aces we have
let mut values: Vec<u8> = [1, 11]
.into_iter()
.combinations_with_replacement(num_aces)
.map(|a| a.iter().sum())
.collect();
// Find the highest value without busting, adding in the rest of the cards. If they all
// bust, then take the lowest value.
values.sort();
values.reverse();
let mut total = 0;
for v in values.iter() {
total = v + sum;
if total <= 21 {
break;
}
}
total
}
/// Returns true if the hand is a blackjack, meaning we have an ace and a 10-valued card
pub fn is_blackjack(&self) -> bool {
self.cards.len() == 2 && self.value() == 21
}
/// Returns true if hand has an ace
pub fn has_ace(&self) -> bool {
for card in &self.cards {
if card.value == "A" {
return true;
}
}
false
}
/// Returns a copy of the cards in the hand
pub fn cards(&self) -> Vec<Card> {
self.cards.clone()
}
/// Add a card to the hand
pub fn push(&mut self, card: Card) {
self.cards.push(card)
}
}
impl From<Vec<Card>> for Hand {
fn from(cards: Vec<Card>) -> Self {
Self {
cards,
discard: None,
}
}
}
impl fmt::Display for Hand {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, c) in self.cards.iter().enumerate() {
if i != 0 {
write!(f, " ")?;
}
write!(f, "{c}")?;
}
Ok(())
}
}
impl Drop for Hand {
fn drop(&mut self) {
match &mut self.discard {
Some(x) => {
let mut discard = x.write().unwrap();
discard.append(&mut self.cards)
}
None => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::card::{Card, Suit};
#[test]
fn no_aces() {
let mut h = Hand::new();
h.cards.push(Card::new(Suit::Club, "K"));
h.cards.push(Card::new(Suit::Club, "7"));
assert_eq!(h.value(), 17);
}
#[test]
fn one_ace() {
let mut h = Hand::new();
h.cards.push(Card::new(Suit::Club, "K"));
h.cards.push(Card::new(Suit::Club, "A"));
assert_eq!(h.value(), 21);
}
#[test]
fn three_aces() {
let mut h = Hand::new();
h.cards.push(Card::new(Suit::Club, "3"));
h.cards.push(Card::new(Suit::Club, "A"));
h.cards.push(Card::new(Suit::Club, "A"));
h.cards.push(Card::new(Suit::Club, "A"));
assert_eq!(h.value(), 16);
}
#[test]
/// If we give the hand a discard pile, then it should throw its cards in there when it gets
/// dropped
fn discard_pile() {
let discard = Arc::new(RwLock::new(Vec::new()));
{
let hand =
Hand::from([Card::new(Suit::Heart, "1"), Card::new(Suit::Heart, "2")].to_vec())
.with_discard(discard.clone());
assert_eq!(hand.value(), 3);
// Hand still in scope, so discard pile is empty
assert_eq!(discard.read().unwrap().len(), 0);
}
let d = discard.read().unwrap();
// Hand went out of scope and got discarded
assert_eq!(d.len(), 2);
}
}