Skip to content

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:

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 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:

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 their choice by index via 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 dealt. The multiplier doesn't 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 × stake

Loading Game Config

The loadConfig response provides game parameters and the full odds/multiplier tables for both 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 multipliers

Using 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:

IndexChoiceFeature pattern
0Higherround_{N}_card_{V}_higher
1Lowerround_{N}_card_{V}_lower
2Skipround_{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

RoundEffective RTPMultiplier formula
Round 198% (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:

CardGuessP(win)MultiplierEV (Round 1)
AHigher100%0.98x0.98
7Higher53.8%1.82x0.98
KLower100%0.98x0.98
7Lower53.8%1.82x0.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 | _skip

Key points:

  • Basegame is a regular feature (not wager). win: 1 establishes totalWin = 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 playerChoice for the next card — no extra placebet needed.
  • Skip entries include playerChoice for 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

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 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).

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 — one int row per drawn card. Array-equal against the live pf.rngData.
  • steps[i] — the IGameResult after guess i, including the revealed card on scenario.
  • 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.

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