summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-09-06 21:00:00 -0400
committerKjetil Orbekk <kj@orbekk.com>2022-09-06 21:00:00 -0400
commit397214caf0fe46cb1cd455908a67d36a931e7ec4 (patch)
tree943cab72b679c069f93744e0bd91eba059318652
parent6f2dbd6db9836b5b60c78ec457b07d1202527690 (diff)
Finish bidding table component
-rw-r--r--webapp/src/bridge_engine.rs32
-rw-r--r--webapp/src/default.css53
-rw-r--r--webapp/src/main.rs35
3 files changed, 109 insertions, 11 deletions
diff --git a/webapp/src/bridge_engine.rs b/webapp/src/bridge_engine.rs
index 702faa9..573b2f1 100644
--- a/webapp/src/bridge_engine.rs
+++ b/webapp/src/bridge_engine.rs
@@ -143,6 +143,18 @@ impl fmt::Debug for Bid {
}
}
+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,
@@ -216,7 +228,7 @@ impl FromStr for Raise {
let caps = RE.captures(s).ok_or(anyhow!("invalid raise: {}", s))?;
debug!("caps: {:?}", caps);
let level = caps[1].parse()?;
- let suit = match &caps[2] {
+ let suit = match caps[2].to_ascii_uppercase().as_str() {
"NT" => None,
x => Some(x.parse()?),
};
@@ -224,14 +236,14 @@ impl FromStr for Raise {
}
}
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum ContractModifier {
None,
Doubled,
Redoubled,
}
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Contract {
highest_bid: Option<Raise>,
modifier: ContractModifier,
@@ -295,12 +307,21 @@ impl Bidding {
}
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum BiddingResult {
InProgress(Bidding),
Contract(Contract, Bidding),
}
+impl BiddingResult {
+ pub fn bidding(self) -> Bidding {
+ match self {
+ BiddingResult::InProgress(bidding) => bidding,
+ BiddingResult::Contract(_, bidding) => bidding,
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -349,9 +370,12 @@ mod tests {
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);
diff --git a/webapp/src/default.css b/webapp/src/default.css
index d4f291b..e76ca28 100644
--- a/webapp/src/default.css
+++ b/webapp/src/default.css
@@ -62,6 +62,59 @@ body {
content: "♣"
}
+.bidding-table {
+ max-width: 250px;
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ border: 2px solid #000;
+ border-radius: 10px;
+ background-color: #eee;
+ padding: 3px;
+}
+.bidding-table .header {
+ font-weight: bold;
+ border-bottom: 2px solid #000;
+}
+
+.bidding-table .bid {
+ display: flex;
+}
+
+.bidding-table .suit-spade {
+ color: #000;
+}
+.bidding-table .suit-heart {
+ color: #d00;
+}
+.bidding-table .suit-diamond {
+ color: #d00;
+}
+.bidding-table .suit-club {
+ color: #000;
+}
+.bidding-table .suit-notrump {
+ background-color: #dde;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+}
+
+.bidding-table .suit-spade:after {
+ content: "♠"
+}
+.bidding-table .suit-heart:after {
+ content: "♥"
+}
+.bidding-table .suit-diamond:after {
+ content: "♦"
+}
+.bidding-table .suit-club:after {
+ content: "♣"
+}
+.bidding-table .suit-notrump:after {
+ content: "NT"
+}
+
.bidding-box {
max-width: 250px;
display: grid;
diff --git a/webapp/src/main.rs b/webapp/src/main.rs
index 81b8b39..846514a 100644
--- a/webapp/src/main.rs
+++ b/webapp/src/main.rs
@@ -4,6 +4,7 @@ use log::{debug, error, info, warn};
use yew::prelude::*;
pub mod bridge_engine;
pub mod card;
+use bridge_engine::Bid;
use bridge_engine::Bidding;
use bridge_engine::Player;
use bridge_engine::Raise;
@@ -49,9 +50,20 @@ pub fn app() -> Html {
})
};
+ let bidding = Bidding::new(Player::North);
+ let bidding = bidding.bid("1NT".parse().unwrap()).unwrap().bidding();
+ let bidding = bidding.bid("pass".parse().unwrap()).unwrap().bidding();
+ let bidding = bidding.bid("2♦".parse().unwrap()).unwrap().bidding();
+ let bidding = bidding.bid("pass".parse().unwrap()).unwrap().bidding();
+ let bidding = bidding.bid("2♥".parse().unwrap()).unwrap().bidding();
+ let bidding = bidding.bid("pass".parse().unwrap()).unwrap().bidding();
+ let bidding = bidding.bid("4♥".parse().unwrap()).unwrap().bidding();
+
html! {
<>
- <p>{ "Bids" }</p>
+ <p>{ "Bidding table" }</p>
+ <BiddingTable { bidding } />
+ <p>{ "Bidding box" }</p>
<BiddingBox lower_limit={"1H".parse::<Raise>().unwrap()} />
<p>{ "North" }</p>
<Hand ..(*north).clone() />
@@ -145,7 +157,7 @@ fn padding(dealer: Player) -> Html {
let mut padding : Vec<Html> = vec![];
let mut player = Player::West;
while player != dealer {
- padding.push(html! { <div /> });
+ padding.push(html! { <div/> });
player = player.next();
}
padding.into_iter().collect()
@@ -153,19 +165,28 @@ fn padding(dealer: Player) -> Html {
#[function_component(BiddingTable)]
pub fn bidding_table(props: &BiddingTableProps) -> Html {
+ let bid = |bid: &Bid| match bid.as_raise() {
+ None => html!{ <div class="bid">{ bid }</div> },
+ Some(raise) => html!{
+ <div class="bid">
+ { raise.level }
+ <div class={ bid_css_class(raise.suit) }/>
+ </div>
+ },
+ };
let bids: Html = props
.bidding
.bids
.iter()
- .map(|bid| {
- html! {
- <div class="bid">{ bid }</div>
- }
- })
+ .map(|b| { bid(b) })
.collect();
let padding : Html = padding(props.bidding.dealer);
html! {
<div class="bidding-table">
+ <div class="header">{ "West" }</div>
+ <div class="header">{ "North" }</div>
+ <div class="header">{ "East" }</div>
+ <div class="header">{ "South" }</div>
{ padding }
{ bids }
</div>