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 pool6 maxSteps: 24 // Optional — max safe picks7};Config Parameters
| Parameter | Type | Description |
|---|---|---|
| totalChoices | number | Total positions to choose from (min 2) |
| losingChoices | number | Number of hazards/mines (min 1, < totalChoices) |
| replacement | boolean | true = constant odds each step, false = odds change as safe tiles are used |
| maxSteps | number | Optional. 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 ID4 stake: window.GAME_STAKE,5 currency: window.GAME_CURRENCY6}, '*');7
8// Response (matched by id):9// {10// sessionId: "abc123", — use this for reveal/cashout11// positionHash: "fa3b...", — provably fair commitment12// gridSize: 25,13// mines: 3,14// maxSteps: 22,15// replacement: false,16// multipliers: [ — full table for UI display17// { 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 position6}, '*');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: false21// }22
23// Response if HIT HAZARD:24// {25// survived: false,26// hitMine: true,27// multiplier: 0,28// payout: 0,29// minePositions: ..., — see format below30// serverSeed: "abc...", — for verification31// gameOver: true32// }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], ...]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.sessionId5}, '*');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 above17// serverSeed: "abc...", — for verification18// gameOver: true19// }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 game2window.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 state7 }8});Provably Fair
Mine positions are determined before the game starts:
| When | What Happens |
|---|---|
| Game start | Server generates serverSeed + mine positions, sends positionHash (commitment) |
| Game end | Server reveals serverSeed + minePositions |
| Verification | SHA256(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' }, '*');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: false16 };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: position61 });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: sessionId78 });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 routing88 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 refresh97 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 variables104 }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: false2// → 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: true2// → Array of arrays, one per step played3minePositions = [4 [3, 12], // step 0: mines were at tiles 3 and 125 [7, 19], // step 1: mines were at tiles 7 and 196 [1, 22] // step 2: mines were at tiles 1 and 227]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 died5 positions = minePositions[currentStep] || [];6 } else {7 // Flat: all mines are in one array8 positions = minePositions;9 }10 positions.forEach(pos => markMine(pos));11}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.