Hi/Lo Game Responses
This guide explains how to read the scenario field returned by placeBet for a Hi/Lo card game, and how to use the loadConfig data to display card odds and multipliers.
Overview
Hi/Lo is a multi-step wager game. The player is dealt a card from an infinite deck (13 equally-likely values, Ace low through King high, 4 suits). They choose Higher, Lower, or Skip:
- Higher / Lower — guess the next card's rank. Win multiplies the cashout; lose forfeits everything.
- Skip — discard the card and receive a new one. The cashout multiplier doesn't change.
The player can collect at any point between guesses.
The game uses wager features (wagerFeatures). Each round's features multiply the accumulated totalWin. The basegame entry (win: 1) establishes the initial stake as the base, and every subsequent feature multiplies from there.
The Hi/Lo Scenario
When placeBet returns a Hi/Lo result, gameResult.scenario contains:
interface IHiLoScenario {
round: number; // Current round (0 = deal, 1+ = guesses)
card: number; // Card value: 1 (Ace) through 13 (King)
suit: string; // "hearts" | "diamonds" | "clubs" | "spades"
action?: string; // "deal" (initial card) or "choose" (skip re-deal choice)
guess?: string; // "higher" | "lower" | "skip" — present on guess results
correct?: boolean; // Whether the guess was correct — present on higher/lower
multiplier?: number; // Win multiplier — present on correct guesses
}The card and suit always represent the drawn card:
- On deal/choose: the card the player sees and must decide on
- On win/lose: the next card that was drawn (the result of the guess)
- On skip: the new card dealt after skipping
Game Flow
Step 1 — Initial Deal + Player Choice
The first placeBet deals a card and presents the player's choices via playerChoice:
const response = await placeBet({ backendURL, token, stake: 100 });
if (response.success) {
const { scenario, engineData } = response.result.result;
const hilo = scenario as IHiLoScenario;
// hilo.round === 0, hilo.card === 7, hilo.suit === "hearts"
console.log(`Dealt: ${hilo.card} of ${hilo.suit}`);
// engineData.playerChoice contains the available actions:
// [
// { feature: "round_1_card_7_higher", count: 1 },
// { feature: "round_1_card_7_lower", count: 1 },
// { feature: "round_1_card_7_skip", count: 1 },
// ]
}Step 2 — Player Guesses
The player selects their choice by index via playerChoiceIndex:
// Player chooses "higher" (index 0)
const guessResponse = await placeBet({
backendURL,
token,
playerChoiceIndex: 0, // 0 = higher, 1 = lower, 2 = skip
});
if (guessResponse.success) {
const { scenario, engineData, totalWin } = guessResponse.result.result;
const hilo = scenario as IHiLoScenario;
if (hilo.correct) {
// Won — scenario shows the drawn card
console.log(`Correct! Drew ${hilo.card} of ${hilo.suit}, multiplier: ${hilo.multiplier}x`);
console.log(`Cashout: ${totalWin}x`);
// engineData.playerChoice has choices for the next card
// engineData.canCollect === true — player can collect or continue
} else {
// Lost — scenario shows the card that beat you
console.log(`Bust! Drew ${hilo.card} of ${hilo.suit}`);
// engineData.inProgress === false — round is over
}
}Skip — New Card, Same Multiplier
When the player skips, the scenario shows the new card dealt. The multiplier doesn't change:
// Player chooses "skip" (index 2)
const skipResponse = await placeBet({
backendURL,
token,
playerChoiceIndex: 2,
});
if (skipResponse.success) {
const { scenario, engineData } = skipResponse.result.result;
const hilo = scenario as IHiLoScenario;
// hilo.guess === "skip"
// hilo.card is the NEW card dealt
console.log(`New card: ${hilo.card} of ${hilo.suit}`);
// engineData.playerChoice has higher/lower/skip for the new card
// totalWin is unchanged
}Collecting
To cash out, call the collect endpoint instead of placing another bet:
const collectResponse = await collect({ backendURL, token });
// Player receives totalWin × stakeLoading Game Config
The loadConfig response provides game parameters and the full odds/multiplier tables for both round 1 (house edge) and round 2+ (fair):
const config = configResponse.result.config;
config.gameType; // 'hilo'
config.maxRounds; // Maximum guess rounds (e.g. 25)
config.tiesWin; // Whether same-value cards count as a win
config.startingCardMin; // Minimum starting card value (e.g. 4)
config.startingCardMax; // Maximum starting card value (e.g. 10)
// Round 1 odds (house edge applied)
config.round1Odds; // Array of { card, pHigher, pLower, higherMultiplier, lowerMultiplier }
// Round 2+ odds (fair, 100% RTP)
config.fairOdds; // Same structure, but with fair multipliersUsing the Odds Tables
interface IHiLoOdds {
card: number; // 1 (Ace) through 13 (King)
pHigher: number; // Probability of winning a "higher" guess
pLower: number; // Probability of winning a "lower" guess
higherMultiplier: number; // Payout multiplier for correct "higher"
lowerMultiplier: number; // Payout multiplier for correct "lower"
}
const round1Odds = config.round1Odds as IHiLoOdds[];
const fairOdds = config.fairOdds as IHiLoOdds[];
// Show odds for current card (e.g. card value 7, round 1)
const odds = round1Odds[6]; // 0-indexed: card 7 is index 6
console.log(`Higher: ${(odds.pHigher * 100).toFixed(1)}% → ${odds.higherMultiplier}x`);
console.log(`Lower: ${(odds.pLower * 100).toFixed(1)}% → ${odds.lowerMultiplier}x`);
// For round 2+, use fairOdds instead
const fair = fairOdds[6];
console.log(`Fair higher: ${fair.higherMultiplier}x`);How the Player Choice Works
Hi/Lo uses playerChoice feature awards to present the higher/lower/skip options. The playerChoiceIndex in the placeBet request maps to the awards array:
| Index | Choice | Feature pattern |
|---|---|---|
| 0 | Higher | round_{N}_card_{V}_higher |
| 1 | Lower | round_{N}_card_{V}_lower |
| 2 | Skip | round_{N}_card_{V}_skip |
TIP
Not all choices are always available. Extreme cards may only have one direction (e.g. Ace with tiesWin: false can't go lower). Check engineData.playerChoice length.
How RTP Works in Hi/Lo
The house edge is taken once, on the first guess
The target RTP (e.g. 98%) is applied at round 1 only. All subsequent guesses use fair odds (100% RTP).
For every card, the invariant holds: P(win) × multiplier = effective RTP
| Round | Effective RTP | Multiplier formula |
|---|---|---|
| Round 1 | 98% (target) | 0.98 / P(win) |
| Round 2+ | 100% (fair) | 1.0 / P(win) |
Dynamic odds — strategy doesn't change the EV
Multipliers are computed per card based on the probability of winning that specific guess. Higher probability = lower multiplier. The expected value is identical for every card:
| Card | Guess | P(win) | Multiplier | EV (Round 1) |
|---|---|---|---|---|
| A | Higher | 100% | 0.98x | 0.98 |
| 7 | Higher | 53.8% | 1.82x | 0.98 |
| K | Lower | 100% | 0.98x | 0.98 |
| 7 | Lower | 53.8% | 1.82x | 0.98 |
Skipping doesn't change the expected value — it changes the variance (risk/reward) but not the RTP. A player who skips to Aces wins almost every time but gains almost nothing. A player who plays middle cards wins less often but gets bigger payouts. Same EV either way.
Multiplier rounding
When multiplier rounding is enabled, multipliers are snapped to clean numbers (e.g. 1x, 1.5x, 2x). The win/lose weights are adjusted automatically to maintain the target RTP exactly.
Feature Structure
Hi/Lo uses a layered feature structure:
basegame (regular feature, deal card, win=1)
└─ playerChoice → round_1_card_7_higher | _lower | _skip
round_1_card_7_higher (wagerFeature, resolves guess)
├─ win entries (per drawn card): multiplier, playerChoice → round_2_card_X_higher | _lower | _skip
└─ lose entries (per drawn card): win=0, bust
round_1_card_7_skip (wagerFeature, re-deals same round)
└─ entries (per drawn card): win=1, playerChoice → round_1_card_X_higher | _lower | _skip
round_N_card_V (wagerFeature, choice after skip re-deal)
└─ single entry: win=1, playerChoice → round_N_card_V_higher | _lower | _skipKey points:
- Basegame is a regular feature (not wager).
win: 1establishestotalWin = 1× stake. - All round features are wager features. Multipliers compound multiplicatively.
- Win and lose entries each show the drawn card in the scenario — win shows what you beat, lose shows what beat you.
- Win entries include
playerChoicefor the next card — no extra placebet needed. - Skip entries include
playerChoicefor the re-dealt card — no extra placebet needed. - Choice features (
round_N_card_V) are only entered via skip re-deals. The basegame bypasses them for the initial card.
Complete Example
import { login, loadConfig, placeBet, collect } from '@hizi.io/engine-sdk';
interface IHiLoScenario {
round: number;
card: number;
suit: string;
guess?: string;
correct?: boolean;
multiplier?: number;
}
const CARD_LABELS: Record<number, string> = {
1: 'A', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7',
8: '8', 9: '9', 10: '10', 11: 'J', 12: 'Q', 13: 'K',
};
// After login and loadConfig...
// Deal initial card
let response = await placeBet({ backendURL, token, stake: 100 });
// Game loop
while (response.success) {
const { scenario, engineData, totalWin } = response.result.result;
const hilo = scenario as IHiLoScenario;
if (hilo.guess && hilo.correct === false) {
// Bust — show the card that beat the player
showBust(CARD_LABELS[hilo.card], hilo.suit);
break;
}
if (!engineData.inProgress) {
// Auto-cashout at max rounds
showWin(totalWin);
break;
}
// Show current card and choices
const choices = engineData.playerChoice;
showCard(CARD_LABELS[hilo.card], hilo.suit);
if (totalWin > 1) {
showCashout(totalWin); // Player can collect
}
// Wait for player decision
const decision = await getPlayerDecision(choices);
if (decision === 'collect') {
await collect({ backendURL, token });
showWin(totalWin);
break;
}
// Continue with chosen action
response = await placeBet({
backendURL,
token,
playerChoiceIndex: decision, // 0=higher, 1=lower, 2=skip
});
}Provably Fair Verification
Hi/Lo is multi-step: each guess (higher / lower / skip) draws a fresh card from the RNG. The verify path replays the round by following the same playerChoice decisions the player made live.
Request
Each continuation step contributes one entry in actions, with playerChoiceIndex matching the offered choice (higher / lower / skip).
import { pfVerify } from '@hizi.io/engine-sdk';
const reply = await pfVerify({
backendURL,
token,
serverSeed: pf.revealedServerSeed,
clientSeed: pf.clientSeed,
stake: 100,
actions: [
{ playerChoiceIndex: 0 }, // higher
{ playerChoiceIndex: 0 }, // higher
{ playerChoiceIndex: 2 }, // skip
{ playerChoiceIndex: 1 }, // lower — round ends here
],
});Reading the response
rngData— oneintrow per drawn card. Array-equal against the livepf.rngData.steps[i]— theIGameResultafter guessi, including the revealed card onscenario.steps[steps.length - 1].totalWin— equals the live terminal multiplier.
The card sequence in the verified steps[] proves the engine couldn't have re-drawn a card to alter the outcome.
if (!reply.success) return;
const { rngData, steps } = reply.result;
const rngOk = deepEqual(rngData, pf.rngData);
const terminalWinOk = steps[steps.length - 1].totalWin === lastLiveResult.totalWin;Next Steps
- Response Handling —
IGameResultstructure andengineDatafields. - Frogger Responses — Another multi-step wager game with collect-or-continue flow.
- Mines Responses — Multi-step wager game with tile picking.