summaryrefslogtreecommitdiff
path: root/webapp/src/bridge_engine.rs
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/bridge_engine.rs')
-rw-r--r--webapp/src/bridge_engine.rs709
1 files changed, 0 insertions, 709 deletions
diff --git a/webapp/src/bridge_engine.rs b/webapp/src/bridge_engine.rs
deleted file mode 100644
index 808045d..0000000
--- a/webapp/src/bridge_engine.rs
+++ /dev/null
@@ -1,709 +0,0 @@
-use crate::card::{Card, Deal, Suit};
-use anyhow::{anyhow, bail};
-use log::{debug, error, info};
-use regex::Regex;
-use std::cmp::Ordering;
-use std::fmt;
-use std::str::FromStr;
-use strum::{EnumCount, IntoEnumIterator};
-use strum_macros::{EnumCount as EnumCountMacro, EnumIter, FromRepr};
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug, FromRepr, EnumCountMacro)]
-#[repr(u8)]
-pub enum Player {
- West = 0,
- North,
- East,
- South,
-}
-
-impl Player {
- pub fn next(&self) -> Self {
- self.many_next(1)
- }
-
- pub fn many_next(self, i: usize) -> Self {
- Player::from_repr(((self as usize + i) % Player::COUNT) as u8).unwrap()
- }
-
- pub fn short_str(&self) -> &str {
- match self {
- Self::West => "W",
- Self::North => "N",
- Self::East => "E",
- Self::South => "W",
- }
- }
-
- pub fn get_cards<'a>(&self, deal: &'a mut Deal) -> &'a mut Vec<Card> {
- match self {
- Self::West => &mut deal.west,
- Self::North => &mut deal.north,
- Self::East => &mut deal.east,
- Self::South => &mut deal.south,
- }
- }
-}
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct Trick {
- pub leader: Player,
- pub cards_played: Vec<Card>,
-}
-
-impl Trick {
- pub fn winner(&self) -> Player {
- error!("XXX: Returning incorrect result for winner");
- self.leader
- }
-}
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct TurnInPlay {
- trick: Trick,
-}
-
-#[derive(PartialEq, Eq, Debug)]
-pub enum TurnInPlayResult {
- InProgress(TurnInPlay),
- Trick(Trick),
-}
-
-impl TurnInPlay {
- pub fn new(p: Player) -> TurnInPlay {
- TurnInPlay {
- trick: Trick {
- leader: p,
- cards_played: Vec::with_capacity(4),
- },
- }
- }
-
- pub fn suit(&self) -> Option<Suit> {
- self.trick
- .cards_played
- .iter()
- .next()
- .map(|&Card(suit, _)| suit)
- }
-
- pub fn leader(&self) -> Player {
- self.trick.leader
- }
-
- pub fn cards_played(&self) -> &[Card] {
- &self.trick.cards_played[..]
- }
-
- pub fn play(mut self: TurnInPlay, card: Card) -> TurnInPlayResult {
- self.trick.cards_played.push(card);
- if self.trick.cards_played.len() >= 4 {
- return TurnInPlayResult::Trick(self.trick);
- }
- TurnInPlayResult::InProgress(self)
- }
-
- pub fn next_player(&self) -> Player {
- self.trick.leader.many_next(self.trick.cards_played.len())
- }
-}
-
-#[derive(Clone, Debug)]
-pub struct DealInPlay {
- deal: Deal,
- tricks_played: Vec<Trick>,
- in_progress: TurnInPlay,
-}
-
-#[derive(Debug)]
-pub enum DealInPlayResult {
- InProgress(DealInPlay),
- PlayFinished(Vec<Trick>),
-}
-
-impl DealInPlay {
- pub fn new(leader: Player, deal: Deal) -> DealInPlay {
- DealInPlay {
- deal,
- tricks_played: Vec::with_capacity(13),
- in_progress: TurnInPlay::new(leader),
- }
- }
-
- pub fn tricks(&self) -> &Vec<Trick> {
- &self.tricks_played
- }
-
- pub fn trick_in_play(&self) -> &TurnInPlay {
- &self.in_progress
- }
-
- pub fn deal(&self) -> &Deal {
- &self.deal
- }
-
- pub fn play(mut self: Self, card: Card) -> Result<DealInPlayResult, anyhow::Error> {
- let player = self.in_progress.next_player();
- let player_cards = player.get_cards(&mut self.deal);
-
- info!(
- "Next player is {:?}, playing card {} from {:?}",
- player, card, player_cards
- );
- let i = player_cards.iter().position(|&c| c == card).ok_or(anyhow!(
- "{:?} does not have {}",
- player,
- card
- ))?;
- player_cards.remove(i);
-
- Ok(match self.in_progress.play(card) {
- TurnInPlayResult::InProgress(turn) => DealInPlayResult::InProgress(Self {
- in_progress: turn,
- ..self
- }),
- TurnInPlayResult::Trick(trick) => DealInPlayResult::InProgress(Self {
- in_progress: TurnInPlay::new(trick.winner()),
- tricks_played: {
- let mut tricks = self.tricks_played;
- tricks.push(trick);
- tricks
- },
- deal: self.deal,
- }),
- })
- }
-}
-
-#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, EnumIter)]
-pub enum ContractLevel {
- One = 1,
- Two,
- Three,
- Four,
- Five,
- Six,
- Seven,
-}
-
-impl fmt::Display for ContractLevel {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- write!(f, "{}", *self as u8)
- }
-}
-
-impl fmt::Debug for ContractLevel {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- write!(f, "{}", self)
- }
-}
-
-impl FromStr for ContractLevel {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
- match s {
- "1" => Ok(ContractLevel::One),
- "2" => Ok(ContractLevel::Two),
- "3" => Ok(ContractLevel::Three),
- "4" => Ok(ContractLevel::Four),
- "5" => Ok(ContractLevel::Five),
- "6" => Ok(ContractLevel::Six),
- "7" => Ok(ContractLevel::Seven),
- _ => Err(anyhow!("invalid string: {}", s)),
- }
- }
-}
-
-#[derive(PartialEq, Eq, Clone, Copy)]
-pub enum Bid {
- Pass,
- Double,
- Redouble,
- Raise(Raise),
-}
-
-impl Bid {
- pub fn as_raise(&self) -> Option<Raise> {
- match self {
- Bid::Raise(raise) => Some(*raise),
- _ => None,
- }
- }
-}
-
-impl fmt::Display for Bid {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- match self {
- Bid::Pass => write!(f, "Pass"),
- Bid::Double => write!(f, "Double"),
- Bid::Redouble => write!(f, "Redouble"),
- Bid::Raise(x) => write!(f, "{}", x),
- }
- }
-}
-
-impl fmt::Debug for Bid {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- match self {
- Bid::Pass => write!(f, "Pass"),
- Bid::Double => write!(f, "Double"),
- Bid::Redouble => write!(f, "Redouble"),
- Bid::Raise(x) => write!(f, "Raise({})", x),
- }
- }
-}
-
-impl FromStr for Bid {
- type Err = anyhow::Error;
- fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
- match s.trim().to_ascii_lowercase().as_str() {
- "pass" => Ok(Bid::Pass),
- "double" => Ok(Bid::Double),
- "redouble" => Ok(Bid::Redouble),
- x => Ok(Bid::Raise(x.parse()?)),
- }
- }
-}
-
-#[derive(PartialEq, Eq, Clone, Copy)]
-pub struct Raise {
- pub level: ContractLevel,
- pub suit: Option<Suit>,
-}
-
-impl Raise {
- pub fn all_raises() -> Vec<Raise> {
- let mut result = Vec::with_capacity(7 * 5);
- for level in ContractLevel::iter() {
- for suit in Suit::iter() {
- result.push(Raise {
- level,
- suit: Some(suit),
- });
- }
- result.push(Raise { level, suit: None });
- }
- result
- }
-}
-
-impl PartialOrd<Self> for Raise {
- fn partial_cmp(&self, o: &Self) -> Option<Ordering> {
- if self.level != o.level {
- return self.level.partial_cmp(&o.level);
- }
- if self.suit != o.suit {
- if self.suit == None {
- return Some(Ordering::Greater);
- }
- if o.suit == None {
- return Some(Ordering::Less);
- }
- return self.suit.partial_cmp(&o.suit);
- }
- return Some(Ordering::Equal);
- }
-}
-
-impl Ord for Raise {
- fn cmp(&self, o: &Self) -> std::cmp::Ordering {
- self.partial_cmp(o).unwrap()
- }
-}
-
-impl fmt::Display for Raise {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- write!(
- f,
- "{}{}",
- self.level,
- self.suit
- .map_or("NT".to_string(), |suit| format!("{}", suit))
- )
- }
-}
-
-impl fmt::Debug for Raise {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- write!(f, "{}", self)
- }
-}
-
-impl FromStr for Raise {
- type Err = anyhow::Error;
- fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> {
- lazy_static::lazy_static! {
- static ref RE: Regex = Regex::new(r#"\s*(.[0-9]*)\s*(.*)"#).unwrap();
- };
- let caps = RE.captures(s).ok_or(anyhow!("invalid raise: {}", s))?;
- info!("caps: {:?}", caps);
- let level = caps[1].parse()?;
- let suit = match caps[2].to_ascii_uppercase().as_str() {
- "NT" => None,
- x => Some(x.parse()?),
- };
- Ok(Raise { level, suit })
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-pub enum ContractModifier {
- None,
- Doubled,
- Redoubled,
-}
-
-impl fmt::Display for ContractModifier {
- fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
- match self {
- ContractModifier::None => Ok(()),
- ContractModifier::Doubled => write!(f, "x"),
- ContractModifier::Redoubled => write!(f, "xx"),
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-pub struct Contract {
- declarer: Player,
- highest_bid: Raise,
- modifier: ContractModifier,
-}
-
-impl fmt::Display for Contract {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
- write!(
- f,
- "{}{}{}",
- self.highest_bid,
- self.declarer.short_str(),
- self.modifier
- )
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct Bidding {
- pub dealer: Player,
- pub bids: Vec<Bid>,
-}
-
-impl Bidding {
- pub fn new(dealer: Player) -> Bidding {
- Bidding {
- dealer,
- bids: vec![],
- }
- }
-
- fn declarer(&self) -> Player {
- // Bids are: [..., winning bid, pass, pass, pass].
- self.dealer.many_next(self.bids.len() - 4)
- }
-
- pub fn highest_bid(&self) -> Option<Raise> {
- for bid in self.bids.iter().rev() {
- if let Some(raise) = bid.as_raise() {
- return Some(raise);
- }
- }
- None
- }
-
- fn passed_out(&self) -> bool {
- let mut passes = 0;
- for b in self.bids.iter().rev().take(3) {
- if b == &Bid::Pass {
- passes += 1
- }
- }
- passes == 3
- }
-
- fn contract(&self) -> Option<Contract> {
- match self.highest_bid() {
- None => None,
- Some(highest_bid) => Some(Contract {
- declarer: self.declarer(),
- highest_bid,
- modifier: ContractModifier::None,
- }),
- }
- }
-
- pub fn bid(mut self, bid: Bid) -> Result<BiddingResult, anyhow::Error> {
- // TODO: Need logic for double and redouble here.
- if bid.as_raise().is_some() && bid.as_raise() <= self.highest_bid() {
- bail!(
- "bid too low: {:?} <= {:?}",
- bid.as_raise(),
- self.highest_bid()
- );
- }
- self.bids.push(bid);
- if self.passed_out() {
- Ok(BiddingResult::Contract(self.contract(), self))
- } else {
- Ok(BiddingResult::InProgress(self))
- }
- }
-}
-
-#[derive(Debug, Clone)]
-pub enum BiddingResult {
- InProgress(Bidding),
- Contract(Option<Contract>, Bidding),
-}
-
-impl BiddingResult {
- pub fn new(dealer: Player) -> Self {
- BiddingResult::InProgress(Bidding::new(dealer))
- }
-
- pub fn bidding(&self) -> &Bidding {
- match self {
- BiddingResult::InProgress(bidding) => bidding,
- BiddingResult::Contract(_, bidding) => bidding,
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use log::info;
-
- fn as_bidding(r: BiddingResult) -> Bidding {
- match r {
- BiddingResult::InProgress(bidding) => bidding,
- _ => panic!("expected BiddingResult::InProgress(): {:?}", r),
- }
- }
-
- fn as_contract(r: BiddingResult) -> Option<Contract> {
- match r {
- BiddingResult::Contract(contract, _) => contract,
- _ => panic!("expected BiddingResult::Contract(): {:?}", r),
- }
- }
-
- #[test]
- fn bidding() {
- crate::tests::test_setup();
- let bidding = Bidding::new(Player::South);
- let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap());
- let bidding = as_bidding(bidding.bid(Bid::Raise("1♦".parse().unwrap())).unwrap());
- let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap());
- let bidding = as_bidding(bidding.bid(Bid::Pass).unwrap());
- let contract = as_contract(bidding.bid(Bid::Pass).unwrap());
- assert_eq!(
- Some(Contract {
- declarer: Player::West,
- highest_bid: "1♦".parse().unwrap(),
- modifier: ContractModifier::None
- }),
- contract
- );
- }
-
- #[test]
- fn bid_conversion() {
- crate::tests::test_setup();
- let bid1d = Raise {
- level: ContractLevel::One,
- suit: Some(Suit::Diamond),
- };
- assert_eq!("1♢", format!("{}", bid1d));
- assert_eq!("1♢", format!("{:?}", bid1d));
- assert_eq!(bid1d, Raise::from_str("1D").unwrap());
-
- assert_eq!(Bid::Pass, Bid::from_str("pass").unwrap());
-
- let mut checked_raises = 0;
- for bid in Raise::all_raises() {
- assert_eq!(bid, Raise::from_str(format!("{}", bid).as_str()).unwrap());
- assert_eq!(
- Bid::Raise(bid),
- Bid::from_str(format!("{}", bid).as_str()).unwrap()
- );
- checked_raises += 1;
- }
- assert_eq!(checked_raises, 35);
- }
-
- #[test]
- fn fmt_contract() {
- assert_eq!(
- format!(
- "{}",
- Contract {
- declarer: Player::West,
- highest_bid: "1♥".parse().unwrap(),
- modifier: ContractModifier::None
- }
- ),
- "1♡W"
- );
-
- assert_eq!(
- format!(
- "{}",
- Contract {
- declarer: Player::East,
- highest_bid: "1♥".parse().unwrap(),
- modifier: ContractModifier::Doubled
- }
- ),
- "1♡Ex"
- );
- }
-
- #[test]
- fn bid_ord() {
- let bid = |s| Raise::from_str(s).unwrap();
- assert!(bid("2♦") < bid("3♦"));
- assert!(bid("3♦") < bid("3♥"));
- assert!(bid("1♠") < bid("2♣"));
- assert!(bid("1♠") < bid("1NT"));
- for bid in Raise::all_raises() {
- assert_eq!(bid, bid);
- }
- }
-
- #[test]
- fn contract_level_conversion() {
- crate::tests::test_setup();
- assert_eq!("2", format!("{}", ContractLevel::Two));
- assert_eq!("3", format!("{:?}", ContractLevel::Three));
-
- let result = ContractLevel::from_str("8");
- info!("{:?}", result);
- assert!(result.unwrap_err().to_string().contains("invalid"));
- assert_eq!(ContractLevel::Seven, "7".parse().unwrap());
- }
-
- #[test]
- fn next_player() {
- let next_players = vec![Player::North, Player::East, Player::South, Player::West]
- .iter()
- .map(Player::next)
- .collect::<Vec<_>>();
- assert_eq!(
- next_players,
- vec![Player::East, Player::South, Player::West, Player::North]
- );
- }
-
- #[test]
- fn many_next_player() {
- assert_eq!(Player::South, Player::South.many_next(4 * 1234567890));
- }
-
- fn as_turn(p: TurnInPlayResult) -> TurnInPlay {
- if let TurnInPlayResult::InProgress(t) = p {
- t
- } else {
- panic!("expected PlayResult::InProgress(): {:?}", p);
- }
- }
-
- fn as_trick(p: TurnInPlayResult) -> Trick {
- if let TurnInPlayResult::Trick(t) = p {
- t
- } else {
- panic!("expected PlayResult::Trick(): {:?}", p);
- }
- }
-
- #[test]
- fn play_turn() {
- let turn = TurnInPlay::new(Player::South);
- assert_eq!(turn.next_player(), Player::South);
- let turn = as_turn(turn.play("♣4".parse().unwrap()));
- assert_eq!(turn.next_player(), Player::West);
- let turn = as_turn(turn.play("♥A".parse().unwrap()));
- assert_eq!(turn.next_player(), Player::North);
- let turn = as_turn(turn.play("♣4".parse().unwrap()));
- assert_eq!(turn.next_player(), Player::East);
- let trick = as_trick(turn.play("♣A".parse().unwrap()));
- assert_eq!(
- trick,
- Trick {
- leader: Player::South,
- cards_played: ["♣4", "♥A", "♣4", "♣A"]
- .into_iter()
- .map(|c| c.parse().unwrap())
- .collect()
- }
- );
- }
-
- #[test]
- fn lead_suit() {
- let turn = TurnInPlay::new(Player::South);
- assert_eq!(turn.suit(), None);
- let turn = as_turn(turn.play("♣4".parse().unwrap()));
- assert_eq!(turn.suit(), Some("♣".parse().unwrap()));
- }
-
- 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("♠5 ♢10 ♡K ♣4 ♡J ♣5 ♢5 ♠9 ♢3 ♠2 ♣2 ♡4 ♠Q"),
- north: mkcards("♢Q ♡9 ♠7 ♠8 ♠A ♡A ♡5 ♠6 ♢9 ♣3 ♡3 ♣9 ♢J"),
- east: mkcards("♣10 ♡7 ♢A ♣6 ♡8 ♣Q ♠K ♡10 ♣K ♠3 ♡Q ♣J ♢4"),
- south: mkcards("♢K ♡6 ♣8 ♢6 ♢7 ♢8 ♣A ♡2 ♣7 ♠10 ♠4 ♠J ♢2"),
- }
- }
-
- fn mini_deal() -> Deal {
- Deal {
- west: mkcards("♢A ♡Q"),
- north: mkcards("♢Q ♡9"),
- east: mkcards("♢7 ♡K"),
- south: mkcards("♢9 ♠9"),
- }
- }
-
- fn as_playing_hand(result: DealInPlayResult) -> DealInPlay {
- match result {
- DealInPlayResult::InProgress(r) => r,
- DealInPlayResult::PlayFinished(_) => {
- panic!("expected PlayingDealResult::InProgress(): {:?}", result)
- }
- }
- }
-
- #[test]
- fn play_hand() {
- let deal = DealInPlay::new(Player::West, mini_deal());
- assert_eq!(deal.tricks_played, vec!());
- {
- let err = deal.clone().play(mkcard("♥9")).unwrap_err().to_string();
- assert_eq!(err, "West does not have ♡9");
- }
-
- let deal = as_playing_hand(deal.play(mkcard("♢A")).unwrap());
- assert_eq!(deal.in_progress.trick.cards_played, vec!(mkcard("♢A")));
-
- let deal = as_playing_hand(deal.play(mkcard("♢Q")).unwrap());
- let deal = as_playing_hand(deal.play(mkcard("♥K")).unwrap());
- let deal = as_playing_hand(deal.play(mkcard("♢9")).unwrap());
- assert_eq!(deal.in_progress.trick.cards_played, []);
- assert_eq!(
- deal.tricks_played,
- vec!(Trick {
- leader: Player::West,
- cards_played: mkcards("♢A ♢Q ♡K ♢9"),
- })
- );
- }
-}