Bridge Training

Utils

Duplicate scoring, vulnerability predicates, and engine.stats.

@workspace/bridge/utils/* maps one import path per file in src/utils/. Each file holds its own code and belongs to no rules module; the rules modules have their own sub-path (@workspace/bridge/auction, @workspace/bridge/hands, …, see Overview). utils/contract is documented in Format. Honour-point counting and PBN hand strings live in Hands.

scoring

Import from @workspace/bridge/utils/scoring. Implements the Laws of Duplicate Bridge scoring table from the declaring side's point of view. Honour bonuses (rubber bridge) are out of scope.

ExportSignatureDescription
isScoringModeSet(mode: ScoringMode) => mode is ScoringModeSettrue unless the mode is "UNSET".
isScoringMode(value: unknown) => value is ScoringModeExact membership in "TPP" | "IMP" | "UNSET"; no alias handling (see Parsers for that).
contractScore(level: number, denomination: Denomination, vulnerable: boolean) => numberScore of an undoubled contract that makes exactly. A tiebreaker for par computations, not a full scorer.
duplicateScore(contract: DisplayContract, tricksWon: number, vulnerable: boolean) => numberFull score of a played contract: positive when made, negative when down.
computeDuplicateScore(input: ComputeScoreInput) => numberSame computation with a flat input object.
ComputeScoreInputtypeInput of computeDuplicateScore; doubling is a CounterStatus.

Prop

Type

Values applied:

ComponentNon-vulnerableVulnerable
Trick pointsminors 20, majors 30, NT 40 then 30 per trick; ×2 doubled, ×4 redoubledsame
Part-score bonus (under 100 trick points)5050
Game bonus (100 trick points or more)300500
Small slam / grand slam bonus500 / 1000750 / 1500
Insult bonus (doubled / redoubled contract made)50 / 10050 / 100
Overtrick, undoubledtrick valuetrick value
Overtrick, doubled (×2 redoubled)100200
Undertricks, undoubled50 each100 each
Undertricks, doubled (×2 redoubled)100, then 200, 200, then 300 each200, then 300 each
utils.ts
contractScore(2, "S", false); // => 110 (60 trick points + 50 part-score bonus)
contractScore(3, "NT", false); // => 400 (100 + 300 game bonus)
contractScore(6, "NT", true); // => 1440 (190 + 500 game + 750 small slam)

duplicateScore({ level: 4, suit: "S", doubled: "none" }, 10, false); // => 420
duplicateScore({ level: 4, suit: "S", doubled: "none" }, 8, true); // => -200
duplicateScore({ level: 2, suit: "S", doubled: "X" }, 8, false); // => 470 (120 + 300 + 50 insult)
duplicateScore({ level: 4, suit: "H", doubled: "XX" }, 11, true); // => 1480

computeDuplicateScore({
  level: 4,
  denomination: "S",
  doubling: "X",
  vulnerable: false,
  tricks: 6,
}); // => -800 (down four, doubled, non-vulnerable)

isScoringMode("IMP"); // => true
isScoringMode("imp"); // => false (exact canonical values only)

vulnerability

Import from @workspace/bridge/utils/vulnerability.

FunctionSignatureDescription
getPlayerVulnerabilityStatus(player: Player, vulnerability: Vulnerability) => VulnerabilityStatus"vulnerable", "non-vulnerable" or "unset" for one seat.
isVulnerabilitySet(vulnerability: Vulnerability) => vulnerability is VulnerabilitySettrue unless "UNSET".
isVulnerability(value: unknown) => value is VulnerabilityExact membership in "UNSET" | "NONE" | "NS" | "EW" | "ALL".
isSideVulnerable(side: Partnership, vulnerability: Vulnerability) => booleanWhether a partnership is at risk; false for "NONE" and "UNSET".
utils.ts
getPlayerVulnerabilityStatus("N", "NS"); // => "vulnerable"
getPlayerVulnerabilityStatus("E", "NS"); // => "non-vulnerable"
getPlayerVulnerabilityStatus("E", "UNSET"); // => "unset"

isSideVulnerable("EW", "ALL"); // => true
isSideVulnerable("EW", "NS"); // => false

isVulnerability("ALL"); // => true

engine.stats

engine.stats groups the hand-evaluation and contract-classification helpers.

MemberSignatureDescription
HCP_VALUES, THT_VALUESconstantsSee Hands.
computeHandHCP(hand: HandData) => numberSee Hands.
honorComputersRecord<HonorMetric, (hand: HandData) => number>HONOR_COMPUTERS.
computeCardCounts(hands: HandsData) => CardCountsSee Hands.
determineContractLevel(contract: string | null) => ContractLevel"partial", "game", "small_slam" or "grand_slam" from a contract string; "partial" for null or invalid. Also in @workspace/bridge/auction.
getPlayerVulnerabilityStatus(player: Player, vulnerability: Vulnerability) => VulnerabilityStatusSee vulnerability.
utils.ts
const north: HandData = { S: ["A", "K"], H: ["Q"], D: ["T", "9"], C: ["J"] };

const engine = createBridgeEngine(
  createBridgeContext({ teachingMode: "auction", locale: "en" }),
);

engine.stats.computeHandHCP(north); // => 10
engine.stats.honorComputers.hcp(north); // => 10
engine.stats.determineContractLevel("4S"); // => "game"
engine.stats.determineContractLevel("6NT"); // => "small_slam"
engine.stats.getPlayerVulnerabilityStatus("S", "EW"); // => "non-vulnerable"