summaryrefslogtreecommitdiff
path: root/protocol/src/simple_bots.rs
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-12-22 08:31:35 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-12-22 08:31:35 -0500
commit42f6ef6d44e893b47e5e2a49496b5dd2122df232 (patch)
treedff2f84bbc41295b27deb2397071cb16bb742058 /protocol/src/simple_bots.rs
parent10ecb9e30568bf20287b053a620252d7a80dbd6b (diff)
Add a simple bot that plays random (legal) cards
- Leads random cards - Follows suit if possible
Diffstat (limited to 'protocol/src/simple_bots.rs')
-rw-r--r--protocol/src/simple_bots.rs123
1 files changed, 119 insertions, 4 deletions
diff --git a/protocol/src/simple_bots.rs b/protocol/src/simple_bots.rs
index b7986b6..7cc2ce8 100644
--- a/protocol/src/simple_bots.rs
+++ b/protocol/src/simple_bots.rs
@@ -1,6 +1,14 @@
use async_trait::async_trait;
+use rand::{
+ prelude::{IteratorRandom, SliceRandom},
+ thread_rng,
+};
-use crate::{bot::BiddingBot, bridge_engine::{BiddingStatePlayerView, Bid}};
+use crate::{
+ bot::{BiddingBot, PlayingBot},
+ bridge_engine::{Bid, BiddingStatePlayerView, PlayStatePlayerView},
+ card::Card,
+};
pub struct AlwaysPassBiddingBot {}
@@ -11,12 +19,113 @@ impl BiddingBot for AlwaysPassBiddingBot {
}
}
+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;
- use crate::bridge_engine::{BiddingState, Bidding, BiddingStatePlayerView};
+
+ #[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);
+ 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);
+ 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<Card> {
+ 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() {
@@ -28,8 +137,14 @@ mod tests {
deal: random(),
bidding: Bidding::new(dealer),
};
- let player_view = BiddingStatePlayerView::from_bidding_state(&bidding_state, player_position);
+ 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);
+ assert_eq!(
+ Bid::Pass,
+ (AlwaysPassBiddingBot {}).bid(&player_view).await
+ );
}
}