Refactor the game flow and support splits
Behaves more like a client/server, rather than just taking a decision function. The CLI logic is more complex now, but the game is more flexible and and support splits (basically branching the hand off)
This commit is contained in:
parent
cb70077f5a
commit
69a4239f90
8 changed files with 820 additions and 334 deletions
73
src/hand.rs
73
src/hand.rs
|
|
@ -1,32 +1,20 @@
|
|||
use crate::card::Card;
|
||||
use itertools::Itertools;
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
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
|
||||
Self { cards: Vec::new() }
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
|
|
@ -80,7 +68,12 @@ impl Hand {
|
|||
false
|
||||
}
|
||||
|
||||
/// Returns a copy of the cards in the hand
|
||||
/// Returns true if the hand has a pair (can be split)
|
||||
pub fn is_pair(&self) -> bool {
|
||||
self.cards.len() == 2 && u8::from(&self.cards[0]) == u8::from(&self.cards[1])
|
||||
}
|
||||
|
||||
/// Returns a read-only copy of the cards in the hand
|
||||
pub fn cards(&self) -> Vec<Card> {
|
||||
self.cards.clone()
|
||||
}
|
||||
|
|
@ -89,13 +82,25 @@ impl Hand {
|
|||
pub fn push(&mut self, card: Card) {
|
||||
self.cards.push(card)
|
||||
}
|
||||
|
||||
/// Split the hand into two
|
||||
pub fn split(&mut self) -> Hand {
|
||||
let other_cards: Vec<Card> = Vec::from(self.cards.pop().as_slice());
|
||||
|
||||
Hand { cards: other_cards }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Card>> for Hand {
|
||||
fn from(cards: Vec<Card>) -> Self {
|
||||
Self { cards }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<(&str, &str)>> for Hand {
|
||||
fn from(str_cards: Vec<(&str, &str)>) -> Self {
|
||||
Self {
|
||||
cards,
|
||||
discard: None,
|
||||
cards: str_cards.into_iter().map(|c| (c).into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,18 +117,6 @@ impl fmt::Display for Hand {
|
|||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
|
@ -154,26 +147,4 @@ mod tests {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue