i don't care anymore

This commit is contained in:
Niklas Kapelle 2024-05-24 16:43:05 +02:00
parent d34a72cf3b
commit e517bed681
Signed by: niklas
GPG Key ID: 4EB651B36D841D16
17 changed files with 1744 additions and 115 deletions

1469
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,3 +5,5 @@ edition = "2021"
[dependencies] [dependencies]
rand = "0.8.5" rand = "0.8.5"
rocket = { version = "0.5.0", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }

View File

@ -1,4 +1,6 @@
use crate::{decks::new_blackjack_shoe, hand::Hand}; use crate::cards::{card::Card, decks::new_blackjack_shoe, hand::Hand};
use super::{gamestate::GameState, play_moves::PlayMoves, player::Player, playing_hand::{HandState, PlayingHand}};
pub struct BlackjackGame { pub struct BlackjackGame {
shoe: Hand, shoe: Hand,
@ -7,73 +9,6 @@ pub struct BlackjackGame {
state: GameState, state: GameState,
} }
pub struct Player {
hands: Vec<PlayingHand>,
}
impl Player {
fn new() -> Self {
Player { hands: Vec::new() }
}
pub fn get_hands(&self) -> &Vec<PlayingHand> {
&self.hands
}
fn next_playing_hand(&self) -> Option<usize> {
self.hands
.iter()
.position(|e| e.state == HandState::Playing)
}
}
pub struct PlayingHand {
hand: Hand,
state: HandState,
}
impl PlayingHand {
fn new() -> Self {
PlayingHand {
hand: Hand::new(),
state: HandState::Playing,
}
}
pub fn get_hand(&self) -> &Hand {
&self.hand
}
pub fn get_state(&self) -> HandState {
self.state
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HandState {
Playing,
Standing,
DoubleDown,
Busted,
Blackjack,
Maxed, // Reached 21
}
#[derive(Clone, Copy, Debug)]
pub enum PlayMoves {
Hit,
Stand,
DoubleDown,
Split,
Deal(usize),
}
pub enum GameState {
Starting,
Over, // Game is over
PlayerTurn(usize, usize), // Its the turn of the player
}
impl BlackjackGame { impl BlackjackGame {
pub fn new() -> Self { pub fn new() -> Self {
BlackjackGame { BlackjackGame {
@ -88,10 +23,18 @@ impl BlackjackGame {
&self.dealer_hand &self.dealer_hand
} }
pub fn get_dealer_upcard(&self) -> Option<&Card> {
self.dealer_hand.get_card(0)
}
pub fn get_state(&self) -> &GameState { pub fn get_state(&self) -> &GameState {
&self.state &self.state
} }
pub fn get_players(&self) -> &Vec<Player>{
&self.players
}
pub fn get_player(&self, index: usize) -> Option<&Player> { pub fn get_player(&self, index: usize) -> Option<&Player> {
self.players.get(index) self.players.get(index)
} }
@ -102,7 +45,13 @@ impl BlackjackGame {
pub fn play(&mut self, action: PlayMoves) -> bool { pub fn play(&mut self, action: PlayMoves) -> bool {
match (&self.state, action) { match (&self.state, action) {
(GameState::PlayerTurn(player_index, hand_index), PlayMoves::Hit) => { (
GameState::PlayerTurn {
player_index,
hand_index,
},
PlayMoves::Hit,
) => {
let Some(player) = self.players.get_mut(*player_index) else { let Some(player) = self.players.get_mut(*player_index) else {
// Player does not exists // Player does not exists
return false; return false;
@ -131,7 +80,13 @@ impl BlackjackGame {
} }
}; };
} }
(GameState::PlayerTurn(player_index, hand_index), PlayMoves::Split) => { (
GameState::PlayerTurn {
player_index,
hand_index,
},
PlayMoves::Split,
) => {
let Some(player) = self.players.get_mut(*player_index) else { let Some(player) = self.players.get_mut(*player_index) else {
// Player does not exists // Player does not exists
return false; return false;
@ -169,7 +124,13 @@ impl BlackjackGame {
player.hands.push(new_hand); player.hands.push(new_hand);
} }
(GameState::PlayerTurn(player_index, hand_index), PlayMoves::DoubleDown) => { (
GameState::PlayerTurn {
player_index,
hand_index,
},
PlayMoves::DoubleDown,
) => {
let Some(player) = self.players.get_mut(*player_index) else { let Some(player) = self.players.get_mut(*player_index) else {
// Player does not exists // Player does not exists
return false; return false;
@ -203,7 +164,13 @@ impl BlackjackGame {
} }
}; };
} }
(GameState::PlayerTurn(player_index, hand_index), PlayMoves::Stand) => { (
GameState::PlayerTurn {
player_index,
hand_index,
},
PlayMoves::Stand,
) => {
let Some(player) = self.players.get_mut(*player_index) else { let Some(player) = self.players.get_mut(*player_index) else {
// Player does not exists // Player does not exists
return false; return false;
@ -221,10 +188,10 @@ impl BlackjackGame {
hand.state = HandState::Standing; hand.state = HandState::Standing;
} }
(GameState::Over, PlayMoves::Deal(player_count)) (GameState::Over, PlayMoves::Deal { players })
| (GameState::Starting, PlayMoves::Deal(player_count)) => { | (GameState::Starting, PlayMoves::Deal { players }) => {
// Create players // Create players
for _ in 0..player_count { for _ in 0..players {
self.players.push(Player::new()); self.players.push(Player::new());
} }
@ -257,16 +224,22 @@ impl BlackjackGame {
// Add 2nd card to the dealer // Add 2nd card to the dealer
self.dealer_hand.add_card(self.shoe.pop_card().unwrap()); self.dealer_hand.add_card(self.shoe.pop_card().unwrap());
self.state = GameState::PlayerTurn(0, 0); self.state = GameState::PlayerTurn {
player_index: 0,
hand_index: 0,
};
} }
(_, PlayMoves::Deal(_)) | (GameState::Over, _) | (GameState::Starting, _) => { (_, PlayMoves::Deal { .. }) | (GameState::Over, _) | (GameState::Starting, _) => {
return false; return false;
} }
} }
// Find next player or dealer turn // Find next player or dealer turn
if let Some(next_turn) = self.next_player_and_hand() { if let Some(next_turn) = self.next_player_and_hand() {
self.state = GameState::PlayerTurn(next_turn.0, next_turn.1); self.state = GameState::PlayerTurn {
player_index: next_turn.0,
hand_index: next_turn.1,
};
} else { } else {
self.dealer_turn(); self.dealer_turn();
} }
@ -294,7 +267,11 @@ impl BlackjackGame {
} }
fn next_player_and_hand(&self) -> Option<(usize, usize)> { fn next_player_and_hand(&self) -> Option<(usize, usize)> {
if let GameState::PlayerTurn(player_index, hand_index) = self.state { if let GameState::PlayerTurn {
player_index,
hand_index,
} = self.state
{
let Some(player) = self.players.get(player_index) else { let Some(player) = self.players.get(player_index) else {
// Player does not exists // Player does not exists
return None; return None;

View File

@ -0,0 +1,12 @@
use serde::Serialize;
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(tag = "type")]
pub enum GameState {
Starting,
Over,
PlayerTurn {
player_index: usize,
hand_index: usize,
},
}

5
src/blackjack/mod.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod blackjack_game;
pub mod gamestate;
pub mod play_moves;
pub mod player;
pub mod playing_hand;

View File

@ -0,0 +1,11 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum PlayMoves {
Hit,
Stand,
DoubleDown,
Split,
Deal { players: usize },
}

24
src/blackjack/player.rs Normal file
View File

@ -0,0 +1,24 @@
use serde::Serialize;
use super::playing_hand::{HandState, PlayingHand};
#[derive(Serialize, Clone)]
pub struct Player {
pub(super) hands: Vec<PlayingHand>,
}
impl Player {
pub(super) fn new() -> Self {
Player { hands: Vec::new() }
}
pub fn get_hands(&self) -> &Vec<PlayingHand> {
&self.hands
}
pub(super) fn next_playing_hand(&self) -> Option<usize> {
self.hands
.iter()
.position(|e| *e.get_state() == HandState::Playing)
}
}

View File

@ -0,0 +1,36 @@
use serde::Serialize;
use crate::cards::hand::Hand;
#[derive(Serialize, Clone)]
pub struct PlayingHand {
pub(super) hand: Hand,
pub(super) state: HandState,
}
impl PlayingHand {
pub(super) fn new() -> Self {
PlayingHand {
hand: Hand::new(),
state: HandState::Playing,
}
}
pub fn get_hand(&self) -> &Hand {
&self.hand
}
pub fn get_state(&self) -> &HandState {
&self.state
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub enum HandState {
Playing,
Standing,
DoubleDown,
Busted,
Blackjack,
Maxed, // Reached 21
}

View File

@ -1,7 +1,10 @@
use std::fmt::Display; use std::fmt::Display;
use crate::{card_index::CardIndex, card_suit::CardSuit}; use serde::{Deserialize, Serialize};
use super::{card_index::CardIndex, card_suit::CardSuit};
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct Card { pub struct Card {
pub suit: CardSuit, pub suit: CardSuit,
pub index: CardIndex, pub index: CardIndex,

View File

@ -1,6 +1,8 @@
use std::fmt::Display; use std::fmt::Display;
#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy)] use serde::{Deserialize, Serialize};
#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Serialize, Deserialize)]
#[repr(u8)] #[repr(u8)]
pub enum CardIndex { pub enum CardIndex {
A = 14, A = 14,

View File

@ -1,6 +1,8 @@
use std::fmt::Display; use std::fmt::Display;
#[derive(Clone, Copy)] use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize)]
pub enum CardSuit { pub enum CardSuit {
Spades, Spades,
Clubs, Clubs,

View File

@ -1,4 +1,4 @@
use crate::{card::Card, hand::Hand, CardIndex as CI, CardSuit as CS}; use super::{card::Card, hand::Hand, card_index::CardIndex as CI, card_suit::CardSuit as CS};
pub fn new_full_deck() -> Hand { pub fn new_full_deck() -> Hand {
let mut hand = Hand::new(); let mut hand = Hand::new();

View File

@ -1,8 +1,10 @@
use rand::{seq::SliceRandom, thread_rng}; use rand::{seq::SliceRandom, thread_rng};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Display; use std::fmt::Display;
use crate::{card::Card, card_index::CardIndex}; use super::{card::Card, card_index::CardIndex};
#[derive(Clone)]
pub struct Hand { pub struct Hand {
cards: Vec<Card>, cards: Vec<Card>,
} }
@ -106,19 +108,40 @@ impl Display for Hand {
} }
} }
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)] #[cfg(test)]
mod tests { mod tests {
use super::super::card_suit::CardSuit;
use super::*; use super::*;
#[test] #[test]
fn is_blackjack_ace_first() { fn is_blackjack_ace_first() {
let mut hand = Hand::new(); let mut hand = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts, suit: CardSuit::Hearts,
index: CardIndex::A, index: CardIndex::A,
}); });
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Diamonds, suit: CardSuit::Diamonds,
index: CardIndex::N10, index: CardIndex::N10,
}); });
assert!(hand.is_backjack()); assert!(hand.is_backjack());
@ -128,11 +151,11 @@ mod tests {
fn is_blackjack_ace_last() { fn is_blackjack_ace_last() {
let mut hand = Hand::new(); let mut hand = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Diamonds, suit: CardSuit::Diamonds,
index: CardIndex::J, index: CardIndex::J,
}); });
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts, suit: CardSuit::Hearts,
index: CardIndex::A, index: CardIndex::A,
}); });
assert!(hand.is_backjack()); assert!(hand.is_backjack());
@ -142,15 +165,15 @@ mod tests {
fn is_not_blackjack_too_many() { fn is_not_blackjack_too_many() {
let mut hand = Hand::new(); let mut hand = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Diamonds, suit: CardSuit::Diamonds,
index: CardIndex::J, index: CardIndex::J,
}); });
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts, suit: CardSuit::Hearts,
index: CardIndex::A, index: CardIndex::A,
}); });
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Spades, suit: CardSuit::Spades,
index: CardIndex::A, index: CardIndex::A,
}); });
assert!(!hand.is_backjack()); assert!(!hand.is_backjack());
@ -160,7 +183,7 @@ mod tests {
fn is_not_blackjack_too_few() { fn is_not_blackjack_too_few() {
let mut hand = Hand::new(); let mut hand = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts, suit: CardSuit::Hearts,
index: CardIndex::A, index: CardIndex::A,
}); });
assert!(!hand.is_backjack()); assert!(!hand.is_backjack());
@ -170,15 +193,15 @@ mod tests {
fn is_not_blackjack_value_21() { fn is_not_blackjack_value_21() {
let mut hand = Hand::new(); let mut hand = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts, suit: CardSuit::Hearts,
index: CardIndex::K, index: CardIndex::K,
}); });
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Clubs, suit: CardSuit::Clubs,
index: CardIndex::N7, index: CardIndex::N7,
}); });
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts, suit: CardSuit::Hearts,
index: CardIndex::N4, index: CardIndex::N4,
}); });
assert!(!hand.is_backjack()); assert!(!hand.is_backjack());
@ -188,7 +211,7 @@ mod tests {
fn blackjack_value() { fn blackjack_value() {
let mut hand = Hand::new(); let mut hand = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Hearts, suit: CardSuit::Hearts,
index: CardIndex::K, index: CardIndex::K,
}); });
assert_eq!(hand.get_blackjack_value(), 10); assert_eq!(hand.get_blackjack_value(), 10);
@ -198,17 +221,17 @@ mod tests {
fn merge_hands() { fn merge_hands() {
let mut hand = Hand::new(); let mut hand = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Diamonds, suit: CardSuit::Diamonds,
index: CardIndex::N5, index: CardIndex::N5,
}); });
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Spades, suit: CardSuit::Spades,
index: CardIndex::Q, index: CardIndex::Q,
}); });
let mut other = Hand::new(); let mut other = Hand::new();
hand.add_card(Card { hand.add_card(Card {
suit: crate::card_suit::CardSuit::Spades, suit: CardSuit::Spades,
index: CardIndex::K, index: CardIndex::K,
}); });

5
src/cards/mod.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod card;
pub mod card_index;
pub mod card_suit;
pub mod decks;
pub mod hand;

View File

@ -1,13 +1,13 @@
use std::io::{self, stdin}; use std::io::{self, stdin};
use crate::blackjack::{BlackjackGame, GameState, PlayMoves}; use crate::blackjack::{blackjack_game::BlackjackGame, gamestate::GameState, play_moves::PlayMoves};
pub fn play() -> Result<(), Box<dyn std::error::Error>> { pub fn play() -> Result<(), Box<dyn std::error::Error>> {
let mut game = BlackjackGame::new(); let mut game = BlackjackGame::new();
loop { loop {
match game.get_state() { match game.get_state() {
GameState::PlayerTurn(player_index, _) => { GameState::PlayerTurn{player_index, ..} => {
print_full_state(&game); print_full_state(&game);
let play_move = get_move(*player_index)?; let play_move = get_move(*player_index)?;
if !game.play(play_move) { if !game.play(play_move) {
@ -20,7 +20,7 @@ pub fn play() -> Result<(), Box<dyn std::error::Error>> {
return Ok(()); return Ok(());
} }
GameState::Starting => { GameState::Starting => {
game.play(PlayMoves::Deal(2)); game.play(PlayMoves::Deal{players: 2});
} }
} }
} }

View File

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

64
src/webserver.rs Normal file
View File

@ -0,0 +1,64 @@
use std::sync::Mutex;
use rocket::{
http::Status,
serde::{json::Json, Serialize},
Build, Rocket, State,
};
use crate::{blackjack::{blackjack_game::BlackjackGame, gamestate::GameState, play_moves::PlayMoves, player::Player}, cards::card::Card};
#[get("/state")]
fn get_state(state: &State<MyState>) -> Json<GamestateJson> {
Json(gamestate_as_json(&state.game.lock().unwrap()))
}
#[post("/state", data = "<request>", format = "application/json")]
fn post_move(
state: &State<MyState>,
request: Json<PlayMoves>,
) -> Result<Json<GamestateJson>, Status> {
let action = request.0;
if state.game.lock().unwrap().play(action) {
Ok(Json(gamestate_as_json(&state.game.lock().unwrap())))
} else {
Err(Status::BadRequest)
}
}
struct MyState {
game: Mutex<BlackjackGame>,
}
#[derive(Serialize)]
struct GamestateJson {
state: GameState,
dealer_upcard: Option<Card>,
players: Vec<Player>,
}
fn gamestate_as_json(game: &BlackjackGame) -> GamestateJson {
match game.get_state() {
GameState::Starting => GamestateJson {
state: *game.get_state(),
dealer_upcard: None,
players: Vec::new(),
},
GameState::Over | GameState::PlayerTurn { .. } => GamestateJson {
state: *game.get_state(),
dealer_upcard: game.get_dealer_upcard().copied(),
players: game.get_players().clone(),
},
}
}
pub fn build() -> Rocket<Build> {
let state = MyState {
game: Mutex::new(BlackjackGame::new()),
};
rocket::build()
.manage(state)
.mount("/", routes![get_state, post_move])
}