Bridge Training

Engine

Complete reference of @workspace/bridge/engine — context, factories, derived axes and every module member.

Import from @workspace/bridge/engine.

The engine is a set of 12 modules composed from a BridgeContext. It resolves the mode's CoreConfig once, derives the context flags, and pre-binds mode-dependent arguments on the functions that need them. Everything else is the same stateless function you can import from its module sub-path (@workspace/bridge/auction, @workspace/bridge/hands, …).

A function that depends on the context (it would take a CoreConfig, BridgeContext, TeachingMode, SuitVariant or Locale) is only reachable here: the config resolver, the named configs and the (x, config) primitives are internal, and engine.<module> is their partial application. Build the engine for the mode you hold, even outside React; createBridgeEngine is memoized, so the call is cheap.

engine.ts
const engine = createBridgeEngine(
  createBridgeContext({ teachingMode: "h", locale: "en" })
);
engine.getCtx(); // => { teachingMode: "h", locale: "en" }

Exports

ExportKindDescription
createBridgeContext(partial?)functionFills teachingMode: "auction" and locale: "en" for omitted fields
createBridgeEngine(ctx)functionBuilds (or returns the memoized) engine for ctx.teachingMode and ctx.locale
BridgeEnginetypeReturn type of createBridgeEngine
BridgeContexttypeEngine input
AuctionInput, ContractDisplay, ContractSourcetypesCapability axes derived from the config
ContextModuleConfig, HandsModuleConfig, AuctionModuleConfig, NotationModuleConfigtypesPer-module configuration objects

BridgeContext

Prop

Type

Both fields are required by the type. At runtime a nullish locale falls back to "en", and getCtx() returns this normalised pair.

Memoization

createBridgeEngine caches one engine per teachingMode|locale key. Engines hold no per-call state, so equal inputs give the same object. Treat every value an engine returns as read-only.

Derived axes

The engine derives the capability flags from the mode's CoreConfig at a single internal point, so engine.ctx cannot drift from the config. Read the flags from the engine built for the mode.

engine.ts
const trumpGoal = createBridgeEngine(
  createBridgeContext({ teachingMode: "show-trump-goal" })
);
trumpGoal.ctx.auctionInput; // => "trump"
trumpGoal.ctx.hasInteractiveAuction; // => false
AxisValuesRule
AuctionInput"sequence", "trump", "contract", nullauctions undefined or "sequence" → "sequence"; "trump"/"goal" → "trump"; "contract" → "contract"; anything else (false) → null
ContractDisplay"full", "trump", "trumpGoal", "hidden"auctions: false → "hidden"; "trump" → "trump"; "goal" → "trumpGoal"; otherwise "full"
ContractSource"user", "auction", "none"userPicksContract → "user"; auctions: false → "none"; otherwise "auction"

The derivation produces auctionsEnabled, auctionInput, dummyVisible, contractDisplay, isObservation (auctions === "sequence"), partnerHandDuringAuction, userControlsContract (ContractSource === "user"), hasInteractiveAuction (auctionsEnabled && (auctionInput === "sequence" || userControlsContract)) and honorMetric ("hcp" unless the config sets "tht").

getCtx()

Returns the BridgeContext the engine was built from, normalised to { teachingMode, locale }.

engine.ctx

A flat, read-only object of config-derived flags. No functions, no logic: dispatch on the axes instead of on teachingMode.

engine.ts
engine.ctx.variant; // => "classic"
engine.ctx.auctionInput; // => "contract"
engine.ctx.contractDisplay; // => "full"
engine.ctx.userControlsContract; // => true
engine.ctx.validDenominations; // => ["NT"]
engine.ctx.visibility.partnerHandDuringAuction; // => true
MemberTypeDescription
variantSuitVariant"classic" or "petit-bridge"
dummyVisiblebooleanDummy hand shown during play
auctionsEnabledbooleanauctions !== false
validDenominationsreadonly Denomination[]Biddable denominations for the mode
auctionInputAuctionInputKind of auction input the editor expects
contractDisplayContractDisplayHow the contract surface renders
isObservationbooleanPre-recorded sequence the user watches (sequence mode)
userControlsContractbooleanThe user picks the contract (minibridge modes)
hasInteractiveAuctionbooleanAn auction phase exists before play
cardSetCardSetDeck the mode uses (CardSet comes from @workspace/bridge/constants)
playContextInfosreadonly PlayContextInfo[]Deal facts exposed during play, in order
honorMetricHonorMetric"hcp" or "tht"
visibility.scoring / .vulnerability / .auction / .contractbooleanAll equal to auctionsEnabled
visibility.partnerHandDuringAuctionbooleanPartner hand visible during the auction
visibility.hcpbooleanauctionsEnabled && dummyVisible

The injected shape is ContextModuleConfig:

Prop

Type

engine.hands

Bound to the mode's CoreConfig. Dedicated page: Hands.

MemberDescription
configThe resolved CoreConfig (getter)
areComplete(hands)Every hand holds nbCardsPerHand cards
isHandComplete(hand)One hand holds nbCardsPerHand cards
hasTooManyCards(hands)Any hand exceeds nbCardsPerHand
completeHands(hands, seed?)Deal the missing cards randomly
redistributeHands(seed?)Fresh complete deal
distributeCards(hands, seed?)Move excess cards to short hands
filterHandsByConfig(hands)Drop ranks outside validRanks
fillHands(hands, seed?)Dedupe, rebalance, then complete to a legal deal
areEmpty(hands)All four hands empty
findDuplicatedCards(hands)Set of cards present more than once
handsAreDifferent(a, b)Shallow array comparison
deepCopyHands(hands)Deep clone
clearHands()Empty hands for all players
rotateHandsClockwise(hands)N→E, E→S, S→W, W→N

engine.auction

Dedicated page: Auction.

MemberDescription
isEnabledauctions !== false for this mode (getter)
calculateContract(auction)ContractResult (confirmed/candidate) or null
getDeclarer(auction)Declarer of the current contract
auctionEntries(auction)Each call with its derived seat and position (BidInfo[])
auctionTurn(auction)Seat whose turn it is to call
createEmptyAuction(dealer?)Auction with no call yet
addBids(auction, ...bids)Append bids in order
alertLastBid(auction)Flag the last bid as alerted
setDealerPlayer(auction, dealer)Set the dealer; the calls move with the seats
rotateToDeclarer(auction, target)Rotate so the declarer lands on target
closeAuction(auction)Append the passes needed to close
isDoubleable(auction) / isRedoubleable(auction)Double / redouble legality for the seat on turn
isAuctionComplete(auction)Three closing passes after a bid, or passed out
isAuctionPassedOut(auction)Four passes and nothing else
isValidBid(bid, auction)Bid legality for the seat on turn
isAuctionCloseable(auction){ canClose, passesNeeded }
isBidAPass(bid)Pass check on a Bid or BidComposite
getCounterStatus(auction)Doubled/redoubled status of the final contract
determineOpener(auction, playerCount?)First non-passing seat

engine.notation

Bound to the mode's CoreConfig (variant, valid ranks) and the locale. Dedicated page: Notation.

MemberDescription
variant / localeBound values (getters)
parseRanks(input)Localised string → Rank[]
formatRanks(ranks)Rank[] → spaced localised string
formatRank(rank)Localised display of one rank (RankFormatter)
sortRanks(ranks)Sorter for the variant's rank order
validRanksRanks of the mode's deck (getter)

engine.play

Bound to validDenominations and validRanks. Dedicated page: Play.

MemberDescription
validateTricks(moves, first, cards, trump)Replay a flat sequence against the deal; collects CARD_NOT_OWNED, REVOKE and INVALID_CARD_RANK errors; trump is neutralised when the mode has no playable suit
deepCopyHands(hands)Deep clone
removeCardFromHand(card, player, hands)Immutable removal
extractLeadFromTricks(tricks)Opening lead, or null
determineTrickWinner(entries, trump, leadSuit)Winning PlayEntry (seat and card), or null for an empty trick
validateCardFollowsRules(card, player, leadSuit, hands)Follow-suit check
hasPlayedSuit(tricks, player, suit)Seat already played that suit

engine.player

Dedicated page: Player.

MemberDescription
getNextPlayer(player)Clockwise neighbour
getCurrentPlayer(dealer, sequenceLength)Seat to bid next
getPlayersFromDealer(dealer)Four seats from the dealer
getPlayersInTrickOrder(lead)Four seats from the leader
getPartnership(player)"NS" or "EW"
partnershipFromSide(side)attack → NS, defense → EW
rotatePlayerNTimes(player, n)N clockwise steps
getRotationCountToTarget(from, to)Steps needed

engine.pbn

Dedicated page: PBN.

MemberDescription
parsePBNFile(file)Promise<ParsePBNFileResult> for a browser File
convertPBNDealToDeal(pbnDeal, converters?)PBNDeal → Deal
createEmptyDeal() / createEmptyPlayerHands()Blank PBNDeal / string hands
handsToPBN(hands, startPlayer)Hands to PBN notation from any seat
serializePBNFile(deal, options?)PBNDeal → PBN text
detectDealComments(deal) / detectDealsComments(deals)Which comment kinds are present
detectFileEncoding(bytes)BOM detection, then a strict UTF-8 decode attempt; "ISO-8859-1" otherwise
decodeBytes(bytes, encoding)Decode with latin1 fallback
toUTF8(str)Recover latin1↔UTF-8 mojibake
removeBOM(content)Strip a leading BOM

engine.format

Dedicated page: Format.

MemberDescription
formatContract(contract)DisplayContract → "3NT", "4SX", … ("" for null)

engine.schemas

Namespaces over the schemas/* files. Dedicated page: Schemas.

NamespaceSource
player@workspace/bridge/schemas/player
card@workspace/bridge/schemas/card
bid@workspace/bridge/schemas/bid
hands@workspace/bridge/schemas/hands
play@workspace/bridge/schemas/play
scoring@workspace/bridge/schemas/scoring
vulnerability@workspace/bridge/schemas/vulnerability
gameParams@workspace/bridge/schemas/game-params
teachParams@workspace/bridge/schemas/teach-params

schemas/deal is not exposed through the engine; import it directly.

engine.constants

A subset of Constants: SUITS, RANKS, NO_TRUMP, BID_LEVELS, DENOMINATIONS, SUIT_SYMBOLS, PLAYERS, DECLARER_PLAYER, DEFAULT_DEALER, DEFAULT_VULNERABILITY, DEFAULT_LOCALE, VULNERABILITIES, DEFAULT_SCORING_MODE, SCORING_MODES.

engine.parse

MemberDescription
parseSuit(value) / parseRank(value)Suit / Rank or null
parseCard(value)Card or null; casing and "10" normalised first
toCard(input)CardInput → canonical Card
suitOf(card) / rankOf(card)Readers on a valid Card
parseBid(value)Bid or null
toBid(input)BidInput → canonical Bid
splitBid(bid)Regular bid → { level, suit }
isRank, isPlayer, isCard, isBidType guards
DEFAULT_RANK_ORDERA…2
sortRanksDefault(ranks)Rank sorting, highest first
parseVulnerability(value)Aliases (Love, Both, …) → Vulnerability, "UNSET" when unknown
parseScoring(value)Aliases (IMPS, MP, …) → ScoringMode, "UNSET" when unknown
parseContractString(value)"4SX" → DisplayContract or null
buildContract(contract, declarer)Strict contract string + declarer → Contract or null
engine.ts
const auction = new AuctionBuilder().fromCompact("N / 1S pass 2S AP").build();
engine.auction.getDeclarer(auction); // => "N"
engine.player.getNextPlayer("N"); // => "E"
engine.parse.parseBid("3NT"); // => "3NT"
engine.schemas.player.playerSchema.parse("N"); // => "N"
engine.constants.SUITS; // => ["S", "H", "D", "C"]

engine.stats

MemberDescription
HCP_VALUES{ A: 4, K: 3, Q: 2, J: 1 }
THT_VALUES{ A: 1, K: 1, Q: 0.5 }
computeHandHCP(hand)High-card points
honorComputersRecord<HonorMetric, (hand) => number>; index with engine.ctx.honorMetric
computeCardCounts(hands)HCP and suit lengths per player
determineContractLevel(contract)"partial", "game", "small_slam" or "grand_slam"
getPlayerVulnerabilityStatus(player, vulnerability)"vulnerable", "non-vulnerable" or "unset"