use async_trait::async_trait; use rand::{ prelude::{IteratorRandom, SliceRandom}, thread_rng, }; use crate::{ actions::Bid, bot::{BiddingBot, PlayingBot}, bridge_engine::{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 crate::{ bridge_engine::SUIT_DISPLAY_ORDER, card::RankOrder, contract::{Contract, ContractLevel, ContractModifier, LevelAndSuit}, core::{Deal, Player, Vulnerability}, move_result::MoveResult, }; use std::str::FromStr; use super::*; use crate::{ bridge_engine::{ Bidding, BiddingState, BiddingStatePlayerView, PlayState, }, 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() { MoveResult::Current(p) => p, MoveResult::Next(_) => { 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() { MoveResult::Current(p) => p, MoveResult::Next(_) => { 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 { dealer: Player::North, vulnerability: Vulnerability::None, 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 = LevelAndSuit { level: ContractLevel::One, suit: Some(Suit::Club), }; let contract = Contract { declarer: Player::West, highest_bid: raise1c, modifier: ContractModifier::Doubled, }; let bidding = Bidding { dealer: deal.dealer, 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 deal: Deal = random(); let dealer = deal.dealer; let player_position = random(); let bidding_state = BiddingState { deal, 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 play_state = example_play_state(); let mut deal = play_state.deal.clone(); let mut result = MoveResult::Current(play_state); while let MoveResult::Current(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(); } let play_result = result.next().unwrap(); // Verify that the deal is intact. deal.sort(&SUIT_DISPLAY_ORDER, RankOrder::Descending); let mut result_deal = play_result.deal().into_owned(); result_deal.sort(&SUIT_DISPLAY_ORDER, RankOrder::Descending); assert_eq!(result_deal, deal); } }