Plinko Responses
This guide explains how to read the scenario field returned by placeBet for a plinko game, and how to use the loadConfig data to map bucket indexes to multiplier values.
Overview
Plinko is a ball-drop game where a ball falls through rows of pegs into prize buckets. Each combination of row count (8–16) and risk level (low/medium/high) produces a different set of bucket multipliers. The game uses buy features to let the player select a row/risk combination.
The Plinko Scenario
When placeBet returns a plinko result, gameResult.scenario contains:
interface IPlinkoScenario {
bucket: number; // Bucket index the ball landed in (0 to rows)
rows: number; // Number of rows played (8–16)
risk: string; // Risk level: "low" | "medium" | "high"
multiplier: number; // Payout multiplier for this bucket
}const response = await placeBet({ backendURL, token, stake, featureToBuy: 'rows_8_risk_low' });
if (response.success) {
const { scenario, totalWin } = response.result.result;
const plinko = scenario as IPlinkoScenario;
console.log(`Rows: ${plinko.rows}, Risk: ${plinko.risk}`);
console.log(`Ball landed in bucket ${plinko.bucket}`);
console.log(`Multiplier: ${plinko.multiplier}x`);
console.log(`Win: ${totalWin * stakeAmount}`);
}Bucket Multipliers from loadConfig
The loadConfig response includes a buckets object in the config, containing pre-computed multiplier arrays for every row/risk combination. Use this to render the bucket labels in your UI before the player drops the ball.
const configResponse = await loadConfig({ backendURL, token });
if (configResponse.success) {
const buckets = configResponse.result.config.buckets as Record<string, Record<string, number[]>>;
// buckets["8"]["low"] → [2.48, 1.48, 1.08, 0.91, 0.86, 0.91, 1.08, 1.48, 2.48]
// buckets["8"]["medium"] → [5.08, 1.99, 1.13, 0.83, 0.75, 0.83, 1.13, 1.99, 5.08]
// buckets["8"]["high"] → [9.97, 2.58, 1.14, 0.73, 0.63, 0.73, 1.14, 2.58, 9.97]
// buckets["16"]["high"] → [597.89, 64.19, 14.36, 4.95, 2.27, 1.26, ...] (17 buckets)
}Each array has rows + 1 entries (one per bucket). Multipliers are symmetric — edge buckets pay more, center buckets pay less.
Rendering bucket labels
function renderBuckets(rows: number, risk: string, buckets: Record<string, Record<string, number[]>>) {
const mults = buckets[String(rows)][risk];
// mults.length === rows + 1
for (let i = 0; i < mults.length; i++) {
renderBucketLabel(i, `${mults[i]}x`);
}
}Selecting Row/Risk via Buy Features
Every row/risk combination is a buy feature — there is no default basegame. Every placeBet call must include a featureToBuy; a call without one will be rejected with PARAMETERMISSING ("This game requires a buy feature. Provide featureToBuy.").
import { placeBet } from '@hizi.io/engine-sdk';
// Buy features are named like "rows_12_risk_high"
const featureId = `rows_${selectedRows}_risk_${selectedRisk}`;
const response = await placeBet({
backendURL,
token,
stake,
featureToBuy: featureId,
});Animating the Ball Drop
The scenario gives you the final bucket index and the rows count. To animate the ball path, simulate the descent:
function animateBallDrop(rows: number, bucket: number) {
// The ball makes `rows` binary decisions (left/right).
// The bucket index equals the number of right turns.
// Generate a random path that ends at the target bucket:
const rights = bucket; // Total right turns needed
const lefts = rows - bucket; // Total left turns needed
// Shuffle to create a random-looking path to the known destination
const moves = [...Array(rights).fill('right'), ...Array(lefts).fill('left')];
shuffleArray(moves);
// Animate each peg bounce
let x = 0;
for (let row = 0; row < rows; row++) {
if (moves[row] === 'right') x++;
animateBounce(row, x);
}
// Ball lands in bucket at index x (=== bucket)
}Single-Result Game
Plinko is a single-result game — each placeBet returns a complete result. engineData.inProgress will be false after each spin, and there are no multi-step scenarios or continuation calls needed.
const response = await placeBet({
backendURL,
token,
stake,
featureToBuy: featureId,
});
if (response.success) {
const { scenario, engineData, totalWin } = response.result.result;
// engineData.inProgress === false — round is complete
// No need to call placeBet again
}Complete Example
import { login, loadConfig, placeBet } from '@hizi.io/engine-sdk';
interface IPlinkoScenario {
bucket: number;
rows: number;
risk: string;
multiplier: number;
}
// After login and loadConfig...
const config = configResponse.result.config;
const buckets = config.buckets as Record<string, Record<string, number[]>>;
// Player selects 12 rows, high risk
const rows = 12;
const risk = 'high';
const featureId = `rows_${rows}_risk_${risk}`;
// Show bucket multipliers before spin
const mults = buckets[String(rows)][risk];
renderBuckets(mults); // [166.95, 25.22, 6.42, 2.55, 1.32, 0.83, 0.63, 0.83, 1.32, 2.55, 6.42, 25.22, 166.95]
// Place the bet
const response = await placeBet({
backendURL,
token,
stake,
featureToBuy: featureId,
});
if (response.success) {
const plinko = response.result.result.scenario as IPlinkoScenario;
const totalWin = response.result.result.totalWin;
// Animate ball drop through 12 rows into bucket
await animateBallDrop(plinko.rows, plinko.bucket);
// Show result
highlightBucket(plinko.bucket, `${plinko.multiplier}x`);
showWin(totalWin * stake);
}Provably Fair Verification
Plinko is one-shot: a single weighted entry draw lands the ball in its bucket. Use pfVerify to replay that draw from the committed seeds.
Request
import { pfVerify } from '@hizi.io/engine-sdk';
const reply = await pfVerify({
backendURL,
token,
serverSeed: pf.revealedServerSeed,
clientSeed: pf.clientSeed,
stake: 100,
featureToBuy: 'rows_12_high', // the round's row-count + risk package
});Reading the response
One-shot — steps has one entry.
rngData— oneintrow (the entry draw). Array-equal against the livepf.rngData.steps[0].scenario.bucket(or whatever field your plinko build exposes) — equals the live bucket.steps[0].totalWin— equals the live multiplier.
if (!reply.success) return;
const { rngData, steps } = reply.result;
const rngOk = deepEqual(rngData, pf.rngData);
const bucketOk = deepEqual(steps[0].scenario, liveScenario);Next Steps
- Response Handling —
IGameResultstructure andengineDatafields. - Slot Game Responses — Reading hizi engine slot game responses.
- Buy Features — How buy features work.