summaryrefslogtreecommitdiff
path: root/webapp/src
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-11-15 15:06:07 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-11-15 15:06:07 -0500
commit1f623cca3ea0937508b8c50f4c32a0972271e8f4 (patch)
tree3f5a36296ffc2afb1199cd65f9758c9667115450 /webapp/src
parent8fa1b37bb705371bf5dee574f1f136019d3db9d1 (diff)
Move shared bridge library code into `protocol` crate
Diffstat (limited to 'webapp/src')
-rw-r--r--webapp/src/bridge_engine.rs709
-rw-r--r--webapp/src/card.rs277
-rw-r--r--webapp/src/components.rs2
-rw-r--r--webapp/src/components/bidding.rs2
-rw-r--r--webapp/src/components/bidding_box.rs2
-rw-r--r--webapp/src/components/bidding_table.rs2
-rw-r--r--webapp/src/components/card.rs2
-rw-r--r--webapp/src/components/game.rs8
-rw-r--r--webapp/src/components/hand.rs2
-rw-r--r--webapp/src/components/show_bid.rs2
-rw-r--r--webapp/src/components/trick_in_play.rs2
-rw-r--r--webapp/src/components/tricks_played.rs2
-rw-r--r--webapp/src/main.rs9
13 files changed, 13 insertions, 1008 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"),
- })
- );
- }
-}
diff --git a/webapp/src/card.rs b/webapp/src/card.rs
deleted file mode 100644
index 621bae1..0000000
--- a/webapp/src/card.rs
+++ /dev/null
@@ -1,277 +0,0 @@
-use anyhow::anyhow;
-use rand::prelude::SliceRandom;
-use rand::Rng;
-use std::fmt;
-use strum::EnumCount;
-use strum::IntoEnumIterator;
-use strum_macros::EnumCount;
-use strum_macros::EnumIter;
-
-#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, EnumIter, EnumCount)]
-pub enum Suit {
- Club,
- Diamond,
- Heart,
- Spade,
-}
-
-#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, EnumIter)]
-pub enum Rank {
- Two = 2,
- Three,
- Four,
- Five,
- Six,
- Seven,
- Eight,
- Nine,
- Ten,
- Jack,
- Queen,
- King,
- Ace,
-}
-
-impl fmt::Display for Suit {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- f.write_str(match self {
- Suit::Club => "♣",
- Suit::Diamond => "♢",
- Suit::Heart => "♡",
- Suit::Spade => "♠",
- })
- }
-}
-
-impl std::str::FromStr for Suit {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
- match s.trim() {
- "♣" => Ok(Suit::Club),
- "C" => Ok(Suit::Club),
- "♢" => Ok(Suit::Diamond),
- "♦" => Ok(Suit::Diamond),
- "D" => Ok(Suit::Diamond),
- "♡" => Ok(Suit::Heart),
- "♥" => Ok(Suit::Heart),
- "H" => Ok(Suit::Heart),
- "♠" => Ok(Suit::Spade),
- "S" => Ok(Suit::Spade),
- _ => Err(anyhow!("invalid suit: {}", s)),
- }
- }
-}
-
-impl fmt::Debug for Suit {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- write!(f, "{}", self)
- }
-}
-
-impl fmt::Display for Rank {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- f.write_str(match self {
- Rank::Ace => "A",
- Rank::King => "K",
- Rank::Queen => "Q",
- Rank::Jack => "J",
- Rank::Ten => "10",
- Rank::Nine => "9",
- Rank::Eight => "8",
- Rank::Seven => "7",
- Rank::Six => "6",
- Rank::Five => "5",
- Rank::Four => "4",
- Rank::Three => "3",
- Rank::Two => "2",
- })
- }
-}
-
-impl fmt::Debug for Rank {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- write!(f, "{}", self)
- }
-}
-
-impl std::str::FromStr for Rank {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
- match s.trim().to_ascii_uppercase().as_str() {
- "A" => Ok(Rank::Ace),
- "K" => Ok(Rank::King),
- "Q" => Ok(Rank::Queen),
- "J" => Ok(Rank::Jack),
- "10" => Ok(Rank::Ten),
- "T" => Ok(Rank::Ten),
- "9" => Ok(Rank::Nine),
- "8" => Ok(Rank::Eight),
- "7" => Ok(Rank::Seven),
- "6" => Ok(Rank::Six),
- "5" => Ok(Rank::Five),
- "4" => Ok(Rank::Four),
- "3" => Ok(Rank::Three),
- "2" => Ok(Rank::Two),
- _ => Err(anyhow!("invalid rank: {}", s)),
- }
- }
-}
-
-#[derive(PartialEq, Eq, Clone, Copy)]
-pub struct Card(pub Suit, pub Rank);
-
-impl fmt::Display for Card {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- let Card(suit, rank) = self;
- write!(f, "{}{}", suit, rank)
- }
-}
-
-impl fmt::Debug for Card {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
- write!(f, "{}", self)
- }
-}
-
-impl std::str::FromStr for Card {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
- let stripped = s.replace(" ", "");
- let mut chars = stripped.chars();
- let suit = chars
- .next()
- .ok_or(anyhow!("missing parts: {}", s))?
- .to_string()
- .parse()?;
- let rank = chars.collect::<String>().parse()?;
- Ok(Card(suit, rank))
- }
-}
-
-fn make_deck() -> Vec<Card> {
- let mut result = vec![];
- for suit in Suit::iter() {
- for rank in Rank::iter() {
- result.push(Card(suit, rank));
- }
- }
- result
-}
-
-#[derive(Default, PartialEq, Eq, Copy, Clone)]
-pub enum RankOrder {
- #[default]
- Descending,
- Ascending,
-}
-
-pub fn sort_cards(suits: &[Suit; 4], ord: RankOrder, cards: &mut [Card]) {
- let mut score: [u8; Suit::COUNT] = [0; Suit::COUNT];
- for (i, suit) in suits.iter().enumerate() {
- score[*suit as usize] = i as u8;
- }
- cards.sort_by(|&Card(s1, r1), &Card(s2, r2)| {
- let order = {
- if s1 == s2 {
- r1.cmp(&r2)
- } else {
- score[s1 as usize].cmp(&score[s2 as usize])
- }
- };
- if ord == RankOrder::Descending {
- order.reverse()
- } else {
- order
- }
- });
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use log::info;
-
- #[test]
- fn sorting_cards() {
- let card = |s: &str| s.parse::<Card>().unwrap();
- assert_eq!([card("♥2"), card("♥3"), card("♥4"),], {
- let mut cards = [card("♥2"), card("♥4"), card("♥3")];
- sort_cards(
- &[Suit::Heart, Suit::Spade, Suit::Club, Suit::Diamond],
- RankOrder::Ascending,
- &mut cards,
- );
- cards
- });
- assert_eq!([card("♥A"), card("♥3"), card("♥2"),], {
- let mut cards = [card("♥2"), card("♥A"), card("♥3")];
- sort_cards(
- &[Suit::Heart, Suit::Spade, Suit::Club, Suit::Diamond],
- RankOrder::Descending,
- &mut cards,
- );
- cards
- });
- assert_eq!([card("♠A"), card("♥A"), card("♣A"), card("♦A"),], {
- let mut cards = [card("♣A"), card("♠A"), card("♥A"), card("♦A")];
- sort_cards(
- &[Suit::Diamond, Suit::Club, Suit::Heart, Suit::Spade],
- RankOrder::Descending,
- &mut cards,
- );
- cards
- });
- }
-
- #[test]
- fn string_conversion() {
- crate::tests::test_setup();
- info!("deck: {:?}", make_deck());
- assert_eq!(
- make_deck(),
- make_deck()
- .iter()
- .map(|card| format!("{}", card).parse().unwrap())
- .collect::<Vec<Card>>(),
- );
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct Deal {
- pub north: Vec<Card>,
- pub west: Vec<Card>,
- pub south: Vec<Card>,
- pub east: Vec<Card>,
-}
-
-impl Deal {
- pub fn sort(&mut self, suits: &[Suit; 4], ord: RankOrder) {
- sort_cards(suits, ord, self.north.as_mut_slice());
- sort_cards(suits, ord, self.west.as_mut_slice());
- sort_cards(suits, ord, self.south.as_mut_slice());
- sort_cards(suits, ord, self.east.as_mut_slice());
- }
-}
-
-pub fn deal<R>(rng: &mut R) -> Deal
-where
- R: Rng,
-{
- let mut deck = make_deck();
- deck.shuffle(rng);
- let mut deck = deck.iter();
- let north = deck.by_ref().take(13).cloned().collect();
- let west = deck.by_ref().take(13).cloned().collect();
- let south = deck.by_ref().take(13).cloned().collect();
- let east = deck.by_ref().take(13).cloned().collect();
- Deal {
- north,
- west,
- south,
- east,
- }
-}
diff --git a/webapp/src/components.rs b/webapp/src/components.rs
index 018b1c3..aa3636f 100644
--- a/webapp/src/components.rs
+++ b/webapp/src/components.rs
@@ -1,4 +1,4 @@
-use crate::card::Suit;
+use protocol::card::Suit;
mod app_context_provider;
mod bidding;
diff --git a/webapp/src/components/bidding.rs b/webapp/src/components/bidding.rs
index 2825cbd..2c48ca3 100644
--- a/webapp/src/components/bidding.rs
+++ b/webapp/src/components/bidding.rs
@@ -1,4 +1,4 @@
-use crate::bridge_engine::{self, BiddingResult, Contract, Player};
+use protocol::bridge_engine::{self, BiddingResult, Contract, Player};
use crate::components::{BiddingBox, BiddingTable};
use log::error;
use yew::prelude::*;
diff --git a/webapp/src/components/bidding_box.rs b/webapp/src/components/bidding_box.rs
index 1d12369..0b384ec 100644
--- a/webapp/src/components/bidding_box.rs
+++ b/webapp/src/components/bidding_box.rs
@@ -1,4 +1,4 @@
-use crate::bridge_engine::{Bid, Raise};
+use protocol::bridge_engine::{Bid, Raise};
use crate::components::bid_css_class;
use yew::prelude::*;
diff --git a/webapp/src/components/bidding_table.rs b/webapp/src/components/bidding_table.rs
index 8576ed6..0f1a824 100644
--- a/webapp/src/components/bidding_table.rs
+++ b/webapp/src/components/bidding_table.rs
@@ -1,4 +1,4 @@
-use crate::bridge_engine::{Bid, Bidding, Player};
+use protocol::bridge_engine::{Bid, Bidding, Player};
use crate::components::bid_css_class;
use yew::prelude::*;
diff --git a/webapp/src/components/card.rs b/webapp/src/components/card.rs
index ff321e1..48d53e3 100644
--- a/webapp/src/components/card.rs
+++ b/webapp/src/components/card.rs
@@ -1,4 +1,4 @@
-use crate::card;
+use protocol::card;
use crate::components::suit_css_class;
use yew::prelude::*;
diff --git a/webapp/src/components/game.rs b/webapp/src/components/game.rs
index 34e073c..7ade948 100644
--- a/webapp/src/components/game.rs
+++ b/webapp/src/components/game.rs
@@ -1,7 +1,7 @@
-use crate::bridge_engine::{self, Contract, DealInPlay, DealInPlayResult, Player};
-use crate::card;
-use crate::card::Deal;
-use crate::card::Suit;
+use protocol::bridge_engine::{self, Contract, DealInPlay, DealInPlayResult, Player};
+use protocol::card;
+use protocol::card::Deal;
+use protocol::card::Suit;
use crate::components::{Bidding, Hand, ShowBid, TrickInPlay, TricksPlayed};
use log::{error, info};
use yew::prelude::*;
diff --git a/webapp/src/components/hand.rs b/webapp/src/components/hand.rs
index 4a01508..7946a5c 100644
--- a/webapp/src/components/hand.rs
+++ b/webapp/src/components/hand.rs
@@ -1,4 +1,4 @@
-use crate::card;
+use protocol::card;
use crate::components::card::Card;
use yew::prelude::*;
diff --git a/webapp/src/components/show_bid.rs b/webapp/src/components/show_bid.rs
index 5914710..81cc7aa 100644
--- a/webapp/src/components/show_bid.rs
+++ b/webapp/src/components/show_bid.rs
@@ -1,4 +1,4 @@
-use crate::bridge_engine::{Bidding, Contract};
+use protocol::bridge_engine::{Bidding, Contract};
use yew::prelude::*;
#[derive(PartialEq, Properties, Clone)]
diff --git a/webapp/src/components/trick_in_play.rs b/webapp/src/components/trick_in_play.rs
index 720fec0..c68239e 100644
--- a/webapp/src/components/trick_in_play.rs
+++ b/webapp/src/components/trick_in_play.rs
@@ -1,4 +1,4 @@
-use crate::bridge_engine::TurnInPlay;
+use protocol::bridge_engine::TurnInPlay;
use crate::components::Card;
use yew::prelude::*;
diff --git a/webapp/src/components/tricks_played.rs b/webapp/src/components/tricks_played.rs
index 97a7cd1..d8f5a04 100644
--- a/webapp/src/components/tricks_played.rs
+++ b/webapp/src/components/tricks_played.rs
@@ -1,4 +1,4 @@
-use crate::bridge_engine::Trick;
+use protocol::bridge_engine::Trick;
use yew::prelude::*;
#[function_component(TricksPlayed)]
diff --git a/webapp/src/main.rs b/webapp/src/main.rs
index 35e69fd..25d12f1 100644
--- a/webapp/src/main.rs
+++ b/webapp/src/main.rs
@@ -4,8 +4,6 @@ use std::rc::Rc;
use log::{debug, error, info, warn};
use yew::prelude::*;
use yew_router::prelude::*;
-pub mod bridge_engine;
-pub mod card;
pub mod components;
use components::{AppContext, AppContextProvider, ErrorInfo, Game, Table};
use gloo_net::http::Request;
@@ -100,10 +98,3 @@ fn switch(routes: &Route) -> Html {
}
}
-#[cfg(test)]
-mod tests {
- pub fn test_setup() {
- dotenv::dotenv().ok();
- let _ = env_logger::builder().is_test(true).try_init();
- }
-}