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.
const engine = createBridgeEngine(
createBridgeContext({ teachingMode: "h", locale: "en" })
);
engine.getCtx(); // => { teachingMode: "h", locale: "en" }Exports
| Export | Kind | Description |
|---|---|---|
createBridgeContext(partial?) | function | Fills teachingMode: "auction" and locale: "en" for omitted fields |
createBridgeEngine(ctx) | function | Builds (or returns the memoized) engine for ctx.teachingMode and ctx.locale |
BridgeEngine | type | Return type of createBridgeEngine |
BridgeContext | type | Engine input |
AuctionInput, ContractDisplay, ContractSource | types | Capability axes derived from the config |
ContextModuleConfig, HandsModuleConfig, AuctionModuleConfig, NotationModuleConfig | types | Per-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.
const trumpGoal = createBridgeEngine(
createBridgeContext({ teachingMode: "show-trump-goal" })
);
trumpGoal.ctx.auctionInput; // => "trump"
trumpGoal.ctx.hasInteractiveAuction; // => false| Axis | Values | Rule |
|---|---|---|
AuctionInput | "sequence", "trump", "contract", null | auctions 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.ctx.variant; // => "classic"
engine.ctx.auctionInput; // => "contract"
engine.ctx.contractDisplay; // => "full"
engine.ctx.userControlsContract; // => true
engine.ctx.validDenominations; // => ["NT"]
engine.ctx.visibility.partnerHandDuringAuction; // => true| Member | Type | Description |
|---|---|---|
variant | SuitVariant | "classic" or "petit-bridge" |
dummyVisible | boolean | Dummy hand shown during play |
auctionsEnabled | boolean | auctions !== false |
validDenominations | readonly Denomination[] | Biddable denominations for the mode |
auctionInput | AuctionInput | Kind of auction input the editor expects |
contractDisplay | ContractDisplay | How the contract surface renders |
isObservation | boolean | Pre-recorded sequence the user watches (sequence mode) |
userControlsContract | boolean | The user picks the contract (minibridge modes) |
hasInteractiveAuction | boolean | An auction phase exists before play |
cardSet | CardSet | Deck the mode uses (CardSet comes from @workspace/bridge/constants) |
playContextInfos | readonly PlayContextInfo[] | Deal facts exposed during play, in order |
honorMetric | HonorMetric | "hcp" or "tht" |
visibility.scoring / .vulnerability / .auction / .contract | boolean | All equal to auctionsEnabled |
visibility.partnerHandDuringAuction | boolean | Partner hand visible during the auction |
visibility.hcp | boolean | auctionsEnabled && dummyVisible |
The injected shape is ContextModuleConfig:
Prop
Type
engine.hands
Bound to the mode's CoreConfig. Dedicated page: Hands.
| Member | Description |
|---|---|
config | The 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.
| Member | Description |
|---|---|
isEnabled | auctions !== 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.
| Member | Description |
|---|---|
variant / locale | Bound 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 |
validRanks | Ranks of the mode's deck (getter) |
engine.play
Bound to validDenominations and validRanks. Dedicated page: Play.
| Member | Description |
|---|---|
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.
| Member | Description |
|---|---|
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.
| Member | Description |
|---|---|
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.
| Member | Description |
|---|---|
formatContract(contract) | DisplayContract → "3NT", "4SX", … ("" for null) |
engine.schemas
Namespaces over the schemas/* files. Dedicated page: Schemas.
| Namespace | Source |
|---|---|
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
| Member | Description |
|---|---|
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, isBid | Type guards |
DEFAULT_RANK_ORDER | A…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 |
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
| Member | Description |
|---|---|
HCP_VALUES | { A: 4, K: 3, Q: 2, J: 1 } |
THT_VALUES | { A: 1, K: 1, Q: 0.5 } |
computeHandHCP(hand) | High-card points |
honorComputers | Record<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" |