initial commit

This commit is contained in:
Niklas Kapelle 2024-05-13 16:12:45 +02:00
commit 1982d27c4f
Signed by: niklas
GPG Key ID: 4EB651B36D841D16
11 changed files with 726 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

75
Cargo.lock generated Normal file
View File

@ -0,0 +1,75 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rustjack"
version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "rustjack"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"

104
src/blackjack.rs Normal file
View File

@ -0,0 +1,104 @@
use crate::{decks::new_blackjack_shoe, hand::Hand};
pub struct BlackjackGame {
shoe: Hand,
player_hand: Hand,
dealer_hand: Hand,
}
pub enum DealResult {
DealerBlackJack, // Dealer has a blackjack. Player does not.
Push, // Dealer has a Blackjack and so does the Player
PlayerBlackJack, // Player has a blackjack. Dealer does not.
Continue, // Game continues
}
pub enum HitResult {
Bust,
Continue,
}
pub enum DealerPlayResult {
Bust,
Push, // Player and dealer have the same value
StandPlayerLose, // Dealer stands and wins
StandPlayerWin, // Dealer stands and loses
}
impl BlackjackGame {
pub fn new() -> Self {
BlackjackGame {
shoe: new_blackjack_shoe(6),
player_hand: Hand::new(),
dealer_hand: Hand::new(),
}
}
pub fn get_dealer_hand(&self) -> &Hand {
&self.dealer_hand
}
pub fn get_player_hand(&self) -> &Hand {
&self.player_hand
}
pub fn deal(&mut self) -> DealResult {
self.player_hand.add_card(self.shoe.pop_card().unwrap());
self.dealer_hand.add_card(self.shoe.pop_card().unwrap());
self.player_hand.add_card(self.shoe.pop_card().unwrap());
self.dealer_hand.add_card(self.shoe.pop_card().unwrap());
if self.dealer_hand.is_backjack() {
if self.player_hand.is_backjack() {
return DealResult::Push;
}
return DealResult::DealerBlackJack;
}
if self.player_hand.is_backjack() {
return DealResult::PlayerBlackJack;
}
DealResult::Continue
}
pub fn hit(&mut self) -> HitResult {
self.player_hand.add_card(self.shoe.pop_card().unwrap());
if self.player_hand.get_blackjack_value() > 21 {
return HitResult::Bust;
}
HitResult::Continue
}
pub fn stand(&mut self) {
// ???
}
pub fn dealer_play(&mut self) -> DealerPlayResult {
// Stand on 17 or above
// Hit on 16 and below
loop {
let dealer_value = self.dealer_hand.get_blackjack_value();
if dealer_value >= 17 {
let player_value = self.player_hand.get_blackjack_value();
return match dealer_value.cmp(&player_value) {
std::cmp::Ordering::Equal => DealerPlayResult::Push,
std::cmp::Ordering::Greater => DealerPlayResult::StandPlayerLose,
std::cmp::Ordering::Less => DealerPlayResult::StandPlayerWin,
};
}
self.dealer_hand.add_card(self.shoe.pop_card().unwrap());
if self.dealer_hand.get_blackjack_value() > 21 {
return DealerPlayResult::Bust;
}
}
}
}

18
src/card.rs Normal file
View File

@ -0,0 +1,18 @@
use std::fmt::Display;
use crate::{card_index::CardIndex, card_suit::CardSuit};
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(())
}
}

102
src/card_index.rs Normal file
View File

@ -0,0 +1,102 @@
use std::fmt::Display;
#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]
#[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);
}
}

47
src/card_suit.rs Normal file
View File

@ -0,0 +1,47 @@
use std::fmt::Display;
#[derive(Clone, Copy)]
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());
}
}

100
src/console_blackjack.rs Normal file
View File

@ -0,0 +1,100 @@
use std::io::stdin;
use crate::blackjack::BlackjackGame;
pub fn play() -> Result<(), Box<dyn std::error::Error>> {
let mut game = BlackjackGame::new();
let deal = game.deal();
println!(
"Dealer hand: {} ?",
game.get_dealer_hand().get_card(0).unwrap()
);
println!(
"Player hand: {} ({})",
game.get_player_hand(),
game.get_player_hand().get_blackjack_value()
);
match deal {
crate::blackjack::DealResult::DealerBlackJack => {
println!("Dealer has a blackjack!");
return Ok(());
}
crate::blackjack::DealResult::PlayerBlackJack => {
println!("Player has a blackjack!");
return Ok(());
}
crate::blackjack::DealResult::Push => {
println!("Both Player and dealer have a blackjack!");
return Ok(());
}
crate::blackjack::DealResult::Continue => {}
}
loop {
println!("(H)it (S)tand");
let mut buffer = String::new();
stdin().read_line(&mut buffer)?;
let hit = buffer.trim() == "h";
if hit {
let hit_result = game.hit();
println!(
"Dealer hand: {} ?",
game.get_dealer_hand().get_card(0).unwrap()
);
println!(
"Player hand: {} ({})",
game.get_player_hand(),
game.get_player_hand().get_blackjack_value()
);
match hit_result {
crate::blackjack::HitResult::Bust => {
println!("Player busts");
return Ok(());
}
crate::blackjack::HitResult::Continue => {}
}
} else {
game.stand();
let dealer_play = game.dealer_play();
println!(
"Dealer hand: {} ({})",
game.get_dealer_hand(),
game.get_dealer_hand().get_blackjack_value()
);
println!(
"Player hand: {} ({})",
game.get_player_hand(),
game.get_player_hand().get_blackjack_value()
);
match dealer_play {
crate::blackjack::DealerPlayResult::Bust => {
println!("Dealer bust. Player wins.");
}
crate::blackjack::DealerPlayResult::Push => {
println!("Player and dealer have same value. Push.");
}
crate::blackjack::DealerPlayResult::StandPlayerLose => {
println!("Dealer wins");
}
crate::blackjack::DealerPlayResult::StandPlayerWin => {
println!("Player wins");
}
}
break;
}
}
Ok(())
}

54
src/decks.rs Normal file
View File

@ -0,0 +1,54 @@
use crate::{card::Card, hand::Hand, CardIndex as CI, 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);
}
}

199
src/hand.rs Normal file
View File

@ -0,0 +1,199 @@
use rand::{seq::SliceRandom, thread_rng};
use std::fmt::Display;
use crate::{card::Card, card_index::CardIndex};
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
}
/**
* 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(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_blackjack_ace_first() {
let mut hand = Hand::new();
hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts,
index: CardIndex::A,
});
hand.add_card(Card {
suit: crate::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: crate::card_suit::CardSuit::Diamonds,
index: CardIndex::J,
});
hand.add_card(Card {
suit: crate::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: crate::card_suit::CardSuit::Diamonds,
index: CardIndex::J,
});
hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts,
index: CardIndex::A,
});
hand.add_card(Card {
suit: crate::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: crate::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: crate::card_suit::CardSuit::Hearts,
index: CardIndex::K,
});
hand.add_card(Card {
suit: crate::card_suit::CardSuit::Clubs,
index: CardIndex::N7,
});
hand.add_card(Card {
suit: crate::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: crate::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: crate::card_suit::CardSuit::Diamonds,
index: CardIndex::N5,
});
hand.add_card(Card {
suit: crate::card_suit::CardSuit::Spades,
index: CardIndex::Q,
});
let mut other = Hand::new();
hand.add_card(Card {
suit: crate::card_suit::CardSuit::Spades,
index: CardIndex::K,
});
hand.merge(&mut other);
assert_eq!(other.count(), 0);
assert_eq!(hand.count(), 3);
}
}

19
src/main.rs Normal file
View File

@ -0,0 +1,19 @@
#![allow(dead_code)]
use std::error::Error;
use console_blackjack::play;
use crate::{card_index::CardIndex, card_suit::CardSuit};
mod blackjack;
mod card;
mod card_index;
mod card_suit;
mod console_blackjack;
mod decks;
mod hand;
fn main() -> Result<(), Box<dyn Error>> {
play()
}