From 15e58777e3919d10abb6ee954c680fbbc5c8dc17 Mon Sep 17 00:00:00 2001 From: Robert Ying Date: Sat, 20 Jan 2024 14:37:51 -0800 Subject: [PATCH] Adds option to protect both triples and tractors from pairs (#453) * fmt * option to protect both triples and tractors from pairs * frontend --- backend/src/shengji_handler.rs | 4 +- core/src/message.rs | 2 + frontend/src/Credits.tsx | 9 +++- frontend/src/Initialize.tsx | 4 ++ frontend/src/gen-types.d.ts | 4 +- frontend/src/gen-types.schema.json | 47 +++++++++++++----- mechanics/src/trick.rs | 76 ++++++++++++++++++++++++++++-- 7 files changed, 124 insertions(+), 22 deletions(-) diff --git a/backend/src/shengji_handler.rs b/backend/src/shengji_handler.rs index 2c01492..165dc28 100644 --- a/backend/src/shengji_handler.rs +++ b/backend/src/shengji_handler.rs @@ -188,9 +188,7 @@ async fn register_user, E: std::fmt::Debug + Send>( let (assigned_player_id, register_msgs) = g.register(name_)?; info!(logger_, "Joining room"; "player_id" => assigned_player_id.0); let mut clients_to_disconnect = vec![]; - let clients = associated_websockets - .entry(assigned_player_id) - .or_default(); + let clients = associated_websockets.entry(assigned_player_id).or_default(); // If the same user joined before, remove the previous entries // from the state-store. if !g.allows_multiple_sessions_per_user() { diff --git a/core/src/message.rs b/core/src/message.rs index 9d2e7be..206a4ff 100644 --- a/core/src/message.rs +++ b/core/src/message.rs @@ -336,6 +336,8 @@ impl MessageVariant { format!("{} protected longer tuples from being drawn out by shorter ones (pair does not draw triple)", n?), TrickDrawPolicySet { policy: TrickDrawPolicy::OnlyDrawTractorOnTractor } => format!("{} protected tractors from being drawn out by non-tractors", n?), + TrickDrawPolicySet { policy: TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor } => + format!("{} protected longer tuples from being drawn out by shorter ones, and tractors from being drawn out by non-tractors", n?), ThrowEvaluationPolicySet { policy: ThrowEvaluationPolicy::All } => format!("{} set throws to be evaluated based on all of the cards", n?), ThrowEvaluationPolicySet { policy: ThrowEvaluationPolicy::Highest } => diff --git a/frontend/src/Credits.tsx b/frontend/src/Credits.tsx index 61d735e..4937ec5 100644 --- a/frontend/src/Credits.tsx +++ b/frontend/src/Credits.tsx @@ -9,7 +9,7 @@ const contentStyle: React.CSSProperties = { transform: "translate(-50%, -50%)", }; -const changeLogVersion: number = 22; +const changeLogVersion: number = 23; const ChangeLog = (): JSX.Element => { const [modalOpen, setModalOpen] = React.useState(false); @@ -66,6 +66,13 @@ const ChangeLog = (): JSX.Element => { game +

1/20/2024:

+
    +
  • + Added the ability to protect both longer tuples and tractors at the + same time. +
  • +

2/24/2023:

  • diff --git a/frontend/src/Initialize.tsx b/frontend/src/Initialize.tsx index dbd02ba..abda0ba 100644 --- a/frontend/src/Initialize.tsx +++ b/frontend/src/Initialize.tsx @@ -1244,6 +1244,10 @@ const Initialize = (props: IProps): JSX.Element => { + diff --git a/frontend/src/gen-types.d.ts b/frontend/src/gen-types.d.ts index 81d7d73..e30cc64 100644 --- a/frontend/src/gen-types.d.ts +++ b/frontend/src/gen-types.d.ts @@ -199,10 +199,10 @@ export type BonusLevelPolicy = export type KittyPenalty = "Times" | "Power"; export type KittyBidPolicy = "FirstCard" | "FirstCardOfLevelOrHighest"; export type TrickDrawPolicy = - | "NoProtections" + | ("NoProtections" | "NoFormatBasedDraw") | "LongerTuplesProtected" | "OnlyDrawTractorOnTractor" - | "NoFormatBasedDraw"; + | "LongerTuplesProtectedAndOnlyDrawTractorOnTractor"; export type ThrowPenalty = "None" | "TenPointsPerAttempt"; export type ThrowEvaluationPolicy = "All" | "Highest" | "TrickUnitLength"; export type PlayTakebackPolicy = "AllowPlayTakeback" | "NoPlayTakeback"; diff --git a/frontend/src/gen-types.schema.json b/frontend/src/gen-types.schema.json index 07d1fbd..44b2ae9 100644 --- a/frontend/src/gen-types.schema.json +++ b/frontend/src/gen-types.schema.json @@ -640,11 +640,22 @@ "enum": ["JokerOrHigherSuit", "JokerOrGreaterLength", "GreaterLength"] }, "BidReinforcementPolicy": { - "type": "string", - "enum": [ - "ReinforceWhileWinning", - "OverturnOrReinforceWhileWinning", - "ReinforceWhileEquivalent" + "oneOf": [ + { + "description": "A bid can be reinforced when it is the winning bid.", + "type": "string", + "enum": ["ReinforceWhileWinning"] + }, + { + "description": "A bid can be reinforced when it is the winning bid, or overturned with a greater bid.", + "type": "string", + "enum": ["OverturnOrReinforceWhileWinning"] + }, + { + "description": "A bid can be reinforced if it is equivalent to the winning bid after reinforcement.", + "type": "string", + "enum": ["ReinforceWhileEquivalent"] + } ] }, "BidTakebackPolicy": { @@ -3131,12 +3142,26 @@ } }, "TrickDrawPolicy": { - "type": "string", - "enum": [ - "NoProtections", - "LongerTuplesProtected", - "OnlyDrawTractorOnTractor", - "NoFormatBasedDraw" + "oneOf": [ + { + "type": "string", + "enum": ["NoProtections", "NoFormatBasedDraw"] + }, + { + "description": "Don't require longer tuples to be drawn if the original format was a shorter tuple.", + "type": "string", + "enum": ["LongerTuplesProtected"] + }, + { + "description": "Only allow tractors to be drawn if the original format was also a tractor.", + "type": "string", + "enum": ["OnlyDrawTractorOnTractor"] + }, + { + "description": "Both `LongerTuplesProtected` and `OnlyDrawTractorOnTractor`", + "type": "string", + "enum": ["LongerTuplesProtectedAndOnlyDrawTractorOnTractor"] + } ] }, "TrickFormat": { diff --git a/mechanics/src/trick.rs b/mechanics/src/trick.rs index 2e466b6..a441394 100644 --- a/mechanics/src/trick.rs +++ b/mechanics/src/trick.rs @@ -47,9 +47,13 @@ pub enum TrickError { pub enum TrickDrawPolicy { #[default] NoProtections, + /// Don't require longer tuples to be drawn if the original format was a + /// shorter tuple. LongerTuplesProtected, /// Only allow tractors to be drawn if the original format was also a tractor. OnlyDrawTractorOnTractor, + /// Both `LongerTuplesProtected` and `OnlyDrawTractorOnTractor` + LongerTuplesProtectedAndOnlyDrawTractorOnTractor, NoFormatBasedDraw, } @@ -205,7 +209,9 @@ impl TrickFormat { std::iter::once_with(move || { subsequent_decomposition_ordering( adj_tuples, - trick_draw_policy != TrickDrawPolicy::OnlyDrawTractorOnTractor, + trick_draw_policy != TrickDrawPolicy::OnlyDrawTractorOnTractor + && trick_draw_policy + != TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor, ) .into_iter() .map(|requirements| { @@ -716,7 +722,8 @@ impl Trick { points: all_card_points, largest_trick_unit_size: tf.units.iter().map(|u| u.size()).max().unwrap_or(0), failed_throw_size: self - .played_cards.first() + .played_cards + .first() .ok_or(TrickError::OutOfOrder)? .bad_throw_cards .len(), @@ -898,7 +905,8 @@ impl UnitLike { TrickDrawPolicy::NoFormatBasedDraw | TrickDrawPolicy::NoProtections | TrickDrawPolicy::OnlyDrawTractorOnTractor => true, - TrickDrawPolicy::LongerTuplesProtected => !matching + TrickDrawPolicy::LongerTuplesProtected + | TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor => !matching .iter() .any(|(card, count)| counts_.get(card).copied().unwrap_or_default() > *count), }; @@ -1759,6 +1767,21 @@ mod tests { &[S_3, S_3, S_5, S_5], TrickDrawPolicy::LongerTuplesProtected )); + assert!(!tf.is_legal_play( + &hand, + &[S_2, S_2, S_2, S_2], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); + assert!(tf.is_legal_play( + &hand, + &[S_2, S_2, S_3, S_3], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); + assert!(tf.is_legal_play( + &hand, + &[S_3, S_3, S_5, S_5], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); assert!(tf.is_legal_play( &hand, &[S_2, S_2, S_2, S_2], @@ -1804,6 +1827,16 @@ mod tests { &[S_2, S_2, S_5, S_5], TrickDrawPolicy::LongerTuplesProtected )); + assert!(tf.is_legal_play( + &hand, + &[S_2, S_2, S_2, S_2], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); + assert!(tf.is_legal_play( + &hand, + &[S_2, S_2, S_5, S_5], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); // This play is tenuously legal, since the 2222 is protected by the 355 is not, and the // trick-format is 2233. Normally we would expect that the 2233 is required, but the player // has decided to break the 22 but *not* play the 55. @@ -1842,6 +1875,16 @@ mod tests { &[S_2, S_2, S_5], TrickDrawPolicy::LongerTuplesProtected )); + assert!(tf.is_legal_play( + &hand, + &[S_2, S_2, S_2], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); + assert!(tf.is_legal_play( + &hand, + &[S_2, S_2, S_5], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); } #[test] @@ -1870,6 +1913,11 @@ mod tests { &[S_5, S_5, S_6], TrickDrawPolicy::LongerTuplesProtected )); + assert!(tf.is_legal_play( + &hand, + &[S_5, S_5, S_6], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); assert!(!tf.is_legal_play( &hand, &[S_6, S_7, S_8], @@ -1899,6 +1947,16 @@ mod tests { &[S_5, S_6, S_7, S_8], TrickDrawPolicy::LongerTuplesProtected )); + assert!(!tf.is_legal_play( + &hand, + &[S_5, S_6, S_7, S_8], + TrickDrawPolicy::OnlyDrawTractorOnTractor + )); + assert!(tf.is_legal_play( + &hand, + &[S_5, S_6, S_7, S_8], + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor + )); } #[test] @@ -1932,6 +1990,11 @@ mod tests { &[S_3, S_5, S_10, S_J, S_Q], TrickDrawPolicy::NoFormatBasedDraw )); + assert!(tf.is_legal_play( + &hand, + &[S_3, S_5, S_10, S_J, S_Q], + TrickDrawPolicy::LongerTuplesProtected + )); assert!(tf.is_legal_play( &hand, &[S_3, S_6, S_8, S_8, S_8], @@ -1944,7 +2007,7 @@ mod tests { )); assert!(tf.is_legal_play( &hand, - &[S_3, S_5, S_10, S_J, S_Q], + &[S_3, S_6, S_8, S_8, S_8], TrickDrawPolicy::LongerTuplesProtected )); } @@ -2000,6 +2063,8 @@ mod tests { TrickDrawPolicy::NoProtections, TrickDrawPolicy::LongerTuplesProtected, TrickDrawPolicy::NoFormatBasedDraw, + TrickDrawPolicy::OnlyDrawTractorOnTractor, + TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor, ] { let mut hands = Hands::new(vec![P1, P2, P3, P4]); @@ -2034,7 +2099,8 @@ mod tests { } TrickDrawPolicy::LongerTuplesProtected | TrickDrawPolicy::NoProtections - | TrickDrawPolicy::OnlyDrawTractorOnTractor => { + | TrickDrawPolicy::OnlyDrawTractorOnTractor + | TrickDrawPolicy::LongerTuplesProtectedAndOnlyDrawTractorOnTractor => { // This play should not succeed, because P2 also has S_K, S_K which is a pair. if let Err(TrickError::IllegalPlay) = trick.play_cards(pc!( P2,