Always forbid bids that add up to the cards in hand

The sum of all bids may never equal the number of cards each player
holds in the round; the last bidder has to adjust their bid. This was an
optional "not equal" lobby setting and is now a fixed rule of the game
engine, so it applies to humans, AI players and training alike.

- Remove GameOptions.not_equal, the set_options field, the lobby toggle
  and the --not-equal training flag
- Explain the rule in the lobby and show the blocked bid while bidding
- Drop the option from the AI observation encoding
- Tests check that no round ends with bids summing to the cards in hand

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 10:15:25 +02:00
co-authored by Claude Opus 5.5
parent 90176e1cca
commit fde8e2fb2e
14 changed files with 35 additions and 41 deletions
+1 -2
View File
@@ -19,7 +19,6 @@ export type Action =
export type Phase = 'trump_selection' | 'bidding' | 'playing' | 'finished'
export interface GameOptions {
not_equal: boolean
hidden_bids: boolean
}
@@ -115,7 +114,7 @@ export type ClientMessage =
| { type: 'leave_lobby' }
| { type: 'add_ai' }
| { type: 'remove_player'; player_id: string }
| { type: 'set_options'; not_equal: boolean; hidden_bids: boolean }
| { type: 'set_options'; hidden_bids: boolean }
| { type: 'start_game' }
| { type: 'action'; action: Action }
| { type: 'return_to_lobby' }
@@ -34,7 +34,7 @@ export function BidPanel({ observation, onBid }: BidPanelProps) {
<p className={styles.hint}>
{!observation.options.hidden_bids &&
`Bids so far: ${sum} for ${observation.round_number} trick${observation.round_number > 1 ? 's' : ''}. `}
{forbidden.length > 0 && `Not-equal rule: you may not bid ${forbidden.join(', ')}.`}
{forbidden.length > 0 && `The bids may not add up to ${observation.round_number}, so you may not bid ${forbidden.join(', ')}.`}
</p>
</div>
)
@@ -205,3 +205,9 @@
margin: 0;
color: var(--text-muted);
}
.rule {
margin: 0 0 4px;
color: var(--text-muted);
font-size: 14px;
}
@@ -98,18 +98,10 @@ export function LobbyScreen({ lobby, playerId, actions }: LobbyScreenProps) {
<section className={styles.card}>
<h2 className={styles.heading}>Rules</h2>
<label className={styles.option}>
<input
type="checkbox"
checked={lobby.options.not_equal}
disabled={!isHost}
onChange={(event) => actions.setOptions({ ...lobby.options, not_equal: event.target.checked })}
/>
<span>
<strong>Not equal</strong>
<small>The sum of all bids must not equal the number of tricks in the round.</small>
</span>
</label>
<p className={styles.rule}>
The sum of all bids may never equal the number of cards in hand — the last player to bid has
to adjust their bid.
</p>
<label className={styles.option}>
<input
type="checkbox"