progress
This commit is contained in:
21
backend/src/cards/card.rs
Normal file
21
backend/src/cards/card.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{card_index::CardIndex, card_suit::CardSuit};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct Card {
|
||||
pub suit: CardSuit,
|
||||
pub index: CardIndex,
|
||||
}
|
||||
|
||||
impl Display for Card {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.suit.fmt(f)?;
|
||||
f.write_str(" ")?;
|
||||
self.index.fmt(f)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
104
backend/src/cards/card_index.rs
Normal file
104
backend/src/cards/card_index.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum CardIndex {
|
||||
A = 14,
|
||||
K = 13,
|
||||
Q = 12,
|
||||
J = 11,
|
||||
N10 = 10,
|
||||
N9 = 9,
|
||||
N8 = 8,
|
||||
N7 = 7,
|
||||
N6 = 6,
|
||||
N5 = 5,
|
||||
N4 = 4,
|
||||
N3 = 3,
|
||||
N2 = 2,
|
||||
}
|
||||
|
||||
impl CardIndex {
|
||||
pub fn get_blackjack_value(&self, ace_as_1: bool) -> u32 {
|
||||
match self {
|
||||
Self::J | Self::K | Self::Q => 10,
|
||||
Self::A => {
|
||||
if ace_as_1 {
|
||||
1
|
||||
} else {
|
||||
11
|
||||
}
|
||||
}
|
||||
_ => (*self as u8) as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<char> for CardIndex {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
'A' => Ok(CardIndex::A),
|
||||
'K' => Ok(CardIndex::K),
|
||||
'Q' => Ok(CardIndex::Q),
|
||||
'J' => Ok(CardIndex::J),
|
||||
'T' => Ok(CardIndex::N10),
|
||||
'9' => Ok(CardIndex::N9),
|
||||
'8' => Ok(CardIndex::N8),
|
||||
'7' => Ok(CardIndex::N7),
|
||||
'6' => Ok(CardIndex::N6),
|
||||
'5' => Ok(CardIndex::N5),
|
||||
'4' => Ok(CardIndex::N4),
|
||||
'3' => Ok(CardIndex::N3),
|
||||
'2' => Ok(CardIndex::N2),
|
||||
_ => Err("Not a card"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CardIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let index = match self {
|
||||
Self::A => "A".to_owned(),
|
||||
Self::K => "K".to_owned(),
|
||||
Self::Q => "Q".to_owned(),
|
||||
Self::J => "J".to_owned(),
|
||||
_ => (*self as u8).to_string(),
|
||||
};
|
||||
|
||||
f.write_str(&index)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn blackjack_value_ace() {
|
||||
assert_eq!(CardIndex::A.get_blackjack_value(true), 1);
|
||||
assert_eq!(CardIndex::A.get_blackjack_value(false), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blackjack_value_face() {
|
||||
assert_eq!(CardIndex::J.get_blackjack_value(true), 10);
|
||||
assert_eq!(CardIndex::Q.get_blackjack_value(true), 10);
|
||||
assert_eq!(CardIndex::K.get_blackjack_value(true), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blackjack_value_num() {
|
||||
assert_eq!(CardIndex::N10.get_blackjack_value(true), 10);
|
||||
assert_eq!(CardIndex::N9.get_blackjack_value(true), 9);
|
||||
assert_eq!(CardIndex::N8.get_blackjack_value(true), 8);
|
||||
assert_eq!(CardIndex::N5.get_blackjack_value(true), 5);
|
||||
assert_eq!(CardIndex::N3.get_blackjack_value(true), 3);
|
||||
assert_eq!(CardIndex::N2.get_blackjack_value(true), 2);
|
||||
}
|
||||
}
|
||||
49
backend/src/cards/card_suit.rs
Normal file
49
backend/src/cards/card_suit.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum CardSuit {
|
||||
Spades,
|
||||
Clubs,
|
||||
Hearts,
|
||||
Diamonds,
|
||||
}
|
||||
|
||||
impl CardSuit {
|
||||
pub fn is_red(&self) -> bool {
|
||||
matches!(self, CardSuit::Hearts | CardSuit::Diamonds)
|
||||
}
|
||||
|
||||
pub fn is_black(&self) -> bool {
|
||||
!self.is_red()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CardSuit {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let icon = match self {
|
||||
CardSuit::Clubs => "♣️",
|
||||
CardSuit::Diamonds => "♦️",
|
||||
CardSuit::Hearts => "♥️",
|
||||
CardSuit::Spades => "♠️",
|
||||
};
|
||||
|
||||
f.write_str(icon)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn color() {
|
||||
assert!(CardSuit::Hearts.is_red());
|
||||
assert!(CardSuit::Diamonds.is_red());
|
||||
assert!(CardSuit::Clubs.is_black());
|
||||
assert!(CardSuit::Spades.is_black());
|
||||
}
|
||||
}
|
||||
54
backend/src/cards/decks.rs
Normal file
54
backend/src/cards/decks.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use super::{card::Card, hand::Hand, card_index::CardIndex as CI, card_suit::CardSuit as CS};
|
||||
|
||||
pub fn new_full_deck() -> Hand {
|
||||
let mut hand = Hand::new();
|
||||
|
||||
for suit in [CS::Clubs, CS::Diamonds, CS::Hearts, CS::Spades] {
|
||||
for index in [
|
||||
CI::A,
|
||||
CI::J,
|
||||
CI::K,
|
||||
CI::Q,
|
||||
CI::N10,
|
||||
CI::N9,
|
||||
CI::N8,
|
||||
CI::N7,
|
||||
CI::N6,
|
||||
CI::N5,
|
||||
CI::N4,
|
||||
CI::N3,
|
||||
CI::N2,
|
||||
] {
|
||||
hand.add_card(Card { suit, index });
|
||||
}
|
||||
}
|
||||
|
||||
hand
|
||||
}
|
||||
|
||||
pub fn new_blackjack_shoe(decks: u32) -> Hand {
|
||||
let mut hand = Hand::new();
|
||||
|
||||
for _ in 0..decks {
|
||||
hand.merge(&mut new_full_deck());
|
||||
}
|
||||
|
||||
hand.shuffle();
|
||||
|
||||
hand
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn full_deck_count() {
|
||||
assert_eq!(new_full_deck().count(), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shoe_count() {
|
||||
assert_eq!(new_blackjack_shoe(6).count(), 6 * 52);
|
||||
}
|
||||
}
|
||||
243
backend/src/cards/hand.rs
Normal file
243
backend/src/cards/hand.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::{card::Card, card_index::CardIndex};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Hand {
|
||||
cards: Vec<Card>,
|
||||
}
|
||||
|
||||
impl Hand {
|
||||
pub fn new() -> Self {
|
||||
Hand { cards: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn add_card(&mut self, card: Card) {
|
||||
self.cards.push(card);
|
||||
}
|
||||
|
||||
pub fn get_card(&self, i: usize) -> Option<&Card> {
|
||||
return self.cards.get(i);
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.cards.len()
|
||||
}
|
||||
|
||||
pub fn remove_card(&mut self, i: usize) -> Card {
|
||||
self.cards.remove(i)
|
||||
}
|
||||
|
||||
pub fn pop_card(&mut self) -> Option<Card> {
|
||||
self.cards.pop()
|
||||
}
|
||||
|
||||
pub fn shuffle(&mut self) {
|
||||
self.cards.shuffle(&mut thread_rng());
|
||||
}
|
||||
|
||||
pub fn is_backjack(&self) -> bool {
|
||||
self.cards.len() == 2 && self.get_blackjack_value() == 21
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this hand could be split in a blackjack game.
|
||||
*/
|
||||
pub fn is_valid_for_bj_split(&self) -> bool {
|
||||
if self.cards.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(card_0) = self.get_card(0) {
|
||||
if let Some(card_1) = self.get_card(1) {
|
||||
if card_0.index.get_blackjack_value(true) != card_1.index.get_blackjack_value(true)
|
||||
{
|
||||
// Cards are not the same value
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/**
|
||||
* Put another hand into this.
|
||||
* Leaves the other hand empty.
|
||||
*/
|
||||
pub fn merge(&mut self, other: &mut Hand) {
|
||||
self.cards.append(&mut other.cards);
|
||||
}
|
||||
|
||||
pub fn get_blackjack_value(&self) -> u32 {
|
||||
let mut sum: u32 = self
|
||||
.cards
|
||||
.iter()
|
||||
.map(|card| card.index.get_blackjack_value(true))
|
||||
.sum();
|
||||
|
||||
// Add 10 for the all aces (adds up to 11) until we go over 21
|
||||
let aces = self
|
||||
.cards
|
||||
.iter()
|
||||
.filter(|card| card.index == CardIndex::A)
|
||||
.count();
|
||||
|
||||
for _ in 0..aces {
|
||||
if sum + 10 > 21 {
|
||||
return sum;
|
||||
}
|
||||
|
||||
sum += 10;
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Hand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for card in self.cards.iter() {
|
||||
card.fmt(f)?;
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Hand {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.cards.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Hand {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// Deserialize into a Vec directly and wrap it in MyVecWrapper
|
||||
let cards = Vec::<Card>::deserialize(deserializer)?;
|
||||
Ok(Hand { cards })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::card_suit::CardSuit;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_blackjack_ace_first() {
|
||||
let mut hand = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Hearts,
|
||||
index: CardIndex::A,
|
||||
});
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Diamonds,
|
||||
index: CardIndex::N10,
|
||||
});
|
||||
assert!(hand.is_backjack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_blackjack_ace_last() {
|
||||
let mut hand = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Diamonds,
|
||||
index: CardIndex::J,
|
||||
});
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Hearts,
|
||||
index: CardIndex::A,
|
||||
});
|
||||
assert!(hand.is_backjack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_blackjack_too_many() {
|
||||
let mut hand = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Diamonds,
|
||||
index: CardIndex::J,
|
||||
});
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Hearts,
|
||||
index: CardIndex::A,
|
||||
});
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Spades,
|
||||
index: CardIndex::A,
|
||||
});
|
||||
assert!(!hand.is_backjack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_blackjack_too_few() {
|
||||
let mut hand = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Hearts,
|
||||
index: CardIndex::A,
|
||||
});
|
||||
assert!(!hand.is_backjack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_blackjack_value_21() {
|
||||
let mut hand = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Hearts,
|
||||
index: CardIndex::K,
|
||||
});
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Clubs,
|
||||
index: CardIndex::N7,
|
||||
});
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Hearts,
|
||||
index: CardIndex::N4,
|
||||
});
|
||||
assert!(!hand.is_backjack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blackjack_value() {
|
||||
let mut hand = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Hearts,
|
||||
index: CardIndex::K,
|
||||
});
|
||||
assert_eq!(hand.get_blackjack_value(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_hands() {
|
||||
let mut hand = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Diamonds,
|
||||
index: CardIndex::N5,
|
||||
});
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Spades,
|
||||
index: CardIndex::Q,
|
||||
});
|
||||
|
||||
let mut other = Hand::new();
|
||||
hand.add_card(Card {
|
||||
suit: CardSuit::Spades,
|
||||
index: CardIndex::K,
|
||||
});
|
||||
|
||||
hand.merge(&mut other);
|
||||
|
||||
assert_eq!(other.count(), 0);
|
||||
assert_eq!(hand.count(), 3);
|
||||
}
|
||||
}
|
||||
5
backend/src/cards/mod.rs
Normal file
5
backend/src/cards/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod card;
|
||||
pub mod card_index;
|
||||
pub mod card_suit;
|
||||
pub mod decks;
|
||||
pub mod hand;
|
||||
Reference in New Issue
Block a user