Skip to content

Commit

Permalink
Add a blackjack client for validating expected results from url
Browse files Browse the repository at this point in the history
  • Loading branch information
Christopher Kolstad committed Mar 24, 2022
1 parent 73d4dcf commit 027eb31
Show file tree
Hide file tree
Showing 8 changed files with 561 additions and 19 deletions.
323 changes: 321 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"logic",
"server",
"client",
]
exclude = [".cargo"]

Expand Down
21 changes: 21 additions & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "client"
version = "0.1.0"
edition = "2018"

[lib]
name = "client"

[dependencies]
lazy_static = "1.4.0"
rand = "0.8.5"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
strum = "0.24.0"
strum_macros = "0.24.0"
reqwest = { version = "0.11.10", features = ["json"] }
clap = { version = "3.1.6", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

[dependencies.logic]
path = "../logic"
161 changes: 161 additions & 0 deletions client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use logic::card::{Card, Rank};
use reqwest::get;
use serde::{Deserialize, Serialize};
type Deck = Vec<Card>;

async fn fetch_deck(url: String) -> reqwest::Result<Deck> {
get(url).await?.json::<Deck>().await
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PlayerResult {
name: String,
hand: Deck,
score: u8,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GameResult {
deck: Deck,
players: Vec<PlayerResult>,
winner: String,
}

pub trait ToInt {
fn to_int(&self) -> u8;
}

impl ToInt for Card {
fn to_int(&self) -> u8 {
self.value.to_int()
}
}

impl ToInt for Rank {
fn to_int(&self) -> u8 {
match &self {
Rank::Two => 2,
Rank::Three => 3,
Rank::Four => 4,
Rank::Five => 5,
Rank::Six => 6,
Rank::Seven => 7,
Rank::Eight => 8,
Rank::Nine => 9,
Rank::Ace => 11,
_ => 10,
}
}
}

trait Blackjack<S: Scorable = Self> {
fn has_blackjack(&self) -> bool;
}
trait Scorable {
fn score(&self) -> u8;
}
trait Bust<S: Scorable = Self> {
fn is_bust(&self) -> bool;
}

trait Draw {
fn hit_me(&self, other_score: u8) -> bool;
}

trait ToPlayerResult {
fn to_result(&self) -> PlayerResult;
}

pub struct Player {
name: String,
hand: Vec<Card>,
strategy: Box<dyn Fn(u8, u8) -> bool>,
}
impl ToPlayerResult for Player {
fn to_result(&self) -> PlayerResult {
PlayerResult {
name: self.name.clone(),
hand: self.hand.clone(),
score: self.score(),
}
}
}
impl Scorable for Player {
fn score(&self) -> u8 {
self.hand.iter().map(|x| x.to_int()).sum()
}
}
impl Draw for Player {
fn hit_me(&self, other_score: u8) -> bool {
(self.strategy)(self.score(), other_score)
}
}
impl Blackjack for Player {
fn has_blackjack(&self) -> bool {
self.hand.len() == 2 && self.score() == 21
}
}
impl Bust for Player {
fn is_bust(&self) -> bool {
self.score() > 21
}
}

fn player_wins(original: Deck, player: Player, dealer: Player) -> GameResult {
GameResult {
winner: player.name.clone(),
players: vec![player.to_result(), dealer.to_result()],
deck: original,
}
}

fn dealer_wins(original: Deck, player: Player, dealer: Player) -> GameResult {
GameResult {
winner: dealer.name.clone(),
players: vec![player.to_result(), dealer.to_result()],
deck: original,
}
}

fn play_game(deck: Deck, player_name: String) -> GameResult {
println!("Playing as {:?}", player_name);
println!("{:#?}", deck);
let original = deck.clone();
let mut playing_deck = deck.clone();
let starting_hand = vec![playing_deck.remove(0), playing_deck.remove(0)];
let mut player = Player {
name: player_name,
hand: starting_hand,
strategy: Box::new(|own_score, _| own_score < 17),
};
let dealer_hand = vec![playing_deck.remove(0), playing_deck.remove(0)];
let mut dealer = Player {
name: "Dealer".into(),
hand: dealer_hand,
strategy: Box::new(|own_score, player_score| own_score <= player_score),
};
if dealer.has_blackjack() {
return dealer_wins(original, player, dealer);
} else if player.has_blackjack() {
return player_wins(original, player, dealer);
}
while (player.hit_me(dealer.score())) {
player.hand.push(playing_deck.remove(0));
}
if (player.is_bust()) {
return dealer_wins(original, player, dealer);
}
let p_score = player.score();
while (dealer.hit_me(p_score)) {
dealer.hand.push(playing_deck.remove(0));
}
if (dealer.is_bust()) {
return player_wins(original, player, dealer);
}
dealer_wins(original, player, dealer)
}

pub async fn play_blackjack(url: String, player_name: String) -> GameResult {
let deck = fetch_deck(url).await.expect("Could not parse deck");
play_game(deck, player_name)
}
22 changes: 22 additions & 0 deletions client/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use clap::Parser;
use client::play_blackjack;

#[derive(Parser, Debug)]
#[clap(version, about)]
pub struct ClientArgs {
#[clap(
short,
long,
default_value = "https://sandbox.getunleash.io/blackjack/shuffle"
)]
url: String,
#[clap(short, long, default_value = "Sam")]
player_name: String,
}

#[tokio::main]
async fn main() {
let client_args = ClientArgs::parse();
let result = play_blackjack(client_args.url, client_args.player_name).await;
println!("{:#?}", result)
}
8 changes: 6 additions & 2 deletions logic/src/card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,15 @@ impl Card {
((self.suit.clone() as u8) * 13 + self.value.clone() as u8) as usize
}
}
pub trait FromAnswer: Sized {
type Err;
fn from_answer(s: &str) -> Result<Self, Self::Err>;
}

impl FromStr for Card {
impl FromAnswer for Card {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
fn from_answer(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars();
if let Some(suit) = {
match chars.next() {
Expand Down
26 changes: 13 additions & 13 deletions logic/src/deck_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,16 @@ pub struct BlackjackQuery {
#[cfg(test)]
mod blackjack {
use super::*;
use std::str::FromStr;
use crate::card::FromAnswer;

#[test]
fn four_aces_returns_four_aces_as_first_four_cards() {
let f = four_aces();
let four_aces = [
Card::from_str("SA").unwrap(),
Card::from_str("HA").unwrap(),
Card::from_str("CA").unwrap(),
Card::from_str("DA").unwrap(),
Card::from_answer("SA").unwrap(),
Card::from_answer("HA").unwrap(),
Card::from_answer("CA").unwrap(),
Card::from_answer("DA").unwrap(),
];
assert_eq!(four_aces, f.chunks(4).next().unwrap())
}
Expand All @@ -196,8 +196,8 @@ mod blackjack {
let first_player_card = b.get(0).unwrap();
let second_player_card = b.get(1).unwrap();

assert_eq!(first_player_card, &Card::from_str("SA").unwrap());
assert_eq!(second_player_card, &Card::from_str("SJ").unwrap())
assert_eq!(first_player_card, &Card::from_answer("SA").unwrap());
assert_eq!(second_player_card, &Card::from_answer("SJ").unwrap())
}

#[test]
Expand All @@ -206,17 +206,17 @@ mod blackjack {
let first_dealer_card = b.get(2).unwrap();
let second_dealer_card = b.get(3).unwrap();

assert_eq!(first_dealer_card, &Card::from_str("SA").unwrap());
assert_eq!(second_dealer_card, &Card::from_str("SJ").unwrap())
assert_eq!(first_dealer_card, &Card::from_answer("SA").unwrap());
assert_eq!(second_dealer_card, &Card::from_answer("SJ").unwrap())
}

#[test]
fn complete_deck_returns_complete_and_correct_deck() {
let four_aces = [
Card::from_str("SA").unwrap(),
Card::from_str("HA").unwrap(),
Card::from_str("CA").unwrap(),
Card::from_str("DA").unwrap(),
Card::from_answer("SA").unwrap(),
Card::from_answer("HA").unwrap(),
Card::from_answer("CA").unwrap(),
Card::from_answer("DA").unwrap(),
];

let new_deck = complete_deck(four_aces.to_vec());
Expand Down
18 changes: 16 additions & 2 deletions logic/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::{Display, Formatter};

#[derive(Serialize)]
#[derive(Serialize, Deserialize, Debug)]
pub struct ErrorMessage {
pub code: u16,
pub message: String,
}

impl Display for ErrorMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", &self)
}
}

impl Error for ErrorMessage {
fn description(&self) -> &str {
&self.message
}
}

0 comments on commit 027eb31

Please sign in to comment.