Skip to content

Bingo Game Responses

This guide explains how to send a Bingo round to placeBet. It explains how to read the enriched scenario the engine returns. It explains how to use the loadConfig ruleset to render the paytable. The guide covers the boards the creator ships as templates: Bingo 75, Bingo 90, and Bingo 80. It also covers any other board that a config itself describes.

Overview

Bingo is a single-call, instant-win draw game. The player buys 1–6 tickets at a chosen per-ticket stake. The engine generates the tickets. The engine draws a fixed number of balls from the pool. The engine daubs the matching numbers. The engine pays every completed pattern. The game has no continuation, no playerChoice, and no collect step. Each placeBet call returns a complete result.

There is exactly one bingo game type. Every config describes its own board: grid, number pool, balls drawn, free cells, column bands, and the full list of prize patterns. The engine plays exactly what that config describes. "Bingo 75", "Bingo 90" and "Bingo 80" are templates in the game creator. They are ordinary configs. A designer can start from a template and edit it freely. A client reads the board shape from the scenario's own rows, cols, and freeCellCount fields. This works the same way for any config.

The three boards the creator ships today:

Bingo 75Bingo 90Bingo 80
Number pool1–751–901–80
Balls drawn403536
Ticket layout5×5, B-I-N-G-O column bands, free centre3×5, one flat pool4×4, one flat pool
Numbers per ticket24 (+ free centre)1516
Prize patterns111310
Default pay modesumsumhighest
Top prize×5000 (full card)×5000 (full card)×5000 (coverall)

Any other board is equally valid. A config can state a 6×2 grid over a pool of 40 with its own patterns. The engine does not need to know about it in advance.

Tickets are independent and identically distributed. Prizes are per ticket. Therefore RTP (return to player, the average share of stakes the game pays back over time) stays constant no matter how many tickets the player buys.

Sending a Round

Send the number of tickets in additionalData. The per-ticket stake is the top-level stake field. The engine generates the tickets and draws the balls on the server. The player does not choose any numbers.

typescript
import { placeBet, bingoTicketData } from '@hizi.io/engine-sdk';

const response = await placeBet({
  backendURL,
  token,
  stake: 20,                       // per-ticket stake
  additionalData: bingoTicketData(3), // buy 3 tickets → total debit 60
});

The helper bingoTicketData(ticketCount) returns { ticketCount }. You may build that object inline instead. ticketCount defaults to 1 when omitted. It must be an integer in the range 1..maxTickets (6 by default). Otherwise the engine returns INVALIDPARAMETER.

Per-ticket vs. total stake

stake is the stake per ticket. The round debit equals stake × ticketCount. Each prize pays multiplier × stake per ticket. The maximum round exposure is 5000 × stake × ticketCount.

The Bingo Scenario

The engine enriches each placeBet response with the full round result:

typescript
interface IBingoScenario {
  gridSize: number;         // size of the number pool
  rows: number;             // ticket rows    - render the ticket
  cols: number;             // ticket columns   from these two
  freeCellCount: number;    // pre-daubed cells on a ticket
  drawCount: number;        // balls drawn per round
  payMode: 'sum' | 'highest';
  stakePerTicket: number;   // smallest currency units
  ticketCount: number;      // 1..maxTickets
  drawnNumbers: number[];   // balls drawn, in draw order
  tickets: IBingoTicketResult[];
  totalStake: number;       // stakePerTicket × ticketCount
  totalWin: number;         // summed cash across all tickets
}

interface IBingoTicketResult {
  cells: number[];          // ticket numbers, row-major; free cells are 0
  daubed: boolean[];        // parallel to cells: is each cell daubed?
  wins: IBingoPatternWin[]; // every completed pattern instance on this ticket
  ticketWin: number;        // total cash won by this ticket
}

interface IBingoPatternWin {
  pattern: string;          // stable pattern key, e.g. "fullCard", "line"
  instance: number;         // which placement of the pattern matched
  multiplier: number;       // payout multiplier for this pattern
  cells: number[];          // cell indices covered (row-major) - for highlighting
  winAmount: number;        // multiplier × stakePerTicket
}

Reading the Result

typescript
if (response.success) {
  const scenario = response.result.result.scenario as IBingoScenario;

  console.log('Balls:', scenario.drawnNumbers);
  scenario.tickets.forEach((ticket, i) => {
    console.log(`Ticket ${i + 1} won ${ticket.ticketWin}`);
    for (const win of ticket.wins) {
      console.log(`  ${win.pattern} (${win.multiplier}x) → ${win.winAmount}`);
    }
  });
  console.log('Total win:', scenario.totalWin);
}

Cell layout and states

cells is row-major: index = row * scenario.cols + col. The array is scenario.rows * scenario.cols cells long. Read rows and cols from the scenario to render the ticket. Do not assume a fixed grid width. Any pre-daubed free cell has cells[i] === 0. scenario.freeCellCount gives the number of free cells on a ticket (0 for a board with no free space). Of the three creator templates, only Bingo 75 has a free cell (the centre cell, cells[12] === 0, 25 cells total). Bingo 90 (15 cells) and Bingo 80 (16 cells) have none.

cells[i]daubed[i]MeaningDisplay
0trueFree cell (pre-daubed, if the board has one)Star / pre-daubed
numbertrueDaubed (its ball was drawn)Highlighted
numberfalseNot yet daubedDefault

To highlight a winning pattern, use win.cells. It lists the exact cell indices that instance covers.

Contained Patterns Stack

The engine evaluates every pattern instance independently. Overlapping patterns all pay. The amounts add together. Completing two full rows on a Bingo 90 ticket pays both single-line (bingoRow) prizes and the two-line (twoLines) prize. You see three entries in wins. A full card also completes the frame, the diagonals, and every line it contains. Each of these pays.

Paytable

Multipliers come from the published paytable. A game can override them via loadConfig.paytable (keyed by pattern key). Prizes equal multiplier × per-ticket stake.

Bingo 75 (creator template)

Pattern keyNameCellsMultiplier
lineSingle Bingo (row/column off-centre)5×0.3
lineCentreSingle Bingo Through Centre (through free centre)4×0.35
innerCornersFour Corners (inner corners)4×1
slimLSlim L7×5
xX (both diagonals)8×10
smallFrameSmall Frame8×20
bigDiamondLarge Diamond8×25
champagneChampagne Glass10 (incl. free centre)×100
cornersFrameFour Corners and Small Frame12×200
bigFrameLarge Frame16×2000
fullCardFull Card24×5000

Bingo 90 (creator template)

Pattern keyNameCellsMultiplier
colThreeThree in a Column3×0.2
rowThreeThree in a Row (no diagonals)3×0.2
squareFour in a Square (2×2)4×0.3
rowFourFour in a Row4×1
lFormL Shape5×2.5
bingoRowBingo (full row)5×10
chevronChevron5×15
plusPlus7×25
pyramidPyramid8×30
centreBlockCentre Block (3×3)9×100
twoLinesTwo Lines10×150
hollowHollow (border)12×1500
fullCardFull Card15×5000

Bingo 80 (creator template)

No published spec exists for this board. The designers built its patterns and multipliers for this task. They tuned the paytable to land close to the RTP target of the other two templates under highest payMode. Its patterns overlap heavily by design. Because of this overlap, sum mode would push RTP well over 100%.

Pattern keyNameCellsMultiplier
horizontalLineAny Line - Horizontal4×0.35
verticalLineAny Line - Vertical4×0.3
diagonalDiagonal4×1
fourCornersFour Corners4×3
centerSquareCenter Square4×5.5
block2x22x2 Block4×2.55
letterTLetter T7×16
letterXLetter X8×22
frameLetter O / Frame12×150
coverallCoverall (Full House)16×5000

The loadConfig Ruleset

A bingo game config ships an IBingoLoadConfig:

typescript
interface IBingoLoadConfig {
  // --- the board (all required: there is no preset to fall back on) ---
  rows: number;                           // ticket rows
  cols: number;                           // ticket columns
  gridSize: number;                       // number pool, 1..gridSize
  drawCount: number;                      // balls drawn per round; a direct RTP lever
  patterns: IBingoPatternConfig[];         // prize patterns (cell masks + multipliers)
  // --- optional ---
  freeCells?: number[];                   // pre-daubed cell indices (default: none)
  columnRanges?: { start: number; end: number }[];  // 1 flat band, or exactly `cols` bands
  payMode?: 'sum' | 'highest';            // how completed patterns pay (default 'sum')
  paytable?: Record<string, number>;      // multiplier-only overrides by pattern key
  maxTickets?: number;                    // cap on tickets per round (defaults to 6)
}

The whole board is data-driven. patterns defines the pattern set. The creator's pattern editor produces this set. paytable overrides individual multipliers on top of it. drawCount sets how many balls the engine draws from the fixed pool. This is a direct lever for RTP.

columnRanges controls how the engine generates tickets. One range means a single flat draw for the whole ticket (Bingo 90 / 80). Exactly cols ranges means each column draws only from its own band (Bingo 75's B 1–15 … O 61–75). Each band must hold at least as many numbers as its column has drawable cells. The bands' widths must total no more than gridSize. The engine rejects a config that breaks either rule at config load, not at bet time. Stake limits and the exposure cap come from gameSettings on the placeBet request, not from this config. The stake ladder and minStake apply to the per-ticket stake. maxStake caps the whole round (stake × ticketCount).

Payout modes

  • sum (default): every completed pattern instance pays. The amounts add together (contained patterns stack, as described above).
  • highest: only the single highest-value completed instance on each ticket pays. The other instances still appear in wins, with winAmount: 0. The scenario's payMode field records which mode ran.

Single-Result Game

Bingo is a single-result game. engineData.inProgress is false after every result. The client needs no continuation or collect calls.

typescript
const { scenario, engineData, totalWin } = response.result.result;
// engineData.inProgress === false - round is complete

How the Math Works

The engine draws balls without replacement, uniformly, from the full pool (40 of 75, or 35 of 90). Consider a pattern instance that needs k specific ticket numbers (the free centre does not count). The probability that the engine draws all k numbers is the hypergeometric coverage:

P(all k drawn) = C(N − k, D − k) / C(N, D)

N is the pool size. D is the draw count. By linearity of expectation, the per-ticket RTP is the sum over every pattern instance of P(all its cells drawn) × multiplier. This sum does not depend on ticket count or on how patterns overlap. The Bingo 90 full card evaluates to 1 in 14,099,900.1. This matches the client's displayed Hauptgewinnchance exactly.

RTP is therefore a property of the paytable and the draw, not of any weighted entry pool. The default paytable lands at approximately 93.9% (Bingo 75) and approximately 93.4% (Bingo 90) in sum mode. Tune loadConfig.paytable or drawCount to reach a specific certified target.

RTP in highest mode

In highest mode, RTP is not the per-pattern sum. The engine pays one instance instead of all of them. RTP still has an exact closed form, so no simulation is needed. Every ticket number is equally likely to be drawn. Therefore the set of daubed cells is a uniformly random subset of the ticket's c non-free cells. The size of this subset is the hypergeometric daub count. So:

RTP = Σ over daubed-subsets B of the c cells P(daubed = B) × maxMult(B)

maxMult(B) is the largest multiplier among patterns whose cells all lie in B. P(daubed = B) = P(count = |B|) / C(c, |B|). This is a finite sum over 2^c subsets (2¹⁶ for Bingo 80, 2¹⁵ for Bingo 90, 2²⁴ for Bingo 75). The engine exposes this calculation as analyticRtpHighest(def).

Bingo 80 ships with highest payMode by default. Its patterns overlap heavily, so sum mode would run well over 100%. Bingo 80 evaluates to approximately 94.3%, the closest of the three to the other templates' target. Bingo 90 and Bingo 75 normally ship sum mode. Run as a hypothetical under highest mode, their own paytables evaluate to 68.7% and 81.3% respectively. Both figures match a Monte-Carlo cross-check.

Provably Fair Verification

Bingo is one-shot. The ticket generation and the ball draw form a single certified-RNG call sequence. The round replays exactly from its seeds. Use pfVerify to reproduce the tickets, the draw, and the payouts.

Pass the ticket count

Send the round's stake (per-ticket) and the ticketCount the player bought. The generated tickets and the draw are functions of the RNG sequence. The win amounts scale with the stake.

Request

typescript
import { pfVerify } from '@hizi.io/engine-sdk';

const reply = await pfVerify({
  backendURL,
  token,
  serverSeed: pf.revealedServerSeed,
  clientSeed: pf.clientSeed,
  stake: 20,        // per-ticket stake from the round
  ticketCount: 3,   // tickets bought on the round
});

Reading the response

Bingo is one-shot. steps has one entry:

  • rngData: the certified draw calls (one getDoubles per ticket, then one for the balls). Check this array for an exact match against the live pf.rngData.
  • steps[0].scenario.tickets / drawnNumbers: check these for an element-wise match against the live scenario.
  • steps[0].totalWin: this equals the live round multiplier.
typescript
if (!reply.success) return;
const { rngData, steps } = reply.result;
const rngOk = deepEqual(rngData, pf.rngData);
const drawOk = deepEqual(steps[0].scenario.drawnNumbers, liveScenario.drawnNumbers);
const winOk = steps[0].totalWin === liveResult.totalWin;

Next Steps