Appearance
Hi/Lo Game Responses
This guide explains how to read the scenario field that placeBet returns for a Hi/Lo card game. It also explains how to use the loadConfig data to display card odds and multipliers.
Overview
Hi/Lo is a multi-step wager game. The engine deals the player a card from an infinite deck (13 equally-likely values, Ace low through King high, 4 suits). The player chooses Higher, Lower, or Skip:
- Higher / Lower: The player guesses the next card's rank. A win multiplies the cashout. A loss forfeits everything.
- Skip: The player discards the card and receives a new one. The cashout multiplier does not 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) sets the initial stake as the base. Every subsequent feature multiplies from there.
The Hi/Lo Scenario
When placeBet returns a Hi/Lo result, gameResult.scenario contains:
typescript
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 the engine drew (the result of the guess)
- On skip: the new card the engine deals after the skip
Game Flow
Step 1: Initial Deal and Player Choice
The first placeBet call deals a card. It presents the player's choices in playerChoice:
typescript
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 a choice by index, using playerChoiceIndex:
typescript
// 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. The multiplier does not change:
typescript
// 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:
typescript
const collectResponse = await collect({ backendURL, token });
// Player receives totalWin × stakeLoading Game Config
The loadConfig response provides game parameters. It also provides the full odds and multiplier tables for round 1 (house edge) and round 2+ (fair):
typescript
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
typescript
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 have only one direction (for example, Ace with tiesWin: false cannot go lower). Check the length of engineData.playerChoice.
How RTP Works in Hi/Lo
The house edge is taken once, on the first guess
The engine applies the target RTP (for example, 98%) 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 Does Not Change the EV
The engine computes multipliers per card, based on the probability of winning that guess. Higher probability means 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 does not change the expected value. It changes the variance (risk and reward), not the RTP. A player who skips to Aces wins almost every time. That player gains almost nothing. A player who plays middle cards wins less often. That player gets bigger payouts. The EV is the same either way.
Multiplier rounding
When the game config enables multiplier rounding, the engine snaps multipliers to clean numbers (for example, 1x, 1.5x, 2x). The engine adjusts the win and lose weights automatically. This keeps the target RTP exact.
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 a wager feature.
win: 1setstotalWin = 1× stake. - All round features are wager features. Multipliers compound multiplicatively.
- Win and lose entries each show the drawn card in the scenario. A win shows the card the player beat. A loss shows the card that beat the player.
- Win entries include
playerChoicefor the next card. The player does not need anotherplaceBetcall. - Skip entries include
playerChoicefor the re-dealt card. The player does not need anotherplaceBetcall. - Choice features (
round_N_card_V) are entered only through skip re-deals. The basegame skips them for the initial card.
Complete Example
typescript
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 a multi-step game. Each guess (higher / lower / skip) draws a fresh card from the RNG. The verify path replays the round. It follows the same playerChoice decisions the player made live.
Request
Each continuation step contributes one entry in actions. The playerChoiceIndex field matches the offered choice (higher / lower / skip).
typescript
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. Compare it against the livepf.rngDataarray for equality.steps[i]: theIGameResultafter guessi. It includes the revealed card inscenario.steps[steps.length - 1].totalWin: equals the live terminal multiplier.
The card sequence in the verified steps[] proves the engine could not have re-drawn a card to alter the outcome.
typescript
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: the
IGameResultstructure andengineDatafields. - Frogger Responses: another multi-step wager game with a collect-or-continue flow.
- Mines Responses: a multi-step wager game with tile picking.