summaryrefslogtreecommitdiff
path: root/webapp
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-09-04 19:42:57 -0400
committerKjetil Orbekk <kj@orbekk.com>2022-09-04 19:44:53 -0400
commitab68d26c6b16f973642d378900f5e6ac2be221b7 (patch)
tree357206052643d3ee0b065fd4ee6cb8af928e11f9 /webapp
parentb7180143745c5ebe571936b0116cca403d082e8d (diff)
Add turn/trick representation
Includes convenience test and parsing utilities
Diffstat (limited to 'webapp')
-rw-r--r--webapp/.env1
-rw-r--r--webapp/Cargo.toml4
-rw-r--r--webapp/src/bridge_engine.rs108
-rw-r--r--webapp/src/card.rs86
-rw-r--r--webapp/src/main.rs9
5 files changed, 208 insertions, 0 deletions
diff --git a/webapp/.env b/webapp/.env
new file mode 100644
index 0000000..edf7f1a
--- /dev/null
+++ b/webapp/.env
@@ -0,0 +1 @@
+RUST_LOG=webapp,bridge
diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml
index 78a5863..15a880a 100644
--- a/webapp/Cargo.toml
+++ b/webapp/Cargo.toml
@@ -12,3 +12,7 @@ strum = "0.24"
strum_macros = "0.24"
rand = "0.8.4"
getrandom = { version = "0.2.7", features = ["js"] }
+
+[dev-dependencies]
+env_logger = "0.8.4"
+dotenv = "0.15"
diff --git a/webapp/src/bridge_engine.rs b/webapp/src/bridge_engine.rs
new file mode 100644
index 0000000..48c0fd2
--- /dev/null
+++ b/webapp/src/bridge_engine.rs
@@ -0,0 +1,108 @@
+use crate::card;
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub enum Player {
+ North,
+ West,
+ South,
+ East,
+}
+
+impl Player {
+ pub fn next(&self) -> Self {
+ match self {
+ Self::North => Self::West,
+ Self::West => Self::South,
+ Self::South => Self::East,
+ Self::East => Self::North,
+ }
+ }
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub struct Trick {
+ pub leader: Player,
+ pub cards_played: Vec<card::Card>,
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub struct Turn {
+ in_progress: Trick,
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub enum PlayResult {
+ InProgress(Turn),
+ Trick(Trick),
+}
+
+impl Turn {
+ pub fn new(p: Player) -> Turn {
+ Turn {
+ in_progress: Trick {
+ leader: p,
+ cards_played: Vec::with_capacity(4),
+ },
+ }
+ }
+
+ pub fn play(mut self: Turn, card: card::Card) -> PlayResult {
+ self.in_progress.cards_played.push(card);
+ if self.in_progress.cards_played.len() >= 4 {
+ return PlayResult::Trick(self.in_progress);
+ }
+ PlayResult::InProgress(self)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn next_player() {
+ let next_players = vec![Player::North, Player::West, Player::South, Player::East]
+ .iter()
+ .map(Player::next)
+ .collect::<Vec<_>>();
+ assert_eq!(
+ next_players,
+ vec![Player::West, Player::South, Player::East, Player::North]
+ );
+ }
+
+ fn as_turn(p: PlayResult) -> Turn {
+ if let PlayResult::InProgress(t) = p {
+ t
+ } else {
+ panic!("expected PlayResult::InProgress(): {:?}", p);
+ }
+ }
+
+ fn as_trick(p: PlayResult) -> Trick {
+ if let PlayResult::Trick(t) = p {
+ t
+ } else {
+ panic!("expected PlayResult::Trick(): {:?}", p);
+ }
+ }
+
+ #[test]
+ fn play_turn() {
+ let turn = Turn::new(Player::South);
+ let turn = as_turn(turn.play("♣4".parse().unwrap()));
+ let turn = as_turn(turn.play("♥A".parse().unwrap()));
+ let turn = as_turn(turn.play("♣4".parse().unwrap()));
+ 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()
+ }
+ );
+ }
+}
diff --git a/webapp/src/card.rs b/webapp/src/card.rs
index 4fda0a7..174676f 100644
--- a/webapp/src/card.rs
+++ b/webapp/src/card.rs
@@ -40,6 +40,33 @@ impl fmt::Display for Suit {
}
}
+#[derive(Debug, PartialEq, Eq)]
+pub enum ParseError {
+ InvalidSuit(String),
+ InvalidRank(String),
+ MissingParts(String),
+}
+
+impl std::str::FromStr for Suit {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> std::result::Result<Self, ParseError> {
+ 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(ParseError::InvalidSuit(s.to_string())),
+ }
+ }
+}
+
impl fmt::Debug for Suit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", self)
@@ -72,6 +99,30 @@ impl fmt::Debug for Rank {
}
}
+impl std::str::FromStr for Rank {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> std::result::Result<Self, ParseError> {
+ 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(ParseError::InvalidRank(s.to_string())),
+ }
+ }
+}
+
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct Card(pub Suit, pub Rank);
@@ -88,6 +139,22 @@ impl fmt::Debug for Card {
}
}
+impl std::str::FromStr for Card {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> std::result::Result<Self, ParseError> {
+ let stripped = s.replace(" ", "");
+ let mut chars = stripped.chars();
+ let suit = chars
+ .next()
+ .ok_or(ParseError::MissingParts(s.to_string()))?
+ .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() {
@@ -98,6 +165,25 @@ fn make_deck() -> Vec<Card> {
result
}
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use log::info;
+
+ #[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>>(),
+ );
+ }
+}
+
pub fn shuffle_deck<R>(rng: &mut R) -> (Vec<Card>, Vec<Card>, Vec<Card>, Vec<Card>)
where
R: Rng,
diff --git a/webapp/src/main.rs b/webapp/src/main.rs
index a99ea94..633f560 100644
--- a/webapp/src/main.rs
+++ b/webapp/src/main.rs
@@ -3,6 +3,7 @@ use crate::card::{Rank, Suit};
use log::{debug, error, info, warn};
use yew::prelude::*;
pub mod card;
+pub mod bridge_engine;
fn main() {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
@@ -118,3 +119,11 @@ impl From<card::Card> for CardProps {
CardProps { suit, rank }
}
}
+
+#[cfg(test)]
+mod tests {
+ pub fn test_setup() {
+ dotenv::dotenv().ok();
+ let _ = env_logger::builder().is_test(true).try_init();
+ }
+}