Skip to content

Mines Game Responses

This guide explains how to read the scenario field returned by placeBet for a mines-style game, and how to use the loadConfig data to display cashout multipliers.

Overview

Mines is a multi-step wager game. The player places an initial bet with a chosen mine count, then reveals tiles on a grid. Each safe tile increases the cashout multiplier. Hitting a mine loses everything. The player can cash out at any point between picks.

The game uses wager stake features (wagerStakeFeatures). Each pick is a separate feature where the entry's win is the absolute cashout multiplier relative to the original stake. This avoids compounding rounding errors across many picks.

Sending a Pick Position

Every placeBet call for a mines game must include a pickPosition in additionalData. This tells the engine which tile the player selected:

typescript
// Initial bet — select mine count via featureToBuy + first tile pick
await placeBet({
  backendURL,
  token,
  stake: 100,
  featureToBuy: 'mines_10',
  additionalData: { pickPosition: 7 },
});

// Continuation — subsequent tile picks
await placeBet({
  backendURL,
  token,
  playerChoiceIndex: 0,
  additionalData: { pickPosition: 12 },
});

WARNING

Omitting pickPosition will return an error. Every bet in a mines game requires a tile selection.

The Board Array

The engine's game layer enriches each placeBet response with a board array representing the full grid state. Each element is one of:

ValueMeaning
nullUnknown — tile has not been revealed
'picked'Safe — player picked this tile and survived
'bomb'Mine — either the player hit it, or it was revealed on game over
typescript
interface IMinesScenario {
  board: (null | 'bomb' | 'picked')[];  // Length = grid size (e.g. 25 for 5x5)
  currentMultiplier: number;            // Current cashout multiplier
  mineCount: number;                    // Mines on this board
}

Reading the Board

typescript
const response = await placeBet({
  backendURL, token, stake: 100,
  featureToBuy: 'mines_10',
  additionalData: { pickPosition: 7 },
});

if (response.success) {
  const { scenario, engineData } = response.result.result;
  const { board, currentMultiplier, mineCount } = scenario as IMinesScenario;

  // board[7] is 'picked' (safe) or 'bomb' (mine hit)
  if (board[7] === 'picked') {
    console.log(`Safe! Cashout: ${currentMultiplier}x`);
    // engineData.canCollect === true — collect or continue
  } else {
    console.log('Boom!');
    // All mine positions are revealed in the board
  }
}

Game Over Reveal

When the round ends (mine hit or auto-cashout on final safe tile), all mine positions are revealed in the board array. Tiles the player never picked remain null unless they contain a mine, in which case they become 'bomb'.

typescript
// After hitting a mine, the board might look like:
// [null, 'picked', null, 'bomb', 'picked', null, null, 'bomb', null, ...]
//         ↑ safe      ↑ mine     ↑ safe               ↑ the hit

Player Actions After a Safe Pick

  1. Collect — cash out at the current multiplier
  2. Continue — reveal another tile for a higher multiplier
typescript
// Player wants to continue
const next = await placeBet({
  backendURL,
  token,
  playerChoiceIndex: 0,
  additionalData: { pickPosition: 12 },
});

if (next.success) {
  const { board, currentMultiplier } = next.result.result.scenario as IMinesScenario;

  if (board[12] === 'picked') {
    console.log(`New cashout: ${currentMultiplier}x`);
  } else {
    console.log('Mine hit — round over');
  }
}

Loading Game Config

The loadConfig response provides grid size, mine count range, and multiplier tables:

typescript
const config = configResponse.result.config;

config.gameType;       // 'mines'
config.gridSize;       // Total tiles (e.g. 25 for 5x5)
config.minMines;       // Minimum mine count (e.g. 1)
config.maxMines;       // Maximum mine count (e.g. 24)

Mine Count Selection via Buy Features

Every mine count is a buy feature — there is no default basegame. Every opening placeBet 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';

// Player selects 10 mines — first pick at position 3
const response = await placeBet({
  backendURL,
  token,
  stake: 100,
  featureToBuy: 'mines_10',
  additionalData: { pickPosition: 3 },
});

featureToBuy is required on the opening bet; subsequent picks within the same round are plain placeBet calls (the engine tracks the active feature).

Multi-Step Game Flow

Mines is a multi-step wager stake game. The typical flow:

  1. Initial betplaceBet with pickPosition and featureToBuy for mine count (required)
  2. Pick loop — each placeBet with playerChoiceIndex: 0 and new pickPosition
  3. End — player collects, hits a mine, or reveals all safe tiles (auto-cashout)
placeBet (pick 1) → [safe] → placeBet (pick 2) → [safe] → ... → collect
                          → [mine] → round ends (loss, mines revealed)

engineData.canCollect is true while the player has an active cashout. engineData.inProgress becomes false when the player hits a mine or reveals all safe tiles.

How the Math Works

The cashout multiplier at pick k is: targetRTP / P(survive k picks)

Where P(survive k picks) = product of (safeTiles - i) / (gridSize - i) for i = 0 to k-1.

The house edge is taken once on the first pick. After that, each step's survive probability matches the fair odds exactly. This means the RTP is the same regardless of when the player collects.

Provably Fair Verification

A PF mines round commits to every entry draw (one per pick) and to the seeded mine-reveal shuffle. Use pfVerify to have the engine replay the round and confirm both. The bomb-reveal shuffle is seeded from the PF commitment itself — (serverSeedHash, clientSeed) — so the verifier reproduces the same bomb layout without any extra inputs.

Request

Replay every pick — including the opening one — by passing each step's pickPosition in actions, in order. Continuation steps' playerChoiceIndex only matters when the engine surfaced a playerChoice (e.g. risk pick mid-round); the common case is playerChoiceIndex: 0.

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

const reply = await pfVerify({
  backendURL,
  token,
  serverSeed: pf.revealedServerSeed,
  clientSeed: pf.clientSeed,
  stake: 100,
  featureToBuy: 'mines_10',     // every mines round opens on a buy feature
  actions: [
    { pickPosition: 7 },        // opening pick
    { pickPosition: 12 },       // continuation
    { pickPosition: 18 },       // continuation that hit a mine — round ends here
  ],
});

Reading the response

steps[i].scenario is an IMinesScenario for pick i, including the cumulative board and (on the terminal step) the revealed mines. Compare against the round's recorded scenarios:

  • rngData — array-equal against the live round's pf.rngData. Each pick contributes one int row (the weighted entry draw).
  • steps[i].scenario.board — element-wise against the live board at each step; terminal-step bomb positions match exactly.
  • steps[steps.length - 1].totalWin (multiplier) — equal to the live round's terminal totalWin.
typescript
if (!reply.success) return; // reply.error has the failure
const { rngData, steps } = reply.result;

const rngOk = rngData.every((row, i) => deepEqual(row, pf.rngData[i]));

const terminal = steps[steps.length - 1];
const liveBoard = lastLiveResult.scenario.board;
const boardOk = liveBoard.every((tile, i) => tile === terminal.scenario.board[i]);
const cashoutOk = terminal.totalWin === lastLiveResult.totalWin;