Appearance
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 75 | Bingo 90 | Bingo 80 | |
|---|---|---|---|
| Number pool | 1–75 | 1–90 | 1–80 |
| Balls drawn | 40 | 35 | 36 |
| Ticket layout | 5×5, B-I-N-G-O column bands, free centre | 3×5, one flat pool | 4×4, one flat pool |
| Numbers per ticket | 24 (+ free centre) | 15 | 16 |
| Prize patterns | 11 | 13 | 10 |
| Default pay mode | sum | sum | highest |
| 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] | Meaning | Display |
|---|---|---|---|
0 | true | Free cell (pre-daubed, if the board has one) | Star / pre-daubed |
| number | true | Daubed (its ball was drawn) | Highlighted |
| number | false | Not yet daubed | Default |
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 key | Name | Cells | Multiplier |
|---|---|---|---|
line | Single Bingo (row/column off-centre) | 5 | ×0.3 |
lineCentre | Single Bingo Through Centre (through free centre) | 4 | ×0.35 |
innerCorners | Four Corners (inner corners) | 4 | ×1 |
slimL | Slim L | 7 | ×5 |
x | X (both diagonals) | 8 | ×10 |
smallFrame | Small Frame | 8 | ×20 |
bigDiamond | Large Diamond | 8 | ×25 |
champagne | Champagne Glass | 10 (incl. free centre) | ×100 |
cornersFrame | Four Corners and Small Frame | 12 | ×200 |
bigFrame | Large Frame | 16 | ×2000 |
fullCard | Full Card | 24 | ×5000 |
Bingo 90 (creator template)
| Pattern key | Name | Cells | Multiplier |
|---|---|---|---|
colThree | Three in a Column | 3 | ×0.2 |
rowThree | Three in a Row (no diagonals) | 3 | ×0.2 |
square | Four in a Square (2×2) | 4 | ×0.3 |
rowFour | Four in a Row | 4 | ×1 |
lForm | L Shape | 5 | ×2.5 |
bingoRow | Bingo (full row) | 5 | ×10 |
chevron | Chevron | 5 | ×15 |
plus | Plus | 7 | ×25 |
pyramid | Pyramid | 8 | ×30 |
centreBlock | Centre Block (3×3) | 9 | ×100 |
twoLines | Two Lines | 10 | ×150 |
hollow | Hollow (border) | 12 | ×1500 |
fullCard | Full Card | 15 | ×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 key | Name | Cells | Multiplier |
|---|---|---|---|
horizontalLine | Any Line - Horizontal | 4 | ×0.35 |
verticalLine | Any Line - Vertical | 4 | ×0.3 |
diagonal | Diagonal | 4 | ×1 |
fourCorners | Four Corners | 4 | ×3 |
centerSquare | Center Square | 4 | ×5.5 |
block2x2 | 2x2 Block | 4 | ×2.55 |
letterT | Letter T | 7 | ×16 |
letterX | Letter X | 8 | ×22 |
frame | Letter O / Frame | 12 | ×150 |
coverall | Coverall (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 inwins, withwinAmount: 0. The scenario'spayModefield 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 completeHow 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
Bof theccellsP(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 (onegetDoublesper ticket, then one for the balls). Check this array for an exact match against the livepf.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
- Response Handling: the
IGameResultstructure andengineDatafields. - Roulette Responses: another single-call draw game with no
.db. - Keno Responses: another draw game built on hypergeometric coverage.