Skip to content

engine: implement the AI's redouble strategy (_should_redouble is a permanent False) #10

Description

@valmathieu

The expert AI never redoubles. The Redouble plumbing is complete and tested;
the strategy behind it is a permanent False.

Split out of #9 (item 6), where it sat as one of seven TODO markers, and removed
from the Contrée-variants roadmap's step 5 agenda: the gap is a classic-ruleset
one — the AI never redoubles under any preset — and resolving the variant space
is not a prerequisite for fixing it.

Line numbers as of fix/engine-open-todos @ 120e6e9.

What exists

packages/contrai-engine/src/contrai_engine/model/player/rule_based/bidding.py

choose_bid resolves the frozen-auction states before the bidding table runs: a
standing Redouble leaves nothing legal but Pass, and a standing Double routes to
_choose_under_double, which builds a RedoubleBid, confirms
auction.is_legal(...), and asks the strategy:

contract_bid = auction.last_contract_bid
if contract_bid is not None and contract_bid.player.team is self.team:
    redouble = RedoubleBid(self._player)
    if auction.is_legal(redouble) and self._should_redouble():
        return redouble
return PassBid(self._player)

The stub

@staticmethod
def _should_redouble():
    """Determine if we should redouble after being doubled."""

    # TODO: Implement a redouble strategy
    return False

Two consequences:

  • The AI declines every surcoinche, so the branch above is dead in play. The
    tests that cover the redouble path reach it by patching the method
    (tests/test_model/test_rule_based_bidding.pyai_player.bidding._should_redouble = lambda: True),
    which means the plumbing is pinned but the decision is not.

  • It takes no arguments, so it cannot see what it would need to decide. Its
    sibling _should_double receives the standing bid and reasons from it:

    strength = self._estimate_tricks(suit) * 20  # each expected trick worth 20
    return strength > 162 - value

What implementing it involves

  1. Give it the inputs. At minimum the standing ContractBid (its value and
    trump suit) so the seat can estimate tricks in the agreed trump the way
    _should_double does. Note the asymmetry: _should_double estimates
    against a suit the opponents chose, while a redouble estimate is for a
    contract our own side named — usually our partner's, so the hand doing the
    estimating is not the hand that valued the contract in the first place.
  2. Decide the confidence bar. A redouble doubles the stake in both
    directions; the mirror of _should_double's rule is a starting point, but the
    threshold should be strictly higher than "we expect to make it" — the downside
    is symmetric and the upside only materialises on a contract we were already
    committed to.
  3. Reckon with partner's opening. The declaring side has two hands' worth of
    information by then: our own strength plus whatever partner's bid implied.
    Whether to model that, or to redouble on our own hand alone, is the main
    design question.

Notes

  • Behaviour change to the expert bidding strategy, so per CLAUDE.md §9 it ships
    with pytest coverage — cases pinning both a redouble and a declined one, and
    the existing patched-stub tests should then assert against the real method
    rather than a lambda.
  • Worth revisiting once the variants roadmap's step 3 lands at_stake_mode:
    under FIXED stakes the payout grid changes enough to move the threshold. That
    is a follow-up tuning pass, not a blocker.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions