Skip to content

Mines Game Responses

This guide explains how to read the scenario field that placeBet returns for a mines-style game. It also explains 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. The player then reveals tiles on a grid. Each safe tile increases the cashout multiplier. If the player hits a mine, the player loses the entire stake. The player can cash out at any point between picks.

The game uses wager stake features (wagerStakeFeatures). Each pick is a separate feature. 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

The engine returns an error if a call omits pickPosition. Every bet in a mines game requires a tile selection.

The Board Array

The engine's game layer adds a board array to each placeBet response. This array shows the state of the full grid. Each element is one of the following:

ValueMeaning
nullUnknown. The engine has not revealed the tile.
'picked'Safe. The player picked this tile and survived.
'bomb'Mine. The player hit this tile, or the engine reveals it when the round ends.
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 (a mine hit, or auto-cashout on the final safe tile), the engine reveals all mine positions in the board array. Tiles that the player never picked remain null. If an unpicked tile contains a mine, it becomes '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 call must include a featureToBuy. If a call omits it, the engine returns 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 },
});

The opening bet requires featureToBuy. Subsequent picks in 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 bet: placeBet with pickPosition and featureToBuy for the mine count (required).
  2. Pick loop: each placeBet call with playerChoiceIndex: 0 and a new pickPosition.
  3. End: the 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 engine takes the house edge once, on the first pick. After that, each step's survive probability matches the fair odds exactly. This means the RTP stays the same, regardless of when the player collects.

Provably Fair Verification

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

Request

Replay every pick, including the opening one. Pass each step's pickPosition in actions, in order. The playerChoiceIndex of a continuation step only matters when the engine surfaces a playerChoice (for example, a risk pick mid-round). In the common case, use 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: compare it array-by-array against the live round's pf.rngData. Each pick contributes one int row (the weighted entry draw).
  • steps[i].scenario.board: compare it element-by-element against the live board at each step. Terminal-step bomb positions must match exactly.
  • steps[steps.length - 1].totalWin (multiplier): compare it 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;