initial commit - basic TUI blackjack game
This commit is contained in:
commit
0a72da768e
9 changed files with 738 additions and 0 deletions
208
src/game.rs
Normal file
208
src/game.rs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
use crate::card::{deck_without_jokers, Card};
|
||||
use crate::hand::Hand;
|
||||
use rand::prelude::*;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub enum PlayerChoice {
|
||||
Hit,
|
||||
Stand,
|
||||
// TODO
|
||||
// DoubleDown,
|
||||
// TODO
|
||||
// Split,
|
||||
// TODO
|
||||
// Surrender,
|
||||
}
|
||||
|
||||
pub enum PlayResult {
|
||||
Win, // Player didn't bust, and either has higher value or dealer busted
|
||||
Blackjack, // Player won with a face card and ace
|
||||
Lose, // Player busts or dealer had higher without busting
|
||||
Push, // Dealer and player have same value
|
||||
DealerBlackjack, // Player lost because dealer had blackjack
|
||||
Bust, // Player lost because they busted
|
||||
}
|
||||
|
||||
/// The state at the end of a round, useful for printing out
|
||||
pub struct EndState {
|
||||
pub result: PlayResult,
|
||||
pub player_winnings: u64,
|
||||
pub dealer_cards: Vec<Card>,
|
||||
pub player_cards: Vec<Card>,
|
||||
}
|
||||
|
||||
impl EndState {
|
||||
fn new(
|
||||
result: PlayResult,
|
||||
player_winnings: u64,
|
||||
dealer_cards: Vec<Card>,
|
||||
player_cards: Vec<Card>,
|
||||
) -> Self {
|
||||
Self {
|
||||
result,
|
||||
player_winnings,
|
||||
dealer_cards,
|
||||
player_cards,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
rng: rand::rngs::ThreadRng,
|
||||
shoe: Vec<Card>,
|
||||
/// Discard pile. When the shoe runs out, this gets mixed with the shoe and reshuffled.
|
||||
discard: Arc<RwLock<Vec<Card>>>,
|
||||
hit_on_soft_17: bool,
|
||||
|
||||
/// Returns when hitting blackjack
|
||||
blackjack_returns: f32,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
// Game settings
|
||||
hit_on_soft_17: false,
|
||||
blackjack_returns: 3.0 / 2.0,
|
||||
|
||||
// Game state
|
||||
rng: rand::rng(),
|
||||
shoe: Vec::new(),
|
||||
discard: Arc::new(RwLock::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add some number of decks to the shoe, and shuffle it
|
||||
pub fn with_decks(mut self, count: u8) -> Self {
|
||||
for _ in 0..count {
|
||||
self.shoe.append(&mut deck_without_jokers())
|
||||
}
|
||||
self.shoe.shuffle(&mut self.rng);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the dealer to hit on a soft 17 (one involving an Ace)
|
||||
pub fn with_hit_on_soft_17(mut self) -> Self {
|
||||
self.hit_on_soft_17 = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the return when getting a blackjack
|
||||
pub fn with_blackjack_returns(mut self, returns: f32) -> Self {
|
||||
self.blackjack_returns = returns;
|
||||
self
|
||||
}
|
||||
|
||||
/// Play one round of Blackjack
|
||||
///
|
||||
/// Takes a bet and a function, which is what the player will do during their turn. This
|
||||
/// function must take the player's hand (Vec<Card>) and the card the dealer is showing (Card),
|
||||
/// and returns a choice (hit, stand, etc.).
|
||||
///
|
||||
/// Returns the winnings of the round
|
||||
pub fn play<F>(self: &mut Self, bet: u32, player_decision: F) -> EndState
|
||||
where
|
||||
F: Fn(&Hand, &Card) -> PlayerChoice,
|
||||
{
|
||||
// Shuffle the deck if we've played over 75% of cards
|
||||
let discard_size = self.discard.read().unwrap().len();
|
||||
if self.shoe.len() < (self.shoe.len() + discard_size) * 1 / 4 {
|
||||
self.shuffle();
|
||||
}
|
||||
|
||||
// Deal cards
|
||||
let mut dealer_hand =
|
||||
Hand::from([self.deal(), self.deal()].to_vec()).with_discard(self.discard.clone());
|
||||
let mut player_hand =
|
||||
Hand::from([self.deal(), self.deal()].to_vec()).with_discard(self.discard.clone());
|
||||
|
||||
// If dealer has blackjack, immediately lose. If player has blackjack, they win
|
||||
if dealer_hand.is_blackjack() {
|
||||
return EndState::new(
|
||||
PlayResult::DealerBlackjack,
|
||||
0,
|
||||
dealer_hand.cards(),
|
||||
player_hand.cards(),
|
||||
);
|
||||
} else if player_hand.is_blackjack() {
|
||||
let returns = bet as f32 * self.blackjack_returns;
|
||||
return EndState::new(
|
||||
PlayResult::Blackjack,
|
||||
returns as u64,
|
||||
dealer_hand.cards(),
|
||||
player_hand.cards(),
|
||||
);
|
||||
}
|
||||
|
||||
// Player turn
|
||||
loop {
|
||||
let decision = player_decision(&player_hand, &dealer_hand.cards()[0]);
|
||||
|
||||
match decision {
|
||||
PlayerChoice::Stand => break,
|
||||
PlayerChoice::Hit => {
|
||||
player_hand.push(self.deal());
|
||||
}
|
||||
}
|
||||
|
||||
// if player busts, immediately lose bet
|
||||
if player_hand.value() > 21 {
|
||||
return EndState::new(
|
||||
PlayResult::Bust,
|
||||
0,
|
||||
dealer_hand.cards(),
|
||||
player_hand.cards(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Dealer turn
|
||||
while dealer_hand.value() < 17 {
|
||||
dealer_hand.push(self.deal());
|
||||
}
|
||||
|
||||
if dealer_hand.value() > 21 {
|
||||
EndState::new(
|
||||
PlayResult::Win,
|
||||
(bet * 2).into(),
|
||||
dealer_hand.cards(),
|
||||
player_hand.cards(),
|
||||
)
|
||||
} else if dealer_hand.value() < player_hand.value() {
|
||||
EndState::new(
|
||||
PlayResult::Win,
|
||||
(bet * 2).into(),
|
||||
dealer_hand.cards(),
|
||||
player_hand.cards(),
|
||||
)
|
||||
} else if dealer_hand.value() == player_hand.value() {
|
||||
EndState::new(
|
||||
PlayResult::Push,
|
||||
bet.into(),
|
||||
dealer_hand.cards(),
|
||||
player_hand.cards(),
|
||||
)
|
||||
} else {
|
||||
EndState::new(
|
||||
PlayResult::Lose,
|
||||
0,
|
||||
dealer_hand.cards(),
|
||||
player_hand.cards(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deal a single card from the shoe
|
||||
fn deal(self: &mut Self) -> Card {
|
||||
self.shoe.pop().unwrap()
|
||||
}
|
||||
|
||||
/// Reset the shoe - combine shoe and discard and shuffle
|
||||
fn shuffle(self: &mut Self) {
|
||||
let mut discard = self.discard.write().unwrap();
|
||||
self.shoe.append(&mut discard);
|
||||
assert_eq!(discard.len(), 0);
|
||||
self.shoe.shuffle(&mut self.rng);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue