logo

Babylon.js Market

PokerAITilt

tabletop-casino · poker-ai

PokerAITilt

1
Unlock

Install with the CLI:

bjs download PokerAITilt

PokerAITilt

An emotional poker NPC: it tightens up when it's winning and plays angry after it loses, plugged into PokerBetting as a strategy.

What it does

PokerAITilt is a poker personality. It plays two games and switches between them on its mood.

  • Calm game — conservative. Raise the premiums, call the strong hands, respect pot odds, fold the trash. (Plays like a tight regular.)
  • Tilted game — aggressive. Raise far wider, revenge-bluff with nothing, call down weak.

A per-seat tilt level (0 calm, 5 steaming) drives the switch. Losing chips between hands pushes the level up — by 2 on a loss over 50 chips, by 1 otherwise — and keeps the seat hot for level * 2 hands. With no further loss the hot counter winds down, and when it reaches zero the level cools by one. So a beaten seat steams, then slowly settles. At level 2 or higher it plays the tilted game; below, the calm one.

updateTilt(state, ctx) does the read: it compares ctx.playerStack against the last stack it saw and returns the next TiltState. It never mutates the state passed in:

PokerAITilt.core.ts
export function updateTilt(state: TiltState, ctx: NpcContext): TiltState {
  let { level, lastStack, handsOnTilt } = state;

  if (lastStack > 0 && ctx.playerStack < lastStack) {
    // Lost chips — tilt increases.
    const loss = lastStack - ctx.playerStack;
    level = Math.min(5, level + (loss > 50 ? 2 : 1));
    handsOnTilt = level * 2; // stay tilted for a while
  } else if (handsOnTilt > 0) {
    handsOnTilt--;
    if (handsOnTilt <= 0) {
      level = Math.max(0, level - 1); // cool down
    }
  }
  lastStack = ctx.playerStack;

  return { level, lastStack, handsOnTilt };
}

decideTilt(available, ctx, state, rng) runs one turn: update the memory, route to the calm or tilted personality by the new level, return the chosen action and the next state so the caller threads it into the following hand. With no context it falls back to a random legal action and leaves the state untouched.

It reads hand equity, starting-hand tiers, and the weighted action draw off PokerAI (which reads PokerHandEval). The one random step — the weighted action pick — takes its generator as the rng parameter, so a test can pin a draw.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Fastest path

bjs download scene PokerAITilt

Pulls this exact scene into your project and runs it — no copy-paste. Add --all to grab the components and assets it needs too.

Or build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add PokerAITilt
    bjs download PokerAITilt

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "TiltBrain": {
    "components": {
      "PokerAITilt": {
        "seatIds": ["npc0", "npc1"]
      }
    }
  }
}

The example scene pairs it with PokerHandEval and PokerBetting. On the first frame the System finds the PokerBetting component and registers a tilt strategy into its strategy table for each named seat. From then on, every time the betting director needs that seat to act, it calls the closure — which threads the seat's live memory through decideTilt and emits poker.tilt.acted.

Leave seatIds empty to claim every non-human seat the table seats:

JSON
"PokerAITilt": {}

Props

PropTypeDefaultMeaning
seatIdsstring[][]Seats this personality plays. Empty means every non-human seat in PokerBetting.

Events

DirectionNameConstPayload
inpoker.tilt.resetPokerAITiltInputEvents.RESET{} — cools every tracked seat back to calm.
outpoker.tilt.actedPokerAITiltEvents.ACTED{ playerId, action, level } after a tilt seat acts.

poker.tilt.acted carries the seat's tilt level after the turn, so a HUD can show who's steaming. Emit poker.tilt.reset between matches to wipe the emotional memory.

Dependencies

  • PokerBetting — the betting director the personality plugs into. It owns ActionType, NpcStrategy, and NpcContext, and reads the registered strategy callback on each NPC turn. PokerAITilt registers into its strategy table; the director never imports a personality.
  • PokerAI — supplies classifyStartingHand, estimateEquity, pickAction, weightedPick, and shortHand, which the calm and tilted personalities read.
  • PokerHandEval — the evaluator PokerAI's equity reads, transitively required by the table.

Notes

  • The emotional memory is non-serializable, so it lives on the System in a per-seat map keyed by seat id — never on the Component. The Component carries only seatIds, which serializes cleanly.
  • Registration is one-shot: the System pushes its strategies into PokerBetting once that component exists, then stops. A PokerAITilt entity added after registration is not claimed — seat the personality before the first deal.
  • level caps at 5 and never cools below 0. A level-1 seat with no hot hands left reads calm, not cooling down — the cooling label only shows while the hot counter is still running down.
  • decideTilt returns the new state rather than mutating the old one; thread it forward each turn or the seat never tilts. The System does this for you; a host calling the core directly must store the returned state.

More like this

PokerAI
PokerBetting
PokerHandEval
AIGoalSeek
AIKick
AIZone
AiPilot
BallPossession
BallPursuit
CardFan
DeckDealer
Enemy
EnemySpawner
FreighterChain
LineOfSight
PokerAIBill
PokerAICali
PokerAIRandy
PokerAIShark
PokerAISurge
PokerHUD
PokerInput
PokerMultiplayer
PokerSeating
PokerTableCards
PokerTableDirector
TurnPacer
WaveDirector

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search