Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds option to protect both triples and tractors from pairs #453

Merged
merged 3 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions backend/src/shengji_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,7 @@ async fn register_user<S: Storage<VersionedGame, E>, 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() {
Expand Down
2 changes: 2 additions & 0 deletions core/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 } =>
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/Credits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(false);
Expand Down Expand Up @@ -66,6 +66,13 @@ const ChangeLog = (): JSX.Element => {
game
</li>
</ul>
<p>1/20/2024:</p>
<ul>
<li>
Added the ability to protect both longer tuples and tractors at the
same time.
</li>
</ul>
<p>2/24/2023:</p>
<ul>
<li>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/Initialize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,10 @@ const Initialize = (props: IProps): JSX.Element => {
<option value="OnlyDrawTractorOnTractor">
Only tractors can draw tractors
</option>
<option value="LongerTuplesProtectedAndOnlyDrawTractorOnTractor">
Longer tuples are protected from shorter, and only tractors can
draw tractors
</option>
<option value="NoFormatBasedDraw">
No format-based requirements (pairs do not draw pairs)
</option>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/gen-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
47 changes: 36 additions & 11 deletions frontend/src/gen-types.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
76 changes: 71 additions & 5 deletions mechanics/src/trick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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),
};
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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],
Expand All @@ -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
));
}
Expand Down Expand Up @@ -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]);

Expand Down Expand Up @@ -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,
Expand Down