Skip to content

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:

typescript
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
}
typescript
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.

typescript
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

typescript
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.").

typescript
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:

typescript
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.

typescript
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

typescript
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

typescript
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 — one int row (the entry draw). Array-equal against the live pf.rngData.
  • steps[0].scenario.bucket (or whatever field your plinko build exposes) — equals the live bucket.
  • steps[0].totalWin — equals the live multiplier.
typescript
if (!reply.success) return;
const { rngData, steps } = reply.result;
const rngOk = deepEqual(rngData, pf.rngData);
const bucketOk = deepEqual(steps[0].scenario, liveScenario);

Next Steps