logo

Babylon.js Market

By Lawrence

5 minutes

The deck can name a card, like "queen_of_spades". But a name is not a spot on the table. Fire deck.dealRequest now and you get a list of strings with nowhere to go. No seat owns them. Nothing knows if a card belongs to the player, an opponent, or the community row. And no two cards know they should sit side by side. Before any card can move, the table needs a coordinate system. That job belongs to PokerSeating, and it draws nothing at all.

Add it to the existing Dealer entity in src/scenes/poker.json, next to the DeckDealer you added last lesson:

"Dealer": {
  "components": {
    "DeckDealer": { "playerCount": 4, "cardsPerPlayer": 2, "riverSize": 5, "localIndex": 0, "roomCode": "table-7", "handNumber": 0 },
    "PokerSeating": { "cardsPerPlayer": 2, "riverSize": 5, "npcCount": 3 }
  }
}

You give it three counts, and it gives back the whole table's geometry. The header of PokerSeating.core.ts is very clear about its job:

 * No card *flip* logic, no animation, no rendering — this is the static plan a
 * runtime reads once to know where everything goes.

A clean top-down schematic of a poker table: the human player's seat glowing at the bottom edge and opponent seats arranged in a graceful half-ellipse across the top, each seat marked with a luminous position node and faint connecting guide-lines on emerald felt, a precise diagram-over-baize look, warm gold accents on near-black.

Turning seat counts into a table layout

It all starts with buildLayout. It takes the variant counts and returns three things: one flat array of cards, a map of named groups that point into that array, and the order to deal them:

src/components/PokerSeating/PokerSeating.core.ts
export function buildLayout(variant: PokerSeatingVariant): PokerLayout {
  const groups = new Map<CardGroupId, { start: number; count: number }>();
  const allCards: DealtCardData[] = [];
  let offset = 0;

  // Player cards
  const playerCards = createDealtCardData(variant.cardsPerPlayer, 10, -4.5, 1.5);
  allCards.push(...playerCards);
  groups.set('player', { start: offset, count: variant.cardsPerPlayer });
  offset += variant.cardsPerPlayer;

  // River cards (if any)
  if (variant.riverSize > 0) {
    const riverCards = createDealtCardData(variant.riverSize, 10, 0, 1.5);
    allCards.push(...riverCards);
    groups.set('river', { start: offset, count: variant.riverSize });
    offset += variant.riverSize;
  }

  // NPC cards
  const npcSeats = computeNpcSeats(variant.npcCount);
  for (let i = 0; i < variant.npcCount; i++) {
    const seat = npcSeats[i];
    const npcCards = createCardGroup(
      variant.cardsPerPlayer,
      seat.origin, seat.target,
      seat.spread, seat.spreadAxis, seat.baseRotY,
    );
    allCards.push(...npcCards);
    const groupId: CardGroupId = `npc${i}`;
    groups.set(groupId, { start: offset, count: variant.cardsPerPlayer });
    offset += variant.cardsPerPlayer;
  }

  // Round-robin deal order: player → npc0 → npc1 → ..., then river last
  const seatGroups: { start: number; count: number }[] = [
    groups.get('player')!,
    ...Array.from({ length: variant.npcCount }, (_, i) => groups.get(`npc${i}` as CardGroupId)!),
  ];

  const dealOrder: number[] = [];
  for (let round = 0; round < variant.cardsPerPlayer; round++) {
    for (const g of seatGroups) {
      if (round < g.count) dealOrder.push(g.start + round);
    }
  }

  // River dealt last
  const riverGroup = groups.get('river');
  if (riverGroup) {
    for (let i = 0; i < riverGroup.count; i++) {
      dealOrder.push(riverGroup.start + i);
    }
  }

  return { allCards, dealOrder, groups };
}

Give it our three counts and it returns the whole table as data:

buildLayout({ cardsPerPlayer: 2, riverSize: 5, npcCount: 3 })
// → allCards: 13 cards in deal order
// → groups: who owns which stretch of that list
//   player 0–1 · river 2–6 · npc0 7–8 · npc1 9–10 · npc2 11–12

All the hands sit end-to-end in one list. groups is the directory that says where each hand begins. Ask for the player's cards and the answer is groups.player: start at 0, take two. A renderer reads that slice. It never searches the table for "the player's cards."

Continue reading

Unlock the Full Course

Every lesson, the runnable examples, and the finished build — yours to keep.

$9one-time

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search