Bridge Training

Usage

How to consume @workspace/bridge — the configured engine versus direct sub-path imports.

Installation

Add the workspace dependency to the consuming app or package:

{
  "dependencies": {
    "@workspace/bridge": "workspace:*"
  }
}

There is no root entry point. Import from the sub-paths listed in the Overview; the two most common are @workspace/bridge/engine and @workspace/bridge/<module> (auction, hands, player, …).

Two ways in

Engine (@workspace/bridge/engine)Direct imports (<module>, utils/*, format/*, pbn, …)
ConfigurationBound to a BridgeContext (teaching mode + locale)None: every function is independent of the context
Typical callerAny code that depends on the teaching mode or the locale, React or notPure helpers, repositories, scripts, tests
Surface13 modules plus getCtx()One file per concern, tree-shakeable

The split follows one rule: a function is public outside the engine if and only if it is independent of the context. Anything that would take a CoreConfig, BridgeContext, TeachingMode, SuitVariant or Locale is only reachable through engine.<module>, which pre-binds those arguments (for example engine.hands.areComplete(hands) applies the mode's CoreConfig). Outside React, build the engine for the mode you hold; createBridgeEngine is memoized. Two documented exceptions take a mode as data: createBridgeContext/createBridgeEngine themselves, and validateDeal, which validates a deal that carries its own mode.

Creating an engine

createBridgeContext(partial?) fills the defaults (teachingMode: "auction", locale: "en"); createBridgeEngine(ctx) takes a complete BridgeContext.

usage.ts
const ctx: BridgeContext = createBridgeContext({
  teachingMode: "auction",
  locale: "fr",
});
const engine = createBridgeEngine(ctx);
usage.ts
createBridgeContext(); // => { teachingMode: "auction", locale: "en" }
createBridgeContext({ locale: "fr" }); // => { teachingMode: "auction", locale: "fr" }

The teachingMode resolves the CoreConfig (cards per hand, valid ranks, auction kind, denominations, deck); the locale drives rank and suit notation. See Game Modes for every mode.

Memoization rule

Engines are pure projections of teachingMode and locale and hold no per-call state, so createBridgeEngine caches one instance per teachingMode|locale key. Calling it again with an equivalent context returns the same object; a nullish locale falls back to "en" before the key is built.

usage.ts
const again = createBridgeEngine(
  createBridgeContext({ teachingMode: "auction", locale: "fr" })
);
again === engine; // => true: one engine per `teachingMode|locale` pair

Because of this cache, never mutate anything returned by an engine (engine.hands.config, engine.constants.*, …): the next caller with the same mode and locale would see the mutation.

Engine modules

MemberRoleReference
engine.getCtx()Returns the normalised BridgeContext (teachingMode, locale) the engine was built fromEngine
engine.ctxFlat read-only flags derived from the CoreConfig (auctionInput, contractDisplay, visibility, …)Engine
engine.handsCompleteness checks, distribution and transforms bound to the mode's CoreConfigHands
engine.auctionPer-call view, building an auction, bid validation, contract calculation, isEnabledAuction
engine.notationLocale-aware rank parsing/formatting, rank sort, validRanksNotation
engine.playvalidateTricks replay against the deal, trick winner, follow-suit rulesPlay
engine.playerSeat navigation, rotation, partnershipsPlayer
engine.pbnPBN file parsing, conversion, serialization, encoding helpersPBN
engine.formatformatContractFormat
engine.schemasZod schema namespaces (player, card, bid, hands, play, scoring, vulnerability, gameParams, teachParams)Schemas
engine.constantsCore constants and defaultsConstants
engine.parseCard, bid, contract, scoring and vulnerability parsers and guardsEngine
engine.statsHCP / THT computers, contract level, player vulnerability statusEngine

The full member list of every module is on the Engine page.

Examples

Hands, bound to the mode's card count:

usage.ts
const hands = handsToHandsData(
  new HandsBuilder().fromCompact("AKQ2.KQ3.A54.K32 JT98.JT9.KQJ.QJ9 7654.A87.T98.AT8 3.6542.7632.7654").build()
);

engine.hands.isHandComplete(hands.N); // => true (13 cards in auction mode)
engine.hands.areComplete(hands); // => true
engine.hands.config.nbCardsPerHand; // => 13

Notation, bound to the locale:

usage.ts
engine.notation.parseRanks("RDV"); // => ["K", "Q", "J"]
engine.notation.formatRanks(["K", "Q", "J"]); // => "R D V"
engine.notation.formatRank("K"); // => "R"

Auction:

usage.ts
const auction = new AuctionBuilder().fromCompact("N / 1S pass 2S AP").build();
const result = engine.auction.calculateContract(auction);
// result => { type: "confirmed", value: { level: 2, suit: "S", declarer: "N", … } }
engine.auction.isAuctionComplete(auction); // => true

Context flags:

usage.ts
engine.getCtx(); // => { teachingMode: "auction", locale: "fr" }
engine.ctx.auctionsEnabled; // => true
engine.ctx.auctionInput; // => "sequence"
engine.ctx.contractDisplay; // => "full"
engine.ctx.cardSet; // => "standard"

Direct imports

Context-free functions are imported from their sub-path. They are stateless and need no BridgeContext.

usage.ts
getPlayersInTrickOrder("W"); // => ["W", "N", "E", "S"]
parseCard("SA"); // => "SA"
parseCard("S1"); // => null
computeHandHCP(hands.N); // => 21

Test data in examples comes from @workspace/bridge/testing (makeDeal and the builders such as HandsBuilder and AuctionBuilder), which is also the recommended way to build fixtures in app tests.