use async_trait::async_trait; use rand::{ prelude::{IteratorRandom, SliceRandom}, thread_rng, }; use crate::{ bot::{BiddingBot, PlayingBot}, bridge_engine::{Bid, BiddingStatePlayerView, PlayStatePlayerView}, card::Card, }; pub struct AlwaysPassBiddingBot {} #[async_trait] impl BiddingBot for AlwaysPassBiddingBot { async fn bid(&self, _bidding: &BiddingStatePlayerView) -> Bid { Bid::Pass } } pub struct RandomPlayingBot {} #[async_trait] impl PlayingBot for RandomPlayingBot { async fn play(&self, state: &PlayStatePlayerView) -> Card { let mut rng = thread_rng(); if let Some(suit) = state.current_trick.suit() { if let Some(card) = state .hand .iter() .filter(|c| c.suit() == suit) .choose(&mut rng) { return *card; } } *state .hand .choose(&mut rng) .expect("must have at least one card") } } #[cfg(test)] mod tests { use std::str::FromStr; use super::*; use crate::{ bridge_engine::{ Bidding, BiddingState, BiddingStatePlayerView, Contract, ContractLevel, ContractModifier, Deal, PlayState, PlayStateResult, Player, Raise, }, card::Suit, }; use log::info; use rand::random; #[tokio::test] async fn random_playing_bot() { crate::tests::test_setup(); let play_state = example_play_state(); info!("Play state: {play_state:#?}"); let south_state = PlayStatePlayerView::from_play_state(&play_state, Player::South); assert!(!play_state.playing_deal.is_dummy_visible()); assert!(south_state.dummy.is_none()); assert!(south_state.previous_trick.is_none()); let card1 = (RandomPlayingBot {}).play(&south_state).await; info!("South state: {south_state:#?}"); let play_state = match play_state.play(card1).unwrap() { PlayStateResult::InProgress(p) => p, PlayStateResult::PlayFinished(_) => { panic!("game should not be over") } }; let west_state = PlayStatePlayerView::from_play_state(&play_state, Player::West); assert!(play_state.playing_deal.is_dummy_visible()); assert!(west_state.dummy.is_some()); let card2 = (RandomPlayingBot {}).play(&west_state).await; info!("West state: {west_state:#?}"); assert_eq!(card1.suit(), card2.suit()); let _play_state = match play_state.play(card2).unwrap() { PlayStateResult::InProgress(p) => p, PlayStateResult::PlayFinished(_) => { panic!("game should not be over") } }; } fn mkcard(s: &str) -> Card { Card::from_str(s).unwrap() } fn mkcards(s: &str) -> Vec { s.split(" ").map(mkcard).collect() } fn example_deal() -> Deal { Deal { west: mkcards("♠Q ♠9 ♠5 ♠2 ♥K ♥J ♥4 ♣5 ♣4 ♣2 ♦10 ♦5 ♦3"), north: mkcards("♠A ♠8 ♠7 ♠6 ♥A ♥9 ♥5 ♥3 ♣9 ♣3 ♦Q ♦J ♦9"), east: mkcards("♠K ♠3 ♥Q ♥10 ♥8 ♥7 ♣K ♣Q ♣J ♣10 ♣6 ♦A ♦4"), south: mkcards("♠J ♠10 ♠4 ♥6 ♥2 ♣A ♣8 ♣7 ♦K ♦8 ♦7 ♦6 ♦2"), } } fn example_play_state() -> PlayState { let deal = example_deal(); let raise1c = Raise { level: ContractLevel::One, suit: Some(Suit::Club), }; let contract = Contract { declarer: Player::West, highest_bid: raise1c, modifier: ContractModifier::Doubled, }; let bidding = Bidding { dealer: random(), bids: vec![Bid::Raise(raise1c), Bid::Pass, Bid::Pass, Bid::Pass], }; PlayState::new(deal, contract, bidding) } #[tokio::test] async fn always_passing_bot_passes() { crate::tests::test_setup(); let dealer = random(); let player_position = random(); let bidding_state = BiddingState { dealer, deal: random(), bidding: Bidding::new(dealer), }; let player_view = BiddingStatePlayerView::from_bidding_state( &bidding_state, player_position, ); info!("Bidding state: {bidding_state:#?}"); assert_eq!( Bid::Pass, (AlwaysPassBiddingBot {}).bid(&player_view).await ); } #[tokio::test] async fn play_until_completion() { crate::tests::test_setup(); let bot = RandomPlayingBot {}; let mut result = PlayStateResult::InProgress(example_play_state()); while let PlayStateResult::InProgress(play_state) = result { info!("Play state: {play_state:#?}"); let player_state = PlayStatePlayerView::from_play_state( &play_state, play_state.current_player(), ); let card = bot.play(&player_state).await; result = play_state.play(card).unwrap(); } } }