Path
Primitive

Multi-step progression — pick tiles, avoid hazards, cash out anytime.

Player picks positions step by step. Each safe pick increases the multiplier. Hit a hazard and lose everything, or cash out early. Good for mines, towers, crossy road.

How It Works

You define:

Grid size, number of hazards, replacement mode

Platform handles:

Hazard placement, multiplier calculation, 97% RTP

Game Config

1window.GAME_CONFIG = {
2 primitive: 'path',
3 totalChoices: 25, // Grid size (options per step)
4 losingChoices: 3, // Number of hazards (mines)
5 replacement: false, // true = constant odds, false = shrinking pool
6 maxSteps: 24 // Optional — max safe picks
7};

Config Parameters

ParameterTypeDescription
totalChoicesnumberTotal positions to choose from (min 2)
losingChoicesnumberNumber of hazards/mines (min 1, < totalChoices)
replacementbooleantrue = constant odds each step, false = odds change as safe tiles are used
maxStepsnumberOptional. Default: 50 (replacement) or totalChoices - losingChoices (no replacement)

Replacement Modes

replacement: true

Hazards are re-randomized every step — same odds each time. Good for endless/infinite games.

P(safe) = (N - M) / N each step

Example: 25 tiles, 3 mines

Step 1: 22/25 = 88.0% safe

Step 2: 22/25 = 88.0% safe

Step 3: 22/25 = 88.0% safe

Odds stay constant forever

replacement: false

Safe tiles are removed from the pool — odds shift as safe positions are used up. Good for mines/towers.

P(safe step k) = (N-M-(k-1)) / (N-(k-1))

Example: 25 tiles, 3 mines

Step 1: 22/25 = 88.0% safe

Step 2: 21/24 = 87.5% safe

Step 3: 20/23 = 87.0% safe

Odds decrease as safe tiles shrink

PostMessage API

Path games use three message types: start, reveal, and cashout.

1. Start a Game

1parent.postMessage({
2 type: 'pathStart',
3 id: Date.now(), // Unique request ID
4 stake: window.GAME_STAKE,
5 currency: window.GAME_CURRENCY
6}, '*');
7
8// Response (matched by id):
9// {
10// sessionId: "abc123", — use this for reveal/cashout
11// positionHash: "fa3b...", — provably fair commitment
12// gridSize: 25,
13// mines: 3,
14// maxSteps: 22,
15// replacement: false,
16// multipliers: [ — full table for UI display
17// { step: 1, multiplier: 1.03, survivalProb: 0.88 },
18// { step: 2, multiplier: 1.07, survivalProb: 0.88 },
19// ...
20// ],
21// stake: 0.01,
22// stakeUsd: 10,
23// currency: "USDT"
24// }

2. Reveal a Tile

1parent.postMessage({
2 type: 'pathReveal',
3 id: Date.now(),
4 sessionId: state.sessionId,
5 position: 5 // 0-indexed tile position
6}, '*');
7
8// Response if SAFE:
9// {
10// survived: true,
11// step: 1,
12// multiplier: 1.03,
13// potentialPayout: 10.30,
14// potentialPayoutUsd: 10.30,
15// nextMultiplier: 1.07,
16// nextSurvivalProb: 0.88,
17// revealedTiles: [5],
18// canCashout: true,
19// canContinue: true,
20// gameOver: false
21// }
22
23// Response if HIT HAZARD:
24// {
25// survived: false,
26// hitMine: true,
27// multiplier: 0,
28// payout: 0,
29// minePositions: ..., — see format below
30// serverSeed: "abc...", — for verification
31// gameOver: true
32// }
33//
34// minePositions format depends on replacement mode:
35// replacement: false → flat array: [3, 12, 19]
36// replacement: true → 2D array (one set per step):
37// [[3, 12], [7, 19], [1, 22], ...]
When replacement: true, minePositions is a 2D array — each inner array holds the hazard positions for that step. Always check Array.isArray(minePositions[0]) before iterating to handle both formats correctly.

3. Cash Out

1parent.postMessage({
2 type: 'pathCashout',
3 id: Date.now(),
4 sessionId: state.sessionId
5}, '*');
6
7// Response:
8// {
9// success: true,
10// step: 5,
11// multiplier: 1.42,
12// payout: 0.0142,
13// payoutUsd: 14.20,
14// profit: 0.0042,
15// profitUsd: 4.20,
16// minePositions: ..., — see minePositions note above
17// serverSeed: "abc...", — for verification
18// gameOver: true
19// }

Active Sessions

Path games are stateful. If a player leaves mid-game and comes back, the platform automatically restores their session. Listen for the active session message:

1// Platform sends this on load if there's an active game
2window.addEventListener('message', (e) => {
3 if (e.data.type === 'sessionResume' && e.data.primitive === 'path') {
4 const s = e.data.session;
5 // s.sessionId, s.currentStep, s.multiplier, s.revealedTiles, etc.
6 // Restore your UI to match the saved state
7 }
8});

Provably Fair

Mine positions are determined before the game starts:

WhenWhat Happens
Game startServer generates serverSeed + mine positions, sends positionHash (commitment)
Game endServer reveals serverSeed + minePositions
VerificationSHA256(minePositions + serverSeed) === positionHash

Balance Reveal Timing

The player's visible balance updates as soon as pathCashout (or the auto-cashout via pathReveal) completes on the server. To show the win animation before the balance updates, send balanceReveal when your celebration animation finishes.

1// In your cashout response handler, after playing the win animation:
2parent.postMessage({ type: 'balanceReveal' }, '*');
A 30-second fallback fires automatically if you never send balanceReveal. Losses (bomb hits) never queue an update since there is no payout — no signal needed.

Complete Template

1<!DOCTYPE html>
2<html>
3<head><title>My Mines Game</title></head>
4<body>
5 <button onclick="startGame()">NEW GAME</button>
6 <div id="grid"></div>
7 <button onclick="cashout()">CASH OUT</button>
8 <div id="status"></div>
9
10 <script>
11 window.GAME_CONFIG = {
12 primitive: 'path',
13 totalChoices: 25,
14 losingChoices: 3,
15 replacement: false
16 };
17
18 var sessionId = null;
19 var pendingRequests = new Map();
20 var requestId = 0;
21
22 // Request/response helper (id-based matching)
23 function sendToParent(data) {
24 return new Promise(function(resolve, reject) {
25 var id = ++requestId;
26 pendingRequests.set(id, { resolve: resolve, reject: reject });
27 var payload = Object.assign({}, data, { id: id });
28 if (window.TEST_MODE) {
29 payload.gameConfig = window.GAME_CONFIG_FOR_API;
30 }
31 parent.postMessage(payload, '*');
32 setTimeout(function() {
33 if (pendingRequests.has(id)) {
34 pendingRequests.delete(id);
35 reject(new Error('Timeout'));
36 }
37 }, 30000);
38 });
39 }
40
41 async function startGame() {
42 try {
43 var d = await sendToParent({
44 type: 'pathStart',
45 stake: window.GAME_STAKE || 1,
46 currency: window.GAME_CURRENCY || 'USDT'
47 });
48 if (d.error) return alert(d.error);
49 sessionId = d.sessionId;
50 renderGrid(d.gridSize);
51 } catch (err) { alert(err.message); }
52 }
53
54 async function pickTile(position) {
55 if (!sessionId) return;
56 try {
57 var d = await sendToParent({
58 type: 'pathReveal',
59 sessionId: sessionId,
60 position: position
61 });
62 if (d.survived) {
63 markSafe(position);
64 updateMultiplier(d.multiplier);
65 } else {
66 showMines(d.minePositions);
67 sessionId = null;
68 }
69 } catch (err) { alert(err.message); }
70 }
71
72 async function cashout() {
73 if (!sessionId) return;
74 try {
75 var d = await sendToParent({
76 type: 'pathCashout',
77 sessionId: sessionId
78 });
79 showWin(d.payout, d.multiplier);
80 sessionId = null;
81 } catch (err) { alert(err.message); }
82 }
83
84 window.addEventListener('message', function(e) {
85 var d = e.data;
86
87 // id-based response routing
88 if (d.id && pendingRequests.has(d.id)) {
89 var h = pendingRequests.get(d.id);
90 pendingRequests.delete(d.id);
91 if (d.error) h.reject(new Error(d.error));
92 else h.resolve(d);
93 return;
94 }
95
96 // Restored session after refresh
97 if (d.type === 'sessionResume' && d.primitive === 'path') {
98 sessionId = d.session.sessionId;
99 restoreGame(d.session);
100 }
101
102 if (d.type === 'stakeUpdate') {
103 // Update local stake variables
104 }
105 });
106
107 parent.postMessage({ type: 'gameReady' }, '*');
108 </script>
109</body>
110</html>

Handling minePositions

When the game ends (loss, cashout, or max steps), the server reveals minePositions. The format depends on your replacement setting:

replacement: false (flat array)

Mine positions are fixed for the entire game — one flat array of indices:

1// minePositions with replacement: false
2// → Flat array of mine indices (0-indexed)
3minePositions = [3, 12, 19]
4
5// Meaning: tiles 3, 12, and 19 are mines for the whole game.
6// These are the same regardless of which step the player was on.

replacement: true (2D array)

Mines are re-randomized per step — a 2D array where each inner array is one step's hazards:

1// minePositions with replacement: true
2// → Array of arrays, one per step played
3minePositions = [
4 [3, 12], // step 0: mines were at tiles 3 and 12
5 [7, 19], // step 1: mines were at tiles 7 and 19
6 [1, 22] // step 2: mines were at tiles 1 and 22
7]
8
9// Each step has its own independent mine placement.

Safe handler for both modes

1function showMines(minePositions, currentStep) {
2 let positions;
3 if (Array.isArray(minePositions[0])) {
4 // 2D: show mines from the step where player died
5 positions = minePositions[currentStep] || [];
6 } else {
7 // Flat: all mines are in one array
8 positions = minePositions;
9 }
10 positions.forEach(pos => markMine(pos));
11}
The multipliers array in the start response contains the full payout table. Show this to players so they can see the risk/reward at each step.